关于React Hooks(上)

133 阅读7分钟

本文正在参加「金石计划」

关于React Hooks(上)

create by db on 2023-4-18 15:01:12
Recently revised in 2023-4-18 18:33:42

闲时要有吃紧的心思,忙时要有悠闲的趣味

目录

前言

返回目录

正文

一、什么是Hook

返回目录

 Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

 在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。

Hooks 优势

能优化类组件的三大问题

  • 能在无需修改组件结构的情况下复用状态逻辑(自定义 Hooks )
  • 能将组件中相互关联的部分拆分成更小的函数(比如设置订阅或请求数据)
  • 副作用的关注点分离:副作用指那些没有发生在数据向视图转换过程中的逻辑,如 ajax 请求、访问原生dom 元素、本地持久化缓存、绑定/解绑事件、添加订阅、设置定时器、记录日志等。以往这些副作用都是写在类组件生命周期函数中的。而 useEffect 在全部渲染完毕后才会执行,useLayoutEffect 会在浏览器 layout 之后,painting 之前执行。

注意事项

  • 只能在函数内部的最外层调用 Hook,不要在循环、条件判断或者子函数中调用
  • 只能在 React 的函数组件中调用 Hook,不要在其他 JavaScript 函数中调用

二、State Hooks

返回目录

useState

useState是react自带的一个hook函数,它的作用就是用来声明状态变量。

useState的使用很简单,一句话带过,返回一个数组,数组的值为当前state和更新state的函数;useState的参数是变量、对象或者是函数,变量或者对象会作为state的初始值,如果是函数,函数的返回值会作为初始值。

使用方式如下:

import React, { useState } from "react";

function Count() {
  // 声明一个叫 “count” 的 state 变量。
  let [count, setCount] = useState(0);
  const handleAdd = () => {
    // 每次只有一个生效
    setCount(count + 1);
    setCount(count + 1);
  };
  return (
    <div>
      <p>{count}</p>
      {/* 每次点击加1 */}
      <button onClick={handleAdd}>加一</button>
    </div>
  );
}

export default Count;

在同一个事件中并不会因为调用了两次setCount而让count增加两次,试想如果在同一个事件中每次调用setCount都生效,那么每调用一次setCount组件就会重新渲染一次,这无疑使非常影响性能的;实际上如果修改的state是同一个,最后一个setCount函数中的新state会覆盖前面的

三、Effect Hook

返回目录

Effect Hook 可以让你在函数组件中执行副作用操作

useEffect

我们写的有状态组件,通常会产生很多的副作用(side effect),比如发起ajax请求获取数据,添加一些监听的注册和取消注册,手动修改dom等等。

我们之前都把这些副作用的函数写在生命周期函数钩子里,比如componentDidMount,componentDidUpdate和componentWillUnmount。而现在的useEffect就相当与这些声明周期函数钩子的集合体。它以一抵三。

#### 使用方式

  • useEffect 第一个参数一个函数,该函数会在组件渲染到屏幕之后才执行
  • useEffect 第二个参数是一个数组,用第二个参数来告诉react只有当这个参数的值发生改变时,才执行我们传的副作用函数(即第一个参数)
    • 组件每次渲染会默认执行一次
    • 如果不传第二个参数只要该组件有state改变就会触发回调函数
    • 如果传一个空数组,只会在初始化执行一次。
    • 如果传数组,只要数组内数据改变就回触发回调函数。
    • 如果用return返回了一个函数,组件每次重新渲染的时候都会先执行该函数再调用回调函数。

示例代码如下:

import React, { useState, useEffect } from "react";

