30分钟带你全面了解React Hooks

 更新时间:2021年05月15日 09:39:17   作者:lovin  
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.4 模拟 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 属性的函数组件

8. useMemo && useCallback

React 框架是通过不断地 render 来得到不同的虚拟 DOM ,然后进行 DOM Diff 来进行页面 DOM 的选择性更新的,因此,在每次的 render 之后都会短时间内存在新旧两个虚拟 DOM 。

对于组件内包含子组件的情况,当父组件内触发 render 时,就算子组件依赖的 props 没有变化,子组件也会因为父组件的重新 render 再次 render 一遍。这样就产生了不必要的 render 。

为了解决不必要的 render ,React 提供了 React.memo() 接口来对子组件进行封装。如下:

function App(){
  const [n, setN] = useState(0)
  const [m, setM] = useState(0)
  return (
    <div>
      我是父组件
      n: {n}
      <button onClick={()=>setN(n+1)}>n+1</button>
      <button onClick={()=>setM(m+1)}>m+1</button>
      <Child value={m}/>  //这样当子组件依赖的 m 值没有变化时,子组件就不会重新 render
    </div>
  )
}

const Child = React.memo((props)=>{
  useEffect(()=>{
    console.log('子组件 render 了')
  })
  return (
  <div>我是子组件,我收到来自父组件的值为:m {props.value}</div>
  )
})

但是上述方式存在 bug,因为 React.memo 在判断子组件依赖的属性有没有发生改变时仅仅是做的前后值是否相等的比较,如果子组件从父组件处接收的依赖是一个对象的话,比较的就会是对象的地址,而不是对象里面的内容,因此在每次父组件重新 render 后得到的会是不同地址的对象,尽管对象里面的值没有更新,但是子组件发现地址变了也会重新 render。

为了解决这个问题,就又出来了 useMemo() Hook,useMemo 是用于在新旧组件交替时缓存复用一个函数或者一个对象,当某个依赖重新变化时才重新生成。

useMemo Hook 接收一个无参数的返回函数(或对象)的函数。并且 useMemo 必须有个依赖,告诉其在什么时候重新计算。有点类似于 Vue 的计算属性的原理。如下:

function App(){
  const [n, setN] = useState(0)
  const [m, setM] = useState(0)
  const onClickChild = useMemo(()=>{
    return () => {
      console.log(m)
    }
  },[m])  
  return (
    <div>
      我是父组件
      n: {n}
      <button onClick={()=>setN(n+1)}>n+1</button>
      <button onClick={()=>setM(m+1)}>m+1</button>
      <Child value={m} onClick = {onClickChild}/>
    </div>
  )
}

const Child = React.memo((props)=>{
  useEffect(()=>{
    console.log('子组件 render 了')
  })
  return (
    <div>
      我是子组件,我收到来自父组件的值为:m {props.value}
      <br/>
      <button onClick={props.onClick}>click</button>
    </div>
  )
})

useCallback() 是 useMemo 的语法糖,因为 useMemo 是接收一个没有参数的返回函数(或对象)的函数,会有些奇怪,因此提供了 useCallback 来直接接收函数或对象。

const onClickChild = useMemo(() => {
      console.log(m)
  },[m])

9. useInperativeHandle

useInperativeHandel 是和 ref 相关的一个 Hook。

我们知道,ref 属性是会将当前的组件实例或 原生DOM 直接赋值给传入的 Ref 对象的 current 属性上,而且函数组件不能接收 ref 属性,因为函数组件没有实例。但是如果函数组件经过 React.forwardRef() 封装过后 可以接收 ref,一般情况下,这个 ref 是访问的经过函数组件转发过后的 原生DOM,但是,如果在函数组件内不仅仅是想让外来的 ref 指向一个 原生DOM 呢?可不可以让函数组件的 ref 像类组件中的 ref 指向实例一样拥有更多的可控性操作呢?React 就为函数组件提供了一种封装返回的 ref 指向的对象的方法,就是 useInperativeHandle Hook。

