反应无限循环调度,来自 useReducer hook

时间:2020-12-19 13:42:22

标签: reactjs react-hooks

该组件的主要逻辑是页面加载时,需要保存来自URL(token)的参数,并从dispatch()函数中传递过来。为此,我有一个最初存储为 null 的状态值,但在呈现组件后我添加了一个标记。

component.js

const [state, dispatch] = useReducer()
const [value, setValue] = useState(null)

  useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [value])

   useEffect(() => {
    if (value && value !== null) {
      // console.log(1)
      dispatch({
        type: 'TEST_TYPE',
        payload: { data: value },
      })
    }
  }, [value])

当我想调用 dispatch() 中的 useEffect() 函数时,组件会重新加载,直到内存已满。我测试了第二种方法,如果有新的变化,将 dispatch() 函数调用到 useCallback() 中调用一次。但是 dispatch 函数没有被调用

const dis = useCallback(() => dispatch, [])

  useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    if (token) {
      dis({
         type: 'TEST_TYPE',
         payload: { data: token },
      })
    }
  }, [dis])

如何在加载组件且没有无限循环的情况下使 dispatch 正常工作一次?

3 个答案:

答案 0 :(得分:1)

你说:I have a state value where initially null is stored, but after rendering the component I add a token. 如果是这样,那么你做错了。而不是:

useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [value])

应该是:

useEffect(() => {
    const query = window.location.search || ''
    const queryString = new URLSearchParams(query || '')
    const token = queryString.get('token')
    setValue(token)
  }, [])

现在只要您的组件呈现,值就会更新。 现在在此之后,下面的 useEffect 将出现:

useEffect(() => {
    if (value && value !== null) {
      // console.log(1)
      dispatch({
        type: 'TEST_TYPE',
        payload: { data: value },
      })
    }
  }, [value])

然后调度将被调用。

答案 1 :(得分:1)

当使用 useReducer 钩子时,你必须指定 reducer 函数和初始状态。

function reducer(state, action) {
  return action.payload;
}

function YourComponent() {
  const [state, dispatch] = useReducer(reducer, { data: null });

...

然后您可以只使用 useReducer 中的状态,而不是在 useReducer 中的状态和 useState 中的状态中存储重复数据

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "TEST_TYPE",
        payload: { data: token },
      });
    }
  }, []);

总而言之,您的代码应该类似于以下内容

import React, { useReducer, useEffect } from "react";

function reducer(state, action) {
  return action.payload;
}

export default function YourComponent() {
  const [state, dispatch] = useReducer(reducer, { data: null });

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "TEST_TYPE",
        payload: { data: token },
      });
    }
  }, []);

  return (
    <div>
      <pre>{JSON.stringify(state)}</pre>
    </div>
  );
}

答案 2 :(得分:1)

就像 Rostyslav 所说的,您没有以正确的方式使用 useReducer。这与 useState 钩子不同:在那个钩子中,它接收到的参数将作为组件状态中定义变量的默认值,这就是为什么可以将其留空的原因。当你要修改它的内容时,你只需要使用钩子返回的 setter 函数来更新它的值。

但是,useReducer 要求您明确定义数据根据您选择的某些操作发生变化的方式。这个方法需要至少接收两个参数:reducer 函数和一个初始状态,它将作为即将到来的数据转换的起点。

reducer 函数的目的是对状态进行更改以响应分派的动作。 此方法将接收当前状态和一个动作对象(这是您要使用的值)通过dispatch发送)。根据接收到的动作,reducer 方法必须在数据中应用一些转换并返回一个将覆盖当前状态的新对象。

下面的一段代码说明了一个可以在您的情况下工作的减速器。默认情况下,状态的 token 属性将设置为 null。正如您在 reducer 的代码中看到的那样,当触发 UPDATE_TOKEN 类型的操作时,您的状态的 token 属性将更新为包含捆绑到操作负载中的 token .

const initialState = {
  token: null
};

const reducer = (state, action) => {
  switch (action.type) {
    case "UPDATE_TOKEN":
      return {
        ...state,
        token: action.payload.token
      };
    default:
      return state;
  }
};

由于您将通过分派操作将 token 存储到 store 中,因此您无需事先使用 useState 将其保存到 value 属性中. 如果您需要从组件中的任何位置访问令牌,您可以直接从 appState 变量中检索它,如下面的代码所示:

const App = () => {
  const [appState, dispatch] = useReducer(reducer, initialState);

  useEffect(() => {
    const query = window.location.search || "";
    const queryString = new URLSearchParams(query || "");
    const token = queryString.get("token");

    if (token) {
      dispatch({
        type: "UPDATE_TOKEN",
        payload: { token }
      });
    }
  }, []);

  return <div className="value">Token: {appState.token}</div>;
};

可以在 here 中找到完整的工作示例。

尽管这应该可行,但您可能需要重新考虑是否真的需要 useReducer添加reducers 和actions 增加了代码的复杂性,所以它只对需要更多逻辑来更新它们的更大的数据结构有意义。在这个特定的例子中,它似乎不值得付出努力,因为您需要执行的唯一操作是更改 token 变量的值:由于此变量只是一个字符串,因此只需使用 useState 即可轻松实现。