React Hooks
优缺点
React Hooks 优缺点
A.Hooks缺点
尽管我们通过上面的例子看到 React Hooks 的强大之处,似乎类组件完全都可以使用 React Hooks 重写。但是当下 v16.8 的版本中,还无法实现 getSnapshotBeforeUpdate 和 componentDidCatch 这两个在类组件中的生命周期函数。官方也计划在不久的将来在 React Hooks 进行实现。
Class | Hooks |
---|---|
代码逻辑清晰(构造函数、componentDidMount 等) | 需要配合变量名和注释 |
不容易内存泄漏 | 容易发生内存泄漏 |
B.Hooks优点
功能更加isolation;
避免wrapper hell
Redux 的作者 Dan Abramov 总结了用class组件类的几个缺点。
- 大型组件很难拆分和重构,也很难测试。
- 业务逻辑分散在组件的各个方法之中,导致重复逻辑或关联逻辑。
- 组件类引入了复杂的编程模式,比如 render props 和高阶组件。
React Hooks 的设计目的,就是加强版函数组件,完全不使用"类",就能写出一个全功能的组件。React Hooks 的意思是,组件尽量写成纯函数,如果需要外部功能和副作用,就用钩子把外部代码"钩"进来。
1. 基础用法
非常好的文章:https://github.com/happylindz/blog/issues/19
useState、useEffect、useContext、useReducer、
useCallback(记忆函数,配合pureComponent/memo更佳)、useMemo(记忆组件)、
useRef(功能强大,可以用来避免function组件的capture value特性)、
useImperativeHandle(配合forwardRef使用)、useLayoutEffect (同步执行副作用,时机:Dom更新-> 同步useLayoutEffect -> 异步useEffect )
Hooks基础用法
import React, { useState, useEffect } from "react";
let timer = null;
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = "componentDidMount" + count;
},[count]); // 监听count变化
useEffect(() => {
timer = setInterval(() => {
setCount(prevCount => prevCount + 1);
}, 1000);
return () => {
document.title = "componentWillUnmount";
clearInterval(timer);
};
}); // case1 不监听任何参数变化.代替 componentDidMount componentDidUpdate 和 componentWillUnmount.
// case2 监听一个空数组. 代替 componentDidMount 和 componentWillUnmount.
// case3 如果有监听数据的话,能类似参与componentDidUpdate的比较阶段,跳过effect进行性能优化 [See. https://zh-hans.reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects] 但是要注意一个坑!(见下方)
return (
<div>
Count: {count}
<button onClick={() => clearInterval(timer)}>clear</button>
</div>
);
}
useState
原理:https://xin-tan.com/passages/2019-10-21-react-hooks/
基于 Array+Cursor 来实现。
useEffect
原理:https://xin-tan.com/passages/2019-10-21-react-hooks/
useEffect 会在每次渲染后都执行吗? 是的,默认情况下,它在第一次渲染之后和每次更新之后都会执行(即:didmount + didupdate)。(我们稍后会谈到如何控制它。)你可能会更容易接受 effect 发生在“渲染之后”这种概念,不用再去考虑“挂载”还是“更新”。React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
一个好处:为什么每次更新的时候都要运行 Effect https://zh-hans.reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update,可以编写更多bug free代码。
使用case3的自定义监听元素此优化方式的时候,请确保数组中包含了所有外部作用域中会随时间变化并且在 effect 中使用的变量,否则你的代码会引用到先前渲染中的旧变量。(比如用swiper的时候有踩坑)。参阅文档,了解更多关于如何处理函数以及数组频繁变化时的措施内容。
如何处理函数 这里提到,如果是在没有办法,可以用useCallback包裹。
比较deps如果是对象object,不是深度比较,而是用的is
。see.https://github.com/facebook/react/blob/c1d3f7f1a9/packages/shared/objectIs.js?spm=ata.13261165.0.0.6dee5600znB8bR#L14
与
componentDidMount
或componentDidUpdate
不同,使用useEffect
调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的useLayoutEffect
Hook 供你使用,其 API 与useEffect
相同。
2. 模拟class的各个生命周期
- componentDidUpdate
function useUpdate(fn) {
// useRef 创建一个引用
const mounting = useRef(true);
useEffect(() => {
if (mounting.current) {
mounting.current = false;
} else {
fn();
}
});
}
- shouldComponentUpdate
https://juejin.im/post/5c8eec1bf265da67cb619e79#heading-5
- forceUpdate
https://juejin.im/post/5c8eec1bf265da67cb619e79#heading-7
- usePrevious
// 理解核心:useEffect是在渲染DOM结束以后才会调用!
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, value);
return ref.current;
}
----
// 帮助理解的例子(from知乎):
const exampleComponent = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
}, value);
return <h1>{ref.current}</h1>
}
第一次exampleComponent被执行,函数参数value为1,ref.current先是undefined (因为这时useEffect还没有被调用),然后根据return的JSX,渲染DOM,页面上被渲染出"undefined". 接着 useEffect被调用,此时ref 的current值被赋值,是这次渲染的props -> value,也就是1。
第二次exampleComponent被再次调用,函数参数value变成了2,依旧是先根据return的JSX渲染DOM,还记得吗,第一次exampleComponent被调用的后期,ref.current变成了1,因为没有任何页面上display改变ref.current的操作,所以页面中渲染出 1 ,渲染完成以后,开始老步骤,执行useEffect,这一次的函数参数value是2,所以ref.current变成了2。
参考:zhihu.com/question/346140951
- 不足
当下 v16.8 的版本中,还无法实现 getSnapshotBeforeUpdate 和 componentDidCatch 这两个在类组件中的生命周期函数。
更多理解
useRef
useRef
返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue
)(如果什么都没传是undefined
)。返回的 ref 对象在组件的整个生命周期内保持不变。
useRef 神奇功能:
- 可以模拟class的实例变量 https://zh-hans.reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables
- 避免function的capture value特点。因为, ref 在所有 Render 过程中保持着唯一引用,因此所有对 ref 的赋值或取值,拿到的都只有一个最终状态,而不会在每个 Render 间存在隔离。
capture value
例子一:state https://github.com/happylindz/blog/issues/19
例子而:props https://juejin.im/post/5c8eec1bf265da67cb619e79#heading-2
useCallback
https://zhuanlan.zhihu.com/p/56975681
当deps是函数的时候,建议都包裹一下。
特别注意,防抖的函数也是要包裹的,不然也防不了父组件的re-render。
useMemo
useCallback(fn, inputs) is equivalent to useMemo(() => fn, inputs).
useCallback 不会执行第一个参数函数,而是将它返回给你,而 useMemo 会执行第一个函数并且将函数执行结果返回给你。
记住,传入 useMemo
的函数会在渲染期间执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于 useEffect
的适用范畴,而不是 useMemo
。
useContext
推荐用unstated-next更好用且安全。https://github.com/jamiebuilds/unstated-next/blob/master/README-zh-cn.md
hooks编码注意事项
- hooks api前面不能有return(否则可能多次渲染的时候执行hooks的数量不一致,引发error)
换句话说,hooks有个要求:在使用 Hook 的时候,请在函数组件顶部使用!不能在循环、判断内部使用 Hook。
原理:《React hooks: not magic, just arrays》中提及,React Hook 看起来非常 Magic 的实现,本质上还是通过 Array 来实现的。
一定要记得开启hooks lint https://www.npmjs.com/package/eslint-plugin-react-hooks
性能优化(减少不必要的渲染)
- 函数移动到组件的外面
- 放在useEffect、useCallback里面
用useEffect的时候一定要小心闭包的坑。如果在useEffect里面监听一个事件,这时候用
setCount(num + 1)
的时候拿到的num
只是第一次的值,并不会每次触发事件的时候更新到新的num
。原因:state在每次更新的时候都是一个新的值。
解决方案:
setCount(prev =>new)
- 用函数的写法写
setCount
并及时销毁事件 - 用useRef迂回。
更多阅读
精度系列 https://zhuanlan.zhihu.com/p/59558396
Should I useState or useReducer? https://kentcdodds.com/blog/should-i-usestate-or-usereducer
2019年了,整理了N个实用案例帮你快速迁移到React Hooks:https://juejin.im/post/5d594ea5518825041301bbcb
[TODO] React Hooks 深入不浅出:https://juejin.im/post/5bfe93566fb9a049c30af2db
一文彻底搞懂react hooks的原理和实现:https://xin-tan.com/passages/2019-10-21-react-hooks/
[TODO] react hooks 原理:https://github.com/brickspert/blog/issues/26
Dan 博客 memory cells https://medium.com/@dan_abramov/making-sense-of-react-hooks-fdbde8803889