9.1 举个例子

function App(){
  const myRef = useRef(null)
  useEffect(()=>{
    console.log(myRef.current.real)
    console.log(myRef.current.getParent())
  }, [])
  return (
    <div>
      我是父组件
      <Child ref={myRef}/>
    </div>
  )
}

const Child = forwardRef((props, ref)=>{
  const childRef = useRef(null)
  useImperativeHandle(ref, ()=>{
    return {
      real: childRef.current,
      getParent(){
        return childRef.current.parentNode
      }
    }
  })
  return (
    <div>
      我是子组件,我有一个子DOM
      <button ref={childRef}>按钮</button>
    </div>
  )
})

10. 自定义 Hook

自定义 Hook 就是自定义一个函数,这个函数必须以 use 开头,并且,该函数里必须用到原生的 Ract Hooks,返回值一般是一个数组或一个对象,用于暴露该 Hooks 的读写接口。

自定义 Hook 通常是将函数组件中多次用到的 hook 整合到一起,尽量在函数组件中不要出现多次 hook 操作。

以上就是30分钟带你全面了解React Hooks的详细内容,更多关于全面了解React Hooks的资料请关注脚本之家其它相关文章!

相关文章

  • 深入了解React中的虚拟DOM

    深入了解React中的虚拟DOM

    欢迎来到今天的探险之旅!在这篇博客中,我们将深入了解 React 中神奇的虚拟DOM,并通过一个简单的例子来揭开其神秘面纱,文中通过代码示例也讲解非常详细,感兴趣的朋友可以参考下
    2024-01-01
  • React表单容器的通用解决方案

    React表单容器的通用解决方案

    本文主要介绍了React表单容器的通用解决方案,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • React实现合成事件的源码分析

    React实现合成事件的源码分析

    React 中的事件,是对原生事件的封装,叫做合成事件。抽象出一层合成事件,是为了做兼容,抹平不同浏览器之间的差异。本文将从事件绑定和事件触发角度,带大家解读下源码,感兴趣的可以了解一下
    2022-12-12
  • React antd tabs切换造成子组件重复刷新

    React antd tabs切换造成子组件重复刷新

    这篇文章主要介绍了React antd tabs切换造成子组件重复刷新,需要的朋友可以参考下
    2021-04-04
  • React深入分析更新的创建源码

    React深入分析更新的创建源码

    React组件分为函数组件与class组件;函数组件是无状态组件,class称为类组件;函数组件只有props,没有自己的私有数据和生命周期函数;class组件有自己私有数据(this.state)和生命周期函数
    2023-01-01
  • react电商商品列表的实现流程详解

    react电商商品列表的实现流程详解

    这篇文章主要介绍了react实现电商商品列表的流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-08-08
  • React组件化学习入门教程讲解

    React组件化学习入门教程讲解

    React是现在前端使用频率最高的三大框架之一,React率先提出虚拟DOM的思想和实现,使其保持有良好的性能。本篇文章将对React组件化的入门学习进行讲解,同时针对模块化的思想进行概述,为接下来组件化开发的文章进行知识储备
    2022-09-09
  • react如何快速设置文件路径别名

    react如何快速设置文件路径别名

    React是用于构建用户界面的JavaScript库, 起源于Facebook的内部项目,这篇文章主要介绍了react如何快速设置文件路径别名,需要的朋友可以参考下
    2021-04-04
  • React Fiber结构的创建步骤

    React Fiber结构的创建步骤

    这篇文章主要介绍了React Fiber结构的创建步骤,帮助大家更好的理解和学习使用React,感兴趣的朋友可以了解下
    2021-04-04
  • React中的axios模块及使用方法

    React中的axios模块及使用方法

    axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中,本文给大家分享React中axios模块的使用方法,感兴趣的朋友一起看看吧
    2022-03-03

最新评论