天天看点

React Hooks 学习

文中很多内容都是来自以下两篇文章,我只是加了点自己的理解,如果有什么不好理解的建议直接查看原文Hook 简介React Hooks 详解【近 1W 字】+ 项目实战

Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。例如,useState 是允许你在 React 函数组件中添加 state 的 Hook。稍后我们将学习其他 Hook。

如果你在编写函数组件并意识到需要向其添加一些 state,以前的做法是必须将其转化为 class。现在你可以在现有的函数组件中使用 Hook。

只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。

只能在 React 的函数组件中调用 Hook。不要在其他 JavaScript 函数中调用。

自定义的 Hook 中调用

使组件管理变得简单使用 Hook 其中一个目的就是要解决 class 中生命周期函数经常包含不相关的逻辑,但又把相关逻辑分离到了几个不同方法中的问题。

通常一个包含副作用的组件,我们会在componentDidMount 中获取初始化数据,当依赖发生改变之后在componentDidUpdate 中更新数据,当组件要销毁时在componentWillUnmount 中清除。但是紧密联系的3个阶段,我们使用class组件,却要放在3个不同的声明周期函数里面去做,而且componentDidMount和componentDidUpdate往往会有相同的逻辑,但是现在使用hooks,我们可以在同一个地方去完成这些事情,变得更简洁清晰。

使组件间逻辑复用变得简单在Hooks出现之前,组件之间共享状态逻辑,我们使用的是render props 和高阶组件,但是现在我们可以通过自定义hooks来实现。

惰性初始化 state:

如果initialState的值是要通过一些计算得到,那么我们更希望将这个计算过程放在惰性初始化的过程之中

虽然我们可以像如下这样,来达到相同的效果,但是someExpensiveComputation(props)会在组件每一次render时,都会执行一遍(虽然这个值只在useState初始化时使用)。采用下面的写法,someExpensiveComputation函数只会在初始化渲染中被调用,后续渲染时会被忽略

如果新的 state 需要通过使用先前的 state 计算得出,那么可以将回调函数当做参数传递给 setState。该回调函数将接收先前的 state,并返回一个更新后的值。

注:Hook 内部使用 Object.is 来比较新/旧 state 是否相等

默认情况下,它在第一次渲染之后和每次更新之后都会执行。

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

componentDidMounted

componentWillUnmount

componentDidUpdate

由于useEffect第一个参数要么返回void要么返回一个回调函数,所以当我们想在useEffect中使用async/await时,需要如下:

当组件同时使用多个useState方法时,需要一个一个的声明。状态多了,就一大溜的声明。比如:

我们可以通过使用useReducer来解决这个问题。useReducer实际是useState 的一个变种,解决了上述多个状态,需要多次使用 useState 的问题。

useReducer使用

useReducer内部实现

React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 dispatch。

Context主要应用场景在于很多不同层级的组件需要访问同样一些的数据,例如theme,userInfo等等

当我们在使用react,有时候会需要使用全局状态用来解决状态跨层级传递的问题。之前我们可以通过react的context来实现。在Hooks中全局状态还是利用React 提供的Context上下文来实现跨层级数据传递,但是在全局中的状态是比较多的,我们这个时候就使用useReducer来进行状态的管理更新。我们把reducer中的state和dispatch通过Provider的value值传递下去。那么在每一个使用的customer中都可以拿到state和更改state的dispatch方法。useReducer是一个状态管理的实现,而useContext用来解决跨组件跨层级的问题 所以两个可以配合使用

当组件上层最近的 <CounterContext.Provider>的value更新时,该Hook会触发重渲染,并使用最新传递给CounterContext Provider的Context value 值。即使祖先使用 React.memo 或 shouldComponentUpdate,也会在组件本身使用 useContext 时重新渲染。

