今天重点讲 useCallback、useMemo、useReducer、useRef

372 阅读4分钟

一、useCallBack的作用以及使用

作用:返回一个被缓存的函数。 配合React.memo使用。

使用:
// 新建父组件
import { useState, useCallback } from 'react';
import Children from './children';

function App() {
  const [count, setCount] = useState(0);
  const [rely, setRely] = useState('aaa');
  
  // 不使用useCallback
  const increment = () => {
    setCount((count) => {
      return count + 1;
    })
  }
  // 通过useCallback包装
  const increment = useCallback(() => {
    setCount((count) => {
      return count + 1;
    })
  }, [rely])
  const change = () => {
    setRely('bbb')
  }
  return (
    <div className="App">
      count: {count}
      <button onClick={change}>父组件改变依赖</button>
      <Children increment={increment} />
    </div>
  );
}

export default App;
// 新建子组件
import React, { memo } from 'react';

const Children = memo((props) => {
    const { increment } = props;
    console.log('渲染111111')
    return (
        <div>
            <button onClick={increment}>子组件count+1</button>
        </div>
    )
})
export default Children;

通过点击子组件按钮可见子组件每次都会渲染。

image.png

使用useCallback后,点击子组件按钮,子组件不会重复渲染,只在初始化的时候渲染一次。

image.png 点击按钮改变依赖值,子组件才会被重新渲染

image.png

二、useMemo的作用以及使用

作用:返回一个被缓存的值

使用:
import { useState, useMemo } from 'react';
import './App.css';

function App() {
  const [count, setCount] = useState(0);
  const [total, setTotal] = useState(0);

  // 没有使用 useMemo,即使是更新 total, countToString 也会重新计算
  const countToString = (() => {
    console.log("countToString 被调用");
    return count.toString();
  })();

  // 使用了 useMemo, 只有 total 改变,才会重新计算
  const totalToStringByMemo = useMemo(() => {
    console.log("totalToStringByMemo 被调用");
    return total + "";
  }, [total]);
  return (
    <div className="App">
      <h3>countToString: {countToString}</h3>
      <h3>countToString: {totalToStringByMemo}</h3>
      <button
        onClick={() => {
          setCount((count) => count + 1);
        }}
      >
        Add Count
      </button>
      <br />
      <button
        onClick={() => {
          setTotal((total) => total + 1);
        }}
      >
        Add Total
      </button>
    </div>
  );
}

export default App;

分别点击按钮观察

image.png

image.png 可以发现使用了useMemo缓存的值不会重新计算

三、useReducer的作用以及使用

作用:useState的一种替代品,在一些state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等,可以使用useReducer。它接收当前应用的state和触发的动作action,计算并返回最新的state。

参数:接收三个参数,分别是:1.定义的reducer函数,用于更新state。2.state的初始值。2.第三个定义的init函数,如果加了这个参数,即对定义的初始值执行init函数,这么做可以将用于计算 state 的逻辑提取到 reducer 外部,这也为将来对重置 state 的 action 做处理提供了便利。

使用:

我们简单写一个登录来感受下useState和useReducer。

使用useState写登录

import React, { useState } from "react";

function Example() {
  const [name, setName] = useState(""); // 用户名
  const [pwd, setPwd] = useState(""); // 密码
  const [isLoading, setIsLoading] = useState(false); // 是否展示loading,发送请求中
  const [error, setError] = useState(""); // 错误信息
  const [isLoggedIn, setIsLoggedIn] = useState(false); // 是否登录

  const login = (name, pwd) => {
    setIsLoading(true);
    if (name === "123" && pwd === "123") {
      setError("");
      setIsLoggedIn(true);
      setIsLoading(false);
    } else {
      setError("error");
      setName("");
      setPwd("");
      setIsLoggedIn(false);
      setIsLoading(false);
    }
  };
  return (
    <div>
      用户名:
      <input
        type="text"
        onChange={(e) => setName(e.target.value)}
        value={name}
      />
      <br />
      密码:
      <input type="text" onChange={(e) => setPwd(e.target.value)} value={pwd} />
      <br />
      <button onClick={() => login(name, pwd)} disabled={isLoading}>
        登录
      </button>
      <br />
      {isLoggedIn ? "登录成功" : error}
    </div>
  );
}

