目录
正文
useEffect
-
注意依赖 刚学 Hooks, 容易犯的一个错误就是, 用 Class 风格组件的生命周期的思路去理解 Hooks. 例如:
useEffect(() => { fetchList() }, []); // 以此方法来模拟加载事件这样做如果
fetchList在使用过程中始终不变, 结果会是正确的, 但是相反情况, 如果这个函数是会变化的, 那么上面这段代码就会出现问题了.
那怎么解决呢?// 第一种方法, 添加对应依赖 useEffect(() => { fetchList() }, [fetchList]);但是这就达不到初始只加载一次的需求了, 你可以用第二种方法
// 第二种方法, 使用 useCallback const fetchList = useCallback(..., []); useEffect(() => { fetchList() }, [fetchList]);但是如果 fetchList, 本身也有依赖呢?
// 第三种方法, 使用 usePersistFn const fetchList = usePersistFn(...); // 方法内使用的参数是会变化的 useEffect(() => { fetchList() }, [fetchList]);// 第四种方法, 对 fetchList 建立 Ref 引用 const fetchListRef = useRef(fetchList); fetchListRef.current = fetchList useEffect(() => { fetchListRef?.current() });我只是想解决加载情况的问题, 有没有简单点的?
// 第五种方法, 把函数写在 useEffect 里面 useEffect(() => { const fetchList = (query) => {}; fetchList(props.query) }, [])// 第六种方法, 使用 useMount useMount(() => { fetchList() })关于依赖问题, 建议把
eslint-plugin-react打开, 能避免很多错误 -
可以写多个 hooks 来避免依赖过多 不要把很多不相干的逻辑写在一个 hooks 里, 这样能避免依赖太多, 导致不相干的依赖更新整个 hooks 也重新更新, 增加额外开销
// bad useEffect(() => { // 逻辑A, 依赖 a,b // 逻辑B, 依赖 c,d }, [a, b, c, d]) // good useEffect(() => { // 逻辑A, 依赖 a,b }, [a, b]) useEffect(() => { // 逻辑B, 依赖 c,d }, [c, d]) -
例如获取数据这样的接口, 如果没有特殊要求, 可以写在组件外部, 或者写在 hooks 内部
// bad const Example = props => { // ... const foo = (params) => {}; useEffect(() => { foo(params) }, [foo, params]) }; // good const _foo = (params) => {}; // 写在组件外面 const Example = props => { // ... useEffect(() => { _foo(params) }, [params]) }; -
如果有不需要加入依赖的数据, 或者计算量很大的数据, 可以使用 useRef, useReducer 的方式来管理数据
const bigDataRef = useRef(/*初始值*/) useEffect(() => { // bigDataRef.current }, [])const [state, dispatch] = useReducer((state, action) => { switch (action.type) { case 'plus': return { ...state, count: state.count + 1}; default: return state; } }, { count: 0 }); useEffect(() => { console.log(state.count) }, []) -
在更新或者卸载组件的时候, 记得清楚副作用函数, 例如定时器, 没有完成异步请求 (可以结合
ahooks中的 useRequest(cancel方法来取消请求))自己在多选项卡风格的后台项目里面吃过这样的亏. 因为切选项卡但副作用没有及时清除, 导致有的回调就作用在了新的标签页页面.
const { data, run, cancel } = useRequest({ manual: true }); useEffect(() => { run() return () => { cancel() } }, [data, run, cancel])
useCallback
- 需要比较引用的场景,如 useEffect,又或者是配合React.Memo使用 (代码参考)
- 能写到组件外部的就写到组件外部
- 不是什么场景下都要用 useCallback, useCallback 本身就有一定的性能损耗
- 小心 useCallback 的使用场景, 参考: React Hooks 第一期:聊聊 useCallback
useRef
-
会频繁变化的属性, 且不是强依赖关系的, 用 useRef
-
在
ahooks项目里面, 发现基本只要参数是 Function 类型的, 都使用了 Ref 来代理// useInterval // ... const fnRef = useRef<() => void>(); fnRef.current = fn; // ...
useMemo
- 缓存计算结果. useMemo 部分场景可以当做 vue 里面的 computed 来用, 在一些有大量计算的地方比较常用到
useLayoutEffect
- 当你的 useEffect 里面的操作需要处理DOM,并且会改变页面的样式,就需要用这个,否则可能会出现出现闪屏问题
技巧
- 组件的一个属性如果是 dom, 要考虑是否是 Ref|Function|Dom|Selector 等类型
- getTargetElement 这个函数挺好用, 可以获取 Ref|Function|Dom 类型对应的dom节点
- 参数类型. 解决状态同步问题的时候, 像 setState 的参数可能是
value, 也可能是个function. 在封装自己的组件时候也可能会用到 - 如果要注入默认参数, 又多个地方会用到. 可以用
createXXX({...params}) => useXXX; useXXX()这种形式 - useReducer的第一个参数 reducer 不一定要安装严格的规范来做, 可以类似
const toggleReducer = (state, nextValue) => (typeof nextValue === 'boolean' ? nextValue : !state);. 以此来达到类似 ref 在处理数据时候的效果
注意事项
- 开启 eslint : eslint-plugin-react
- 如果函数中出现异步更新, 例如 setTimeout, Promise. 且在短时间内会触发多次, 有可能会出现取值错误
- hooks 里如果有
异步请求, 要确保每次每次结果的最新, 及旧的请求或者结果要被即使清除
吐槽
因为新工作的需要, 努力的恶补 React 基础. 目前刚学 React 三个月, 还有好多不懂不熟练了, 好在有一些前辈指点, 少走了一些弯路.
目前的项目, 基本都是在用 Hooks 的写法. 因为基本功不扎实, 也犯过几次错, 看别人的代码也发现有很多错误的用法, 所以哪里不足补哪里. 找了市面上比较靠谱的 hooks 库, 来学学别人的代码. 并做了这些总结.
意外经验.
这次学源码有个有意思的地方. 就是在看源码的时候, 把 Git History 打开, 去翻翻看版本的迭代历史, 看看最初他们是怎么做的, 又怎么改到现在的版本, 挺有收获的. 不要觉得目前的版本是理当如此的, 到自己上阵的时候这些错误自己可能还会再犯.
Hooks 的技巧和问题远不止这些, 以后发现新的技巧就继续维护.