useReducer - keydown 处理程序重新评估每个渲染的初始状态

useReducer - keydown handler re-evaluates initial state on each render

https://codesandbox.io/s/suspicious-chebyshev-vy4mf2

我有一个 useReducer 钩子和一个带有 keydown 事件处理程序的 useEffect。问题在于,在处理键盘按钮按下而导致的每次重新渲染时,组件函数都会重新运行函数以获取初始状态。

可见的不正确结果是每次渲染时在控制台中记录的随机数(我希望没有记录)。换句话说,这里的目标是在每次渲染时停止 getInitialState 函数 运行。

import { useEffect, useReducer } from "react";
import "./styles.css";

interface IAction {
  type: string;
  payload: string;
}

export default function App() {
  const reducer = (state: string, action: IAction) => {
    switch (action.type) {
      case "KEYDOWN":
        return action.payload;
      default:
        return state;
    }
  };

  const getInitialState = () => {
    const rand = Math.random().toString();
    console.log(rand);
    return rand;
  };

  const [state, dispatch] = useReducer(reducer, getInitialState());

  const keyDownHandler = (e: KeyboardEvent): void => {
    dispatch({
      type: "KEYDOWN",
      payload: e.key
    });
  };

  useEffect(() => {
    document.addEventListener("keydown", keyDownHandler);
    return () => document.removeEventListener("keydown", keyDownHandler);
  }, [state, dispatch]);

  return <div className="App">{state}</div>;
}

当一个组件发现它需要 re-render 时,它的功能将再次 运行。你这样做相当于问为什么

export default function App() {
  // ...
  const [state, dispatch] = useReducer(reducer, getInitialState());

运行s getInitialState 每次App 运行s,相当于

export default function App() {
  // ...
  const parameterToPass = getInitialState();
  const [state, dispatch] = useReducer(reducer, parameterToPass);

这应该让事情变得清楚。

并不是说 useReducer 需要 再次计算初始值 - 它不需要,它只在第一次使用第二个参数 运行s,确定初始状态。就是你在每次 App re-renders.

时调用计算初始状态的函数

只要你确定计算初始状态的函数没有side-effects,就可以忽略它——你现在做的就好了。

另一种选择是将第三个参数传递给 useReducer 作为调用以计算初始状态的函数。

const [state, dispatch] = useReducer(reducer, null, getInitialState);

您在每次渲染时都调用了 getInitialState,这并不意味着它正在获取 re-initialized。

您可以通过 运行 为函数使用而设计的第 3 个参数位置的初始化器来避免这种情况。

您还应该在渲染函数之外为 reducer 创建函数,否则它 re-runs 比它应该的更频繁。

const { useEffect, useReducer } = React;

interface IAction {
  type: string;
  payload: string;
}
const reducer = (state: string, action: IAction) => {
  switch (action.type) {
    case "KEYDOWN":
      return action.payload;
    default:
      return state;
  }
};
function App() {
  const getInitialState = () => {
    const rand = Math.random().toString();
    console.log(rand);
    return rand;
  };

  const [state, dispatch] = useReducer(reducer, null, getInitialState);

  const keyDownHandler = (e: KeyboardEvent): void => {
    dispatch({
      type: "KEYDOWN",
      payload: e.key
    });
  };

  useEffect(() => {
    document.addEventListener("keydown", keyDownHandler);
    return () => document.removeEventListener("keydown", keyDownHandler);
  }, [state, dispatch]);

  return <div className="App">{state}</div>;
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
    <div id="root"></div>