簡體   English   中英

React 在功能組件中的組件的每次更新時調用構造函數

[英]React calls constructor on every update for component within functional component

我做了一個演示,顯示了我的問題。 在我的實際情況中,我有更復雜的數據結構,以及我需要它的更多理由。

原因 - 我制作了一個非常通用的組件包裝器(示例中為App )。 消費者程序員可以提供自己的方式,用於如何在我的組件中顯示內容。 他必須提供一個 function,它決定何時渲染什么(以App上的componentSwitch屬性和myComponentSwitch()為例)。 他提供的所有組件都必須具有繼承特定接口(示例中為IItemBaseProps )的道具。 當然,在某些情況下,他需要為組件提供更多的 props。 在這種情況下,我這樣編寫組件開關:

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

這實現了我想要的 - c將是一個將IItemBaseProps作為道具的組件。 但它會返回帶有附加color屬性的TypeB組件。

出現的問題 - 每次呈現此功能組件時,都會調用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

有一個按鈕,用於更新第一項。 控制台中的 output:

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"}

我有點理解,為什么它會這樣。 我想知道 - 這是一個性能漏洞嗎? 我猜想,更新組件會比每次更新都制作新組件便宜。 我該怎么做,以便這些組件只獲得更新?

每次調用 setState 時,組件都會保存 state,並且他的所有孩子都會獲得一個完全重新實例化的循環。

如果您想優化循環,您可以重新修改應用程序以將 state 的部分聲明到應該真正重新渲染的位置。

或者使用一個全局的 state 解決方案(redux,easy-peasy ...)

但是 react 團隊移動和推廣的方式是組件中的碎片化狀態,並使用上下文來獲得更多可共享的 state。

重新創建 TypeB 組件的原因是this.props.componentSwitch(item.type)是在每次 App 組件重新渲染時計算的。 雖然它的返回值在語義上是相等的,但它們是不同的 js 對象,從 reactJs 的角度來看,它們被視為不同的 prop 值。 所以,ItemWrapper 組件認為它的 prop.component 改變了什么並重新創建它的子組件。

會不會導致性能問題? 萬一你的構造函數做了一些繁重的工作,但通常你不會在那里放太多邏輯。

但是可能還有其他問題,例如在使用某些內部 state 時。 例如,TypeB 組件在初始化時從 api 獲取一些數據,然后顯示它。 除非需要,否則您不想調用 api,但是如果您在每個父級重新渲染時重新創建一個組件,您最終會得到比您需要的更多的 api 調用。

這里可以做些什么來防止這些更新? this.props.componentSwitch(item.type)結果可以包裝成類似useMemo()的東西,這樣您就可以控制該值何時真正更新。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM