React18 Hooks,useContext(),useReducer(),useCallback()和useMemo(),useRef()

169 阅读3分钟

useContext()基本使用

image.png

import { createContext } from "react";
const Theme = createContext();
const User = createContext();
export { Theme, User };
import ReactDOM from "react-dom/client";
import App from "./pages-learn-hooks/useContext使用/App";
import { Theme, User } from "./context";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <User.Provider value={{ name: "wzm", age: 21 }}>
    <Theme.Provider value={{ color: "blue", fontSize: "30px" }}>
      <App />
    </Theme.Provider>
  </User.Provider>
);
import React, { memo, useContext } from "react";
import { Theme, User } from "../../context";
const App = memo(() => {
  const user = useContext(User);
  const theme = useContext(Theme);
  return (
    <div>
      <h2>useContext App</h2>
      <div>
        user:{user.name}-{user.age}
      </div>
      <div style={{ color: theme.color, fontSize: theme.fontSize }}>theme</div>
    </div>
  );
});
export default App;

image.png

useReducer()了解

image.png

import React, { memo, useReducer } from "react";
function reducer(state, action) {
   //逻辑代码太多,会造成reducer函数逻辑难以维护
  switch (action.type) {
    case "add":
      return { ...state, count: state.count + 1 };
    case "sub":
      return { ...state, count: state.count - 1 };
    case "add_num":
      return { ...state, count: state.count + action.num };
    case "sub_num":
      return { ...state, count: state.count - action.num };
    default:
      return state;
  }
}
const App = memo(() => {
  const [state, dispatch] = useReducer(reducer, { count: 0, ikun: [], user: {} });
  // 使用useReducer()就不用写多个useState()了,第二个参数是state默认值
  //   const [count, setCount] = useState();
  //   const [ikun, setIkun] = useState();
  //   const [user, setUser] = useState();
  return (
    <div>
      <h2>useReducer Page</h2>
      <div>
        <h2>当前计数:{state.count}</h2>
        <button onClick={(e) => dispatch({ type: "add" })}>+1</button>
        <button onClick={(e) => dispatch({ type: "sub" })}>-1</button>
        <button onClick={(e) => dispatch({ type: "add_num", num: 5 })}>+5</button>
        <button onClick={(e) => dispatch({ type: "sub_num", num: 5 })}>-5</button>
        <button onClick={(e) => dispatch({ type: "add_num", num: 100 })}>+100</button>
      </div>
    </div>
  );
});
export default App;

useCallback()

image.png useCallback性能优化的点:

  • 当需要将一个函数传递给子组件时,最好使用useCallback进行优化,将优化之后的函数,传递给子组件
import React, { memo, useCallback, useState } from "react";

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

  //   闭包陷阱:useCallback
  const increment = useCallback(
    function () {
      console.log("increment");
      setCount(count + 1);
    },
    [count]
  );

  return (
    <div>
      <h2>useCallback useMemo App Page </h2>
      <button onClick={increment}>+1({count})</button>
    </div>
  );
});

function foo(name) {
  function bar() {
    console.log(name);
  }
  return bar;
}
const bar1 = foo("zm");
bar1(); //zm
bar1(); //zm

const bar2 = foo("ww");
bar2(); //ww
bar1(); //zm

export default App;
import React, { memo, useCallback, useState } from "react";

const ZMPage = memo((props) => {
  const { increment } = props;
  console.log("ZMPage render");
  return (
    <div>
      <button onClick={increment}>ZMPage increment+1</button>
      {/* 很多子组件 */}
    </div>
  );
});

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

  const [msg, setMsg] = useState("hello ww");

  //   闭包陷阱:useCallback
  const increment = useCallback(
    function () {
      console.log("increment");
      setCount(count + 1);
    },
    [count]
  );
  //只依赖count,父组件改变msg之后子组件不会重新渲染,
  //普通函数会重新渲染,因为会重新定义函数,传递的是新函数,子组件props改变,重新渲染

  //   普通函数
  // const increment = () => {
  //     setCount(count + 1)
  // }

  return (
    <div>
      <h2>useCallback useMemo App Page </h2>
      <button onClick={increment}>App +1({count})</button>

      <ZMPage increment={increment} />
      <h2>msg:{msg}</h2>
      <button onClick={(e) => setMsg(Math.random())}>changeMsg</button>
    </div>
  );
});

