官方

useRef

当您希望组件“记住”某些信息,但又不希望该信息触发新的渲染时,您可以使用 ref.

什么意思呢 ? 我自己定义一个对象不能 “记住” 某些信息吗 ? 如下的代码

  1. import { useState } from "react";
  2. export default function Counter() {
  3. const obj = { current: "折木" };
  4. const [count, setCount] = useState(0);
  5. function handleClick() {
  6. setCount(count + 1);
  7. obj.current = `zhemu ${Math.random()}`;
  8. }
  9. console.log("what is obj now", obj);
  10. // 每一次re-render都是{ current: "折木" };
  11. return <button onClick={handleClick}>You clicked {count} times</button>;
  12. }

但是当你点击按钮试图修改 obj 这个对象的current 属性 时, 在 下一次 Counter re-render中, 你会发现obj还是最初的样子, what, 这跟我们期望的有点差距奥, 原因很简单, 函数组件的每次调用, 内部的变量, 函数都会被重新创建, 为了解决这个问题, 我们应该使用useRef , useRef 会在每次渲染时返回同一个 ref 对象, 它永远不会改变

为了更好的理解这句话的意思, 我们来看一个例子, 是关于useEffect的奥, 链接

  1. import { useState, useEffect } from "react";
  2. export default function Demo() {
  3. const [count, setCount] = useState(0);
  4. const options = { step: 2 };
  5. useEffect(() => {
  6. console.log("render");
  7. }, [options]);
  8. function handleClick() {
  9. setCount(count + options.step);
  10. }
  11. return <button onClick={handleClick}>You clicked {count} times</button>;
  12. }

现象是什么? 每点击一次总会打印 render , 让我们回顾下代码, 每当我们点击设置了 count, Demo组件重新渲染, options 重新初始化, 变成 的了, useEffect 回调重新执行了, 我们知道 useEffect 只有当 “依赖变化” 时才会执行, 但是, 它内部使用的是Object.is, 来看看

js对象是mutable

在javascript中,对象是一种引用类型,他存在内存的堆中,当你 let obj = {} 就相当于在栈中创建了一块区域用来存放obj这个变量,然后让这个变量指向堆中存放 {} 的位置,这也是为什么下面这段代码打印false的原因

  1. var a = {}
  2. var b = {}
  3. console.log(a === b); // false

那原因很清楚了,我们怎样去改造一下useEffect, 使得当依赖(对象或者数组) 真正变化 的时候, 才去执行呢?

思路很明显, 我们定一个ref变量 为0之类的, 当深比较上一次依赖引用类型跟最新的依赖引用类型不一致时, ref 变量变化一下, 将这个ref变量当作useEffect的依赖, 这样就可以了

  1. function useDeepCompareEffect(effect, deps) {
  2. const signalRef = useRef(0);
  3. if (deps === undefined || !isEqual(上一次deps, deps)) {
  4. signalRef.current += 1;
  5. }
  6. useEffect(effect, signalRef.current);
  7. }

原因
想到这里,还有一个疑问点,怎么知道上一次的依赖呢 ? 很简单, 我们使用了全局变量,在eatWhat第一次执行时,保存这次的值, 第二次执行时,判断上一次保存在全局变量的值是否跟传进来最新的一致,从而决定是否更新

  1. let previousFood = null;
  2. function eatWhat(food) {
  3. if (!Object.is(food, previousFood)) {
  4. console.log(`上一次你问的是什么${previousFood}吧!`);
  5. previousFood = food;
  6. }
  7. console.log(`今天去吃${food}吧!`);
  8. }
  9. eatWhat("汉堡王🍔");
  10. eatWhat("炒菜");

那在FC的场景下,一样的,只不过我们需要使用 useRef

  1. const isPrimitive = (val: any) => val !== Object(val);
  2. export const useDeepCompareEffect = (effect: EffectCallback, deps: any[]) => {
  3. if (process.env.NODE_ENV !== 'production') {
  4. if (!deps || !deps.length) {
  5. console.warn(
  6. '`useDeepCompareEffect` should not be used with no dependencies. Use React.useEffect instead.',
  7. );
  8. }
  9. if (deps.every(isPrimitive)) {
  10. console.warn(
  11. '`useDeepCompareEffect` should not be used with dependencies that are all primitive values. Use React.useEffect instead.',
  12. );
  13. }
  14. }
  15. const ref = useRef<DependencyList | undefined>(undefined);
  16. if (!ref.current || !isEqual(deps, ref.current)) {
  17. ref.current = deps;
  18. }
  19. useEffect(effect, [ref.current]);
  20. };

好漫长, useRef的例子还有好多, 例如在异步回调中读取最新状态使用 React Hooks 使 setInterval 声明式

useEffect

一个勾子, 当组件被渲染到屏幕后 去执行一些 副作用, 通常是数据请求, 手动变更DOM, 记录日志等…

主要需要明确几个点,

  • useEffect 是在组件commit 后触发的勾子回调, (也就是将render阶段收集到的fiber树🌲转为dom的过程
  • 副作用 的含义理解, react 你可以看作是一个 f(state, props) => ui 的一个纯函数 , 这样设计是为了确保组件的渲染可控, 你可以简单理解跟这个
  • 更好的获取数据的方式

useCallback

useMemo

自定义

状态管理