React hooks使用经验

useState

useState接收的值只作为初始渲染时的状态,后续的重新渲染的值都是通过setState去设置

1. 函数式更新

除了常规的setState(value)的方式去更新状态以外,setState还可以接收一个函数来更新状态。这种更新状态的方式通常使用在新的 state 需要通过使用先前的 state 计算得出的场景。

在effect 的依赖频繁变化的场景下有时也可以通过函数式更新状态来解决问题,例如一个每秒中自增状态的场景:
React hooks使用经验_第1张图片

但是依赖项设置后会导致每次改变发生时定时器都被重置,这并不是我们想要的,所以这时就能够通过函数式更新状态并且不引用当前state。

React hooks使用经验_第2张图片

2. 惰性初始 state

一些需要复杂计算的初始状态如果直接将函数运行结果传入useState,会在每次重新渲染时执行所传入的函数,比如:
React hooks使用经验_第3张图片

codesandbox

所以可以向useState中传入一个函数,在函数中计算并返回初始的 state,此函数只在初始渲染时被调用codesandbox

useEffect

useEffect用来完成副作用。

## 1. 处理副作用与class组件的区别
在class组件中,通常做法是在生命周期中去检查props.A 和 state.B,如果符合某个条件就去触发XXX副作用。而在function组件中,思维模式是我需要处理XXX副作用,它的依赖数据是props.A 和 state.B。从之前的命令式转变到function组件的声明式,开发者不再需要将精力投入到思考不同生命周期判断各种条件再执行副作用这样的事中,而可以将精力聚焦在更高的抽象层次上。

## 2. 需要将effect中用到的所有组件内的值都要包含在依赖中
React在每次渲染都有这次对应的state、props、事件处理函数和effect,如果设置了错误的依赖就可能会导致副作用函数中所使用到的值并不是最新的。

举个例子,我们来写一个每秒递增的计数器。在Class组件中,我们的直觉是:“开启一次定时器,清除也是一次”。这里有一个例子说明怎么实现它。当我们理所当然地把它用useEffect的方式翻译,直觉上我们会设置依赖为[],因为“我只想运行一次effect”。

React hooks使用经验_第4张图片

然而,这个例子只会递增一次。因为副作用函数只在第一次渲染时候执行,第一次渲染对应的count是0,所以定时器中永远是setCoun(1)。

依赖项是我们给react的提示,告诉react不必每次渲染都去执行effect,只需要在依赖项变动时才去执行对应的effect。错误的依赖项导致effect中拿到的状态的值可能跟上一次effect执行时一样而不是最新的状态。

类似于这样的问题是很难被想到的,所以需要启用 eslint-plugin-react-hooks 中的 exhaustive-deps 规则,此规则会在添加错误依赖时发出警告并给出修复建议。

## 3. 优化依赖项
尽量设置少的依赖项

### 在effect内部去声明它所需要的函数
比如在某些情况下,组件内函数和effect依赖同一个state

React hooks使用经验_第5张图片

因为doSomething这个函数并没有被使用到多个地方,所以可以将它声明到effect内部去减少依赖项
React hooks使用经验_第6张图片

### 函数使用useCallback去包裹
和上面一种情况类似,但是doSomething这个函数在多个地方使用

React hooks使用经验_第7张图片

这种情况不方便把一个函数移动到 effect 内部,可以将函数使用useCallback去包裹这个函数

React hooks使用经验_第8张图片

### 将函数声明到组件外部
如果函数没有使用到组件内的值,可以将函数声明到组件外部以减少依赖项

React hooks使用经验_第9张图片

### 使用函数式更新状态来减少依赖项
比如在依赖当前状态来更新状态的情况下,可以使用函数式更新状态来减少依赖项,就像上面useState中所举的例子一样。

# useRef

## 1. 使用useRef保存组件中所需要的的唯一实例对象

比如在function组件中去使用rxjs数据流时,需要在组件挂载和销毁时监听和取消监听,如果在组件外去定义subject,全局监听的都是同一个observable

React hooks使用经验_第10张图片

这样在其中任一个组件中next值时,都会被所有观察者接收到。

所以这里需要保持每个组件有自己独立的observable,并且它又不需要作为状态去参与渲染,所以这块使用useRef去保存这个observable。

React hooks使用经验_第11张图片

这样就达到了目的。

2. 保存一些不参与渲染的值

当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染,所以我们可以使用useRef去保存一些不参数渲染的值。

# useMemo useCallback

useMemo和useCallback一起作为组件渲染优化的选择而出现,但是它们不能作为性能优化的银弹而去在任何情况下去使用。

我们需要知道这两个api本身也有开销。它们 会「记住」一些值,同时在后续 render 时,将依赖数组中的值取出来和上一次记录的值进行比较,如果不相等才会重新执行回调函数,否则直接返回「记住」的值。这个过程本身就会消耗一定的内存和计算资源。因此,过度使用 useMemo/useCallback 可能会影响程序的性能。

所以要想合理使用 useMemo/useCallback,我们需要搞清楚 它们 适用的场景:

  • 有些计算开销很大,我们就需要「记住」它的返回值,避免每次 render 都去重新计算。
  • 由于值的引用发生变化,导致下游组件重新渲染,我们也需要「记住」这个值。

# useReducer
useState的替代模式,在状态之间逻辑复杂时使用useReducer可以将what和how分开,只需要在组件中声明式的dispatch对应的行为,所有行为的具体实现都在reducer中维护,让我们的代码可以像用户的行为一样,更加清晰。

这是一个登录demo, 通过useReducer将登录的相关状态抽离出组件内部,防止组件内部多处去维护这些状态,组件内部只需要通过dispatch行为来完成各种交互。

如果你的页面state比较复杂(state是一个对象或者state非常多散落在各处)请使用userReducer
# useContext

## 与useReducer结合使用,代替将回调函数作为参数向下传递的方式,改为共享context中的dispatch

改写之前的登录demo,将登录button改为子组件,通过useContext去拿到dispatch

如果你的页面组件层级比较深,并且需要子组件触发state的变化,可以考虑useReducer + useContext

参考文章:

你可能感兴趣的