function Count() {
  // 声明一个叫 “count” 的 state 变量。
  let [count1, setCount1] = useState(0);
  let [count2, setCount2] = useState(0);

  useEffect(() => {
    console.log("count1更改");
  }, [count1]); // 仅在 count1 更改时更新
  useEffect(() => {
    console.log("count1或者count2更改");
  }, [count1, count2]); // 仅在 counth或者count2 更改时更新
  useEffect(() => {
    console.log("State更改");
  }); // 仅在 State更改时更新
  useEffect(() => {
    console.log("组件更新");
  }, []); // 仅在组件更新时更新
  useEffect(() => {
    const timer = setInterval(() => {
      console.log("timer...count:", count1);
    }, 1000);
    return () => clearInterval(timer);  //组件卸载时调用,不然会timer会一直在
  }, []);
  const handleAdd = () => {
    // 每次只有一个生效
    setCount1(count1 + 1);
    setCount1(count1 + 1);
  };
  const handleAdd2 = () => {
    // 每次只有一个生效
    setCount2(count2 + 1);
  };
  return (
    <div>
      <p>{count1}</p>
      <button onClick={handleAdd}>加一</button>
      <p>{count2}</p>
      <button onClick={handleAdd2}>加一</button>
    </div>
  );
}

export default Count;

注意

如果你在仅在开发模式("development")下,且使用了严格模式("Strict Mode")下会,useEffect会打印两次,不要担心,这不是 Bug,这是 React18 新加的特性,原因及解决方案如下:

useLayoutEffect

useLayoutEffect基本上与useEffect一样

相同点 useLayoutEffecty与useEffect其函数签名与 useEffect 相同,用法一致。

不同点

  • 表面上看,这两个hook的区别是执行时机不同,useEffect的回调函数会在页面渲染后执行;useLayoutEffect会在页面渲染前执行。实际上是React对这两个hook的处理不同,useEffect是异步调用,而useLayoutEffect是同步调用。

使用场景 那什么时候用useEffect,什么时候用useLayoutEffect呢?

视情况而定

  • 如果回调函数会修改state导致组件重新渲染,可以useLayoutEffect,因为这时候用useEffect可能会造成页面闪烁;
  • 如果回调函数中去请求数据或者js执行时间过长,建议使用useEffect;因为这时候用useLayoutEffect堵塞浏览器渲染。

四、Ref Hooks

返回目录

useRef

useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。

这个hook通常用来获取组件实例,还可以用来缓存数据。

useRef 创建一个不受组件刷新而影响的变量

示例代码如下:

function UseRef() {
  const [x, setX] = useState(0);

  //函数组件只要更新了,a就会被重新为 0,所以函数组件需要借助useRef存储变量
  let a = 0;
  //useRef可以生成一个变量,用于在函数组件中存储数据
  let currentVal = useRef(0);

  useEffect(() => {
    console.log(` =================`);
    console.log(` x --- :${x}`);
    console.log(` currentVal --- :${currentVal.current}`);
    console.log(` a--- :${a}`);
  }, [x]);

  const clickAdd = () => {
    setX(v => v + 1);
    currentVal.current += 2;
    a += 2;
  };

  return (
    <>
      <p>{x} ----</p>
      <button onClick={clickAdd}>+1</button>
    </>
  );
}

因为a是普通变量,只要按钮点击,就会导致函数组件刷新,重新生成一个新的a,值永远都为0。所以在函数组件内如果想创建一个不受组件刷新而影响的变量,必须借助useRef生成

使用场景: 如果我们只是想保存状态不影响视图更新,而且可以同步更新&获取我们的状态,那么就使用 useRef。

注意事项

  • ref.current 不可以作为其他 hooks(useMemo, useCallback, useEffect)依赖项;
  • ref.current 的值发生变更并不会造成 re-render, Reactjs 并不会跟踪 ref.current 的变化。

命令式地获取及操作DOM:

示例代码如下:

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` 指向已挂载到 DOM 上的文本输入元素
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

五、其他 Hooks

返回目录

useContext

useReducer

useCallback

useMemo

总结

返回目录

 未完待续

参考文档

后记:Hello 小伙伴们,如果觉得本文还不错,记得点个赞或者给个 star,你们的赞和 star 是我编写更多更丰富文章的动力!GitHub 地址

文档协议


db 的文档库db 采用 知识共享 署名-非商业性使用-相同方式共享 4.0 国际 许可协议进行许可。
基于github.com/danygitgit上的作品创作。
本许可协议授权之外的使用权限可以从 creativecommons.org/licenses/by… 处获得。