在 ReactJS 中如何在销毁 child 时将 Child 状态保存在 Parent 状态?

In ReactJS How to save Child State in Parent state on destroying child?

我有一个 parent 组件和两个 child 组件如下:

父组件:

import React, { useState } from "react";
import { Child1Comp } from "./Child1Comp";
import { Child2Comp } from "./Child2Comp";

export function ParentComp() {
  const [step, setStep] = useState(1);
  const [parentState, setParentState] = useState({});
  const handleNextBtn = () => setStep((step) => step + 1);
  const handlePrevBtn = () => setStep((step) => step - 1);

  return (
    <div>
      <div>{`the parent state is: ${JSON.stringify(parentState)}`}</div>
      {step === 1 && (
        <Child1Comp
          step={step}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      {step === 2 && (
        <Child2Comp
          step={step}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      <br />
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handleNextBtn}
        disabled={step === 2}
      >
        Next
      </button>
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handlePrevBtn}
        disabled={step === 1}
      >
        Prev
      </button>
      <br />
      <br />
      {`current step is : ${step}`}
    </div>
  );
}

子组件 1:

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

export function Child1Comp({ step, parentState, setParentState }) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child1), []);
  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child1: inputValue
      })),
    [step]
  );

  return (
    <div>
      <br />
      name:
      <input
        id="child1"
        name="child1"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}

子组件:

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

export function Child2Comp({ step, parentState, setParentState }) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child2), []);
  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      })),
    [step]
  );

  return (
    <div>
      <br />
      family:
      <input
        id="child2"
        name="child2"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}

现在我希望当用户通过单击下一步按钮更改步长值时,child 状态值保存在 parent 状态,所以我在每个 [=45] 中使用此代码=]:

child1:

useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child1: inputValue
          })),
        [step]
      );

child2:

useEffect(
        () =>
          setParentState((parentState) => ({
            ...parentState,
            child2: inputValue
          })),
        [step]
      );

但是当 parent 组件中的步骤状态发生变化时,顶部 useEffect 的 none 会运行。 child 状态未保存在 parent 状态。:(

你有解决这个问题的办法吗?

codesandbox link

将状态保留在父组件中,并将 getter 和 setter 作为 props 传递下去。 像这样:

export function ParentComp() {
    const [step, setStep] = useState(1);
    const [child1Input, setChild1Input] = useState(null);
    const [child2Input, setChild2Input] = useState(null);
    
    return (
      <div>
      
        {step === 1 && (
          <Child1Comp
            input={child1Input}
            setInput={setChild1Input}
          />
        )}
        {step === 2 && (
          <Child1Comp
          input={child2Input}
          setInput={setChild2Input}
        />
        )}

        ...

现在,子组件不再需要状态变量或 useEffects,这意味着一旦代码增长,出现错误的风险就会降低,并且更容易理解流程。

export function Child1Comp({ input, setInput }) {

 return (
    <div>
      <br />
      name:
      <input
        id="child1"
        name="child1"
        value={inputValue}
        onChange={e => setInput(e.target.value)}
      />
    </div>
  );
}

当然,如果您愿意,您仍然可以将状态保留在一个对象中,不过我希望您能了解总体思路。

解决方案:

useEffect

中关注 inputValue 而不是 step

孩子 1

useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child1: inputValue
      })),
    [inputValue]
  );

孩子 2

useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      })),
    [inputValue]
  );

问题:

When you click on next/prev, the step value changes to 1 or 2 respectively in the parent. And accordingly, the respective child components get hidden and no longer exist on DOM. So that's why your useEffect code does not execute.

子组件在step改变时卸载,它看不到step prop何时改变。相反,您应该观察 inputValue 并在每次更改时将其设置为父对象。

  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      })),
    [inputValue]
  );

如果你真的要使用useEffect,你的useEffect中缺少依赖(useEffect的第二个参数,依赖在一个数组中):

  useEffect(
    () => setInputValue(parentState.child2), 
   [parentState.child2] //If parentState.child2 change, useEffect function will be called
  );
  
  useEffect(
    () =>
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      })),
    [step, inputValue, setParentState] //If one of these variables change, useEffect function will be called
  );

