React.useEffect堆栈执行会阻止父级设置默认值

时间:2019-11-20 16:27:58

标签: reactjs react-hooks

我已附上一个简化的示例来演示我的问题:

https://codesandbox.io/s/reactusehook-stack-issue-piq15

我有一个接收配置的父组件,应该渲染其屏幕。渲染的屏幕应该可以控制父级外观。在上面的示例中,我用颜色进行了演示。但是实际的用例是流屏幕,该屏幕具有可以由孩子控制的下一步和后退按钮。

在示例中,我为屏幕定义了常见的道具:

type GenericScreenProps = {
  setColor: (color: string) => void;
};

我创建了第一个屏幕,该屏幕不关心颜色(父级应该默认)

const ScreenA = (props: GenericScreenProps) => {
  return <div>screen A</div>;
};

我创建了第二个屏幕,该屏幕在安装时明确定义了颜色

const ScreenB = ({ setColor }: GenericScreenProps) => {
  React.useEffect(() => {
    console.log("child");
    setColor("green");
  }, [setColor]);
  return <div>screen B</div>;
};

我创建了一个地图,以便能够通过索引引用组件

const map: Record<string, React.JSXElementConstructor<GenericScreenProps>> = {
  0: ScreenA,
  1: ScreenB
};

最后,我创建了父级,该父级具有一个按钮,该按钮可交换组件并在组件更改时设置默认值

const App = () => {
  const [screenId, setScreenId] = useState(0);
  const ComponentToRender = map[screenId];
  const [color, setColor] = useState("red");
  React.useEffect(() => {
    console.log("parent");
    setColor("red"); // default when not set should be red
  }, [screenId]);
  const onButtonClick = () => setScreenId((screenId + 1) % Object.keys(map).length)
  return (
    <div>
      <button style={{ color }} onClick={onButtonClick}>
        Button
      </button>
      <ComponentToRender setColor={setColor} />
    </div>
  );
};

在此示例中,屏幕A的默认颜色应为red,第二个屏幕的默认颜色应为green

但是,颜色保留为red,因为useEffect正在使用堆栈来执行代码。如果您运行代码,则在单击按钮后将看到child,然后是parent

我已经考虑了以下解决方案,但它们并不理想:

  • 每个孩子必须明确定义颜色,没有自定义棉绒规则就无法强制执行颜色
  • 将父级转换为React类组件,必须有一个钩子解决方案

这可能是一个反模式,子组件控制其父组件的行为,因为如果不为每个屏幕复制父容器,我将无法确定这样做的方式。我想要保留单亲的原因是要在屏幕之间启用过渡。

1 个答案:

答案 0 :(得分:0)

如果我对问题的理解正确,则无需将setColor传递给孩子们。使表达式更明确可能会使代码更长一些,但我认为它有助于提高可读性。由于您分享的只是一个简化的示例,请让我知道它是否适合您的实际情况:

const ScreenA = () => {
    return <div>screen A</div>;
};

const ScreenB = () => {
    return <div>screen B</div>;
};

const App = () => {
    const [screen, setScreen] = useState<"a" | "b">("a");
    const [color, setColor] = useState<"red" | "green">("red");
    const onButtonClick = () => {
        if (screen === "a") {
            setScreen("b");
            setColor("green");
        } else {
            setScreen("a");
            setColor("red");
        }
    };

    return (
        <div>
            <button style={{ color }} onClick={onButtonClick}>
                Button
            </button>
            {screen === "a" ? <ScreenA /> : <ScreenB />}
        </div>
    );
};

render(<App />, document.getElementById("root"));