React Hooks基本使用详解

MRI图像切片(nii格式转2D) 代码参考:将一个MRI(nii格式)数据进行切片 想要批量抽取MRI数据的几个切片,借鉴了上述代码的Python版本,原始代码抽取了全部切片。本次实验主要是抽取sMRI的横断面(axial)方向的其中30张切片,数量根据自己需求可自行设置。 import scipy, numpy, shutil, os, nibabel import sys, getopt import imageio def niito2D(filepath): inputfiles = os.listdir(filep 阅读详情

React Hooks基本使用详解

Hooks let you use state and other React features without writing a class

Hooks可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性

一、类组件不足:

  • 状态难以复用(渲染属性Render Props、高阶组件HOC、Mixin、Hooks可以解决这个问题)在hooks出来之前,常见的代码重用方式是 HOC 和render props,这两种方式带来的问题是:你需要解构自己的组件,同时会带来很深的组件嵌套
    • 缺少复用机制
    • 渲染属性Render Props和高阶组件HOC导致层级冗余
      • Render Props
        • å
        • image-20210912214946082
      • HOC(调用时候比render props方便)
        • image-20210912215446423
        • image-20210912215522321
  • 趋向复杂难以维护
    • 生命周期函数经常包含不相干逻辑
    • 相干逻辑被打散在不同生命周期,理解代码逻辑也很吃力
  • this指向困扰
    • 内联函数过度创建新句柄(每次都是新的,会重新触发,导致子组件不停渲染)
    • 类成员函数不能保证this

image-20210912204940022

image-20210912220051867

二、Hooks优势(优化类组件的三大问题):

  • 函数组件无this问题(都在函数内部,没有实例化的概念)
  • 自定义Hook方便复用状态逻辑
  • 副作用的关注点分离(不是发生在数据向视图转化之中,都是在之外的。例如:发起网络请求、访问原型上的DOM元素、写本地持久化缓存、绑定解绑事件都是数据渲染视图之外的。这些一般都是放在生命周期中的。useEffect都是在每次渲染完成之后调用)

image-20210912205030495

image-20210912221047875

研发挑战:

image-20210912192246856

一开始的时候觉得 hooks 非常地神秘,写惯了 class 式的组件后,我们的思维就会定格在那里,生命周期,state,this等的使用。 因此会以 class 编写的模式去写函数式组件,导致我们一次又一次地爬坑,接下来我们就开始我们的实现方式讲解。(提示:以下是都只是一种简单的模拟方法,与实际有一些差别,但是核心思想是一致的)

三、Hooks的API

1.使用 State Hook

演示地址:2.useState class类 Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useClassState.js

import React, { Component } from "react";

class UseClassState extends Component {
  state = {
    count: 0
  };
  render() {
    const { count } = this.state;
    return (
      <button
        type="button"
        onClick={() => {
          this.setState({
            count: count + 1
          });
        }}
      >
        Click({count})
      </button>
    );
  }
}
export default UseClassState;

以上代码很好理解,点击按钮让 count 值加 1。

接下来我们使用 useState 来实现上述功能。

栗子:1.useState Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useStateHook.js

import React, {useState} from 'react'

function App () {
  //返回两个参数 一个是当前的状态,还有一个是修改当前状态的函数
  const [count, setCount] = useState(0)
  // 动态传入参数 //延迟初始化,提高效率,只会在初始化时候执行一次
  // const [count,setCount] = useState(()=>{return props.defaultCount||0})
  
  onst [name, setName] = useState('小圆脸儿')
  return (
    <button type="button"
      onClick={() => {setCount(count + 1) }}
    >Click({count})</button>
  )
}

在这里,useState 就是一个 Hook。通过在函数组件里调用它来给组件添加一些内部 state,React 会在重复渲染时保留这个 state。

useState 会返回一对值**:当前状态**和一个让你更新它的函数。你可以在事件处理函数中或其他一些地方调用这个函数。它类似 class 组件的 this.setState,但是它不会把新的 state 和旧的 state 进行合并。useState 唯一的参数就是初始 state。

useState 让代码看起来简洁了,但是我们可能会对组件中,直接调用 useState 返回的状态会有些懵。既然 useState 没有传入任何的环境参数,它怎么知道要返回的的是 count 的呢?而且还是这个组件的 count 不是其它组件的 count?

初浅的理解: useState 确实不知道我们要返回的 count,但其实也不需要知道,它只要返回一个变量就行了。数组解构的语法让我们在调用 useState 时可以给 state 变量取不同的名字。

useState 怎么知道要返回当前组件的 state?

因为 JavaScript 是单线程的。在 useState 被调用时,它只能在唯一一个组件的上下文中。

有人可能会问,如果一个组件内有多个 usreState,那 useState 怎么知道哪一次调用返回哪一个 state 呢?

这个就是按照第一次运行的次序来顺序来返回的。

接着上面的例子我们在声明一个 useState:

...
const [count, setScount] = useState(0)
const [name, setName] = useState('小圆脸儿')
...

然后我们就可以断定,以后APP组件每次渲染的时候,useState 第一次调用一定是返回 count,第二次调用一定是返回 name。

为了防止我们使用 useState 不当,React 提供了一个 ESlint 插件帮助我们检查。

优化点

通过上述我们知道 useState 有个默认值,因为是默认值,所以在不同的渲染周期去传入不同的值是没有意义的,只有第一次传入的才有效。如下所示:

...
const defaultCount = props.defaultCount || 0
const [count, setCount] = useState(defaultCount)
...

state 的默认值是基于 props,在 APP 组件每次渲染的时候 const defaultCount = props.defaultCount || 0 都会运行一次,如果它复杂度比较高的话,那么浪费的资料肯定是可观的。

useState 支持传入函数,来延迟初始化:

const [count, setCount] = useState(() => {
  return props.defaultCount || 0
})

2.使用Effect Hooks

Effect Hook 可以让你在函数组件中执行副作用操作(绑定事件、发送请求、访问DOM元素)。数据获取,设置订阅以及手动更改 React 组件中的 DOM 都属于副作用。不管你知不知道这些操作,或是"副作用"这个名字,应该都在组件中使用过它们。

副作用的时机
  • Mount 之后 对应 componentDidMount
  • Update 之后 对应 componentDidUpdate
  • Unmount 之前 对应 componentWillUnmount

现在使用 useEffect 就可以覆盖上述的情况。

为什么一个 useEffect 就能涵盖 Mount,Update,Unmount 等场景呢?

useEffect 标准上是在组件每次渲染之后调用,并且会根据自定义状态来决定是否调用还是不调用。

第一次调用就相当于componentDidMount,后面的调用相当于 componentDidUpdate。useEffect 还可以返回另一个回调函数,这个函数的执行时机很重要。作用是清除上一次副作用遗留下来的状态。

image-20210912230254965

比如一个组件在第三次,第五次,第七次渲染后执行了 useEffect 逻辑,那么回调函数就会在第四次,第六次和第八次渲染之前执行。严格来讲,是在前一次的渲染视图清除之前。如果 useEffect 是在第一次调用的,那么它返回的回调函数就只会在组件卸载之前调用了,也就是
componentWillUnmount 。

image-20210912230856569

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做componentDidMount,componentDidUpdate 和 componentWillUnmount 这三个函数的组合。

在线举栗子说明一下:4.class类 useClassEffect Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useClassEffect.js

import React, { Component } from "react";
class UseClassEffect extends Component {
  state = {
    count: 0,
    size: {
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    }
  };
  onResize = () => {
    this.setState({
      size: {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    });
  };
  // https://developer.mozilla.org/zh-CN/docs/Web/API/Window/resize_event
  componentDidMount() {
    document.title = this.state.count;
    window.addEventListener("resize", this.onResize, false);
  }
  componentWillMount() {
    window.removeEventListener("resize", this.onResize, false);
  }
  componentDidUpdate() {
    document.title = this.state.count;
  }
  render() {
    const { count, size } = this.state;
    return (
      <button
        type="button"
        onClick={() => {
          this.setState({ count: count + 1 });
        }}
      >
        Click({count}) size: {size.width}x{size.height}
      </button>
    );
  }
}
export default UseClassEffect;

对于生命周期我们在复习一遍:

image-20210913001249095

上面主要做的就是网页 title 显示count 值,并监听网页大小的变化。这里用到了componentDidMount,componentDidUpdate 等副作用,因为第一次挂载我们需要把初始值给 title, 当 count 变化时,把变化后的值给它 title,这样 title 才能实时的更新。

注意,我们需要在两个生命周期函数中编写重复的代码。

这边我们容易出错的地方就是在组件结束之后要记住销毁事件的注册,不然会导致资源的泄漏。现在我们把 App 组件的副作用用 useEffect 实现。

在线栗子5.useEffect Demo:useEffectHook:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useEffectHook.js

import React, {useState, useEffect } from "react";
function UseEffectHook(props) {
  const [count, setCount] = useState(0);

  const [size, setSize] = useState({
    width: document.documentElement.clientWidth,
    height: document.documentElement.clientHeight
  });
  const onResize = () => {
    setSize({
      width: document.documentElement.clientWidth,
      height: document.documentElement.clientHeight
    });
  };
  // 第一个useEffect代表生命周期中的componentDidUpdate,
  useEffect(() => {
    document.title = count;
  }); // 不用第二个参数的情况执行几次呢((不传数组意味着每一次执行都会))
  // (只调用一次) 第二个useEffect代表生命周期中的componentDidMount组件更新 和 componentWillMount组件卸载
  useEffect(() => {
    window.addEventListener("resize", onResize, false);
    return () => {
      window.removeEventListener("resize", onResize, false);
    };
  }, []); //第二个参数才是useEffect精髓,并且能优化性能,只有数组的每一项都不变的情况下,useEffect才不会执行
  useEffect(() => {
    console.log("count:", count);
  }, [count]); //第二个参数我们传入 [count], 表示只有 count 的变化时,我才打印 count 值,resize 变化不会打印。

  return (
    <button
      type="button"
      onClick={() => {
        setCount(count + 1);
      }}
    >
      Click({count}) size: {size.width}x{size.height}
    </button>
  );
}
export default UseEffectHook;

对于上述代码的第一个 useEffect,相比类组件,Hooks 不在关心是 mount 还是 update。用 useEffect统一在渲染后调用,就完整追踪了 count 的值。

对于第二个 useEffect,我们可以通过返回一个回调函数来注销事件的注册。回调函数在视图被销毁之前触发,销毁的原因有两种:重新渲染和组件卸载。

这边有个问题,既然 useEffect 每次渲染后都执行,难道我们每次都要绑定和解绑事件吗?当然是完全不需要,只要使用 useEffect 第二个参数,并传入一个空数组即可。第二个参数是一个可选的数组参数,只有数组的每一项都不变的情况下,useEffect 才不会执行。第一次渲染之后,useEffect 肯定会执行。由于我们传入的空数组,空数组与空数组是相同的,因此 useEffect 只会在第一次执行一次。

这也说明我们把 resize 相关的逻辑放在一起写,不在像类组件那样分散在两个不同的生命周期内。同时我们处理 title 的逻辑与 resize 的逻辑分别在两个 useEffect 内处理,实现关注点分离,不同的事情要分开放。

以上验证了hooks组件相对于类组件编写副作用逻辑类型有两点:

1.提高了代码复用

2.实现了关注点分离

我们在定义一个 useEffect,来看看通过不同参数,第二个参数的不同作用。

 useEffect(() => {
    console.log('count:', count)
  }, [count])//第二个参数我们传入 [count], 表示只有 count 的变化时,我才打印 count 值,resize 变化不会打印。


image-20210913002339993

第二个参数的三种形态,undefined,空数组及非空数组,我们都经历过了,但是咱们没有看到过回调函数的执行。

我现在描述一种场景就是在组件中访问 Dom 元素,在 Dom元素上绑定事件,在上述的代码中添加以下代码:

...
 const onClick = () => {
  console.log('click');
 }
// 新增一个 DOM 元素,在新的 useEffect 中监听 span 元素的点击事件。
// useEffect(() => {
//   document.querySelector('#size').addEventListener('click', onClick, // // // false);
// },[])
  useEffect(() => {
    document.querySelector("#size").addEventListener("click", onClick, false);
    return () => {
      document
        .querySelector("#size")
        .removeEventListener("click", onClick, false);
    };
  }, []);
  return (
    <div>
    ...
   {/* 情况一::*/}   
   {/* <span id="size">size: {size.width}x{size.height}</span>*/}
     {/* 情况二:假如我们 span 元素可以被销毁重建,我们看看会发生什么情况,改造一下代码:*/}
     {
        count%2
        ? <span id="size">我是span</span>
        : <p id="size">我是p</p>
      }
      
  </div>
)

情况一打印结果:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useEffectHook.js

image-20210914153129502

情况二打印结果:

image-20210914153816522

可以看出一旦 dom 元素被替换,我们绑定的事件就失效了,所以咱们始终要追踪这个dom 元素的最新状态。

使用 useEffect ,最合适的方式就是使用回调函数来处理了,同时要保证每次渲染后都要重新运行,所以不能给第二次参数设置 [],改造如下:

useEffect(() => {
 document.querySelector('#size').addEventListener('click', onClick, false);
   return () => {
     document.querySelector('#size').removeEventListener('click', onClick, false);
   }
})

情况三:

image-20210914153225979

无论是之前的生命周期函数,还是useEffect都是处理副作用的,之前的生命周期函数在命名的时候比较容易理解,但其实都是围绕着组件的渲染和重渲染的;useEffect抽象了一层,通过第二个参数执行的时机与生命周期是等价的,大家需要理解什么样的useEffect参数与什么样的生命周期函数是对应的,差不多也就灵活运用useEffect了。当然参数化的useEffect肯定不止有这点能耐,只要你能精确控制第二个参数,就能节省运行时性能还能写出可维护性很高的代码,无论如何还是建议多练习。

问题:useEffect的第二个参数只要在数组成员都不变的情况下才不会运行副作用,那么如何理解这个不变呢?

3.使用 Context Hooks

我们已经学了useState和useEffect,他俩已经能满足大部分的开发需求了,但是为了开发效率和性能着想,我们接下来还要认识使用率没那么高,但是在函数组件的编写中依然发挥着重要角色的几个hook函数

useContext

这个context就是React高级用法中提到的Context,复习一下Context,Context能够允许数据跨越组件层级直接传递。

使用 Context ,首先顶层先声明 Provier 组件,并声明 value 属性,接着在后代组件中声明 Consumer组件,这个 Consumer 子组件,只能是唯一的一个函数,函数参数即是 Context 的负载。如果有多个 Context , Provider 和 Consumer 任意的顺序嵌套即可。

image-20210913005848115

此外我们还可以针对任意一个 Context 使用 contextType 来简化对这个 Context 负载的获取。但在一个组件中,即使消费多个 Context, contextType 也只能指向其中一个。

image-20210913010105164

在 Hooks 环境中,依旧可以使用 Consumer,但是 ContextType 作为类静态成员肯定是用不了。Hooks 提供了 useContext,不但解决了 Consumer 难用的问题同时也解决了 contextType 只能使用一个 context 的问题。

image-20210913010242138

最基本的Context使用栗子:6.Context Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/contextDemo.js

import React, { Component, createContext, useState,useContext } from "react";
const CountContext = createContext();
// 1.使用类形式的栗子:
class Foo extends Component {
  render() {
    return (
      <CountContext.Consumer>
        {(count) => <h1>基本 Context Demo:{count}</h1>}
      </CountContext.Consumer>
    );
  }
}
// 2.接着将 Foo 改成用 contextType 的形式:
class Bar extends Component {
  static contextType = CountContext;

  render() {
    const count = this.context;

    return <h1>使用contextType Context Demo:{count}</h1>;
  }
}
// 3.接着使用 useContext 形式:
// 在函数组件中,useContext可不是紧紧可以获取一个Context,从语法上看对Context的数量完全没有限制;解决了 Consumer 难用的问题同时也解决了 contextType 只能使用一个 context 的问题。
function Counter() {
  const count = useContext(CountContext);

  return <h1>在函数组件中,useContext demo:{count}</h1>;
}
function ContextDemo(props) {
  const [count, setCount] = useState(0);
  return (
    <div>
      <button
        type="button"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        点击({count})
      </button>
      <CountContext.Provider value={count}>
        <Foo />
        <Bar />
        <Counter/>
      </CountContext.Provider>
    </div>
  );
}
export default ContextDemo;

useContext 接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的 <CountContext.Provider> 的 value prop 决定。

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

别忘记 useContext 的参数必须是 context 对象本身:

  • 正确:useContext(MyContext)
  • 错误:useContext(MyContext.Consumer)
  • 错误:useContext(MyContext.Provider) 调用了 useContext 的组件总会在 context 值变化时重新渲染。如果重渲染组件的开销较大,你可以 通过使用 memoization 来优化。

4.使用 Memo /CallBack Hooks

useCallback是useMemo的变种。

回顾:meno 用来优化函数组件重渲染的行为,当传入属性值都不变的情况下,就不会触发组件的重渲染,否则就会触发组件重渲染。

image-20210913013025135image-20210913013149740image-20210913013149740

useMemo 与 memo

image-20210913013342998

memo针对的是一个组件的渲染是否重复执行,而 useMemo 定义的是一段函数逻辑是否重复执行,本质都是利用同样的算法来判定依赖是否发生改变,进而决定是否触发特定逻辑。有很多这样的逻辑输入输出是对等的,相同的输入一定产生相同的输出,数学上称之为幂等,useMemo就可以减少重复的重复计算减少资源浪费。所以严格来讲不适用memo或者useMemo不应该会导致你的业务逻辑发生变化,换句话说memo和useMemo仅仅用来做性能优化使用。

memo和useMemo紧紧用来性能优化使用。

举个栗子:7.useMemo Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useMemoHook.js

import React, { useState, useMemo } from "react";
function Foo(props) {
  return <h1>{props.count}</h1>;
}

function UseMemoHook(props) {
  const [count, setCount] = useState(0);

  const double = useMemo(() => {
    return count * 2;
  }, [count]);

  return (
    <div>
      <button
        type="button"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click ({count}) double : ({double})
      </button>

      <Foo count={count} />
    </div>
  );
}
export default UseMemoHook;

如上所示, useMemo 语法与 useEffect 是一致的。第一个参数是需要执行的逻辑函数,第二个参数是这个逻辑依赖输入变量组成的数组,如果不传第二个参数,这 useMemo 的逻辑每次就会运行, useMemo 本身的意义就不存在了,所以需要传入参数。所以传入空数组就只会运行一次,策略与 useEffect 是一样的,但有一点比较大的差异就是调用时机, useEffect 执行的是副作用,所以一定是渲染之后才执行,但 useMemo 是需要返回值的,而返回值可以直接参与渲染,因此 useMemo 是在渲染期间完成的,有这样一前一后的区别。

稍微改动一下,count===3,则参数就变成了布尔值Boolean

const double = useMemo(() => {
    return count * 2;
  }, [count === 3]);

https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useMemoHook.js

现在能断定, count 在等于 3 之前,由于这个条件一直保存 false 不变,double 不会重新计算,所以一直是 0,当 count 等于 3, double 重新计算为 6,当 count 大于 3, double 在重新计算,变成 8,然后就一直保存 8 不变。所以只要找到你真正依赖了哪些参数,就能尽可能的避免没有必要的计算。

useMemo也可以依赖另外一个useMemo,比如:

//useMemo也可以依赖另外一个useMemo,比如:
  const half = useMemo(()=>{
    return double/4
  },[double])

但是一定不要循环依赖,会把浏览器搞崩溃的

记住,传入的 useMemo 的函数会在渲染期间执行,请不要在这个函数内部执行与渲染无关的听任,诸如副作用这类操作属于 useEffect 的适用范畴,而不是 useMemo。

你可以把 useMemo 作为性能优化的手段,但不要把它当成语义上的保证。

使用Callback Hooks

先重现一个使用 memo 优化子组件重渲染的场景:8.memo Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useMemoHook2.js

import React, { useState, useMemo, memo } from "react";
const Counter = memo(function Counter(props) {
  console.log("counter");
  return <h1>{props.count}</h1>;
});

function UseMemoHook(props) {
  const [count, setCount] = useState(0);

  //稍微改动一下,count===3,则参数就变成了布尔值Boolean
  const double = useMemo(() => {
    return count * 2;
  }, [count === 3]);
  //useMemo也可以依赖另外一个useMemo,比如:
  const half = useMemo(() => {
    return double / 4;
  }, [double]);
  return (
    <div>
      <button
        type="button"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click ({count}) double : ({double}) half:({half})
      </button>

      <Counter count={double} />
    </div>
  );
}
export default UseMemoHook;

使用 memo 包裹 Counter 组件,这样只有当 double 变化时, Counter 组件才会重新渲染,执行里面的 log,运行结果请看8.memo Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useMemoHook2.js

现在在给 Counter 中的 h1 添加一个 click 事件:

const Counter = memo(function Counter(props) {
  console.log("counter");
  return <h1 onClick={props.onClick}>{props.count}</h1>;
});

然后在 UseMemoHook2 组件中声明 onClick 并传给 Counter组件:

function UseMemoHook2(props) {
  ...
  // 优化前
  const onClick=()=>{
    console.log('onClick')
  }
  return (
    <div>
      ...
      <Counter count={double} onClick={onClick} />
    </div>
  );
}
export default UseMemoHook2;

结果请看8.memo Demo 优化前片段

image-20210914203606205

可以看出,每次点击,不管 Counter 是否有变化, Counter 组件都会被渲染。那就说明每次UseMemoHook2 重新渲染之后, onClick 句柄的变化,导致 Counter 也被连带重新渲染了。 count 经常变化可以理解,但是 onClick就不应该经常变化了,毕竟只是一个函数而已,所以我们要想办法让 onClick 句柄不变化。

想想我们上面讲的 useMemo,可以这样来优化 onClick:

// 优化后
   const onClick = useMemo(() => {
     return ()=>{
       console.log("onClick")
      } 
   },[])

由于我们传给 useMemo 的第二个参数是一个空数组,那么整个逻辑就只会运行一次,理论上我们返回的 onClick 就只有一个句柄。

运行效果:请看8.memo Demo 优化后片段

现在我们把 useCallback 来实现上页 useMemo 的逻辑。

// 优化后
   const onClick = useCallback(() => {
       console.log("onClick")
   },[])

如果 useMemo 返回的是一个函数,那么可以直接使用 useCallback 来省略顶层的函数。
u s e C a l l b a c k ( f n , d e p s ) = u s e M e m o ( ( ) = > f n , d e p s ) useCallback(fn,deps)=useMemo(()=>fn,deps) useCallback(fn,deps)=useMemo(()=>fn,deps)
大家可能有一个疑问, useCallback 这几行代码明明每次组件渲染都会创建新的函数,它怎么就优化性能了呢?

注意,大家不要误会,使用 useCallback 确实不能阻止创建新的函数,但这个函数不一定会被返回,换句话说很可能这个新创建的函数就被抛弃不用了。 useCallback解决的是:传入子组件的函数参数过度变化,导致子组件过度渲染的问题,这里一定要理解好,不要对useCallback有误解。

上述我们这个onClick函数什么都不依赖,因此useCallback的第二个参数才是空数组,也就是让useCallback的逻辑只运行一次,在实际业务中不会这么简单,至少也要更新一下状态,举个栗子:

function useMemo(props) {
...
  const [clickCount, setClickCount] = useState(0);

  const onClick = useCallback(() => {
    console.log("Click");

    setClickCount(clickCount + 1);
  }, [clickCount, setClickCount]);//setClickCount不需要写,因为 React 能保证 setState 每次返回的都是同个句柄
  ...
}

在 UseMemoHook2 组件中在声明一个 useState,然后在 onClick 中调用 setClickCount,此时 onClick 依赖 clickCount, setClickCount。

其实这里的 setClickCount 是不需要写的,因为 React 能保证 setState 每次返回的都是同个句柄。不信,可以看下官方文档 :

注意

React 会确保 dispatch 函数的标识是稳定的,并且不会在组件重新渲染时改变。这就是为什么可以安全地从 useEffect 或 useCallback 的依赖列表中省略 dispatch。

这里的场景,除了直接使用 setClickCount+1 赋值以外, 还有一种方式甚至连 clickCount都不用依赖。 setState 除了传入对应的 state 最新值以外,还可以传入一个函数,函数的参数即这个 state的当前值,返回就是要更新的值:

// 传入一个函数,函数的参数即这个 `state`的当前值,返回就是要更新的值
  const onClick = useCallback(() => {
    console.log("Click");
    setClickCount((clickCount) => clickCount + 1);
  }, []);

useMemo、useCallback小结

和 memo 根据属性来决定是否重新渲染组件一样, useMemo 可以根据指定的依赖来决定一段函数逻辑是否重新执行,从而优化性能。

如果 useMemo 的返回值是函数的话,那么就可以简写成 useCallback 的方式,只是简写而已,实际并没有区别。

需要特别注意的是,当依赖变化时,我们能断定 useMemo 一定重新执行。但是注意,即使依赖不变化我们不能假定它就一定不会重新执行,也就是说,它也可能重新执行,就是考虑内存优化的结果。总之,useMemo和useCallback是用来锦上添花的优化手段,不可以过度依赖他是否触发重新渲染,因为React没有给我们打包票说一定重新执行或者一定不重新执行,useMemo使用场景很多,特别是useCallback传递给useCallback子组件

我们可以把 useMemo, useCallback 当做一个锦上添花优化手段,不可以过度依赖它是否重新渲染,因为 React 目前没有打包票说一定执行或者一定不执行。

5.使用 Ref Hooks

image-20210915145137527

类组件中使用 Ref 一般有:
  • String Ref
  • Callback Ref
  • CreateRef

上述在函数组件中没有办法使用它们,取而代之的是 useRef Hooks。

useRef 主要有两个使用场景:

  • 获取子组件或者 DOM 节点的句柄
  • 渲染周期之间的共享数据的存储

大家可能会想到 state 也可跨越渲染周期保存,但是 state 的赋值会触发重渲染,但是 ref 不会,从这点看 ref 更像是类属性中的普通成员。

粟例说明一下:使用useRef获取子组件或者 DOM 节点的句柄:9.useRef Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/useRefHook.js

import React, {
  PureComponent,
  useState,
  useMemo,
  useCallback,
  useRef
} from "react";
// const Counter = memo(function Counter(props) {
//   console.log("counter");
//   return <h1 onClick={props.onClick}>{props.count}</h1>;
// });
class Counter extends PureComponent {
  speak() {
    console.log(`now counter is:${this.props.count}`);
  }
  render() {
    const { props } = this;
    return <h1 onClick={props.onClick}>{props.count}</h1>;
  }
}

function UseRefHook(props) {
  const [count, setCount] = useState(0);
  // 依赖多个数据变化的时候
  const [clickCount, setClickCount] = useState(0);
  const counterRef = useRef();

  //稍微改动一下,count===3,则参数就变成了布尔值Boolean
  const double = useMemo(() => {
    return count * 2;
  }, [count === 3]);
  const onClick = useCallback(() => {
    console.log("Click");
    setClickCount((clickCount) => clickCount + 1);
    console.log(counterRef.current);
    counterRef.current.speak();
  }, [counterRef]);
  return (
    <div>
      <button
        type="button"
        onClick={() => {
          setCount(count + 1);
        }}
      >
        Click ({count}) double : ({double})
      </button>
      <Counter ref={counterRef} count={double} onClick={onClick} />
    </div>
  );
}
export default UseRefHook;

下面我们来尝试一下,不用ref来保存DOM或者组件,而是保存一个普通变量:

粟例说明一下:同步渲染周期之间的共享数据的存储:

假设我有这样一个场景,组件一挂载就让count状态每秒钟自动+1,当count>=10以后就不再自动增加,显然这是一个副作用,首先我们需要引入useEffect,在UseRefHook中我们需要定义两个副作用:第一个仅作用一次,用来启动定时器;第二个始终作用用来判断count的值是否满足>=10的条件;

let it;
// 第一个ueeEffect:仅作用一次
useEffect(() => {
    it = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []);
// 第二个ueeEffect始终作用用来判断count的值是否满足>=10的条件
  useEffect(() => {
    if (count >= 10) {
      clearInterval(it);
    }
  });

image-20210915155402209

然而结果到10以后并没有停止增加,为什么?显然就是因为在clearInterval的时候,定时器的句柄it这个变量已经不是setInterval的赋值了,每次UseRefHook重渲染都会重置他,那我们把it放在state中么?用useState声明能解决么?但是it并没有参与渲染,而且弄不好在副作用里面更新或导致死循环,那么现在useRef就派上用场了:

let it = useRef();
useEffect(() => {
    it.current = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []);
  useEffect(() => {
    if (count >= 10) {
      clearInterval(it.current);
    }
  });

使用 useRef 来创建一个 it, 当 setInterval 返回的结果赋值给 it 的 current 属性。

image-20210915160013664

这回效果到10停止了更新,ref帮我们解决了这个问题,是不是很像类属性成员,如果遇见

需要访问上一次渲染时候的数据,甚至是state,就把他们同步到ref中,下一次渲染就能够正确的获取到了。

两种ref的使用场景都讲过了:

useRef 主要有两个使用场景:

  • 获取子组件或者 DOM 节点的句柄
  • 渲染周期之间的共享数据的存储

你应该熟悉 ref 这一种访问 DOM 的主要方式。如果你将 ref 对象以 <divref={myRef}/> 形式传入组件,则无论该节点如何改变,React 都会将 ref 对象的 .current 属性设置为相应的 DOM 节点。

然而, useRef() 比 ref 属性更有用**。它可以很方便地保存任何可变值**,其类似于在 class 中使用实例字段的方式。

这是因为它创建的是一个普通 Javascript 对象。而 useRef() 和自建一个 {current:...} 对象的唯一区别是, useRef 会在每次渲染时返回同一个 ref 对象。

请记住,当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。如果想要在 React 绑定或解绑 DOM 节点的 ref 时运行某些代码,则需要使用回调 ref 来实现。

在副作用里面如何判定一个元素或者组件在本次渲染和上次渲染之间有过重新创建呢?

6.自定义 Hooks

前面三篇,我们讲到优化类组件的三大问题:

  • 方便复用状态逻辑
  • 副作用的关注点分离
  • 函数组件无 this 问题

对于组件的复用状态没怎么说明,现在使用自定义 Hook 来说明一下。

首先我们把上面的例子用到 count 的逻辑的用自定义 Hook 封装起来:10.自定义Hook Demo:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/customHook.js

import React, { useState, useEffect, useRef } from "react";
function useCount(defaultCount) {
  const [count, setCount] = useState(0);
  let it = useRef();
  useEffect(() => {
    it.current = setInterval(() => {
      setCount((count) => count + 1);
    }, 1000);
  }, []);
  useEffect(() => {
    if (count >= 10) {
      clearInterval(it.current);
    }
  });

  return [count, setCount];
}

function CustomHook(props) {
  const [count, setCount] = useCount(0);
  return (
    <div>
      <h1>{count}</h1>
    </div>
  );
}
export default CustomHook;

image-20210915172334394

可以看出运行效果跟上面是一样的。

定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook。我们在函数自定义写法上似乎和编写函数组件没有区别,确实自定义组件与函数组件的最大区别就是输入与输出的区别。

再来一个特别的 Hook 加深一下映像。在上述代码不变的条件下,我们在加一个自定义 Hook 内容如下:

function useCounter(count) {
  return <h1>{count}</h1>;
}
function CustomHook(props) {
  const [count, setCount] = useCount(0);
  const Counter = useCounter(count);
  return (
    <div>
      <h2>{Counter}</h2>
    </div>
  );
}

我们自定义 useCounter Hook返回的是一个 JSX,运行效果是一样的,所以 Hook 是可以返回 JSX 来参与渲染的,更说明 Hook 与函数组件的相似性。

7.Hooks使用法则

只在最顶层使用 Hook

不要在循环,条件或嵌套函数中调用 Hook, 确保总是在你的 React 函数的最顶层以及任何 return 之前调用他们。遵守这条规则,你就能确保 Hook 在每一次渲染中都按照同样的顺序被调用。这让 React 能够在多次的 useState 和 useEffect 调用之间保持 hook 状态的正确。(如果你对此感到好奇,我们在下面会有更深入的解释。)

只在 React 函数中调用 Hook

**不要在普通的 JavaScript 函数中调用 Hook。**你可以:

  • ✅ 在 React 的函数组件中调用 Hook
  • ✅ 在自定义 Hook 中调用其他 Hook (我们将会在下一页 中学习这个。)

遵循此规则,确保组件的状态逻辑在代码中清晰可见。

8.Hooks 使用过程中注意常见问题

官方文章上说的很明白,此处不在赘述:https://zh-hans.reactjs.org/docs/hooks-faq.html

小结

本文主要是对React Hooks的基本使用的介绍,其中的DEMO链接如下:https://codesandbox.io/s/react-hooks-demo-odfnt?file=/src/index.js

所有demo都被引用在了一个index.js文件中,可根据文章介绍demo序号进行操作演示

image-20210915212403933

参考

  • 慕课《React劲爆新特性Hooks 重构旅游电商网站火车票PWA》
  • react 官网

感谢阅读

❤️关注+点赞+收藏+评论+转发❤️,原创不易,鼓励笔者创作更好的文章

关注公众号小圆脸儿,一个专注于web前端基础、工程化、面试的前端公众号

基于simulink的电动汽车电机驱动系统IGBT开路故障检测 本文介绍了基于Simulink的电动汽车电机驱动系统IGBT开路故障检测方法。通过搭建永磁同步电机(PMSM)驱动系统模型,实现IGBT开路故障注入,并设计基于电流平均值与PWM逻辑比较的故障检测算法。系统可实时采集三相电流信号,通过MATLAB Function模块实现故障诊断,当检测到PWM信号与电流不匹配时输出故障报警。仿真结果显示,系统能在2.05秒内准确识别A相上管开路故障,并通过Scope和XYGraph直观展示故障特征。该方法为电动汽车电力电子系统故障诊断与容错控制提供了实用解决方案,后续可扩 阅读详情

相关推荐

Proteus 8.17安装部署全指南:避坑、汉化与STM32库配置

Proteus是一款广泛用于单片机仿真与电路设计的EDA工具,其核心价值在于支持嵌入式系统原型验证与教学实践。Proteus 8.17版本重构了许可证机制(LIC Engine)、依赖环境(VC++ 2019/NET 4.8)及路径加载逻辑,导致传统安装方法失效。掌握正确安装部署流程,是保障原理图绘制、VSM仿真(如51/STM32/ESP32)及外设波形观测(OLED、SPI、ADC)稳定运行的前提。本文聚焦Windows平台下零失败安装路径选择、权限配置、汉化实施与高频器件(如STM32F103)元件库

weixin_33377602的博客 402

React项目知识积累(二)

是 JavaScript 中的一个内置方法,用于选择多个元素并返回一个 NodeList 对象。在上述例子中,选择了所有元素,并将它们存储在一个NodeList对象中。可以使用length属性获取所选元素的数量,并使用索引获取每个元素的属性和内容。需要注意的是,返回的是一个静态的 NodeList 对象,这意味着如果匹配的元素发生变化,NodeList 不会自动更新。如果需要动态更新匹配的元素列表,请使用或者其他动态查询节点的方法。

吃蛋糕的居居 707

中小团队必看!用Harbor+Jenkins打造轻量级K3S交付流水线(含成本优化方案)

本文为中小团队详细介绍了如何利用K3S、Harbor与Jenkins构建一套高性价比的自动化交付流水线。内容涵盖单节点K3S集群的效能配置、Harbor镜像仓库的磁盘空间优化、Jenkins共享库实现流水线复用,并提供了完整的实战案例与成本优化方案,帮助团队以较低成本快速落地高效的自动化部署。

weixin_29223603的博客 503

React 入门教程 querySelectorAll() 方法遇到的问题

虽然是英文版的,但是写得简洁明了。 https://reactjs.org/docs/add-react-to-a-website.html 打开页面之后,搜索: Reuse a Component 找到复用组件这一节。 点击这一节中的:Download the full example (2KB zipped) 代码很小,只有两个文件,直接在浏览器打开 index.html 即可看效果。 存在的...

jieming2002的博客 3918

网络攻防实验——网络监听

利用CAIN实现ARP监听。

Trieyes_of_feng的博客 3194

产品经理如何运用AI提升工作效率

AI技术的应用为产品经理的工作流程带来了显著的效率提升。从需求收集到产品上线,AI可以在多个阶段提供智能化支持,帮助产品经理更好地理解用户需求、优化产品设计、提高开发和测试效率。在未来,随着AI技术的不断发展,产品经理将能够更加高效地管理产品生命周期,为用户提供更优质的产品体验。应用 | AI + 千行百业欢迎关注我的后续博文,我将分享更多关于人工智能、自然语言处理和计算机视觉的精彩内容。

不想宅的冷同学 1336

事件捕获与冒泡(react,vue,原生)

react vue js原生事件,默认都是以冒泡的形式触发。 事件流 总是以 先捕获 从根节点 —> 祖父节点 —> 节点本身 后冒泡 节点本身 —> 祖父节点 —> 根节点 所有的事件都是默认冒泡触发,在捕获阶段不会触发。 如果将该事件设置为捕获,那么该事件则不会在冒泡阶段触发。 vue代码 <div @click = "divClick"> <a href="www.baidu.com" @click="linkClick"

秋来九月八 2212

【机器学习】随机森林(Random Forest,简称 RF):预测光伏电站功率 || 缺失数据处理 || 重复行处理...

【机器学习】随机森林(Random Forest,简称 RF):预测光伏电站功率。

追光者♂:记录、分享、总结、提升,现象级专栏《Python从入门到人工智能》作者,无惧黑暗,坚信曙光 1647

React DOM document.querySelector的替代

React DOM document.querySelector的替代

weixin_45703889的博客 768

融合双主干网络提升YOLOv8性能一种基于SOTA思想的目标检测改进

YOLOv8采用了单一的主干网络进行特征提取,通常是基于CSPDarknet作为骨干网络,在目标检测过程中通过不同的卷积层提取不同尺度的信息。虽然这种设计在大多数应用场景下表现出色,但在面对复杂背景、细节丰富的物体或小物体检测时,YOLOv8有时难以保持良好的精度。在本文中,我们提出了一种基于双主干网络的YOLOv8改进方案,通过结合先进的SOTA思想,全面提升了YOLOv8在目标检测任务中的表现。双主干网络架构。

博客 1153

被Cloudflare 5秒盾虐了半个月,终用Python破局:UA+TLS指纹伪装实战,拦截率从42%干到3%

其实5秒盾不难破,难的是摸透它的检测逻辑——它不是“一刀切”地拦截爬虫,而是通过“多维度指纹校验”,判断你是不是“真实用户”。表层:UA和HTTP头要完整、匹配;中层:TLS指纹要和真实浏览器一致(核心);深层:行为要符合人类习惯(请求间隔、JS执行)。这套方案不仅适用于Cloudflare 5秒盾,很多基于指纹检测的反爬系统(如Akamai、Imperva)也适用。2025年的爬虫,早已不是“能爬就行”,而是“爬得稳、不被封”——指纹伪装就是实现这一点的关键。

专注于Python爬虫开发,分享爬虫技巧、项目实战与反爬经验,使用Scrapy、BeautifulSoup等工具,解决数据抓取难题。 1849

React Hooks详解

React的函数式组件式本身是没有状态的和其他类似于class组件的功能,所以一开始,函数组件一般只作为容器组件存在,用来展示父级组件传入的值。而16.8之后出现的 hooks(钩子),打开了函数式组件的新大门。 useState 作用:用来记录函数式组件的状态。 使用方式: import React, { useState } from 'react' const [n, setN] = React.useState(0) 例子: import React, {useState} from

duansamve的博客 6332

ImportError: DLL load failed while importing _pydantic_core: 找不到指定的程序对应解决方案

本文主要介绍了ImportError: DLL load failed while importing _pydantic_core: 找不到指定的程序对应解决方案,希望能对使用Python的同学们有所帮助。 文章目录 1. 问题描述 2. 解决方案

weixin_43178406的博客 19万+

ReactJS Hooks 总结

ReactJS Hooks 总结 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。 新的改变 我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客: 全新的界面设计 ,将会带来全新的写作体验; 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的

Mr_Four 557

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

React Hooks 实战案例

在 React 之前的版本中,组件要么是函数组件,要么是类组件。函数组件可以使用纯函数的方式编写,但是没有状态(state)和其他 React 特性;类组件则可以拥有状态和其他 React 特性,但是需要使用 class 语法来定义。React Hooks 就是在函数组件中引入了状态和其他 React 特性的工具。useState:在函数组件中添加状态,相当于在类组件中使用 this.state。

程序边界 6816

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

React Hooks 使用详解

本文对 16.8 版本之后 React 发布的新特性 Hooks 进行了详细讲解,并对一些常用的 Hooks 进行代码演示,希望可以对需要的朋友提供点帮助。 一、Hooks 简介 Hooks 是 React v16.7.0-alpha 中加入的新特性。它可以让你在 class 以外使用 state 和其他 React 特性。 本文就是演示各种 Hooks API 的使用方式,对于内部的原理这里...

大灰狼的小绵羊哥哥的博客 7819

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

React Hooks中常用Hooks的用法详解

React Hooks中常用Hooks的用法详解

杨杨吖的博客 3939

深入浅出 React Hooks

React Hooks 是什么? Hooks 顾名思义,字面意义上来说就是 React 钩子的概念。通过一个 case 我们对 React Hooks 先有一个第一印象。 假设现在要实现一个计数器的组件。如果使用组件化的方式,我们需要做的事情相对更多一些,比如说声明 state,编写计数器的方法等,而且需要理解的概念可能更多一些,比如 Javascript 的类的概念,this 上下文的指向等...

大灰狼的小绵羊哥哥的博客 5232

【React】详解 React Hooks 使用规则

Hooks 只能在 React 的函数组件和自定义 Hooks 中调用,不能在普通的 JavaScript 函数中使用。示例:错误的使用方式// 错误:在普通函数中调用 Hook示例:正确的使用方式// 正确:在函数组件中调用 Hook// ...// 正确:在自定义 Hook 中调用 Hook// ...自定义 Hooks 允许我们将重复的状态逻辑封装在一个函数中,从而提高代码的可读性和复用性。}, [url]);

lph159的博客 1307

关于React Hooks使用

为什么使用 React Hooks 对于一个新玩意(这也不算新玩意了)的使用肯定是有他的原因的撒,我们先来个简单例子看看,现在我们做过超级超级简单的例子:点击按钮数字增加1 先用我们原来的做法(用class的方式定义的组件) import React, { Component } from 'react' class Increased extends Component { const...

pz1021的博客 3476

React开发高级篇 - React Hooks以及自定义Hooks实现思路

初始状态(initialState):这是 state 的初始值。更新函数(setState):这是一个你可以调用以更新 state 的函数。当前状态(current state):这是 state 的当前值。更新函数(setState):用于更新 state 的函数。具体解释:useState 允许你在函数组件内部使用状态。这是通过将状态存储在组件的执行上下文中实现的,而不是像类组件那样存储在实例对象中。

qq_37779333的博客 1985

react常用 Hooks

React Hooks 使得函数组件能够实现与类组件相同的功能,提供了更简单的 API 和更强大的功能。通过 useState、useEffect、useContext、useReducer、useMemo 和 useCallback 等 Hooks,你可以在函数组件中管理状态、处理副作用、使用上下文等,构建更复杂和高效的 React 应用。React Hooks 是 React 16.8 引入的一项功能,它允许你在函数组件中使用状态和其他 React 特性,而不需要编写类组件。• 缓存的计算结果。

be willing to share 1436

React Hooks

React Hooks 是 React 16.8 引入的功能,使函数组件能使用状态和生命周期等特性。它简化代码结构,提高复用性,替代类组件的复杂逻辑。核心 Hooks 包括:useState(状态管理)、useEffect(副作用处理)、useContext(跨组件数据传递)、useReducer(复杂状态逻辑)、useCallback/useMemo(性能优化)、useRef(DOM 操作)等。此外,开发者可创建自定义 Hooks 封装复用逻辑。Hooks 仅用于函数组件,使代码更简洁高效,同时保持 Re

likuoelie的博客 992

React Hooks 用法详解

React 中提供的 hooks: useState:setState useReducer:setState,同时 useState 也是该方法的封装 useRef: ref useImperativeHandle: 给 ref 分配特定的属性 useContext: context,需配合 createContext 使用 useMemo: 可以对 setState 的优化 useCallback: useMemo 的变形,对函数进行优化

weixin_42333548的博客 5467

React中的Hooks

和。类组件可以使用state来管理内部状态,也能使用生命周期方法(如)来处理副作用。而函数组件是“无状态的”,它们只能接收props并返回JSX,无法拥有自己的状态和生命周期。简单来说,Hooks 让函数组件也能拥有和类组件几乎同等的能力,从此你可以在不编写 class 的情况下使用 state 和其他 React 功能。这使得函数组件成为现代React开发的首选。

2301_81253185的博客 1711
上一篇: 使用execCommand将文字样式设置成css样式的办法
下一篇: React基本使用你都掌握了么?
LittleRoundFace
博客等级 码龄7年 5粉丝 17原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值