ChatGPT解决这个技术问题 Extra ChatGPT

我可以在 useEffect 挂钩中设置状态吗

假设我有一些依赖于其他状态的状态(例如,当 A 更改时,我希望 B 更改)。

创建一个观察 A 并在 useEffect 钩子内设置 B 的钩子是否合适?

效果是否会级联,当我单击按钮时,第一个效果会触发,导致 b 发生变化,导致第二个效果在下一次渲染之前触发?像这样构建代码是否有任何性能劣势?

let MyComponent = props => {
  let [a, setA] = useState(1)
  let [b, setB] = useState(2)
  useEffect(
    () => {
      if (/*some stuff is true*/) {
        setB(3)
      }
    },
    [a],
  )
  useEffect(
    () => {
      // do some stuff
    },
    [b],
  )

  return (
    <button
      onClick={() => {
        setA(5)
      }}
    >
      click me
    </button>
  )
}

H
Hossam Mourad

一般来说,在 useEffect 中使用 setState 会创建一个您很可能不想导致的无限循环。该规则有几个例外,我稍后会介绍。

useEffect 在每次渲染后调用,当在其中使用 setState 时,它将导致组件重新渲染,这将调用 useEffect 等等。

useEffect 中使用 useState 不会导致无限循环的流行情况之一是当您将空数组作为第二个参数传递给 useEffect 时,例如 useEffect(() => {....}, []),这意味着应该调用一次效果函数:仅在第一次安装/渲染之后。当您在组件中进行数据获取并且希望将请求数据保存在组件的状态中时,这会被广泛使用。


useEffect 中使用 setState 的另一种情况是在订阅或事件侦听器中使用 setting state。但别忘了取消订阅reactjs.org/docs/hooks-effect.html#effects-with-cleanup
这并非严格意义上的正确 - useState 仅在您更新状态的值与前一个不同时才会触发,因此除非值在周期之间发生变化,否则会阻止无限循环。
这个答案是不正确的,而不是问题的重点:在代码中,单击按钮时组件仅呈现两次,没有无限循环
在 useEffect 中设置状态是一个非常常见的用例。想想数据加载,useEffect 调用 API,获取数据,使用 set 部分的 useState 进行设置。
我更新了答案以解决您提到的问题,现在更好了吗?
M
Mosia Thabo

为了将来的目的,这也可能有所帮助:

可以在 useEffect 中使用 setState,您只需要注意已经描述的不要创建循环。

但这不是可能发生的唯一问题。见下文:

假设您有一个组件 Comp,它从父级接收 props,并根据 props 更改设置 Comp 的状态。出于某种原因,您需要为不同的 useEffect 中的每个道具进行更改:

不要这样做

useEffect(() => {
  setState({ ...state, a: props.a });
}, [props.a]);

useEffect(() => {
  setState({ ...state, b: props.b });
}, [props.b]);

如您在此示例中所见,它可能永远不会改变 a 的状态:https://codesandbox.io/s/confident-lederberg-dtx7w

在本例中发生这种情况的原因是,当您同时更改 prop.aprop.b 时,两个 useEffects 在同一个反应周期中运行,所以当您更改 setState{...state} 的值是两个 useEffect 完全相同,因为它们处于相同的上下文中。当您运行第二个 setState 时,它将替换第一个 setState

改为这样做

这个问题的解决方案基本上是这样调用 setState

useEffect(() => {
  setState(state => ({ ...state, a: props.a }));
}, [props.a]);

useEffect(() => {
  setState(state => ({ ...state, b: props.b }));
}, [props.b]);

在此处查看解决方案:https://codesandbox.io/s/mutable-surf-nynlx

现在,当您继续执行 setState 时,您始终会收到最新且正确的状态值。

我希望这可以帮助别人!


上述解决方案帮助了我setName(name => ({ ...name, a: props.a }));
这在箭头函数 setItems(items => [...items, item]) 部分也帮助了我
早上 7 点到下午 3 点都没有解决办法,现在你救了我。
我试过这个解决方案。 setState(state => ({ ...state, useEffectValue })); 当我登录 state 时,它仍然是空的。如果我在输出数组中传递 useEffectValue,我会得到一个无限循环。 ¯_(ツ)_/¯
非常感谢您提供这个令人难以置信的提示。我确实覆盖了多个 useEffect 调用的状态,这些调用在安装组件时触发了所有这些调用。
v
vsync

即使您在一个效果内设置状态,效果也总是在渲染阶段完成后执行,另一个效果将读取更新的状态并仅在渲染阶段后对其进行操作。

话虽如此,最好以相同的效果采取两种操作,除非 b 可能由于 changing a 以外的原因而改变,在这种情况下,您也希望执行相同的逻辑


