1. Hook的定义
React Hooks
设计的目的:加强版函数组件,让函数组件也拥有类组件的功能
"Hook"的意思是钩子
React Hooks想要达到的效果就是在尽量使用纯函数,且如需要外部功能或副作用,就用Hooks将它们“钩”进来
React默认提供了一些常用的钩子,钩子一律使用use
前缀命名,常用的钩子如下:
- useState()
- useReducer()
- useContext()
- useEffect()
2. Hook:useState
State Hook
使用的例子:
const MyCount = () => {
// 声明了count这个state变量
const [count, setCount] = useState(0); // 数组解构
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>Click me</button>
</div>
);
}
调用useState
时React为我们定义了一个state变量
,我们将这个变量命名为count
,useState
还会返回一个更新state的函数,同样我们将之命名为setCount
。
在函数组件中,函数退出后变量就会消失,而React就通过useState替我们保存了变量,让函数也拥有保存state
的能力。
问题:React是怎么知道useState对应的是哪个函数组件?
3. Hook:useEffect
Effect Hook可以让你在函数组件中执行副作用操作,这里的副作用有点像生命周期
可以把
useEffect
Hook 看做componentDidMount
,componentDidUpdate
和componentWillUnmount
这三个函数的组合。
3.1 无需清除的effect操作
在使用React class中,我们通常将副作用放在componentDidMount
和componentDidUpdate
中,如下面计时器实例:
class MyCountClass extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
};
}
componentDidMount() {
document.title = You clicked ${this.state.count} times
;
}
componentDidUpdate() {
document.title = You clicked ${this.state.count} times
;
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>Click me</button>
</div>
);
}
}
我们需要在两个生命周期函数中编写重复的代码,因为我们希望组件在初次加载和更新时执行同样的操作
而我们使用Hook Effect
,如下:
const MyCountHook = () => {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = You clicked ${count} times
;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count => count + 1)}>Click me</button>
</div>
);
};
通过使用useEffect
这个Hook,我们可以告诉React组件在渲染后执行哪些操作,React会在Dom更新后执行我们传入的函数(这个函数称之为effect
)
useEffect
在首次渲染后和每次更新后都会执行,我们可以简单理解为effect
发生在渲染之后,React保证每次运行effect
时,Dom都已经更新完毕。
在effect
中我们可以获取到最新的count
值,所以传给useEffect Hook
的函数(effect
)在每次渲染中都是不同的;每次重新渲染都会生成新的effect
——每个effect
都属于某一次特定的渲染。
与
componentDidMount
或componentDidUpdate
不同,使用useEffect
调度的 effect 不会阻塞浏览器更新屏幕;使用useLayoutEffect
则会在Dom更新完毕前调度effect,会触发页面阻塞。
3.2 需要清除的effect操作
有一些副作用是需要清除的(如clearInterval
),这种情况下清除工作可以防止内存泄露。
如下面的例子:
const MyCountHook = () => {
const [count, setCount] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setCount(count + 1);
}, 1000);
// 清除interval的"副作用"
return () => {
clearInterval(interval);
};
});
return (
<div>
<p>Now, the count is {count}</p>
</div>
);
};
effect
的返回函数是effect
中可选的清除机制,这样可以有效地将添加副作用与清除工作绑定在一起。
React会在组件卸载时执行
effect
的清除操作,effect
在每次渲染时都会执行,所以React在执行当前effect
时会对上一个effect
进行清除。
3.3 useEffect的第二个参数
useEffect(() => {...}, [count]);
第二个参数传入一个数组,只有数组中的“state变量”发生变化时,传入的effect
才会被执行。
当第二个参数为空数组([]
)时,传入的effect
只有在第一次渲染时会执行,之后函数组件更新时均不会执行,当然也不会有effect的卸载。
4. Hook:useReducer
useState
的替代方案,它接收形如(state, action) => newState
的reducer,并返回当前最新的state,以及配套的工作方法dispatch
,用法与redux类似。
下面是使用useReducer
的计数器例子:
// 初始化状态值
const initialState = {
count: 0,
};
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
const MyCountHook = () => {
const [state, dispatch] = useReducer(reducer, initialState);
useEffect(() => {
const interval = setInterval(() => {
dispatch({ type: 'increment' });
}, 1000);
return () => {
clearInterval(interval);
};
});
return (
<div>
<p>Now, the count is {state.count}</p>
</div>
);
};
5. Hook:useRef
const refContainer = useRef(initialValue);
useRef
返回一个可变的ref对象,其.current
属性被初始化为传入的参数(initialValue
)。返回的ref对象在组件的整个生命周期内保持不变。
const MyCountHook = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const [name, setName] = useState('xuanxiao');
const inputEl = useRef(null);
useEffect(() => {
console.log(inputEl); // {current: input}
console.log('effect invoked');
return () => console.log('effect detached');
}, [state.count, name]);
return (
<div>
<input ref={inputEl} value={name} onChange={e => setName(e.target.value)}></input>
<button onClick={() => dispatch({ type: 'increment' })}>{state.count}</button>
</div>
);
};
发表评论