1

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

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

我可以确定reducer函数中的所有逻辑都使用参数 'a' 执行,并且使用参数 'b' 执行(使用参数 'b' 的调用使用第一次调用修改的状态)?

MODIFIED:那么 reducer 和 setStates 的组合呢?他们的通话顺序是否也保持不变?例如,一个 setState,一个 reducer(它使用 setState 中设置的状态的值)。

4

2 回答 2

0

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

这可以帮助您更好地理解 reducer 函数:https ://redux.js.org/basics/data-flow

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

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

鉴于:

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

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

更新

useState您可以查看和钩子的源代码,useReducer看看它们将以相同的同步、顺序方式处理更新。

派遣

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);
}

两者都useState使用useReducer相同的内部调度系统(即resolveDispatcher)。由于useReducer无法处理异步操作(例如 Redux-Thunks),因此没有其他选择,只能在处理下一个分派操作之前同步处理 reducer 中的操作。

更新 2

MODIFIED:那么 reducer 和 setStates 的组合呢?他们的通话顺序是否也保持不变?例如,一个 setState,一个 reducer(它使用 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
于 2020-06-06T06:59:51.190 回答
-1

我看到的行为是dispatchinreact不是同步的,尽管许多人坚持。是同步的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按钮,我将一个接一个地执行调度,您将在控制台中看到 BOTH 调度的日志语句在任何 reducer 调用之前显示。

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

保证reducer的执行是按照排队的顺序执行的,第二个reducer收到的状态确实是前一个reducer的状态,正确。因此,基本上这仅在第二次调度的排队取决于减速器在开始下一次调度之前完成时才重要。

于 2021-09-17T18:34:52.480 回答