Hooks复习

54 阅读4分钟

1. useState 是 同步还是异步?

  • 合成事件-- react 为了解决平台兼容性,封装了一套自己的事件机制,代理了浏览器原生事件,像onCLick,onChange等等。zh-hans.legacy.reactjs.org/docs/events…
  • 批量处理
    • isBatchingUpdates 、bachedUpdates函数
    setCount(count=>count+1)
    setCount(count=>count+1)
    结果依赖第二次,只触发一次更新。
    setTimer(123);
    涉及多个变量变更,只触发一次更新, 可以减少渲染次数
- 关闭批量处理
https://react.nodejs.cn/reference/react-dom/flushSync
    flushSync(() => {  
        setSomething(123);  
    });//react 18提供的函数,可以强制更新 --影响性能
  • react18之前
    • 在合成事件和生命周期方法中,setState是异步的。在浏览器原生DOM事件、setTimeout、promise中setState是异步的。
    • react setState的实现中,会根据一个 isBatchingUpdates(是否批处理)变量判断是直接更新state还是放到队列中,直接更新就是同步的,默认是false,即同步。而react 在调用处理函数之前会调用一个bachedUpdates函数,将isBatchingUpdates改为true,即异步更新。
  • 在react 18之后
    • 任何情况都会自动执行批处理,多次更新合并为一次更新

2.useEffect 和 useLayoutEffect区别

  • useEffect 是异步执行的,而useLayoutEffect是同步执行的。
  • useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前,和 componentDidMount 等价。
  • zhuanlan.zhihu.com/p/348701319

image.png

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

export default function Layout(){
    const [state, setState] = useState("hello world")
    // useEffect(() => {
    //     let i = 0;
    //     while(i <= 100000000) {
    //       i++;
    //     };
    //     setState("world hello");
    //   }, []);
    
      useLayoutEffect(() => {
        let i = 0;
        while(i <= 100000000) {
          i++;
        };
        setState("world hello");
      }, []);
    
  useEffect(() => {
    let i = 0;
    while(i <= 100000000) {
      i++;
    };
    setState("world hello");
  }, []);
  return (<>
    <div>{state}</div>
    </>)

}
  • useEffect在渲染之后,异步执行,所以可能有闪烁,useLayout在渲染之前同步执行,执行完之后再进行渲染,避免了闪烁情况。
  • useLayoutEffect不会在服务端执行,可能会导致SSR渲染出来的的内容与实际首屏不一致,避免在SSR服务器端使用,可以通过判断window对象是否存在来选择使用useState、useEffect

image.png

3. useRef

image.png

2. useRef.current = setTimeout等等,用于保存常量,常见的倒计时,在xiao hui shi

useRef 更新,不会触发重新渲染操作。

import React, { useRef } from "react";

export default function Reff() {
  let count = useRef(1);
  console.log("render");
  return (
    <>
      <div>{count.current}</div>
      <button
        onClick={() => {
          count.current = 2;
          console.log("count.current===", count.current);
        }}
      >
        useREF
      </button>
    </>
  );
}

4. useMemo

用于缓存计算属性,一些在渲染过程中就有返回值的函数,以一个值为目标的函数等。 埋点、用户统计等初始化时需要保存的数据,可以用useMemo进行缓存。

image.png

  • 如下,func 的值一直没有变过,返回值也没有改变,而每次刷新会重新执行func() 额外的性能开销,类似于这种我们可以用useMemo实现,这样返回值只有在num更新时才会重新执行

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

export default function MeMMM() {
  console.log("render");
  let [count, setCount] = useState(0);
  let [num, setNum] = useState(0);
  //   const func = () => {
  //     console.log("渲染时打印");
  //     return "str";
  //   };
  const func = useMemo(() => {
    console.log("useMemo包裹执行");
    return "str2222222" + num;
  }, [num]);
  return (
    <>
      <div>{count}</div>
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        渲染+++
      </button>
      <div>{func}</div>
      <button
        onClick={() => {
          setNum((num) => num + 1);
        }}
      >
        setNum++
      </button>
    </>
  );
}


![image.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3d2a29fa15884fbdb2454a780d0db727~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=1894&h=1334&s=196425&e=png&b=ffffff)

juejin.cn/post/710827…

useMemo 消耗缓存,不要经常食用

  1. 使用场景1 缓存计算属性
  2. 使用场景2 当子组件依赖副组件某一个计算属性/对象/函数 并且子组件使用了React.memo进行包裹时。
如下,如果传递一个计算属性,由于引用地址改变,memo也会触发重新渲染
import React, { useState, useMemo, memo } from "react";
const Children1 = memo(({ func }) => {
  console.log("这个打印触发了证明子组件重新渲染了");
  return (
    <>
      <div>count:{func}</div>
    </>
  );
});
export default function MeMMM() {
  console.log("render");
  let [count, setCount] = useState(0);
  let [num, setNum] = useState(0);
  // const func = () => {
  //   console.log("渲染时打印");
  //   return "str";
  // };

  const func = useMemo(() => {
    console.log("useMemo包裹执行");
    return "str2222222" + num;
  }, [num]);
  return (
    <>
      <div>{count}</div>
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        渲染+++
      </button>
      {/* <div>{func}</div> */}
      <button
        onClick={() => {
          setNum((num) => num + 1);
        }}
      >
        setNum++
      </button>

      <Children1 func={func} />
    </>
  );
}

5. CallBACK

  • 当传递给子组件,子组件通过memo包裹了,接收一个方法,这个方法不变或者仅依赖某个值改变,当组件其他字段更新触发渲染时,这个函数会被重新创建,这样会导致他的内存地址会改变,这样会触发子组件重新刷新。可以用Callback包裹。
    
  • callback能力可以用useMemo实现
    
  • 看如下返回值,两者实现能力是一样的
    

image.png

import React, { useState, useCallback, useMemo, memo } from "react";
const Child = memo(function ({ func }) {
  console.log("Child 重新渲染");
  return <div>123</div>;
});
export default function CallBACK1() {
  let [count, setCount] = useState(0);
  let [num, setNum] = useState(0);

  //   function test() {
  //     console.log("test");
  //   }
  const test = useCallback(() => {
    console.log("test");
  }, [num]);
    const test2 = useMemo(() => {
    return () => {
      console.log("test");
    };
  }, [num]);
  return (
    <>
      <div>count:{count}</div>
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        ++++setCount+++++++
      </button>
      <div>Num:{num}</div>
      <button
        onClick={() => {
          setNum((num) => num + 1);
        }}
      >
        ++++num+++++++
      </button>
      <Child func={test} />
    </>
  );
}

///shouldComponentUpdate