React 的 reducer returns 与记录值不一致的意外状态

React's reducer returns unexpected state that is not consistent with logged value

我创建了一个 reducer,它在每次使用操作 increment 分派时递增一个全局 UID。 UID的初始值为0.

预期:

我预计 UID 在每次调度时都会递增 1。

现实:

UID加2,登录状态与真实状态不一致

我的组件:

import React, { useReducer }  from "react"

let uid = 0

function nextUID() {
    uid = uid + 1
    return uid
}

function reducer(state, action) {
    switch (action.type) {
        case "increment":
            const uid = nextUID()
            const newState = `current UID is ${uid}!`
            console.log(newState)
            return newState
        default:
            return state
    }
}

function TestComponent() {
    const [state, dispatch] = useReducer(reducer, "not clicked yet")
    return <button onClick={() => dispatch({ type: "increment" })}>{state}</button>
}

export default TestComponent

输出:

Num. Clicks Button Label Console Output
0 not yet clicked
1 current UID is 1! current UID is 1!
2 current UID is 3! current UID is 2!
3 current UID is 5! current UID is 4!

问题:

按钮标签是 current UID is 3!,而状态更改的控制台输出是 current UID is 2!,这怎么可能?有没有可能react多次调用reducer,第二次就丢弃console输出?

附加信息:

谢谢。

好吧 StrictMode 给你添麻烦了,不过是个好方法。根据 React,您编写的代码是不受欢迎的。 StrictMode 调用您的 reducer 两次以消除任何 副作用 。在您的情况下,递增 uid 的实现就是这样一个 side-effect.

(简单地做 console.log 不会告诉你 reducer 被调用了两次,因为 React 将它从 [=34 的第二次调用中静音=]17.0。所以我用了一个log函数来引用同一个console.log);

这是您的代码:-

import React, { useReducer } from "react";

const log = console.log;

let uid = 0;

function nextUID() {
  uid = uid + 1;
  return uid;
}

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      log("called reducer");
      const uid = nextUID();
      const newState = `current UID is ${uid}!`;
      console.log(newState);
      return newState;
    default:
      return state;
  }
}

function TestComponent() {
  const [state, dispatch] = useReducer(reducer, "not clicked yet");
  return (
    <button onClick={() => dispatch({ type: "increment" })}>{state}</button>
  );
}

export default TestComponent;

现在您的原始代码在生产中可以正常工作,因为 reducer 只会被调用一次(因为生产没有 严格模式 包装器组件)但这不是一个好的做法。

以下是另一个实现,其中状态没有直接改变,因此在两次调用 reducer 之前的状态保持不变,下一个增量值是一致的。如果您像 state.val += 1 那样直接改变状态,您会看到与您在 严格模式 .

中的示例相同的行为
import React, { useReducer } from "react";

const log = console.log;

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      log("called reducer");
      const nextStateVal = state.val + 1;
      return {
        ...state,
        text: `current UID is ${nextStateVal}`,
        val: nextStateVal
      };
    default:
      return state;
  }
}

function TestComponent() {
  const [state, dispatch] = useReducer(reducer, {
    text: "not clicked yet",
    val: 0
  });
  return (
    <button onClick={() => dispatch({ type: "increment" })}>
      {state.text}
    </button>
  );
}

export default TestComponent;