Hook
如何理解React的副作用?
说到React的副作用,我们先说下纯函数(Pure function)、纯组件(Pure Component)。
纯函数 和 纯组件
纯函数 (Pure Function) 是 函数式编程 里面非常重要的概念 。
如果一个函数是 纯函数 (Pure Function) ,它必须符合两个条件:
- 函数返回结果只依赖入参,入参固定,则返回值永远不变。
- 函数执行过程中不会产生对外可观察的变化。
示例:
1 | function sqrt(a) { |
**纯组件 **就是纯函数:给一个component相同的props,永远会渲染出相同的视图,并且不会产生对外可观察的变化。
副作用
纯函数不会产生对外可观察的变化,这个对外可观察的变化就是副作用。
副作用包括但不限于:
- 修改外部变量;
- 调用另一个非纯函数(纯函数内调用纯函数,不会产生副作用,依然还是纯函数);
- 发送HTTP请求;
- 调用DOM API 修改页面;
- 调用
window.reload
刷新页面,甚至console.log()
往控制台打印数据; Math.random()
;- 获取当前时间;
- …
钩子 Hook
https://www.ruanyifeng.com/blog/2020/09/react-hooks-useeffect-tutorial.html
https://www.ruanyifeng.com/blog/2019/09/react-hooks.html
函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副作用)都必须通过钩子(hook)引入。
由于副作用非常多,所以hook有许多种。React 为许多常见的副作用提供了专用的hook:
- useState():保存状态
- useContext():保存上下文
- useRef():保存引用
- …
上面这些hook,都是引入某种特定的副作用,而 useEffect()
是通用的副作用hook 。找不到对应的hook时,就可以用它。其实,从名字也可以看出来,它跟副作用(side effect)直接相关。
useEffect
1 | useEffect(didUpdate); |
只要是副作用,都可以使用useEffect()
引入。它的常见用途有下面几种:
- 获取数据(data fetching)
- 事件监听或订阅(setting up a subscription)
- 改变 DOM(changing the DOM)
- 输出日志(logging)
重点:
赋值给
useEffect
的函数会在组件渲染到屏幕之后执行。清除 effect:useEffect 函数可以返回一个清除函数,用以清除 effect 创建的诸如订阅或计时器 ID 等资源,示例:
1
2
3
4
5
6
7
8
9
10export const useDebounce = <V>(value: V, delay: number = 1000) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
// 每次在value变化以后,设置一个定时器
const timeout = setTimeout(() => setDebouncedValue(value), delay);
// 每个定时器的名称都是timeout,所以只有最后一个定时器能存活下来
return () => clearTimeout(timeout); // 返回一个清除函数
}, [value, delay]);
return debouncedValue;
};如果该 useEffect 只调用一次,清除函数会在组件卸载前执行;如果组件多次渲染(通常如此),则 在执行下一个 effect 之前,上一个 effect 就已被清除
effect 的执行时机:传给
useEffect
的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用,虽然useEffect
会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React 总会先清除上一轮渲染的 effect。effect 的条件执行:给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。
react 中组件重新渲染是很频繁的,为了避免重复的网络请求,所以发送网络请求的操作一定要放在 useEffect 中
useState
1 | const [state, setState] = useState(initialState); |
返回一个 state,以及更新 state 的函数。
在初始渲染期间,返回的状态 (state
) 与传入的第一个参数 (initialState
) 值相同。
setState
函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。就是说每次使用setState
都会重新渲染组件。
1 | setState(newState); |
在后续的重新渲染中,useState
返回的第一个值将始终是更新后最新的 state
注意:
React 会确保
setState
函数的标识是稳定的,并且不会在组件重新渲染时发生变化。这就是为什么可以安全地从useEffect
或useCallback
的依赖列表中省略setState
。
useReducer
useReducer 是 useState 的替代方案。当 useState 不能很好的满足需要的时候,useReducer 可能会解决我们的问题。
1 | import { useReducer } from "react"; |
useContext
React.createContext
1 | const MyContext = React.createContext(defaultValue); |
创建一个 Context 对象。
Context.Provider
1 | <MyContext.Provider value={/* 某个值 */}> |
Provider 接收一个 value
属性,传递给 consumers 组件。一个 Provider 可以和多个 consumers 组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value
值发生变化时,它内部的所有 consumers 组件都会重新渲染。
通过新旧值检测来确定变化,使用了与 Object.is
相同的算法(基本等同于“===”)。
注意
当传递对象给
value
时,检测变化的方式会导致一些问题:详见注意事项。