原文地址:
用动画和实战打开 React Hooks(一):useState 和 useEffect
用动画和实战打开 React Hooks(二):自定义 Hook 和 useCallback
# 用动画和实战打开 React Hooks(三):useReducer 和 useContext
在 Hooks 出现之前,类组件和函数组件的分工一般是这样的:
- 类组件提供了完整的状态管理和生命周期控制,通常用来承接复杂的业务逻辑,被称为“聪明组件”
- 函数组件则是纯粹的从数据到视图的映射,对状态毫无感知,因此通常被称为“傻瓜组件”
Hooks的出现,解决了函数组件没有状态
理解函数式组件的运行过程
当我们第一次调用组件函数时,触发初次渲染;然后随着 props 的改变,便会重新调用该组件函数,触发重渲染。
useState 使用浅析
const [state, setState] = useState(initialValue);
useState这个钩子为函数式组件提供了状态管理,通过调用setState函数,可以触发组件的重渲染。每次渲染具有独立的状态值(毕竟每次渲染都是完全独立的嘛)。也就是说,每个函数中的 state 变量只是一个简单的常量,每次渲染时从钩子中获取到的常量,并没有附着数据绑定之类的神奇魔法。
function Counter() {
const [count, setCount] = useState(0);
function handleAlertClick() {
setTimeout(() => {
alert('You clicked on: ' + count);
}, 3000);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
<button onClick={handleAlertClick}>
Show alert
</button>
</div>
);
}
-
每次渲染相互独立,因此每次渲染时组件中的状态、事件处理函数等等都是独立的,或者说只属于所在的那一次渲染
-
我们在
count为 3 的时候触发了handleAlertClick函数,这个函数所记住的count也为 3 -
三秒种后,刚才函数的
setTimeout结束,输出当时记住的结果:3
useEffect 使用浅析
useEffect(effectFn, deps)
1.useEffect 的第一个参数**effectFn参数** 是一个执行某些可能具有副作用的 Effect 函数(例如数据获取、设置/销毁定时器等),它可以返回一个清理函数(Cleanup),例如大家所熟悉的 setInterval 和 clearInterval :
useEffect(() => {
const intervalId = setInterval(doSomething(), 1000);
return () => clearInterval(intervalId);
});
2.useEffect 的第二个参数deps(依赖数组), 依赖数组就是用来控制是否应该触发 Effect,从而能够减少不必要的计算,从而优化了性能。具体而言,只要依赖数组中的每一项与上一次渲染相比都没有改变,那么就跳过本次 Effect 的执行。
-
初次渲染
componentDidMount,deps为空数组useEffect(() => { // 组件加载完成执行 return () => { // 返回一个函数,在组件卸载时执行 } }, []) -
重渲染
componentDidUpdate,deps传入需要监听的statefunction App() { const [count, setCount] = useState(0); useEffect(() => { console.log('count 更新后' + count); }, [count]); const handleClick = () => { setCount(count++); }; return ( <div> <button onclick={handleClick}>Click me</button> </div> ) }
useLayoutEffect钩子在渲染之前执行,使用方法与 useEffect 完全一致,只是执行的时机不同。
React 官方文档 Rules of Hooks 中强调过一点:Only call hooks at the top level. 只在最顶层使用 Hook
不要在循环,条件或嵌套函数中调用 Hook,确保总是在你的 React 函数的最顶层调用他们:
useEffect(() => {
if() {
...
}
})
一个简单的自定义Hook
function useCustomHook() {
const [scrollPosition, setScrollPosition] = useState(null);
useEffect(() => {
const handleScroll = () => setScrollPosition(window.scrollY);
document.addEventListener('scroll', handleScroll);
return () =>
document.removeEventListener('scroll', handleScroll);
}, []);
return scrollPosition;
}
const Custom = () => {
const scroll = useCustomHook();
console.log(scroll)
return (
<div style={{minHeight:800}}>
</div>
)
}
一个名为useCustomHook的函数,但不是React函数组件,内部通过使用 React 自带的一些 Hook (例如 useState 和 useEffect )来实现某些通用的逻辑。
这里推荐两个强大的 React Hooks 库:React Use 和 Umi Hooks。它们都实现了很多生产级别的自定义 Hook,非常值得学习。
useCallback
const memoizedCallback = useCallback(callback, deps);
第一个参数 callback 就是需要记忆的函数,第二个参数就是大家熟悉的 deps 参数,同样也是一个依赖数组(有时候也被称为输入 inputs)。
在大多数情况下,我们都是传入空数组 [] 作为 deps 参数,这样 useCallback 返回的就始终是同一个函数,永远不会更新。
useMemo
实际上,useMemo 的功能是 useCallback 的超集。与 useCallback 只能缓存函数相比,useMemo 可以缓存任何类型的值(当然也包括函数)。useMemo 的使用方法如下:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
其中第一个参数是一个函数,这个函数返回值的返回值(也就是上面 computeExpensiveValue 的结果)将返回给 memoizedValue 。因此以下两个钩子的使用是完全等价的:
useCallback(fn, deps);
useMemo(() => fn, deps);
useContext
useContext可以使组件之间共享状态
const AppContext = createContext();
const Child1 = () => {
const { name } = useContext(AppContext)
return (
<div>
<h1>子组件1: {name}</h1>
</div>
)
}
const Child2 = () => {
const { name } = useContext(AppContext)
return (
<div className="messages">
<h1>子组件2: {name}</h1>
</div>
)
}
const Father = () => {
return (
<AppContext.Provider value={{
name: 'superawesome'
}}>
<Child1 />
<Child2 />
</AppContext.Provider>
)
}
useReducer
const [state, dispatch] = useReducer(reducer, initialState);
上面是useReducer()的基本用法,它接受 Reducer 函数和状态的初始值作为参数,返回一个数组。数组的第一个成员是状态的当前值,第二个成员是发送 action 的dispatch函数。
下面是一个计数器的例子。用于计算状态的 Reducer 函数如下。
const myReducer = (state, action) => {
switch(action.type) {
case('countUp'):
return {
...state,
count: state.count + 1
}
case('countDown'):
return {
...state,
count: state.count - 1
}
default:
return state;
}
}
function App() {
const [state, dispatch] = useReducer(myReducer, { count: 0 });
return (
<div className="App">
<button onClick={() => dispatch({ type: 'countUp' })}>+1</button>
<button onClick={() => dispatch({ type: 'countDown' })}>+1</button>
<p>Count: {state.count}</p>
</div>
);
}
将useContext和useReducer配合使用,实现一个轻量级的、类似 Redux 的状态管理模型。
useRef
适用场景:
-
获取子组件的实例,只有类组件可以使用
function Example(){ const inputEl=useRef(null) function handleClick(){ console.log(inputEl) inputEl.current.focus() } return ( <> <input type="text" ref={inputEl}/> <button onClick={handleClick}>点击</button> </> ) } -
在函数组件中定义一个全局变量,不会因为重复render重复声明,类似于组件的this.xxx
const App = () => { const [count, setCount] = useState(0); const latestCount = useRef(); useEffect(() => { latestCount.current = count }) const handleAlertClick = () => { setTimeout(() => { alert(latestCount.current) }, 3000); } return ( <div> <p>count:{count} </p> <button onClick={() => setCount(count + 1)}> Click me </button> <button onClick={handleAlertClick}> Show alert </button> </div> ) }