Hook

本文最后更新于:2023年12月5日 晚上

如何理解React的副作用?

说到React的副作用,我们先说下纯函数(Pure function)、纯组件(Pure Component)。

纯函数 和 纯组件

纯函数 (Pure Function) 是 函数式编程 里面非常重要的概念 。

如果一个函数是 纯函数 (Pure Function) ,它必须符合两个条件:

  1. 函数返回结果只依赖入参,入参固定,则返回值永远不变。
  2. 函数执行过程中不会产生对外可观察的变化。

示例:

function sqrt(a) {
  return a * a
}

纯组件就是纯函数:给一个component相同的props,永远会渲染出相同的视图,并且不会产生对外可观察的变化。

副作用

纯函数不会产生对外可观察的变化,这个对外可观察的变化就是副作用。

副作用包括但不限于:

  • 修改外部变量;
  • 调用另一个非纯函数(纯函数内调用纯函数,不会产生副作用,依然还是纯函数);
  • 发送HTTP请求;
  • 调用DOM API 修改页面;
  • 调用 window.reload 刷新页面,甚至console.log()往控制台打印数据;
  • Math.random()
  • 获取当前时间;

钩子 Hook

https://www.ruanyifeng.com/blog/2020/09/react-hooks-useeffect-tutorial.html

https://www.ruanyifeng.com/blog/2019/09/react-hooks.html

函数组件的主体只应该用来返回组件的 HTML 代码,所有的其他操作(副作用)都必须通过钩子(hook)引入。

由于副作用非常多,所以hook有许多种。React 为许多常见的副作用提供了专用的hook:

  • useState():保存状态
  • useContext():保存上下文
  • useRef():保存引用

上面这些hook,都是引入某种特定的副作用,而 useEffect()是通用的副作用hook 。找不到对应的hook时,就可以用它。其实,从名字也可以看出来,它跟副作用(side effect)直接相关。

useEffect

useEffect(didUpdate);

只要是副作用,都可以使用useEffect()引入。它的常见用途有下面几种:

  • 获取数据(data fetching)
  • 事件监听或订阅(setting up a subscription)
  • 改变 DOM(changing the DOM)
  • 输出日志(logging)

重点:

  1. 赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。

  2. 清除 effect:useEffect 函数可以返回一个清除函数,用以清除 effect 创建的诸如订阅或计时器 ID 等资源,示例:

    export const useDebounce = <V>(value: V, delay: number = 1000) => {
      const [debouncedValue, setDebouncedValue] = useState(value);
      useEffect(() => {
        // 每次在value变化以后,设置一个定时器
        const timeout = setTimeout(() => setDebouncedValue(value), delay);
        // 每个定时器的名称都是timeout,所以只有最后一个定时器能存活下来
        return () => clearTimeout(timeout); // 返回一个清除函数
      }, [value, delay]);
      return debouncedValue;
    };

    如果该 useEffect 只调用一次,清除函数会在组件卸载前执行;如果组件多次渲染(通常如此),则 在执行下一个 effect 之前,上一个 effect 就已被清除

  3. effect 的执行时机:传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用,虽然 useEffect 会在浏览器绘制后延迟执行,但会保证在任何新的渲染前执行。在开始新的更新前,React 总会先清除上一轮渲染的 effect。

  4. effect 的条件执行:给 useEffect 传递第二个参数,它是 effect 所依赖的值数组。

 react 中组件重新渲染是很频繁的,为了避免重复的网络请求,所以发送网络请求的操作一定要放在 useEffect 中  

useState

const [state, setState] = useState(initialState);

返回一个 state,以及更新 state 的函数。

在初始渲染期间,返回的状态 (state) 与传入的第一个参数 (initialState) 值相同。

setState 函数用于更新 state。它接收一个新的 state 值并将组件的一次重新渲染加入队列。就是说每次使用setState都会重新渲染组件。

setState(newState);

在后续的重新渲染中,useState 返回的第一个值将始终是更新后最新的 state

注意:

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

useReducer

useReducer 是 useState 的替代方案。当 useState 不能很好的满足需要的时候,useReducer 可能会解决我们的问题。

import { useReducer } from "react";
import { Button, Space } from "antd";

type Action = {
  type: "incr" | "decr" | "set";
  present?: number;
};
const Test = () => {
  const reducer = (state: number, action: Action) => {
    switch (action.type) {
      case "decr": {
        return state - 1;
      }
      case "incr": {
        return state + 1;
      }
      case "set": {
        if (action.present) {
          return action.present;
        } else {
          throw new Error("未设置有效值");
        }
      }
    }
    // return state
  };
  const initial = 0;
  const [state, dispatch] = useReducer(reducer, initial);
  return (
    <Space>
      {state}
      <Button onClick={() => dispatch({ type: "incr" })}>+</Button>
      <Button onClick={() => dispatch({ type: "decr" })}>-</Button>
      <Button onClick={() => dispatch({ type: "set" })}>reset</Button>
    </Space>
  );
};

export default Test;

useContext

React.createContext

const MyContext = React.createContext(defaultValue);

创建一个 Context 对象。

Context.Provider

<MyContext.Provider value={/* 某个值 */}>

Provider 接收一个 value 属性,传递给 consumers 组件。一个 Provider 可以和多个 consumers 组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。

当 Provider 的 value 值发生变化时,它内部的所有 consumers 组件都会重新渲染。

通过新旧值检测来确定变化,使用了与 Object.is 相同的算法(基本等同于“===”)。

注意

当传递对象给 value 时,检测变化的方式会导致一些问题:详见注意事项

Class.contextType

Context.Consumer

Context.displayName


Hook
http://blog.lujinkai.cn/前端/React/Hook/
作者
像方便面一样的男子
发布于
2021年8月29日
更新于
2023年12月5日
许可协议