二十五、React Hooks

郁子大约 5 分钟约 1586 字笔记React18Hooks李立超

(一)useMemo

  • 引入 useMemo() 解决 sum() 执行过慢导致 count 增加慢的问题
  • 作用:缓存函数执行结果
    • useCallback() 缓存函数对象本身
  • 组件也是函数,可以缓存组件渲染结果

1. src/index.js

import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

2. src/App.js

import React, { useState } from "react";
import { useMemo } from "react";
import Some from "./components/Some";

function sum(a, b) {
  console.log("App---sum");

  // 模拟复杂函数
  const begin = Date.now();
  while (1) {
    if (Date.now() - begin > 3000) break;
  }

  return a + b;
}

const App = () => {
  // 触发组件渲染
  const [count, setCount] = useState(1);

  // sum函数每次组件渲染都会执行
  // const result = sum(123, 456);

  //  模拟变量
  let a = 123,
    b = 456;
  if (count % 10 === 0) a += count;
  const result = useMemo(() => {
    return sum(a, b);
  }, [a, b]);
  const someEle = useMemo(() => {
    return <Some a={a} b={b} />;
  }, [a, b]);

  return (
    <div>
      <h1>App</h1>
      <h2>result: {result}</h2>
      <h2>count: {count}</h2>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
      <hr />
      {someEle}
    </div>
  );
};

export default App;

3. src/components/Some.js

import React from "react";

const Some = (props) => {
  // 模拟复杂组件
  const begin = Date.now();
  while (1) {
    if (Date.now() - begin > 3000) break;
  }
  return (
    <div>
      <h2>Some</h2>
      <h3>{props.a + props.b}</h3>
    </div>
  );
};

export default Some;

(二)useImperativeHandle

1. src/App.js

import React, { useState } from "react";
import { useEffect } from "react";
import { useRef } from "react";
import Some from "./components/Some";

const App = () => {
  const [count, setCount] = useState(1);

  const someRef = useRef();
  useEffect(() => {
    // console.log(someRef);
    // someRef.current.innerText = "Some" + count;
    someRef.current.changeInpValue(count);
  }, [count]);

  return (
    <div>
      <h1>App</h1>
      <h2>count: {count}</h2>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
      <hr />

      {/* 
        无法直接获取react组件的dom对象
        => 因为组件内部有多个标签,需要配合forwardRef使用
        => 组件属性不再由组件本身控制,应配合useImperativeHandle()使用
      */}
      <Some ref={someRef} />
    </div>
  );
};

export default App;

2. src/components/Some.js

import React, { useRef, useImperativeHandle } from "react";

// const Some = () => {
//   const inputRef = useRef();
//   const clickHandler = () => {
//     console.log(inputRef.current.value);
//   };

//   return (
//     <div>
//       <h2>Some</h2>
//       <input type="text" ref={inputRef} />
//       <button onClick={clickHandler}>提交</button>
//     </div>
//   );
// };

/*
  forwardRef用于指定组件向外部暴露的ref(父组件访问子组件的ref值)
    参数1:props
    参数2:ref,父组件想访问的ref
  【最好不用,组件属性脱离了组件本身的控制】
*/
// const Some = React.forwardRef((props, ref) => {
//   const inputRef = useRef();
//   const clickHandler = () => {
//     console.log(inputRef.current.value);
//   };

//   return (
//     <div>
//       <h2 ref={ref}>Some</h2>
//       <input type="text" ref={inputRef} />
//       <button onClick={clickHandler}>提交</button>
//     </div>
//   );
// });

/*
  配合useImperativeHandle()钩子保证组件属性由组件控制
  用于指定ref返回的值
  参数1:暴露给外部组件的ref值
  参数2:回调函数,该函数返回值会成为ref的值,通常返回一个操作DOM对象的方法
*/
const Some = React.forwardRef((props, ref) => {
  const inputRef = useRef();
  const clickHandler = () => {
    console.log(inputRef.current.value);
  };

  useImperativeHandle(ref, () => {
    return {
      changeInpValue(val) {
        inputRef.current.value = val;
      },
    };
  });

  return (
    <div>
      <h2>Some</h2>
      <input type="text" ref={inputRef} />
      <button onClick={clickHandler}>提交</button>
    </div>
  );
});

export default Some;

(三)三个 effect

  • 执行顺序: useInsertionEffect => useLayoutEffect => useEffect
  • React18 中 useLayoutEffectuseEffect 区别微乎其微,因为 useEffect 执行时机会动态调整
  • 开发中通常先用 useEffect ,当它不满足需求时再考虑另外两个钩子

1. src/App.js

import React from "react";
import { useState, useEffect, useLayoutEffect, useInsertionEffect, useRef } from "react";
import Some from "./components/Some";

const App = () => {
  const [count, setCount] = useState(1);

  const h3Ref = useRef();
  // 绘制屏幕之后执行
  useEffect(() => {
    console.log("useEffect", h3Ref);
  });
  // DOM改变之后绘制屏幕之前执行
  useLayoutEffect(() => {
    console.log("useLayoutEffect", h3Ref);
  });
  // DOM改变之前执行,用于向DOM对象插入样式
  useInsertionEffect(() => {
    console.log("useInsertionEffect", h3Ref);
  });

  return (
    <div>
      <h1>App</h1>
      <h3 ref={h3Ref}>count: {count}</h3>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
      <hr />
      <Some />
    </div>
  );
};