我们定义了5个state来描述页面的状态,在login函数中当登录成功、失败时进行了一系列复杂的state设置。可以想象随着需求越来越复杂更多的state加入到页面,更多的setState分散在各处,很容易设置错误或者遗漏,维护这样的老代码更是一个噩梦。

使用useReducer写登录
import React, { useReducer } from "react";

const initState = {
  name: "",
  pwd: "",
  isLoading: false,
  error: "",
  isLoggedIn: false,
};
function loginReducer(state, action) {
  switch (action.type) {
    case "name":
      return {
        ...state,
        name: action.payload.name,
      };
    case "pwd":
      return {
        ...state,
        pwd: action.payload.pwd,
      };
    case "login":
      return {
        ...state,
        isLoading: true,
        error: "",
      };
    case "success":
      return {
        ...state,
        isLoggedIn: true,
        isLoading: false,
        error: "",
      };
    case "error":
      return {
        ...state,
        error: action.payload.error,
        name: "",
        pwd: "",
        isLoading: false,
        isLoggedIn: false,
      };
    default:
      return state;
  }
}
function Example() {
  const [state, dispatch] = useReducer(loginReducer, initState);
  const { name, pwd, isLoading, error, isLoggedIn } = state;
  const login = (name, pwd) => {
    dispatch({ type: "login" });
    if (name === "123" && pwd === "123") {
      dispatch({ type: "success" });
    } else {
      dispatch({
        type: "error",
        payload: { error: "登录失败" },
      });
    }
  };
  return (
    <div>
      用户名:
      <input
        type="text"
        onChange={(e) => {
          dispatch({
            type: "name",
            payload: { name: e.target.value },
          });
        }}
        value={name}
      />
      <br />
      密码:
      <input
        type="text"
        onChange={(e) => {
          dispatch({
            type: "pwd",
            payload: { pwd: e.target.value },
          });
        }}
        value={pwd}
      />
      <br />
      <button onClick={() => login(name, pwd)} disabled={isLoading}>
        登录
      </button>
      <br />
      {isLoggedIn ? "登录成功" : error}
    </div>
  );
}

四、useRef的作用以及使用

1.对useRef的理解
  • 返回一个可变的 ref 对象,该对象只有个 current 属性,初始值为传入的参数( initialValue )。
  • 返回的 ref 对象在组件的整个生命周期内保持不变
  • 当更新 current 值时并不会 re-render ,这是与 useState 不同的地方
  • 更新 useRef 是 side effect (副作用),所以一般写在 useEffect 或 event handler 里
  • useRef 类似于类组件的 this
案例1.获取组件内的dom值
import React, { useRef } from 'react'
const App = () => {
   const inputEl = useRef(null)
   const handleFocus = () => {
       // `current` 指向已挂载到 DOM 上的文本输入元素
       inputEl.current.focus()
   }
   return (
       <p>
           <input ref={inputEl} type="text" />
           <button onClick={handleFocus}>Focus the input</button>
       </p>
   )
}
export default App
案例2.采用alert弹出实时的值
不采用useRef
import { useState, useMemo } from 'react';
import './App.css';

function App() {
  const [like, setLike] = useState(0)
  console.log('11111')
    function handleAlertClick() {
        setTimeout(() => {
            alert(`you clicked on ${like}`) 
        }, 2000)
    }
    return (
        <>
            <button onClick={() => setLike(like + 1)}>{like}赞</button>
            <button onClick={handleAlertClick}>Alert</button>
        </>
    )
}

export default App;

当我们点击使like + 1,当点击到4时,点击alert,然后继续点击使得like + 1

image.png 这样子并不能拿到当时的值。

使用useRef
import { useState, useRef, useEffect } from 'react';
import './App.css';

function App() {
  // 定义一个实例变量
  let like = useRef(0);
  const [stat, setStat] = useState(like.current);
  function handleAlertClick() {
    setTimeout(() => {
      alert(`you clicked on ${like.current}`);
    }, 2000);
  }
  useEffect(() => {
    console.log(like.current, 'like.current');
  }, [like])
  return (
    <>
      <button
        onClick={() => {
          like.current = like.current + 1;
          setStat(like.current)
        }}
      >
        {stat}
      </button>
      <button onClick={handleAlertClick}>Alert</button>
    </>
  );
}

export default App;

执行上面同样的操作

image.png 可以看到可以实时拿到值。

前端小白,感谢关注😊