工作示例here

但更好的方法是处理这样的变化:

import React from "react";

export function Child1Comp({ parentState, setParentState }) {
  const handleChange = (e) =>
    setParentState((oldParentValues) => ({
      ...oldParentValues, //Spread old parent states
      [e.target.name]: e.target.value // override child1 input value here
    }));

  return (
    <div>
      <br />
      name:
      <input
        id="child1"
        name="child1"
        value={parentState.child1}
        onChange={handleChange}
      />
    </div>
  );
}

沃金示例here

注意: 标签(name、familly、...)和名称(child1、child2、...)是多余的,可以作为 props 传递,按顺序有一个 ChildComponent。像这样demo

我终于使用了这个解决方案:

我将 saveStep 状态添加到作为 'step,req,conf,action' 对象的父组件:

  • step=> 当前步数。
  • req=>显示更改步骤请求的布尔值。
  • conf=> bool 值显示保存在父项中的子状态或否。
  • 操作=>单击了下一个或上一个按钮。

仅当 req 和 conf 的值都为 true 时才执行步骤。

父组件:

import React, { useEffect, useState } from "react";
import { Child1Comp } from "./Child1Comp";
import { Child2Comp } from "./Child2Comp";

export function ParentComp() {
  const [parentState, setParentState] = useState({});
  const [saveStep, setSaveStep] = useState({
    step: 1,
    req: false,
    conf: false,
    action: ""
  });

  useEffect(() => {
    if (saveStep.req && saveStep.conf) {
      setSaveStep((saveStep) => ({
        step:
          saveStep.action === "next"
            ? saveStep.step + 1
            : saveStep.action === "prev"
            ? saveStep.step - 1
            : saveStep.step,
        req: false,
        conf: false
      }));
    }
  }, [saveStep]);

  const handleNextBtn = () =>
    setSaveStep((saveStep) => ({
      ...saveStep,
      req: true,
      action: "next"
    }));

  const handlePrevBtn = () =>
    setSaveStep((saveStep) => ({
      ...saveStep,
      req: true,
      action: "prev"
    }));

  return (
    <div>
      <div>{`the parent state is: ${JSON.stringify(parentState)}`}</div>
      {saveStep.step === 1 && (
        <Child1Comp
          saveStep={saveStep}
          setSaveStep={setSaveStep}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      {saveStep.step === 2 && (
        <Child2Comp
          saveStep={saveStep}
          setSaveStep={setSaveStep}
          parentState={parentState}
          setParentState={setParentState}
        />
      )}
      <br />
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handleNextBtn}
        disabled={saveStep.step === 2}
      >
        Next
      </button>
      <button
        id="nextBtn"
        name="nextBtn"
        onClick={handlePrevBtn}
        disabled={saveStep.step === 1}
      >
        Prev
      </button>
      <br />
      <br />
      {`current step is : ${saveStep.step}`}
    </div>
  );
}

子组件 1:

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

export function Child1Comp({
  saveStep,
  setSaveStep,
  parentState,
  setParentState
}) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child1), []);
  useEffect(() => {
    if (saveStep.req) {
      setParentState((parentState) => ({
        ...parentState,
        child1: inputValue
      }));
      setSaveStep((saveStep) => ({ ...saveStep, conf: true }));
    }
  }, [saveStep.req]);

  return (
    <div>
      <br />
      child1:
      <input
        id="child1"
        name="child1"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}

子组件2:

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

export function Child2Comp({
  saveStep,
  setSaveStep,
  parentState,
  setParentState
}) {
  const [inputValue, setInputValue] = useState();
  const handleChange = (e) => setInputValue(e.target.value);

  useEffect(() => setInputValue(parentState.child2), []);
  useEffect(() => {
    if (saveStep.req) {
      setParentState((parentState) => ({
        ...parentState,
        child2: inputValue
      }));
      setSaveStep((saveStep) => ({ ...saveStep, conf: true }));
    }
  }, [saveStep.req]);

  return (
    <div>
      <br />
      child2:
      <input
        id="child2"
        name="child2"
        value={inputValue}
        onChange={handleChange}
      />
    </div>
  );
}

codesandbox Link