export default App;

继续优化,上面count发生改变,子组件仍然要重新渲染,如果子组件有很多页面,都会重新渲染,如何优化呢?

  • 方法1:将count依赖移除掉,缺点:闭包陷阱
  • 方法2:useRef,在组件多次渲染时,返回的是同一个值
import React, { memo, useCallback, useRef, useState } from "react";

const ZMPage = memo((props) => {
  const { increment } = props;
  console.log("ZMPage render");
  return (
    <div>
      <button onClick={increment}>ZMPage increment+1</button>
      {/* 很多子组件 */}
    </div>
  );
});

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

    const [msg, setMsg] = useState("hello ww");

  //   闭包陷阱:useCallback
  //   const increment = useCallback(
  //     function () {
  //       console.log("increment");
  //       setCount(count + 1);
  //     },
  //     [count]
  //   );

  //   进一步优化
  //   方法1:将count依赖移除掉,缺点:闭包陷阱
  // 方法2:useRef,在组件多次渲染时,返回的是同一个值
    const countRef = useRef();
    countRef.current = count;
    const increment = useCallback(function () {
        console.log("increment");
        setCount(countRef.current + 1);
    }, []);

  //   普通函数
  // const increment = () => {
  //     setCount(count + 1)
  // }

    return (
        <div>
        <h2>useCallback useMemo App Page </h2>
        <button onClick={increment}>App +1({count})</button>

        <ZMPage increment={increment} />
        <h2>msg:{msg}</h2>
        <button onClick={(e) => setMsg(Math.random())}>changeMsg</button>
        </div>
    );
});
export default App;

这样修改count,msg,子组件都不会重新渲染了

useMemo()函数使用

image.png

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

const HelloWorld = memo(function (props) {
  console.log("HelloWorld render");
  return <h2>HelloWorld</h2>;
});

function calcNumTotal(num) {
  console.log("calcNumTotal的计算过程被调用~");
  let total = 0;
  for (let i = 0; i < num; i++) {
    total += i;
  }
  return total;
}

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

  //   1.不依赖任何的值,进行计算,calcNumTotal只会调用一次
  //   const result = useMemo(() => {
  //     return calcNumTotal(10);
  //   }, []);

  //   2.依赖count
  const result = useMemo(() => {
    return calcNumTotal(count * 2);
  }, [count]);

  //   3.useMemo(值)和useCallback(函数)的对比
  //   function fn() {}  下面二者写法等价
  //   const increment = useCallback(fn, []);
  //   const increment2 = useMemo(() => fn, []);

  //   4.使用useMemo对组件渲染进行优化
  //   const info = { name: "zm", age: 21 }; //改变count组件重新渲染,字面量是新的,props更新,子组件会被重新渲染
  const info = useMemo(() => ({ name: "zm", age: 21 }), []);

  return (
    <div>
      <h2>useMemo App Page</h2>
      <div>
        计算结果:{result}
        <button onClick={(e) => setCount(count + 1)}>+1({count})</button>
        <HelloWorld result={result} info={info} />
      </div>
    </div>
  );
});
export default App;

useRef()使用

image.png

  • useRef绑定DOM
import React, { memo, useRef } from "react";
const App = memo(() => {
  const titleRef = useRef();
  const inputRef = useRef();
  function showTitleDOM() {
    console.log(titleRef.current);
    inputRef.current.focus();
  }
  return (
    <div>
      <h2 ref={titleRef}>useRef App Page</h2>
      <input type="text" ref={inputRef} />
      <button onClick={showTitleDOM}>查看title的dom</button>
    </div>
  );
});
export default App;
  • useRef绑定值,解决闭包陷阱问题
import React, { memo, useCallback, useRef, useState } from "react";
const ValueRefCpn = memo(() => {
  const [count, setCount] = useState(0);
  // 通过useRef解决闭包陷阱
  const countRef = useRef();
  countRef.current = count;

  const add = useCallback(() => {
    setCount(countRef.current + 1);
    // setCount(count + 1); //闭包陷阱 0 + 1 = 1
  }, []);
  return (
    <div>
      <h2> ValueRefCpn Page</h2>
      <button onClick={(e) => setCount(count + 1)}>+1</button>
      <button onClick={add}>+1({count})</button>
    </div>
  );
});
export default ValueRefCpn;