版本React 16.8及以上
使用规则:
const [xxx, setxxx] = useState(initialState)
Class组件写法
import React, { Component } from 'react';
class Example extends Component{
constructor(){
super();
this.state = {
count: 0
}
}
render() {
return (
<div>
<h2>You clicked { this.state.count } times</h2>
<button onClick={() => {this.setState({count: this.state.count + 1})}}>Click me</button>
</div>
);
}
}
export default Example;
Hook写法
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return(
<div>
<h2>You clicked { count } times</h2>
<button onClick={() => {setCount(count + 1)}}>Click me</button>
</div>
);
}
export default Example;
知识点:
setCount(prevCount => {
// 也可以使用 Object.assign
return {...prevCount, ...updatedValues};
});
const [count, setCount] = useState(() => {
const initialCount = someComputation(props);
return initialCount;
});
无需清除的effect
比如发送网络请求,手动变更 DOM,记录日志、
需要清除的effect
例如订阅外部数据源,监听器,定时器
无需清除的effect
Class组件写法
import React, {Component} from 'react';
class Example extends Component{
constructor(props){
super(props);
this.state = {
count: 0
}
}
componentDidMount(){
console.log(`You clicked ${this.state.count} times`);
}
componentDidUpdate(){
console.log(`You clicked ${this.state.count} times`);
}
render(){
return(
<div>
<h2>You clicked {this.state.count} times</h2>
<button onClick={() => {this.setState({ count: this.state.count + 1})}}>Click me</button>
</div>
);
}
}
export default Example;
Hook
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(()=>{
console.log(`You clicked ${count} times`);
});
return(
<div>
<h2>You clicked {count} times</h2>
<button onClick={() => {setCount(count + 1)}}>Click me</button>
</div>
);
}
export default Example;
需清除的effect
Class组件写法
import React, {Component} from 'react';
class Example extends Component{
constructor(props){
super(props);
this.state = {
innerWidth: 0,
innerHeight: 0
};
this.handleWindowSizeChange = this.handleWindowSizeChange.bind(this);
}
componentDidMount(){
window.addEventListener('resize', this.handleWindowSizeChange);
}
componentWillUnmount(){
window.removeEventListener('resize', this.handleWindowSizeChange);
}
handleWindowSizeChange(){
this.setState({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
})
}
render(){
return(
<div>
<h2>
window innerWidth: {this.state.innerWidth}<br/>
window innerHeight: {this.state.innerHeight}
</h2>
</div>
);
}
}
export default Example;
Hook
import React, { useState, useEffect } from 'react';
function Example() {
const [windowSize, setWindowSize] = useState({innerWidth: 0, innerHeight: 0});
useEffect(()=>{
window.addEventListener('resize', handleWindowSizeChange);
return () => window.removeEventListener('resize', handleWindowSizeChange);
}, []); //空数组作为第二个参数,表明执行只运行一次的effect。等同于告诉React我的effect不依赖于props或state中的任何值(仅在组件挂载和卸载时执行)
function handleWindowSizeChange() {
setWindowSize(
{
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
}
)
}
return(
<div>
<h2>
window innerWidth: {windowSize.innerWidth}<br/>
window innerHeight: {windowSize.innerHeight}
</h2>
</div>
);
}
export default Example;
知识点:
useEffect(()=>{
console.log(`You clicked ${count} times`);
});
useEffect(()=>{
console.log(`You clicked ${count} times`);
}, [count]);
用于父子组件传值及跨越多个组件传值, 可以避免多次中间元素传递 props
Context 在class组件用法
import { createContext } from 'react';
export const ParentContext = createContext();
import React, { Component } from 'react';
import { ParentContext } from './CommonContext'
import Child from "./Child";
class Parent extends Component{
constructor(){
super();
this.state = {
count: 0
}
}
render() {
return (
<div>
<h2>You clicked { this.state.count } times</h2>
<button onClick={() => {this.setState({count: this.state.count + 1})}}>Click me</button>
<ParentContext.Provider value={this.state.count}>
<Child/>
</ParentContext.Provider>
</div>
);
}
}
export default Parent;
import React, { Component } from 'react';
import { ParentContext } from './CommonContext';
class Child extends Component{
static contextType = ParentContext;
render() {
return (
<div>
<h2>{this.context}</h2>
</div>
);
}
}
export default Child;
注意: 不要把createContext放在父级组件里面,否则会发生
Uncaught ReferenceError: Cannot access ‘ParentContext’ before initialization
原因是:造成了循环依赖。Index引用 Parent组件, Parent组件引用了Child组件。Child引用了 Parent组件中创建的’ParentContext’.
正确做法: 是单独创建一个文件保存Context. Parent和Child都从该文件中引用context
Context 在Hooks用法
import React, {useState, createContext} from 'react';
import Child from "./Child";
export const ParentContext = createContext();
function Parent() {
const [count, setCount] = useState(0);
return(
<div>
<h2>You click {count} times</h2>
<button onClick={() => {setCount(count + 1)}}>Click me</button>
<ParentContext.Provider value={count}>
<Child/>
</ParentContext.Provider>
</div>
);
}
export default Parent;
import React, {useContext} from 'react';
import {ParentContext} from './Parent';
function Child() {
let count = useContext(ParentContext);
return(
<h2>{count}</h2>
)
}
export default Child;
知识点:
import React, { useReducer } from 'react';
function useReducerDemo() {
const [state, dispatch] = useReducer((state, action)=>{
switch (action) {
case 'add':
return state + 1;
case 'sub':
return state - 1;
default:
return state;
}
}, 0);
return(
<div>
<p>current count is {state}</p>
<button onClick={() => {dispatch('add')}}>Add</button>
<button onClick={() => {dispatch('sub')}}>Subtract</button>
</div>
)
}
export default useReducerDemo;
知识点:
const [state, dispatch] = useReducer(reducer, initialArg, init);
第一个参数reducer可以是匿名函数,也可以是普通函数。
第二个参数initialArg 设置state的初始值
第三个参数init 惰性初始化。引用组件时再传入初始值。
ReactDOM.render(<Counter initialCount={0} />, document.getElementById('root'));
import React, { useReducer } from 'react';
function init(initialCount) {
return {count: initialCount};
}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
case 'reset':
return init(action.payload);
default:
throw new Error();
}
}
function Counter({initialCount}) {
const [state, dispatch] = useReducer(reducer, initialCount, init);
return (
<>
Count: {state.count}
<button
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
export default Counter;
TodoList example – useReducer + useContext
import React from 'react';
import {TodoReducer} from "./TodoReducer";
import TodoList from "./TodoList";
function HookDemo() {
return(
<TodoReducer>
<TodoList/>
</TodoReducer>
)
}
export default HookDemo;
import React, { useContext } from 'react';
import {TodoContext, ADD_ITEM} from './TodoReducer';
let inputVal;
function TodoList() {
const {state, dispatch} = useContext(TodoContext);
return(
<div>
<input type="text" id='todo' ref={el => inputVal = el}/>
<button onClick={() => {dispatch({type: ADD_ITEM, payload: {id: state.nextId + 1, newItem: {id: state.nextId + 1, value: inputVal.value}}})}}>add Todo</button>
<ul>
{
state.itemList.map(item => (
<li key={item.id}>{item.value}</li>
))
}
</ul>
</div>
)
}
export default TodoList;
import React, {createContext, useReducer} from 'react';
export const ADD_ITEM = 'add_item';
export const TodoContext = createContext([]);
const reducer = (state, action) => {
switch (action.type) {
case ADD_ITEM:
return {
...state,
nextId: action.payload.id,
itemList: [...state.itemList, action.payload.newItem]
};
default:
return state;
}
};
const initialState = {
nextId: 0,
itemList: []
};
export const TodoReducer = props => {
const[state, dispatch] = useReducer(reducer, initialState);
return(
<TodoContext.Provider value={{state, dispatch}}>
{props.children}
</TodoContext.Provider>
);
};
TodoList example – React + Redux
import React, {Component} from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import TodoList from './TodoList';
import todo from './reducer';
const store = createStore(todo);
class ReduxDemo extends Component{
render() {
return (
<Provider store={store}>
<TodoList/>
</Provider>
);
}
}
export default ReduxDemo;
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {addNewItem} from "./actions";
class TodoList extends Component{
constructor(){
super();
this.inputVal = null;
}
handleBtnClick(){
this.props.AddItem(this.inputVal.value);
}
render() {
return(
<div>
<input type="text" ref={el => this.inputVal = el}/>
<button onClick={this.handleBtnClick.bind(this)}>Add Item</button>
<ul>
{
this.props.todoList.list.map(item => (
<li>{item}</li>
))
}
</ul>
</div>
);
}
}
const mapStateToProps = state => ({
todoList: state
});
const mapDispatchToProps = dispatch => {
return{
AddItem: (inputVal) =>{
dispatch(addNewItem(inputVal));
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
export const ADD_ITEM = 'add_item';
export const addNewItem = (inputVal) => ({
type: ADD_ITEM,
payload: inputVal
});
import {ADD_ITEM} from "./actions";
const initialState = {
list: [
'add item here'
]
};
const todo = (state = initialState, action) => {
let newList = state.list;
switch (action.type) {
case ADD_ITEM:
newList.push(action.payload);
return{
...state,
list: newList
};
default:
return state;
}
};
export default todo;
const refContainer = useRef(initialValue);
Hooks写法
import React, {useRef} from 'react';
function UseRef() {
let inputRef = useRef( null);
const handleClick = () => {
alert(`input value is ${inputRef.current.value}`);
};
return(
<div>
<input type="text" ref={inputRef}/>
<button onClick={handleClick}>Get Input Value</button>
</div>
)
}
export default UseRef;
Class组件 createRef法 (适用于版本React 16.3以上)
import React, { Component } from 'react';
class RefDemo extends Component{
constructor(props){
super(props);
this.inputRef = React.createRef();
}
handleClick = () => {
alert(`input value is ${this.inputRef.current.value}`);
};
render() {
return(
<div>
<input type="text" ref={this.inputRef}/>
<button onClick={this.handleClick}>Get Input Value</button>
</div>
);
}
}
export default RefDemo;
Class组件 回调ref法(适用于版本React 16.3以下)
import React, { Component } from 'react';
class RefDemo extends Component{
constructor(props){
super(props);
this.inputRef = null;
}
handleClick = () => {
alert(`input value is ${this.inputRef.value}`);
};
render() {
return(
<div>
<input type="text" ref={el => {this.inputRef = el}}/>
<button onClick={this.handleClick}>Get Input Value</button>
</div>
);
}
}
export default RefDemo;
ESlint插件
npm install eslint-plugin-react-hooks --save-dev
// 你的 ESLint 配置{
“plugins”: [
// …
“react-hooks”
],
“rules”: {
// …
“react-hooks/rules-of-hooks”: “error”, // 检查 Hook 的规则
“react-hooks/exhaustive-deps”: “warn” // 检查 effect 的依赖
}}
自定义Hooking
以useSomething开头。useSomething 的命名约定可以让我们的 linter 插件在使用 Hook 的代码中找到 bug。记住是必须以use开头。不遵循这条约定的话,由于无法判断某个函数是否包含对其内部Hook的调用。React将无法自动检查的你的Hook是否违反了Hook的规则
在两个组件中使用相同的 Hook 会共享 state 吗?不会。自定义 Hook 是一种重用状态逻辑的机制(例如设置为订阅并存储当前值),所以每次使用自定义 Hook 时,其中的所有 state 和副作用都是完全隔离的。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
知识点:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
参数一:函数
参数二:依赖项数组
有依赖项数组:仅会在某个依赖项改变时才重新计算memoizedValue
无依赖项数组:每次渲染的时都会计算新的值
注:传入 useMemo 的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect 的适用范畴,而不是 useMemo。
useCallback and useMemo 例子
import React from 'react';
const CountButton = React.memo(function CountButton({onClick, count}) {
return <button onClick={onClick}>{count}</button>
});// 因为被count1 ,count2重用。如果不用memo, 每次点击都会导致CountButton re-render
function DualCounter() {
const [count1, setCount1] = React.useState(0);
const increment1 = React.useCallback(() => setCount1(c => c + 1), []);
const [count2, setCount2] = React.useState(0);
const increment2 = React.useCallback(() => setCount2(c => c + 1), []);
return (
<>
<CountButton count={count1} onClick={increment1} />
<CountButton count={count2} onClick={increment2} />
</>
)
}
export default DualCounter;
以上这个例子来自文章“When to useMemo and useCallback”
英文链接:https://kentcdodds.com/blog/usememo-and-usecallback
译文链接:https://jancat.github.io/post/2019/translation-usememo-and-usecallback/
相较于直接把ref暴露给父组件,useImperativeHandle可以定义对ref的操作来限制父组件的对ref的使用
useImperativeHandle(ref, createHandle, [deps])
参数一: 需要操作的ref
参数二:对ref操作的函数
参数三:依赖项,当依赖项变化的时候调用
import React, { forwardRef, useRef, useImperativeHandle } from 'react';
function Parent() {
const inputRef= useRef();
const handleChildRef = () => {
console.log(inputRef.current); //这时取到的ref.current里面就只有focus属性了
inputRef.current.focus();
};
return(
<>
<InputEle ref={inputRef} />
<button onClick={handleChildRef}>click to focus input</button>
</>
)
}
const InputEle = forwardRef((props, ref) => {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} placeholder="put whatever you want"/>;
});
export default Parent;
官方推荐使用useEffect就没有深入学习了。
useDebugValue(value)
可用于显示自定义的hooks label 在React DevTools
以上code都可以到我的github上面下载
https://github.com/CassyWu/reactHooks