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输出?
附加信息:
- 我还尝试在
TestComponent
中使用 const uidRef = useRef(0)
和一个递增 uidRef.current
的匿名化简器。行为是一样的。
- 我还尝试将控制台输出添加到
nextUID()
。输出为 new uid 1; new uid 2; new uid 4; ...
谢谢。
好吧 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;
我创建了一个 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输出?
附加信息:
- 我还尝试在
TestComponent
中使用const uidRef = useRef(0)
和一个递增uidRef.current
的匿名化简器。行为是一样的。 - 我还尝试将控制台输出添加到
nextUID()
。输出为new uid 1; new uid 2; new uid 4; ...
谢谢。
好吧 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;