30分钟带你全面了解React Hooks

概述

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的资料请关注我们其它相关文章!

(0)

相关推荐

  • React 使用Hooks简化受控组件的状态绑定

    开始之前 阅读本文需要对以下几项有一定了解 ECMAScript 6 文章中大量用到了 ES6 语法,比如解构赋值和函数参数默认值.剩余参数.展开语法.箭头函数等. Hooks React 在 16.8 版本中推出了 Hooks,它允许你在"函数组件"中使用"类组件"的一些特性. React 本身提供了一些 Hooks,比如 useState.useReducer 等.通过在一个以"use"作为命名起始的函数中调用这些 Hooks,就得到了一个

  • React Hooks 实现和由来以及解决的问题详解

    与React类组件相比,React函数式组件究竟有何不同? 一般的回答都是: 类组件比函数式组件多了更多的特性,比如 state,那如果有 Hooks 之后呢? 函数组件性能比类组件好,但是在现代浏览器中,闭包和类的原始性能只有在极端场景下才会有明显的差别. 性能主要取决于代码的作用,而不是选择函数式还是类组件.尽管优化策略有差别,但性能差异可以忽略不计. 参考官网:(https://zh-hans.reactjs.org/docs/hooks-faq.html#are-hooks-slow-b

  • 如何对react hooks进行单元测试的方法

    写在前面 使用 react hook 来做公司的新项目有一段时间了,大大小小的坑踩了不少.由于是公司项目,因此必须要编写单元测试来确保业务逻辑的正确性以及重构时代码的可维护性与稳定性,之前的项目使用的是 react@15.x 的版本,使用 enzyme 配合 jest 来做单元测试毫无压力,但新项目使用的是 react@16.8 ,编写单元测试的时候,遇到不少阻碍,因此总结此篇文章算作心得分享出来. 配合 enzyme 来进行测试 首先,enzyme 对于 hook 的支持程度,可以参考这个 i

  • 30分钟精通React今年最劲爆的新特性——React Hooks

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? --拥有了Hooks,生命周期钩子函数可以先丢一边了. 你在还在为组件中的this指向而晕头转向吗? --既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this. 这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张.如果你也对react

  • react的hooks的用法详解

    hooks的作用 它改变了原始的React类的开发方式,改用了函数形式;它改变了复杂的状态操作形式,让程序员用起来更轻松;它改变了一个状态组件的复用性,让组件的复用性大大增加. useState // 声明状态 const [ count , setCount ] = useState(0); // 使用状态 <p>You clicked {count} times</p> <button onClick={()=>{setCount(count+1)}}>cli

  • React Hooks的深入理解与使用

    你还在为该使用无状态组件(Function)还是有状态组件(Class)而烦恼吗? --拥有了hooks,你再也不需要写Class了,你的所有组件都将是Function. 你还在为搞不清使用哪个生命周期钩子函数而日夜难眠吗? --拥有了Hooks,生命周期钩子函数可以先丢一边了. 你在还在为组件中的this指向而晕头转向吗? --既然Class都丢掉了,哪里还有this?你的人生第一次不再需要面对this. 这样看来,说React Hooks是今年最劲爆的新特性真的毫不夸张.如果你也对react

  • React hooks的优缺点详解

    前言 Hook 是 React 16.8 的新增特性.它是完全可选的,并且100%向后兼容.它可以让你使用函数组件的方式,运用类组件以及 react 其他的一些特性,比如管理状态.生命周期钩子等.从概念上讲,React 组件一直更像是函数.而 Hook 则拥抱了函数,同时也没有牺牲 React 的精神原则. 优点: 1.代码可读性更强,原本同一块功能的代码逻辑被拆分在了不同的生命周期函数中,容易使开发者不利于维护和迭代,通过 React Hooks 可以将功能代码聚合,方便阅读维护.例如,每个生

  • 记录一次完整的react hooks实践

    写在前面 React在16.8版本正式发布了Hooks.关注了很久,最近正好有一个小需求,赶紧来试一下. 需求描述 需求很简单,部门内部的一个数据查询小工具.大致长成下面这样: 用户首次访问页面,会拉取数据展示.输入筛选条件,点击查询后,会再次拉取数据在前端展示. 需求实现 使用React Class Component的写法 如果使用以前的class写法,简单写一下,代码可能大概长成下面这样: import React from 'react'; import { Tabs, Input, R

  • 详解如何使用React Hooks请求数据并渲染

    前言 在日常的开发中,从服务器端异步获取数据并渲染是相当高频的操作.在以往使用React Class组件的时候,这种操作我们已经很熟悉了,即在Class组件的componentDidMount中通过ajax来获取数据并setState,触发组件更新. 随着Hook的到来,我们可以在一些场景中使用Hook的写法来替代Class的写法.但是Hook中没有setState.componentDidMount等函数,又如何做到从服务器端异步获取数据并渲染呢?本文将会介绍如何使用React的新特性Hook

  • 30分钟带你全面了解React Hooks

    概述 1. Hooks 只能在函数组件内使用: 2. Hooks 用于扩充函数组件的功能,使函数组件可以完全代替类组件 React Hooks 都挂在 React 对象上,因此使用时为 React.useState() 的形式,若嫌麻烦,可以提前导入,如下: import React, { useState } from "react" React 内置的 Hooks 有很多,这里介绍一些常用到的.全部的请看 Hooks API 用到了 Hook 的函数组件名必须首字母大写,否则会被

  • 30分钟带你了解Docker(推荐)

    最近一直在忙项目,不知不觉2个多月没有更新博客了.正好自学了几天docker就干脆总结一下,也顺带增加一篇<30分钟入门系列>.网上能够查到的对于docker的定义我就不再重复了,说说我自己对它的理解:Docker一个方便多次部署的虚拟化Linux容器,与当下流行的SpringBoot和微服务框架搭配更加相得益彰,从而真正的做到从开发到部署的全流程灵敏.请注意这里的三个关键词:Linux容器,SpringBoot,灵敏.首先,Docker不能别用来部署本地应用(如果你有开发过基于Qt的桌面应用

  • 六分钟带你快速学会react中的useMemo

    目录 概念 useMemo 栗子 筛选偶数 每秒获取时间刷新页面 如何优化 useCallback.React.memo 总结 概念 react 中是通过一次次的 re-render (重新渲染)保持我们的值及时的更新到页面上的,每次重新渲染都是一次快照,可以把它想象成一张张的照片,在某个时刻它应该是什么样子的 useMemo 把创建函数和依赖数组项作为参数传入 useMemo,它仅仅会在数组依赖项中的值改变时才会重新计算值 这种优化有助于避免在每次渲染时都进行高开销的计算 useMemo 的函

  • 让你30分钟快速掌握vue3教程

    经过了漫长的迭代,Vue 3.0终于在上2020-09-18发布了,带了翻天覆地的变化,使用了Typescript 进行了大规模的重构,带来了Composition API RFC版本,类似React Hook 一样的写Vue,可以自定义自己的hook ,让使用者更加的灵活,接下来总结一下vue 3.0 带来的部分新特性. setup() ref() reactive() isRef() toRefs() computed() watch() LifeCycle Hooks(新的生命周期) Te

  • JS组件系列之MVVM组件 vue 30分钟搞定前端增删改查

    正文 前言:关于Vue框架,好几个月之前就听说过,了解一项新技术之后,总是处于观望状态,一直在犹豫要不要系统学习下.正好最近有点空,就去官网了解了下,看上去还不错的一个组件,就抽空研究了下.最近园子里vue也确实挺火,各种入门博文眼花缭乱,博主也不敢说写得多好,就当是个学习笔记,有兴趣的可以看看. 一.MVVM大比拼 关于MVVM,原来在介绍knockout.js的时候有过讲解,目前市面上比较火的MVVM框架也是一抓一大把,比如常见的有Knockout.js.Vue.js.AvalonJS.An

  • PHP利用Cookie设置用户30分钟未操作自动退出功能

    登陆控制器需要做的登陆成功把用户ID等信息存入cookie: $this->systemSetKey(array('name'=>$admin_info['admin_name'], 'id'=>$admin_info['admin_id'],'gid'=>$admin_info['admin_gid'],'sp'=>$admin_info['admin_is_super']));//登陆成功之后做得事情 父类中的 systemSetKey 方法: /** * 系统后台 会员

  • c#学习之30分钟学会XAML

    1.狂妄的WPF 相对传统的Windows图形编程,需要做很多复杂的工作,引用许多不同的API.例如:WinForm(带控件表单).GDI+(2D图形).DirectX API(3D图形)以及流媒体和流文档等,都需要不同的API来构建应用程序. WPF就是看着上面的操作复杂和不爽,自己决定做老大,想用DirectX技术涵盖一切,于是想要将上述的东西全部融合到自身,减少复杂度,让编程变得爽起来的技术. 而不可否认的是,WPF虽然很狂妄,但是这种技术里面还是有不少的可圈可点的东西.而支持WPF狂妄的

  • 30分钟快速入门掌握ES6/ES2015的核心内容(下)

    前言 在 30分钟掌握ES6/ES2015核心内容(上)我们讲解了es6最常用的一些语法:let, const, class, extends, super, arrow functions, template string, destructuring, default, rest arguments 俗话说打铁要趁热,今天我们继续讲es6其他几个非常有用的新特性. import export 这两个家伙对应的就是es6自己的module功能. 我们之前写的Javascript一直都没有模块化

随机推荐