useContext(MyContext) 相当于 class 组件中的 static contextType = MyContext 或者 <MyContext.Consumer> useContext(MyContext) 只是让你能够读取 context 的值以及订阅 context 的变化。你仍然需要在上层组件树中使用 <MyContext.Provider> 来为下层组件提供 context context实际上就是发布订阅的原理

当我们的父组件因为自身状态改变而重新渲染时,会带动子组件的重新渲染,及时此时子组件的状态并没有发生改变,为了解决子组件的非必要渲染,我们使用memo函数来包装子组件,这样当子组件的props没有发生改变时,及时父组件重新渲染了,子组件也不会重新渲染。

在下面这个demo中,当我们每次点击加1按钮的时候,子组件都会重新渲染,因为父组件重新渲染了。但是实际上子组件的重新渲染是没有必要的。

为了解决上述问题,使用memo包装子组件。

当我们使用React提供的memo高阶函数包装Child组件,此时父组件重新渲染时,子组件不会跟着重新渲染

当我们的子组件中有引用类型的props时

这里我们每点击一次加1按钮,子组件都会执行,这是因为每次加1,父组件肯定是要重新render的,父组件重新render,此时即使使用了memo,但是由于Child组件的onClick属性的值是一个内联引用类型的值,而且每次父组件渲染时,这个引用值肯定会发生了变化的,所以子组件肯定也是会更新的。这时我们需要引入useCallback Hooks函数来解决这个问题。

该回调函数仅在某个依赖项改变时才会更新。

useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。

同样如果props是一个普通的引用型变量,当父组件重新渲染的时候,子组件也会重新渲染。

这个时候就要用到useMemo Hooks来解决这个问题。使用useMemo,返回一个和原本一样的对象,第二个参数是依赖项,当name发生改变的时候,才产生一个新的对象

useCallback使用场景

在组件内部,那些会成为其他useEffect依赖项的方法,建议用 useCallback 包裹,或者直接编写在引用它的useEffect中  这种情况通常出现在reset等等这些函数,可能还有多个地方调用这个函数。

如果你的function会作为props传递给子组件,请一定要使用 useCallback 包裹,对于子组件来说,如果每次render都会导致你传递的函数发生变化,可能会对它造成非常大的困扰。同时也不利于react做渲染优化。

useMemo使用场景

有些计算开销很大,我们就需要「记住」它的返回值,避免每次render都去重新计算。

由于值的引用发生变化,导致下游组件重新渲染,我们也需要「记住」这个值。

useRef 返回的 ref 对象在组件的整个生命周期内保持不变,也就是说每次重新渲染函数组件时,返回的ref 对象都是同一个(使用 React.createRef ,每次重新渲染组件都会重新创建 ref)。从概念上讲,你可以认为 refs 就像是一个 class 的实例变量。

useRef不仅仅是用来管理DOM ref的它可以存放任何变量,更改.current属性不会导致重新渲染。

使用场景:

我希望一个变量在这个组件中,即使组建重新渲染,这个值也不会变。

保存子组件提供的某一个值(可能是一个DOM元素,也可能就是一个普通对象)

因为函数组件没有实例,所以函数组件无法像类组件一样可以接收ref属性,为了使函数组件能够像类组件一样接受ref属性,我们需要使用forwardRef包装函数组件,使函数组件能够接受ref属性,包装之后的组件不会把ref属性当做props传入。

这时就要使用forwardRef  forwardRef可以将父组件中的ref对象转发到子组件中的dom元素上,子组件接受 props 和 ref 作为参数

有时候我们希望在父组件中执行,子组件提供的某些方法,在类组件中我们可以通过ref获取子组件,然后执行子组件(子组件也为类组件)中的方法,但是在函数组件中,我们无法实现这一点。因为函数组件中没有this,我们无法获取到函数子组件中的方法。这时我们可以使用useImperativeHandle配合forwardRef使用。

useImperativeHandle这个Hooks会返回一个对象, 该对象会作为父组件 current属性的值

其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新

继续阅读