【问题标题】:React infinite loop dispatch, from useReducer hookReact 无限循环调度,来自 useReducer 钩子
【发布时间】:2020-12-19 13:42:22
【问题描述】:

组件的主要逻辑是当页面加载时,需要从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])

当我想调用useEffect() 中的dispatch() 函数时,组件会重新加载,直到内存已满。我测试了第二种方法,将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 在组件加载后正常工作一次且没有无限循环?

【问题讨论】:

    标签: reactjs react-hooks


    【解决方案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 存储到商店中,因此您无需事先使用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添加 reducer 和操作会增加代码的复杂性,因此它只适用于需要更多逻辑来更新它们的大型数据结构。 在这个特定的示例中,它似乎不值得付出努力,因为您需要执行的唯一操作是更改token 变量的值:由于该变量只是一个字符串,因此只需使用useState 即可轻松实现。

    【讨论】:

    • 感谢您的回答,但在我的情况下,问题原来是我在子组件中运行调度功能,我在 HOC 组件中检查了它,一切正常,没有无限循环.
    • 现在需要考虑如何才能正确运行函数,以免在HOC中编写不必要的代码,因为根据项目的逻辑,许多组件使用HOC
    【解决方案2】:

    你说: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])
    

    然后 dispatch 会被调用。

    【讨论】:

    • 感谢您的回答,但dispatch函数被称为不间断
    • 哪个调度函数?component.js里面的那个?
    • 但无论如何你需要修复你的第一个 useEffect
    • 那么现在你得到了什么?
    • 我按照你的指示更新了代码,第一个useEffect没有依赖,第二个有值依赖,但是dispatch是不间断触发的
    【解决方案3】:

    当使用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>
      );
    }
    

    【讨论】:

    • 感谢您的评论,我也尝试了您的方法和其他方法,但仍然有问题,调度函数调用不停。有什么方法可以停止功能吗?我用 useCallback 测试,但同样的问题
    • 我上面的代码在没有连续调度调用的情况下工作。如果您的问题仍然存在,则意味着问题不在您提供的代码中。它在别的地方
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-10-05
    • 1970-01-01
    • 2020-09-16
    • 1970-01-01
    • 2019-12-09
    • 2020-10-22
    相关资源
    最近更新 更多