React系列三:Hooks

206 阅读6分钟

3:React 之 Hooks

    3.1:Hooks 概览

    3.2:Hooks 详解

        3.2.1:Hooks 之 useState

        3.2.2:Hooks 之 useReducer

        3.2.3:Hooks 之 useContext

        3.2.4:Hooks 之 useEffect&&useLayoutEffect

        3.2.5:Hooks 之 useMemo

        3.2.6:Hooks 之 useRef

        3.2.7:自定义 Hook

        3.2.8:自定义 Hook解决模拟 componentDidUpdate的问题

        3.2.9:stale closure


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 HookHook 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去管理数据

3.2.9:stale closure