export default App;

2. src/components/Some.js

import React, { useRef } from "react";

const Some = () => {
  const inputRef = useRef();
  const clickHandler = () => {
    console.log(inputRef.current.value);
  };

  return (
    <div>
      <h2>Some</h2>
      <input type="text" ref={inputRef} />
      <button onClick={clickHandler}>提交</button>
    </div>
  );
};

export default Some;

(四)useDebugValue

  • 用来给自定义钩子设置标签,标签会在 React 开发工具中显示
  • 用来区分调试自定义钩子,不常用

1. src/App.js

import React from "react";
import { useState } from "react";
import useMyHook from "./hooks/useMyHook";

const App = () => {
  const [count, setCount] = useState(1);

  useMyHook();

  return (
    <div>
      <h1>App</h1>
      <h3>count: {count}</h3>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
    </div>
  );
};

export default App;

2. src/hooks/useMyHook.js

import { useEffect, useDebugValue } from "react";

const useMyHook = () => {
  useDebugValue("哈哈哈");

  useEffect(() => {
    console.log("自定义钩子的代码");
  });
};

export default useMyHook;

(五)useDeferredValue

  • useDeferredValue()
    • 为传入的 state 创建一个延迟值
    • 参数: state
  • 设置延迟值后,每次 state 修改时都会触发两次组件渲染
    • 这两次渲染对其他部分没有区别,但是延迟值会变化,两次值分别是 state 的旧值和新值
  • 延迟值总会比原版的 state 慢一步更新

1. src/App.js

import React from "react";
import { useState, useDeferredValue } from "react";
import StudentList from "./components/StudentList";

const App = () => {
  console.log("组件重新渲染了~~~");
  const [count, setCount] = useState(1);

  const deferredCount = useDeferredValue(count);
  console.log(count, deferredCount);

  /*
    多个组件用到同一个state时,某一个组件的卡顿会导致所有组件不能及时渲染
    此时可以给延迟组件设置为延迟值,同时使用React.memo()包裹,延迟值变化时再渲染
  */
  const [filterWord, setFilterWord] = useState("");
  const changeHandler = (e) => {
    setFilterWord(e.target.value);
  };
  const deferredFilterWord = useDeferredValue(filterWord);

  return (
    <div>
      <h1>App</h1>
      <h3>count: {count}</h3>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
      <hr />
      <input type="text" value={filterWord} onChange={changeHandler} />
      <StudentList filterWord={deferredFilterWord} />
    </div>
  );
};

export default App;

2. src/components/StudentList.js

import React from "react";

const STU_DATA = [];
for (let i = 1; i < 101; i++) {
  STU_DATA.push("学生" + i);
}

const StudentList = (props) => {
  const stu = STU_DATA.filter((item) => item.indexOf(props.filterWord) !== -1);

  // 模拟组件卡顿
  const begin = Date.now();
  while (1) {
    if (Date.now() - begin > 3000) break;
  }

  return (
    <div>
      <ul>
        {stu.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default React.memo(StudentList);

(六)useTransition 和 useId

  • startTransition
    • 在回调函数中设置 setState ,会在其他 setState 生效后再执行,也可以优化卡顿问题
  • useId()
    • 生成唯一 id ,使用于需要唯一 id 的场景,但不适用于列表的 key

1. src/App.js

import React from "react";
import { useState, useTransition, useId } from "react";
// import {  startTransition } from "react";// 使用方法一
import StudentList from "./components/StudentList";

const App = () => {
  console.log("组件重新渲染了~~~");
  const [count, setCount] = useState(1);

  // 多个组件用到同一个state时,某一个组件的卡顿会导致所有组件不能及时渲染
  const [filterWord, setFilterWord] = useState("");
  const [filterWord2, setFilterWord2] = useState("");

  // 使用方法二
  const [isPending, startTransition] = useTransition();
  console.log(isPending);

  const changeHandler = (e) => {
    setFilterWord(e.target.value);

    startTransition(() => {
      setFilterWord2(e.target.value);
    });
  };

  const id = useId();
  console.log(useId(), useId());

  return (
    <div>
      <h1>App</h1>
      <h3>count: {count}</h3>
      <button onClick={() => setCount((pre) => pre + 1)}>点我+1</button>
      <hr />
      <label htmlFor={"keyword" + id}>关键词</label>
      <input id={"keyword" + id} type="text" value={filterWord} onChange={changeHandler} />
      {!isPending && <StudentList filterWord={filterWord2} />}
    </div>
  );
};

export default App;

2. src/components/StudentList.js

import React from "react";

const STU_DATA = [];
for (let i = 1; i < 101; i++) {
  STU_DATA.push("学生" + i);
}

const StudentList = (props) => {
  const stu = STU_DATA.filter((item) => item.indexOf(props.filterWord) !== -1);

  // 模拟组件卡顿
  const begin = Date.now();
  while (1) {
    if (Date.now() - begin > 3000) break;
  }

  return (
    <div>
      <ul>
        {stu.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default React.memo(StudentList);
上次编辑于: