React中化简器执行的顺序



这里提到"你能相信 React 按照调用 setState 的相同顺序更新状态吗......是的"。我的问题是调度(useReducer(事件是否也以与调用相同的顺序运行?例如,考虑这样的事情:

const [states, dispatch] = useReducer(reducer, initialState)
dispatch('a')
dispatch('b')

我能否确定化简器函数中的所有逻辑都使用参数"a"执行,并且使用参数"b"执行(参数为"b"的调用使用第一次调用修改的状态(?

修改:那么化简器和集合状态的组合呢?他们的通话顺序是否也保持不变?例如,setState,reducer(它使用setState中设置的状态值(。

是的,化简器是同步运行的纯函数,因此任何单个调度操作都会在下一个操作之前处理。

这可以帮助您更好地了解减速器功能:https://redux.js.org/basics/data-flow

功能组件主体是完全同步的,因此一切都按照每个渲染周期调用的顺序进行处理。链接来自 redux,但化简器的工作方式相同,并且可以互换,即化简器功能具有签名(state, action) => nextState

所有钩子值都在渲染周期之间工作,这意味着,在渲染周期中"排队"的所有状态更新和调度操作都按该顺序进行批处理。

鉴于:

dispatch('a')
dispatch('b')

dispatch('b')减速器案例将在dispatch('a')案例完成后运行

更新

您可以查看useStateuseReducer挂钩的源代码,并查看它们是否以相同的同步顺序方式处理更新。

type Dispatch<A> = A => void;

在这里你可以看到dispatch是一个同步函数。

使用状态

export function useState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}

用途减速机

export function useReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const dispatcher = resolveDispatcher();
return dispatcher.useReducer(reducer, initialArg, init);
}

useStateuseReducer都使用相同的内部调度系统(即resolveDispatcher(。由于useReducer无法处理异步操作(即像 Redux-Thunks(,因此除了在处理下一个调度操作之前同步处理化简器中的操作之外,别无选择。

更新 2

修改:那么化简器和集合状态的组合呢?是他们的 还保留了通话顺序?例如,setState,一个化简器(它使用 在 setState 中设置的状态的值(。

将保留顺序,但你无法将状态更新排队,然后根据该更新状态调度操作。这是由于 React 状态更新在渲染周期之间异步处理的方式。换句话说,在下一个渲染周期之前,任何setState(newState)都无法访问。

例:

const [countA, setCountA] = useState(0);
const [countB, dispatch] = useReducer(.....);
...
setCountA(c => c + 1); // enqueues countA + 1, next countA is 1
dispatch(setCountB(countA)); // dispatches with countA === 0
setCountA(c => c + 1); // enqueues countA + 1, next countA is 2
dispatch(setCountB(countA)); // dispatches with countA === 0

我看到的行为是,尽管许多人坚持,react中的dispatch不是同步的。reducer是同步的,但似乎不能保证完成dispatch保证由此产生的reducer已经完成。这在redux中是不同的,但如果我们谈论React至少在17.0.2.

下面是一个示例测试用例:

import { useReducer } from "react";
import "./styles.css";
function reducer(state, action) {
console.log("REDUCER", state, action);
switch (action.type) {
case "INCREMENT":
return state + 1;
default:
return state;
}
}
export default function App() {
const [state, dispatch] = useReducer(reducer, 0);
const myDispatch = function (...args) {
console.log("dispatched", args);
return dispatch(...args);
};
const onClick = function () {
// BOTH dispatches fired before any reducer!
myDispatch({ type: "INCREMENT" });
myDispatch({ type: "INCREMENT" });
};
const onClickAsync = async function () {
// reducer finishes before next dispatch called
await myDispatch({ type: "INCREMENT" });
await myDispatch({ type: "INCREMENT" });
};
return (
<>
<pre>{state}</pre>
<button onClick={onClick}>Increment Broken</button>
<button onClick={onClickAsync}>Increment Working</button>
</>
);
}

沙盒:https://codesandbox.io/s/dispatch-not-sync-db9wn?file=/src/App.tsx

在该示例中,如果您单击Increment Broken按钮,我将一个接一个地执行调度,您将在控制台中看到两个调度的日志语句显示在任何化简器调用之前。

另一方面,如果您单击Increment Working按钮,您将看到它正确地调度 -> 减速器 -> 调度 -> 减速器。

化简器的执行保证是按照排队的顺序进行的,第二个化简器接收到的状态确实是前一个化简器的状态,正确。因此,基本上,只有当第二次调度的排队取决于在开始下一次调度之前完成的减速器时,这才有意义。

相关内容

  • 没有找到相关文章

最新更新