3.2.4:Hooks 之 useEffect&&useLayoutEffect
3.2.8:自定义 Hook解决模拟 componentDidUpdate的问题
3:React 之 Hooks
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。
3.1:Hooks 概览
Hook 是一些可以让你在函数组件里“钩入” React state 及生命周期等特性的函数。Hook 不能在 class 组件中使用 —— 这使得我们不使用 class 也能使用 React。Hook 在 class 内部是不起作用的。我们可以使用它们来取代 class 。
基础的 Hook 有 useState,useEffect,useContext,额外的 Hook 有 useReducer,useCallback,useMemo,useRef,useImperativaHandle,useLayoutEffect,useDebugValue。
3.2:Hooks 详解
3.2.1:Hooks 之 useState
看这个例子以及解释:
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
第一行: 引入 React 中的 useState Hook。它让我们在函数组件中存储内部 state。
第四行: 在 Example 组件内部,我们通过调用 useState Hook 声明了一个新的 state 变量。它返回一对值给到我们命名的变量上。我们把变量命名为 count,因为它存储的是点击次数。我们通过传 0 作为 useState 唯一的参数来将其初始化为 0。第二个返回的值本身就是一个函数。它让我们可以更新 count 的值,所以我们叫它 setCount。
第九行: 当用户点击按钮后,我们传递一个新的值给 setCount。React 会重新渲染 Example 组件,并把最新的 count 传给它。
注意事项:
如果 state 是一个对象,不能部分 setState,这一点和 class 组件的 this.setState 不一样。在 calss 组件中,是会自动合并更新 state 的。
如果更新函数返回值与当前 state 完全相同,则随后的重渲染会被完全跳过。即在改变 state 的时候,如果是引用,那么地址要改变,否则 就不会重渲染。
函数式更新:如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。该函数将接收先前的 state,并返回一个更新后的值。建议最好使用这种形式进行更新,即如果想对 n 进行加 1 操作,最好写成这种形式:setn(n=>n+1),不过 setn(n+1)也是可以的。
具体可以参考使用 state Hook和Hook API 索引
3.2.2:Hooks 之 useReducer
useReducer 可以看作是 useState 的复杂版,使用方法:
1》创建初始值 initialData
2》创建所有操作 reducer(state,action)
3》传给 useReducer,得到读和写 api
4》使用读写 api 进行具体操作({type:'操作类型'})
具体例子,页面上有三个按钮,点击不同的按钮,n 的值会进行改变:
3.2.3:Hooks 之 useContext
useContext 上下文, 使用方法:
1》使用 C=React.createContext(null)创建上下文
2》使用<C.provider>圈定作用域 3》在作用域内使用 useContext(c)去使用上下文
4》使用读写 api 进行具体操作({type:'操作类型'})
具体例子:
但需要注意的是 useContext 不是响应式的,在一个模块中将值改变,另外一个模块是不会感知到的。
3.2.4:Hooks 之 useEffect&&useLayoutEffect
useEffect 副作用,可以理解为 afterRender,每次 render 之后会调用的函数。可以代替之前的三种钩子。作为 componentDidMount 使用,[]作第二个参数;作为 componentDidUpdate 使用,可指定依赖;作为 componentWillUnmount 使用,通过 return。这三种用途可同时存在。如果同时存在多个 useEffect,会按照出现次序执行。
具体例子:
下面三张图是使用 useEffect 作为 componentWillUnmount 使用。
此处还有一个 useLayoutEffect 布局副作用,它是在浏览器渲染前执行。但因为大部分时候,我们很少去改变 DOM。为了用户体验,优先使用 useEffect。
3.2.5:Hooks 之 useMemo
首先理解 React.memo,看下面的例子,点击 n 的时候,Child 组件虽然没有变化,但也会重新执行。
这是个多余的操作,如何消除,使用 React.memo。
但是有 bug,即添加了监听函数之后,就又不行了。原因在于》点击 n 之后,app 会重新执行,导致 onClickChild 也重新执行,虽然都是空函数,但每次生成的地址不一样。这样在 Child2 组件中,onClick 这个 props 会被认为变化了,因此 Child 组件还是会重新渲染。
如何解决这个问题?使用 useMemo:
useMemo 可以使用 useCallback 作为语法糖。即 useMemo(()=>x=>console.log(x),[m])和 useCallBack(x=>console.log(x),[m])效果是一样的。
3.2.6:Hooks 之 useRef
如果需要一个值,在组件不断 render 的时候保持不变,这时就要用到 useRef。初始化 const count = React.useRef(0);读取 count.current。另外 useRef 变化的时候不会自动 render。
接下来说一下 forwardRef,我们可以像下图那样使用一个 ref 去直接得到 DOM 对象:
但在函数组件中,props 无法传递 ref 的值,所以会有如下报错:
使用 React.forwardRef 去解决:
此外和 ref 相关的还有一个 useImperativeHandle,但不常用,就不多赘述了。
3.2.7:自定义 Hook
可以在自定义 Hook 里使用 Context,而且 useState 只说了不能在 if 里,但是能在函数组件里运行。下面是一个自定义 Hook 的例子。自定义了一个 useList 的 Hook,返回一个 list 和 setList 的读和写操作。
接下来就可以使用自定义的 useList,
3.2.8:自定义 Hook解决模拟 componentDidUpdate的问题
因为class组件里的componentDidUpdate 这个钩子时,首次渲染是不会执行的,但是我们在函数组件中用 useEffect 去模拟该生命周期的时候,首次渲染是会执行的。这个问题怎么解决?
const useUpdate = (fn,deps)=>{
const count = useRef(0)
useEffect(()=>{
count.current++
})
useEffect(()=>{
if(count.current>1){
fn()
}
},[fn,deps])
}
上面这个自定义hook-useUpdate,接收一个函数和变量。当我们需要在函数组件里使用类似class组件里的componentDidUpdate这个生命周期时,就用useState去模拟。
另外我们还可以使用自定义hook代替redux去管理数据