Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
Hook 是什么? Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。
react hooks的内置hook有:
useState()、useEffect()、useContext()、useMemo()、useCallback()、useRef()、useImperativeHandle()、自定义hook
useState()
先来看一下用useState()和class组件操作state的不同吧!
使用useState()
使用class操作state
在constructor()构造函数中,通过this.state对象进行初始化设置{count:0}把this.state.count的初始值设置为1,在函数组件中,没有this,需要用到useState()Hook来操作state变量。
调用 useState 方法的时候做了什么? 它定义一个 “state 变量”--count,这是一种在函数调用时保存变量的方式 ,它与 class 里面的 this.state 提供的功能完全相同。一般来说,在函数退出后变量就会”消失”,而 state 中的变量会被 React 保留 ---- 闭包原理。
useState 需要哪些参数? useState() 方法里面唯一的参数就是初始 state,在示例中,只需使用数字来记录用户点击的次数,所以我们传了 0 作为变量的初始 state(如果需要在 state 中存储两个不同的变量,只需调用 useState() 两次即可)。
useState 方法的返回值是什么? 返回值为:当前 state 以及更新 state 的函数。这就是我们写 const [count, setCount] = useState() 的原因。这与 class 里面this.state.count 和 this.setState 类似,唯一区别就是你需要成对的获取它们。
读取state变量方式的不同:
通过class定义的state:{ this.state.count }函数式组件获取state变量:{ count }
更新state变量方式的不同:
通过class定义的state:需要调用 this.setState() 来更新 count 值
通过function定义的state:在定义的函数式组件中已经有了 setCount 和 count 变量,所以我们不需要 this,可通过setCount直接操作state。
函数式组件代码解读:
第一行: 引入 React 中的 useState Hook。它让我们在函数组件中存储内部 state。
第四行: 在 Test 组件内部,我们通过调用 useState Hook 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。我们把变量命名为 count,因为它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化为 0。第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount。
第八行: 当用户点击按钮后,我们传递一个新的值给 setCount。React 会重新渲染 Test 组件,并把最新的 count 传给它。
"[ ]"是数组解构的操作
const[fruit,setFruit]=useState('banana'); 等价于下边代码 varfruitStateVariable=useState('banana');// 返回一个有两个元素的数组varfruit=fruitStateVariable[0];// 数组里的第一个值 varsetFruit=fruitStateVariable[1];// 数组里的第二个值
useEffect()
在useEffect hook可以在函数里执行一些副作用操作,如:数据获取的请求,设置订阅(事件监听)、手动更改 React 组件中的 DOM 都属于useEffect()(副作用)。
我理解的副作用就是在不停的完善通过脚手架搭建出来的一个空架子,从形式上看,useEffect()=componentDidMount+componentDidUpdate+componentWillUnmount。useEffect中的方法,是在render()完DOM后再执行的,好比于class组件中执行完render后再执行componentDidMount方法一样。
在 React 组件中有两种常见副作用操作:需要清除的和不需要清除的
无需清除的 effect:
有时候,我们只想在 React 更新 DOM 之后运行一些额外的代码。比如发送网络请求,手动变更 DOM,记录日志,这些都是常见的无需清除的操作。
class组件中的副作用操作:
在 React 的 class 组件中,render 函数是没有任何副作用的。这就是为什么在 React class 中,我们把副作用操作放到 componentDidMount 和 componentDidUpdate 函数中。如下:
使用function组件实现dom的加载和更新:
默认情况下,useEffect在第一次渲染之后和每次更新之后都会执行。稍后进行配置后可以实现非默认操作。同时React 保证了每次运行 effect 的同时,DOM 都已经更新完毕。
性能方面:
与 componentDidMount 或 componentDidUpdate 不同,使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 使用,其 API 与 useEffect 相同。
需要清除的 effect副作用:
像订阅外部数据源,清除工作是非常重要的,可以防止引起内存泄露!
使用 Class 的示例:在 React class 中,通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它。
在 React class 中,你通常会在 componentDidMount 中设置订阅,并在 componentWillUnmount 中清除它。
使用 Hook 的示例
由于添加和删除订阅的代码的紧密性,所以 useEffect 的设计是在同一个地方执行。如果你的 effect 返回一个函数,React 将会在执行清除操作时调用它:
为什么要在 effect 中返回一个函数? 这是 effect 可选的清除机制。每个 effect 都可以返回一个清除函数。如此可以将添加和移除订阅的逻辑放在一起。它们都属于 effect 的一部分。
React 何时清除 effect? React 会在组件卸载的时候执行清除操作。正如之前学到的,effect 在每次渲染的时候都会执行。这就是为什么 React 会在执行当前 effect 之前对上一个 effect 进行清除。
使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。
可以发现 document.title 的逻辑被分割到 componentDidMount 和 componentDidUpdate 中,订阅逻辑又被分割到 componentDidMount 和 componentWillUnmount 中。而且 componentDidMount 中同时包含了两个不同功能的代码。当功能需求i更多时,逻辑处理非常容易混乱。
那么 Hook 如何解决这个问题呢?可以使用多个 effect,会将不相关逻辑分离到不同的 effect 中:
并不是必须为 effect 中返回的函数命名。这里我们将其命名为 cleanup 是为了表明此函数的目的,但其实也可以返回一个箭头函数或者给起一个别的名字。
class组件:
从 class 中 props 读取 friend.id,然后在组件挂载后componentDidMount()订阅好友的状态,并在卸载组件componentWillUnmount()的时候取消订阅。但是当组件已经显示在屏幕上时,state变化时会发生什么?-- 我们的组件将继续展示原来的state。而且我们还会因为取消订阅时使用错误的 ID 导致内存泄露或崩溃的问题,所以在 class 组件中,我们需要添加 componentDidUpdate 来解决这个问题:
https://react.docschina.org/docs/hooks-effect.html -- useEffect hook官网链接
并不是每一次的dom渲染都必须执行useEffect()的,毕竟渲染是很消耗性能能的,所以在当dom加载的数据与上次渲染的数据一致时,应当跳过useEffect()操作,优化性能,那么该如何实现这样的操作呢?
在 class 组件中,我们可以通过在 componentDidUpdate 中添加对 prevProps 或 prevState 的比较逻辑解决:
如果某些特定值在两次重渲染之间没有发生变化,你可以通知 React 跳过对 effect 的调用,只要传递数组作为 useEffect 的第二个可选参数即可:
而useEffect 默认就会处理更新问题,对前一个useEffect() 进行清理。
同理,对于有清除操作的 effect 同样适用
1、仅执行一次。给useEffect多传一个空数组[],比如:
useEffect(()=>{document.title=`Clicked ${count} times`;},[]);
2、选择性的执行。给useEffect多传一个[count],比如:
useEffect(()=>{document.title=`Clicked ${count} times`;},[count]);
数组参数前面的方法,是否执行,依赖于数组的值前后两次是否变化。
3、每次刷新都执行一遍。不传任何参数,比如:
useEffect(()=>{document.title=`Clicked ${count} times`;});
useContext
看下方代码便可大致了解useContext()的用法啦~
借助React.createContext 和 useContext(),我们拥有了一种 “透传” 的的能力,能将顶层的属性,一次传递到任意子层级的组件,而不需要层层接力式的传递。
useReducer:
useState 的替代方案。它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
useCallback:
当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate)的子组件时,它将非常有用
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)
useMemo:
useMemo能记忆一个方法执行的结果值,假如下次刷新组件时,依赖不变,则useMemo不会执行这个方法,而是直接拿到上次记忆的值 -- 类似于vue中的computed。如果这个方法是个耗时运算,或是返回一个组件,当依赖不变,就直接拿记忆值,这样就能起到性能优化的效果。如果没有提供依赖项数组,useMemo 在每次渲染时都会计算新的值。
useRef:
是用对象引用方式,用户代码可以用它来做一般数据的缓存。说白了还是一种持久化。
seRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内保持不变。
React.memo:
functionMyComponent(props){// render using props}functionareEqual(prevProps,nextProps){// return true if passing nextProps to render would return// the same result as passing prevProps to render,// otherwise return false}exportdefaultReact.memo(MyComponent,areEqual);
总结一下
1、useState和useRef钩子行为相似。
2、useContext具有透传能力
3、其他钩子在于依赖。
4、捕获值的这个特性是我们写钩子最最需要注意的问题,它是函数特有的一种特性,并非函数式组件专有。函数的每一次调用,会产生一个属于那一次调用的作用域,不同的作用域之间不受影响。
其他
react-redux的钩子
状态管理方面,React 社区最有名的工具当然是 Redux。在 react-redux@7.1 中新引用了三个 API:
useSelector。它有点像 connect() 函数的第一个参数 mapStateToProps,把数据从 state 中取出来;
useStore 。返回 store 本身;
useDispatch。返回 store.dispatch。
关于测试
觉得还是到改了一部分 hooks 写法后,在加单元测试。现在堆积了很多逻辑的class组件真心难写。如何测试使用了 Hook 的组件