• React Hooks 全解(一)


    写在前面

    什么是 React Hooks ?

    来段官网的解释: Hooks 是一种函数,该函数允许您从函数式组件 “勾住(hook into)” React 状态和生命周期功能。 Hooks 在类内部不起作用 - 它们允许你无需类就使用 React。

    因此,记住重点 1. Hooks 只能在函数组件内使用; 2. Hooks 用于扩充函数组件的功能,使函数组件可以完全代替类组件

    React Hooks 都挂在 React 对象上,因此使用时为 React.useState() 的形式,若嫌麻烦,可以提前导入,如下:

    import React, { useState } from "react"

    React 内置的 Hooks 有很多,这里介绍一些常用到的。全部的请看 Hooks API

    用到了 Hook 的函数组件名必须首字母大写,否则会被 ESLint 报错

    1. useState

    const [state, setState] = useState(initialState)

    1.1 概念三连问

    • 调用 useState 有什么作用?
      useState 是用于声明一个状态变量的,用于为函数组件引入状态。

    • 我们传递给 useState 的参数是什么?
      useState 只接收一个参数,这个参数可以是数字、字符串、对象等任意值,用于初始化声明的状态变量。也可以是一个返回初始值的函数,最好是函数,可在渲染时减少不必要的计算。

    • useState返回的是什么?
      它返回一个长度为2的读写数组,数组的第一项是定义的状态变量本身,第二项是一个用来更新该状态变量的函数,约定是 set 前缀加上状态的变量名。如 setState,setState() 函数接收一个参数,该参数可以是更新后的具体值,也可以是一个返回更新后具体值的函数。若 setState 接收的是一个函数,则会将旧的状态值作为参数传递给接收的函数然后得到一个更新后的具体状态值。

    1.2 举个例子

    function App(){
      const [n, setN] = useState(0)
      const [m, setM] = useState(() => 0)
      return (
        <div>
          n: {n}
          <button onClick={() => setN(n+1)}>+1</button>
          <br/>
          m: {m}
          <button onClick={() => setM(oldM => oldM+1)}>+1</button>
        </div>
      )
    }
    

    1.3 注意事项

    • useState Hook 中返回的 setState 并不会帮我们自动合并对象状态的属性
    • setState 中接收的对象参数如果地址没变的话会被 React 认为没有改变,因此不会引起视图的更新

    2. useReducer

    useReducer 是 useState 的升级版。在 useState 中返回的写接口中,我们只能传递最终的结果,在 setN 的内部也只是简单的赋值操作。
    也就是说,得到结果的计算过程需要我们在函数组件内的回调函数中书写,这无疑增加了函数组件的体积,而且也不符合 Flux 的思想(状态由谁产生的,谁负责进行各种处理,并暴露处理接口出去给别人用)

    因此,React 就提供了比 useState 更高级的状态管理 Hook:useReducer,介绍如下:

    2.1 使用方法

    • 创建初始状态值 initialState

    • 创建包含所有操作的 reducer(state, action) 函数,每种操作类型均返回新的 state 值

    • 根据 initialState 和 reducer 使用 const [state, dispatch] = useReducer(reducer, initialState) 得到读写 API

    • 调用写接口,传递的参数均挂在 action 对象上

    2.2 举个例子

    import React, { useReducer } from 'react';
    import ReactDOM from 'react-dom';
    
    const initialState = {
      n: 0
    }
    
    const reducer = (state, action) => {
      switch(action.type){
        case 'addOne':
          return { n: state.n + 1 }
        case 'addTwo':
          return { n: state.n + 2 }
        case 'addX':
          return { n: state.n + action.x }
        default: {
          throw new Error('unknown type')
        }
      }
    }
    
    function App(){
      const [state, dispatch] = useReducer(reducer, initialState)
      return (
        <div>
          我是 App
          {state.n}
          <button onClick={()=>dispatch({type: 'addOne'})}>+1</button>
          <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button>
          <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button>
        </div>
      )
    }
    
    
    ReactDOM.render(<App/>,document.getElementById('root'));
    

    3. useContext

    context 是上下文的意思,上下文是局部的全局变量这个局部的范围由开发者自己指定。

    3.1 使用方法

    useContext 的使用方法分三步走:

    • 使用 const x = createContext(null) 创建上下文,在创建时一般不设置初始值,因此为 null,一般是在指定上下文作用域时初始化。

    • 使用 <x.Provider value={}></x.Provider> 圈定上下文的作用域

    • 在作用域中使用 const value = useContext(x) 使用上下文的数据

    3.2 举个例子

    import React, { useState, createContext, useContext } from 'react';
    import ReactDOM from 'react-dom';
    
    const Context = createContext(null)
    
    function App(){
      const [n, setN] = useState(0)
      return (
        <Context.Provider value={{n, setN}}>
          <div>
            <Baba />
            <Uncle />
          </div>
        </Context.Provider>
      )
    }
    
    function Baba(){
      return (
        <div>
          我是爸爸
          <Child />
        </div>
      )
    }
    
    function Uncle(){
      const {n, setN} = useContext(Context)
      return (
        <div>
          我是叔叔
          我拿到的 context 数据为 {n}
        </div>
      )
    }
    
    function Child(){
      const {n, setN} = useContext(Context)
      return (
        <div>
          我是儿子
          我拿到的 context 数据为 {n}
          <button onClick={() => setN(n+5)}>
            点击改变 context 数据
          </button>
        </div>
      )
    }
    
    
    ReactDOM.render(<App/>,document.getElementById('root'));
    

    4. useEffect

    effect 是副作用的意思,对环境的改变就是副作用。副作用好像是函数式编程里的一个概念,这里不做过多解读,也不太懂。
    在 React 中,useEffect 就是在每次 render 后执行的操作,相当于 afterRender, 接收的第一个参数是回调函数,第二个参数是回调时机。可用在函数组件中模拟生命周期。

    如果同时出现多个 useEffect ,会按出现顺序依次执行

    4.1 模拟 componentDidMount

    useEffect(()=>{
      console.log('只在第一次 render 后执行')
    },[])
    

    4.2 模拟 componentDidMount + componentDidUpdate

    useEffect(()=>{
       console.log('每次 render 后都执行,包括第一次 render')
    })
    

    4.3 可添加依赖

    useEffect(()=>{
        console.log('只在 x 改变后执行,包括第一次 x 从 undefined 变成 initialValue')
    },[x])
    //如果有两个依赖,则是当两个依赖中的任何一个变化了都会执行
    

    4.3 模拟 componentWillUnmount

    useEffect(()=>{
      console.log('每次 render 后都执行,包括第一次 render')
      return ()=>{
        console.log('该组件要被销毁了')
      }
    })
    //直接 return 一个函数即可,该函数会在组件销毁前执行
    

    5. useLayoutEffect

    useEffect 总是在浏览器渲染完视图过后才执行,如果 useEffect 里面的回调函数有对 DOM 视图的操作,则会出现一开始是初始化的视图,后来执行了 useEffect 里的回调后立马改变了视图的某一部分,会出现一个闪烁的状态。
    为了避免这种闪烁,可以将副作用的回调函数提前到浏览器渲染视图的前面执行,当还没有将 DOM 挂载到页面显示前执行 Effect 中对 DOM 进行操作的回调函数,则在浏览器渲染到页面后不会出现闪烁的状态。

    layout 是视图的意思,useLayoutEffect 就是在视图显示出来前执行的副作用。

    useEffect 和 useLayoutEffect 就是执行的时间点不同,useLayoutEffect 是在浏览器渲染前执行,useEffect 是在浏览器渲染后执行。但二者都是在 render 函数执行过程中运行,useEffect 是在 render 完毕后执行,useLayoutEffect 是在 render 完毕前(视图还没渲染到浏览器页面上)执行。

    因此 useLayoutEffect 总是在 useEffect 前执行。

    一般情况下,如果 Effect 中的回调函数中涉及到 DOM 视图的改变,就应该用 useLayoutEffect,如果没有,则用 useEffect。

    6. useRef

    useRef Hook 是用来定义一个在组件不断 render 时保持不变的变量。
    组件每次 render 后都会返回一个虚拟 DOM,组件内对应的变量都只属于那个时刻的虚拟 DOM。
    useRef Hook 就提供了创建贯穿整个虚拟 DOM 更新历史的属于这个组件的局部的全局变量。
    为了确保每次 render 后使用 useRef 获得的变量都能是之前的同一个变量,只能使用引用做到,因此,useRef 就将这个局部的全局变量的值存储到了一个对象中,属性名为:current

    useRef 的 current 变化时不会自动 render

    useRef 可以将创建的 Refs 对象通过 ref 属性的方式引用到 DOM 节点或者 React 实例。这个作用在 React—ref 属性 中有介绍。

    同样也可以作为组件的局部的全局变量使用,如下例的记录当前是第几次渲染页面。

    function App(){
      const [state, dispatch] = useReducer(reducer, initialState)
      const count = useRef(0)
      useEffect(()=>{
        count.current++;
        console.log(`这是第 ${count.current} 次渲染页面`)
      })
      return (
        <div>
          我是 App
          {state.n}
          <button onClick={()=>dispatch({type: 'addOne'})}>+1</button>
          <button onClick={()=>dispatch({type: 'addTwo'})}>+2</button>
          <button onClick={()=>dispatch({type: 'addX', x: 5})}>+5</button>
        </div>
      )
    }
    

    7. forwardRef(不是 Hook)

    forwardRef 主要是用来对原生的不支持 ref属性 函数组件进行包装使之可以接收 ref属性 的,具体使用方法可参考 React—ref 属性

    forwardRef 接收一个函数组件,返回一个可以接收 ref 属性的函数组件

  • 相关阅读:
    web策略类游戏开发(五)数据库表设计
    web策略类游戏开发(一) WebGame架构篇
    SQL Server2005之初体验!
    SQL SERVER2005关于如何找到表的说明。
    脚本实现CheckBox父选中,子全选中,子选中父也选中!
    今天看到人家系统中有此效果,不知道是怎么实现的。
    忆风伶夜雨
    GridView双击某行弹出一新窗口!
    DIV位置永恒居中。
    关于:装了Visual.SourceSafe.2005之后,打开VS2005文件,找不到源代码管理这项的问题
  • 原文地址:https://www.cnblogs.com/lovevin/p/13526553.html
Copyright © 2020-2023  润新知