所以如果 A 改变 B,组件会渲染两次,对吗?
@alaboudi 是的,如果 A 更改导致 useeffect 运行设置 B 则组件会渲染两次
@alaboudi 是的.. 正如 Shubham Khatri 所说,它将再次渲染。但是您可以在使用第二个参数重新渲染后跳过调用效果参考 reactjs.org/docs/…
M
Mosia Thabo

useEffect 可以挂钩某个道具或状态。所以,为了避免无限循环钩子,你需要做的就是绑定一些变量或状态来生效

例如:

useEffect(myeffectCallback, [])

仅在组件渲染后才会触发上述效果。这类似于 componentDidMount 生命周期

const [something, setSomething] = withState(0)
const [myState, setMyState] = withState(0)
useEffect(() => {
  setSomething(0)
}, myState)

上面的效果只会触发我的状态已经改变这类似于componentDidUpdate,除了不是每个变化的状态都会触发它。

您可以通过此link阅读更多详细信息


谢谢,这个答案以其他答案没有的方式解决了 useEffect 的依赖数组。包含一个空数组作为 useEffect 的第二个参数将确保 useEffect 在组件渲染后执行,但包含具有特定状态或特定状态的数组将导致 useEffect 在引用状态发生更改时执行。
我不明白 withState() 代表什么。我在文档中找不到任何对它的引用。
f
ford04

▶ 1. 我可以在 useEffect 挂钩中设置状态吗?

原则上,您可以在需要的地方自由设置状态 - 包括在 useEffecteven during rendering 内。只需通过正确设置 Hook deps 和/或有条件地声明来确保避免无限循环。

▶ 2. 假设我有一些依赖于其他状态的状态。创建一个观察 A 并在 useEffect 钩子内设置 B 的钩子是否合适?

您刚刚描述了 useReducer 经典用例:

当您具有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常比 useState 更可取。 (React 文档)当设置状态变量取决于另一个状态变量的当前值时,您可能想尝试将它们都替换为 useReducer。 [...] 当您发现自己在编写 setSomething(something => ...) 时,现在是考虑改用 reducer 的好时机。 (丹·阿布拉莫夫,反应过度的博客)

let MyComponent = () => { let [state, dispatch] = useReducer(reducer, { a: 1, b: 2 }); useEffect(() => { console.log("B 的一些影响"); }, [state.b]); return (

A: {state.a}, B: {state.b}

); }; // B 依赖于 A。如果 B >= A,则将 B 重置为 1。 function reducer(state, { type, payload }) { const someCondition = state.b >= state.a; if (type === "SET_A") return someCondition ? { a: 有效载荷, b: 1 } : { ...状态, a: 有效载荷 };否则 if (type === "INCREMENT_B") return { ...state, b: state.b + 1 };返回状态; } ReactDOM.render(, document.getElementById("root"));

▶ 3. 效果是否会级联,当我单击按钮时,第一个效果会触发,导致 b 发生变化,导致第二个效果在下一次渲染之前触发?

useEffect 始终在提交渲染并应用 DOM 更改之后运行。第一个效果触发,更改 b 并导致重新渲染。此渲染完成后,由于 b 更改,将运行第二个效果。

让 MyComponent = props => { console.log("render");让 [a, setA] = useState(1);让 [b, setB] = useState(2);让 isFirstRender = useRef(true); useEffect(() => { console.log("useEffect a, value:", a); if (isFirstRender.current) isFirstRender.current = false; else setB(3); return () => { console.log( "卸载 useEffect a, value:", a); }; }, [a]); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b ]); return (

a: {a}, b: {b}

); }; ReactDOM.render(, document.getElementById("root"));

▶ 4. 像这样结构化代码是否有任何性能劣势?

是的。通过将 b 的状态更改包装在 a 的单独 useEffect 中,浏览器有一个额外的布局/绘制阶段 - 这些效果可能对用户可见。如果您无法尝试 useReducer,您可以直接将 b 状态与 a 一起更改:

让 MyComponent = () => { console.log("render");让 [a, setA] = useState(1);让 [b, setB] = useState(2); useEffect(() => { console.log("useEffect b, value:", b); return () => { console.log("unmount useEffect b, value:", b); }; }, [b ]); const handleClick = () => { console.log("Clicked!");集A(5); b >= 5 ?集合B(1):集合B(b + 1); }; return (

a: {a}, b: {b}

); }; ReactDOM.render(, document.getElementById("root"));


很好的解释,谢谢 ford04
M
M. R. Mallick

尝试将 setState 包装在一个 if 语句中,以检查状态是否需要更改 - 如果是,请更改它,否则 return () => {}

例如,

useEffect(() => {
    if(a.currentCondition !== a.desiredCondition) {
        setA();
    }
    return cleanup;
}, [b])