React Hooks 你不来了解下?

2022-10-15,,,,

前言

最近在看 react 的新语法—— react hooks,只能一句话概括:react 语法真的是越来越强大,越写代码越少。

强烈推荐还没看 react hooks 的同学去学习下,这会让你写react 项目变得非常爽!

以前 react 组件可以看成是: 无状态组件(function定义)和有状态组件(class 定义),react hooks 出现之后,我们基本所有的组件都可以用function定义,包括有组态组件,基本废除了 写 class 语法的 复杂性,让我们写代码真正变成了函数式编程。

状态 hooks(usestate)

这里说明一点,react中所有的hooks都是一种函数,函数都是用来实现特定功能的。

usestate 提供了创建组件state的功能,用法:

const [count, setcount] = usestate(0)

usestate() 接受唯一一个状态初始值参数,返回包含状态和改变状态对应的函数的数组,这里采用 数组解构方法获得 状态变量 count ,改变状态方法 setcount。

强调一点:

usestate() 传入的初始值不一定非要是个对象,可以为普通数据类型,比如:number,string等,初始值用作组件初次渲染。

setcount() 接受一个全新的state状态,react会直接全部替换掉原来的state状态,这点和 setstate() 有所不同。

example:

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>
  )
}
export default example;

当把 example 组件渲染到页面上时,可以通过点击按钮实时改变 count 状态,react 也会根据状态改变重新渲染页面。

执行副作用操作hooks(useeffect)

之前在写 react 组件时,往往我们在组件的生命周期函数里面做一些额外的操作,比如发送ajax请求获取数据,清除定时器和异步执行任务等。

我们发现这样操作重复的代码很多,而且如果对于 react 生命周期钩子不熟悉的话,很容易出错,于是乎,useeffect() hooks 出现了。

useeffect() hooks 可以允许我们在 react 函数式组件中执行一些额外的副作用操作。

在 react 组件中有两种常见副作用操作:

  • 需要清除的,比如开启的定时器,订阅外部数据源等,这些操作如果在组件消亡后不及时清除会导致内存泄漏。

  • 不需要清除的,比如发起网络请求,手动变更 dom,记录日志等。

在 react 官网中有一段话很重要:

如果你熟悉 react class 的生命周期函数,你可以把 useeffect hook 看做 componentdidmount,componentdidupdate 和 componentwillunmount 这三个函数的组合。

example:

import react, { usestate , useeffect } from 'react';
function example(){
  const [ count , setcount ] = usestate(0);
  useeffect(()=>{
    console.log(`you clicked ${count} times`)
  })
  return (
    <div>
      <p>you clicked {count} times</p>
      <button onclick={()=>{setcount(count+1)}}>click me</button>
    </div>
  )
}
export default example;

我们在刚才的例子上新增了一个功能,每次我们 count 状态改变的时候都会在控制台打印出我们点击的次数。可以看到使用 useeffect() hooks 轻松实现。

强调一点:

  • react首次渲染和之后的每次渲染都会调用一遍useeffect函数,而之前我们要用两个生命周期函数分别表示首次渲染(componentdidmonut)和更新导致的重新渲染(componentdidupdate)。

  • useeffect中定义的函数的执行不会阻碍浏览器更新视图,也就是说这些函数是异步执行的,而componentdidmonut和componentdidupdate中的代码都是同步执行的。

  • 可以为 useeffect() 传入第二个参数,它是一个数组,数组里面表示这个副作用的操作依赖的状态变量,换句话说:如果这个副作用的操作依赖的状态变量没有改变,则不会执行副作用操作。

组件之间传值hooks(usecontext)

之前在用类声明组件时,父子组件的传值是通过组件属性和props进行的,那现在使用方法(function)来声明组件,已经没有了constructor构造函数也就没有了props的接收,那父子组件的传值就成了一个问题。react hooks 为我们准备了usecontext。

usecontext它可以帮助我们跨越组件层级直接传递变量,实现共享。

example:

一:利用 createcontext 创建上下文

import react, { usestate , createcontext } from 'react';

// 创建一个 countcontext
const countcontext = createcontext()

function example(){
  const [ count , setcount ] = usestate(0);
  return (
    <div>
      <p>you clicked {count} times</p>
      <button onclick={()=>{setcount(count+1)}}>click me</button>
      {/* 将 context 传递给 子组件,context 值由value props决定*/}
      <countcontext.provider value={count}>
        <counter/>
      </countcontext.provider>
    </div>
  )
}
export default example;

二:使用usecontext 获取上下文

对于要接收context的后代组件,只需引入 usecontext() hooks 即可。

function counter(){
  const count = usecontext(countcontext)  //一句话就可以得到count
  return (<h2>{count}</h2>)
}

强调一点:

