在 useEffect 中使用 useState 值而不使状态更新 useEffect

Posted

技术标签:

【中文标题】在 useEffect 中使用 useState 值而不使状态更新 useEffect【英文标题】:Use useState value in useEffect without making the state update useEffect 【发布时间】:2020-10-20 20:15:56 【问题描述】:

我正在开发一个基于对象键管理字符串数组的函数。假设它看起来像这样:

import React,  useState, useEffect  from "react";
import FieldContext from "../contexts/FieldContext";
import io from "socket.io-client";

const [socket, setSocket] = useState(null);
// the `data` array gets changed every second due to a WebSocket, in case that's important
const [data, setData] = useState( foo: [], bar: [] );
const [connections, setConnections] = useState(["conn1", "conn2"]);

const  checkedFields  = useContext(FieldContext); // ["foo", "moo"];

useEffect(() => 
  setConnections(prevConnections => 
    // The code below does the following: 
    // Loop through the temporary connections (which is a copy of checkedFields)
    // A. if `tempConn` is a key in the `data` object, push the name with `update_` prefix to the `_conns` array
    // B. If not, just push it without a prefix to the `_conns` array
    // Since the `checkedFields` array is ["foo", "moo"], the first element will get the prefix,
    // the other won't and will just get pushed.
    let _tempConns = [...checkedFields];
    let _conns = [];
    _tempConns.forEach(tempConn => 
      if (data[tempConn] !== undefined) _conns.push(`update_$tempConn`);
      else _conns.push(tempConn);
    );
    return _conns;
  );
, [checkedFields]);

// the websocket hook
useEffect(() => 
  const _socket = io(WS_URI);
  _socket.on("info", data => 
    // some magic happens here to add to the `data` object which is not important for this question
  );
  setSocket(_socket);
, [])

我在使用这个钩子时收到以下警告:React Hook useEffect has a missing dependency: 'data'. Either include it or remove the dependency array。我明白,但如果我在依赖数组中包含data,我会得到大量不必要的更新。我该如何防止这种情况发生? (请不要使用// eslint-disable-next-line

【问题讨论】:

data 发生变化时,您还有其他需要运行的效果吗? 是的,我有一个,这只是演示代码 你好。通过您的更新(websocket),我发现了一个类似的示例。它正在使用 Reducer ^^: adamrackis.dev/state-and-use-reducer 。希望对你有用 我打算建议const data = useRef(foo: [], bar: [] ) 使用套接字直接更新data.current 属性值...但是如果您希望在data 的引用更改时运行其他效果,那么它不是可行且行不通... 【参考方案1】:

您不仅在 useEffect 钩子的依赖数组中丢失了 data,而且还从依赖数组中丢失了 setConnections() 函数。

您可以使用useReducer 挂钩将状态更新逻辑移出useEffect 挂钩。

const initialState = 
   data:  foo: [], bar: [] ,
   connections: ["conn1", "conn2"]
 

const reducerFunc = (state, action) => 
   switch(action.type) 
       case 'UPDATE_CONNECTIONS':
          let _tempConns = [...action.checkedFields];
          let _conns = [];
          _tempConns.forEach(tempConn => 
              if (state.data[tempConn] !== undefined) _conns.push(`update_$tempConn`);
              else _conns.push(tempConn);
          );
          return  ...state, connections: _conns ;
        default: 
          return state;
   
;

const [myState, dispatch] = useReducer(reducerFunc, initialState);

const  checkedFields  = useContext(FieldContext); // ["foo", "moo"];

useEffect(() => 
    dispatch( type: 'UPDATE_CONNECTIONS', payload: checkedFields );
, [checkedFields]);

由于 React 确保 dispatch 函数不会改变,你可以从 useEffect 钩子的依赖数组中省略它。

useReducer钩子的具体使用方法见以下链接:

useReducer Hook - React Hooks Cheatsheet useReducer Hook - React docs

【讨论】:

我根本不懂reducer,所以我不知道如何管理这个.. 我添加了 reducer 函数的实现,它应该让您了解如何在代码中实现 useReducer 钩子【参考方案2】:

如果您可以在 websocket 中使用以下内容并且没有其他 useEffects 依赖于 data 参考,则可以尝试使用 useRef

useRef 返回一个可变的 ref 对象,其 .current 属性初始化为传递的参数 (initialValue)。返回的对象将在组件的整个生命周期内持续存在。

// make `data` a ref object
// Pitfall: any changes to `data.current` will NOT be tracked 
const data = useRef( foo: [], bar: [] )


// the websocket hook
useEffect(() => 
  ...
  _socket.on("info", data => 
    ...
    // update properties on `data.current`
    data.current.foo = 'some new value'
    data.current.bar = 'anoher new value;
  );
  
  ...
, [])

useEffect(() => 
  setConnections(prevConnections => 
    ... 
    // reference data value from data.current
    const data = data.current

    ...
    return _conns;
  );
, [checkedFields])

【讨论】:

你在useEffect钩子的依赖数组中是不是缺少datasetConnection() @Yousaf useRef 不需要在 useEffect 中添加依赖项【参考方案3】:

随着您的更新,您的套接字似乎永远不会关闭。 如果您的组件卸载/重新安装,则在后台添加一个新套接字。 然后在每次更新时,它都会触发所有以前的套接字。

你的套接字钩子应该返回一个关闭套接字函数。 (我猜,我从不直接使用socket)

  useEffect(() => 
  const _socket = io(WS_URI);
  _socket.on("info", data => 
    // some magic happens here to add to the `data` object which is not important for this question
  );
  setSocket(_socket);
  return () => _socket.close();
, [])

(这不是您的回答,但它可以提供帮助:))

【讨论】:

我更新了我的答案以更准确。您只会在卸载/重新安装时出现内存泄漏;)。真的很奇怪哈哈

以上是关于在 useEffect 中使用 useState 值而不使状态更新 useEffect的主要内容,如果未能解决你的问题,请参考以下文章

在异步函数中正确使用 useEffect 和 useState [重复]

useState 中的变量未在 useEffect 回调中更新

useState和useEffect

HOOK—useState、useEffect的使用

我可以在不使用 useState 的情况下访问 useEffect 中的变量吗?

useMemo 与 useEffect + useState