首页 > 解决方案 > React 在功能组件中的组件的每次更新时调用构造函数

问题描述

我做了一个演示,显示了我的问题。在我的实际情况中,我有更复杂的数据结构,以及我需要它的更多理由。

原因 - 我制作了一个非常通用的组件包装器(App在示例中)。消费者程序员可以提供自己的方式,用于如何在我的组件中显示内容。他必须提供一个函数,该函数决定 - 何时渲染什么(以道具componentSwitch为例)。他提供的所有组件都必须具有继承特定接口的道具(在示例中)。当然,在某些情况下,他需要为组件提供更多的 props。在这种情况下,我这样编写组件开关:AppmyComponentSwitch()IItemBaseProps

c = (props: IItemBaseProps) => <TypeB {...props} color="red" />;

这实现了我想要的 -c将成为一个将IItemBaseProps作为道具的组件。但它会返回TypeB带有附加属性的组件color

出现的问题 - 每次呈现此功能组件时,TypeB都会调用构造函数。componentDidUpdate永远不会被调用。

我制作了一个示例应用程序。这是代码:

import React, { Component } from "react";
import { render } from "react-dom";

interface IItemBaseProps {
  value: string;
}
interface IItemAProps extends IItemBaseProps {}
interface IItemBProps extends IItemBaseProps {
  color: string;
}

interface IItem {
  type: "A" | "B";
  props: IItemBaseProps;
}

class TypeA extends Component<IItemAProps> {
  constructor(props: IItemAProps) {
    super(props);
    console.log("TypeA::constructor", props);
  }
  componentDidUpdate(props: IItemAProps) {
    console.log("TypeA::componentDidUpdate", props, this.props);
  }
  render() {
    return (
      <div>
        <b>{this.props.value}</b>
      </div>
    );
  }
}
class TypeB extends Component<IItemBProps> {
  constructor(props: IItemBProps) {
    super(props);
    console.log("TypeB::constructor", props);
  }
  componentDidUpdate(props: IItemBProps) {
    console.log("TypeB::componentDidUpdate", props, this.props);
  }
  render() {
    return (
      <div>
        <u style={{ color: this.props.color }}>{this.props.value}</u>
      </div>
    );
  }
}
interface IITemWrapperProps {
  props: IItemBaseProps;
  component: React.ComponentType<IItemBaseProps>;
}
class ItemWrapper extends Component<IITemWrapperProps> {
  render() {
    return <this.props.component {...this.props.props} />;
  }
}

interface AppProps {
  componentSwitch: (type: "A" | "B") => React.ComponentType<IItemBaseProps>;
}
interface AppState {
  items: IItem[];
}

class App extends Component<AppProps, AppState> {
  constructor(props) {
    super(props);
    this.state = {
      items: [
        {
          type: "A",
          props: {
            value: "Value A1"
          }
        },
        {
          type: "A",
          props: {
            value: "Value A2"
          }
        },
        {
          type: "B",
          props: {
            value: "Value B1"
          }
        },
        {
          type: "ERR",
          props: {
            value: "Erroring item"
          }
        }
      ]
    };
  }

  onClick = () => {
    console.log("---- click");
    this.setState(state => {
      return {
        ...state,
        items: state.items.map((item, i) => {
          if (i === 0) {
            console.log("Updating items[" + i + "], type: " + item.type);
            return {
              ...item,
              props: {
                ...item.props,
                value: item.props.value + "!"
              }
            };
          }
          return item;
        })
      };
    });
  };

  render() {
    return (
      <div>
        <div>
          {this.state.items.map((item, i) => {
            return <ItemWrapper key={i} component={this.props.componentSwitch(item.type)} props={item.props} />;
          })}
        </div>
        <div>
          <button onClick={this.onClick}>Update first item!</button>
        </div>
      </div>
    );
  }
}

function myComponentSwitch(
  type: "A" | "B"
): React.ComponentType<IItemBaseProps> {
  if (type === "A") {
    return TypeA;
  }
  if (type === "B") {
    return (props: IItemBaseProps) => <TypeB {...props} color="red" />;
  }
  return (props: IItemBaseProps) => (
    <div>
      <i>{props.value}</i>
    </div>
  );
}

render(<App componentSwitch={myComponentSwitch}/>, 
document.getElementById("root"));

而且,你可以在这里演示: https ://stackblitz.com/edit/react-ts-a95cd6

有一个按钮,用于更新第一项。控制台中的输出:

TypeA::constructor {value: "Value A1"}
TypeA::constructor {value: "Value A2"}
TypeB::constructor {value: "Value B1", color: "red"} // So far so good, 1st render
---- click // event on click
Updating items[0], type: A
TypeB::constructor {value: "Value B1", color: "red"} // TypeB is constructed again as a new instance
TypeA::componentDidUpdate {value: "Value A1"} {value: "Value A1!"} // TypeA components are just updated
TypeA::componentDidUpdate {value: "Value A2"} {value: "Value A2"}

我有点理解,为什么它会这样。我想知道 - 这是一个性能漏洞吗?我猜想,更新组件会比每次更新都制作新组件便宜。我该怎么做,以便这些组件只获得更新?

标签: javascriptreactjstypescript

解决方案


每次调用 setState 时,组件都会保存该状态,并且他的所有孩子都会获得一个完全重新实例化的循环。

如果您想优化循环,您可以重新修改应用程序以将状态部分声明到应该真正重新渲染的位置。

或者使用一个全局状态解决方案(redux、easy-peasy ...)

但是 React 团队移动和提升的方式是组件中的碎片状态,并使用上下文来获得更多可共享的状态。


推荐阅读