usecontext 的参数必须是 context 对象本身:

  • 正确: usecontext(mycontext)
  • 错误: usecontext(mycontext.consumer)
  • 错误: usecontext(mycontext.provider)

当组件上层最近的 <mycontext.provider> 更新时,该 hook 会触发重渲染,并使用最新传递给 <mycontext.provider> 的 context value 值。

处理更为复杂state结构的hooks(usereducer)

前面我们使用的 usestate hooks可以为组件提供 state和操作改变state状态的方法,但往往有时候我们的state 结构更为复杂,例如 state 包含多个子值,或者下一个 state 依赖于之前的 state 等,这时候 usereducer 比 usestate 更合适。

usereducer 的用法:

const [state, dispatch] = usereducer(reducer, initialarg, init);

usereducer 接收一个形如 (state, action) => newstate 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。

我们可以使用 usereducer 来重新写我们开篇计数器的demo:

example:

const initialstate = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      return state
  }
}
// 定义 counter 组件
function counter() {
  const [state, dispatch] = usereducer(reducer, initialstate);
  return (
    <div>
      count: {state.count}
      <button onclick={() => dispatch({type: 'increment'})}>+</button>
      <button onclick={() => dispatch({type: 'decrement'})}>-</button>
    </div>
  );
}

对于熟悉 redux 的伙伴,这种写法很容易就能理解,每次通过点击按钮分发一个action来更新state状态数据。

强调一点:

react 不使用 state = initialstate 这一由 redux 推广开来的参数约定。有时候初始值依赖于 props,因此需要在调用 hook 时指定。如果你特别喜欢上述的参数约定,可以通过调用 usereducer(reducer, undefined, reducer) 来模拟 redux 的行为,但我们不鼓励你这么做。

这里是 react 官网提供的一句话,也就是说我们为 state 提供初始值的时候不能够像 redux 中利用 es6 默认参数来指定,必须得通过 usereducer来指定。这个切记,因为我也踩过坑!

对于 usereducer 和 usestate的区别主要是以下两点:

  • 当 state 状态值结构比较复杂时,使用 usereducer 更有优势。
  • 使用 usestate 获取的 setstate 方法更新数据时是异步的;而使用 usereducer 获取的 dispatch 方法更新数据是同步的。

性能优化hooks(usememo)

我们知道 使用class 形式的组件有着生命周期 shouldcompnentupdate函数用来优化组件的性能,防止每次渲染造成昂贵的开销。
使用function的形式来声明组件,失去了shouldcompnentupdate(在组件更新之前)这个生命周期,也就是说我们没有办法通过组件更新前条件来决定组件是否更新。而且在函数组件中,也不再区分mount和update两个状态,这意味着函数组件的每一次调用都会执行内部的所有逻辑,就带来了非常大的性能损耗。

usememo主要用来解决使用react hooks产生的无用渲染的性能问题。

举个例子:

在 a 组件中有两个子组件 b 和 c,当 a 组件中传给 b 的 props 发生变化时,a 组件状态会改变,重新渲染。此时 b 和 c 也都会重新渲染。其实这种情况是比较浪费资源的,现在我们就可以使用 usememo 进行优化,b 组件用到的 props 变化时,只有 b 发生改变,而 c 却不会重新渲染。

example:

// a 组件

import react from 'react';

export default ({ text }) => {
  console.log('component a:', 'render');
  return <div> a 组件:{ text }</div>
}

// b 组件

import react from 'react';

export default ({ text }) => {
  console.log('component b:', 'render');
  return <div> b 组件:{ text }</div>
}

// app 组件

import react, { usestate, usememo } from 'react';
import a from './examplea';
import b from './exampleb';

export default () => {
  const [a, seta] = usestate('a');
  const [b, setb] = usestate('b');

  const examplea = usememo(() => <a text={a} />, [a]);
  const exampleb = usememo(() => <b text={b} />, [b]);

  return (
    <div>
      { examplea }
      { exampleb }
        <br />
        <button onclick={ () => seta('a改变了') }>修改传给 a 的属性</button>
        &nbsp;&nbsp;&nbsp;&nbsp;
        <button onclick={ () => setb('b改变了') }>修改传给 b 的属性</button>
    </div>
  )
}

我们点击不同的按钮,控制台都只会打印一条输出,改变 a 或者 b,a 和 b 组件都只有一个会重新渲染。

其实 react 还有很多常用的 hooks,比如用来获取dom元素 和保存变量的useref,优化函数式组件性能的usecallback等,对于这些的hooks大家可以去react 官网了解下,相信大家应该也能搞清楚,这里就不做进一步探讨。

react hooks api

结语

本篇文章出自于我们 web-study 仓库,如果喜欢的话,欢迎给个 star!

地址:

《React Hooks 你不来了解下?.doc》

下载本文的Word格式文档,以方便收藏与打印。