简体   繁体   English

React 在重新渲染父组件时如何重用子组件/保留子组件的 state?

[英]How does React re-use child components / keep the state of child components when re-rendering the parent component?

In React, every time a component is rendered/re-rendered, it regenerates all of it's child nodes/components using createElement .在 React 中,每次渲染/重新渲染组件时,它都会使用createElement重新生成它的所有子节点/组件。 How does React know when to persist the components state between re-renders? React 如何知道何时在重新渲染之间保留组件 state?

As an example, consider the following code:例如,考虑以下代码:

class Timer extends Component {
  constructor(props) {
    super(props);
    this.state = { seconds: 0 };
  }
  tick() {
    this.setState(state => ({ seconds: state.seconds + 1 }));
  }
  componentDidMount() {
    this.interval = setInterval(() => this.tick(), 1000);
  }
  componentWillUnmount() {
    clearInterval(this.interval);
  }
  render() {
    return createElement('div', null,
      'Seconds: ',
      this.state.seconds
    );
  }
}
class Button extends Component {
  constructor(props) {
    super(props);
    this.state = { clicks: 0 };
  }
  click() {
    this.setState(state => ({ clicks: state.clicks + 1 }));
  }
  render() {
    return createElement('button', { onClick: () => this.click() },
      createElement(Timer, null),
      'Clicks: ',
      this.state.clicks
    );
  }
}
render(createElement(Button, null), document.getElementById('root'));

You can try this code with the Preact REPL here .您可以在此处使用 Preact REPL 尝试此代码。

Notice that when the button is pressed and the clicks value is updated, the state of the Timer component persists and is not replaced.请注意,当按下按钮并更新 clicks 值时, Timer组件的 state 保持不变,不会被替换。 How does React know to re-use the component instance? React 如何知道重用组件实例?

While this may seem like a simple question at first, it becomes more complex when you consider stuff like changing the props passed to a child component or lists of child components.虽然一开始这似乎是一个简单的问题,但当您考虑更改传递给子组件的道具或子组件列表等事情时,它会变得更加复杂。 How does React handle changing the props of a child component? React 如何处理更改子组件的 props? Does the child component's state persist even though it's props have changed?即使子组件的道具已更改,子组件的 state 是否仍然存在? (In Vue, the state of a component does persist when it's props change) How about lists? (在 Vue 中,组件的 state 在其 props 更改时确实存在)列表怎么样? What happens when an entry in the middle of a list of child components is removed?当子组件列表中间的条目被删除时会发生什么? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。

createElement vs render vs mount createElement vs render vs 挂载

When a React Component such as your Button is rendered, a number of children are created with createElement .当渲染一个 React 组件(例如您的Button )时,会使用createElement创建许多子组件。 createElement(Timer, props, children) does not create an instance of the Timer component, or even render it, it only creates a "React element" which represents the fact that the component should be rendered. createElement(Timer, props, children)不会创建Timer组件的实例,甚至不会渲染它,它只会创建一个“React 元素”,它表示应该渲染组件的事实。

When your Button is rendered, react will reconcile the result with the previous result, to decide what needs to be done with each child element:当您的Button被渲染时, react 会将结果与之前的结果进行协调,以决定需要对每个子元素执行什么操作:

  • If the element is not matched to one in the previous result, then a component instance is created then mounted then rendered (recursively applying this same process).如果元素与先前结果中的元素不匹配,则创建一个组件实例,然后安装然后渲染(递归地应用相同的过程)。 Note that when Button is rendered for the first time, all of the children will be new (because there is no previous result to match against).请注意,当第一次渲染Button时,所有的孩子都将是新的(因为没有以前的结果可以匹配)。
  • If the element is matched to one in the previous result, then the component instance is reused: its props are updated, then the component is re-rendered (again, recursively applying this same process).如果元素与上一个结果中的一个匹配,则重用组件实例:更新其 props,然后重新渲染组件(再次递归地应用相同的过程)。 If the props did not change, React might even choose not to re-render as an efficiency.如果 props 没有改变,React 甚至可能选择不重新渲染以提高效率。
  • Any elements in the previous result that was not matched to an element in the new result will be unmounted and destroyed.先前结果中与新结果中的元素不匹配的任何元素都将被卸载并销毁。

React's diffing algorithm React 的 diffing 算法

An element "matches" another one if React compares them and they have the same type.如果 React 比较它们并且它们具有相同的类型,则一个元素“匹配”另一个元素。

The default way for React to compare children, is to simply iterate over both lists of children at the same time, comparing the first elements with each other, then the second, etc. React 比较子元素的默认方式是简单地同时遍历两个子元素列表,将第一个元素相互比较,然后再比较第二个,等等。

If the children have key s, then each child in the new list is compared to the child in the old list that has the same key.如果孩子有key s,则将新列表中的每个孩子与旧列表中具有相同密钥的孩子进行比较。

See the React Reconciliation Docs for a more detailed explanation.有关更详细的说明,请参阅React Reconciliation Docs

Examples例子

Your Button always returns exactly one element: a button .你的Button总是只返回一个元素:一个button So, when your Button re-renders, the button matches, and its DOM element is re-used, then the children of the button are compared.因此,当您的Button重新渲染时,该button匹配,并且其 DOM 元素被重新使用,然后比较该button的子项。

The first child is always a Timer , so the type matches and the component instance is reused.第一个孩子总是一个Timer ,所以类型匹配并且组件实例被重用。 The Timer props did not change, so React might re-render it (calling render on the instance with the same state), or it might not re-render it, leaving that part of the tree untouched. Timer props 没有改变,所以 React 可能会重新渲染它(在具有相同状态的实例上调用render ),或者它可能不会重新渲染它,从而保持树的那部分保持不变。 Both of these cases would result in the same result in your case - because you have no side-effects in render - and React deliberately leaves the decision of when to re-render as an implementation detail.这两种情况都会在你的情况下产生相同的结果——因为你在render中没有副作用——并且 React 故意将何时重新渲染的决定作为实现细节。

The second child is always the string "Clicks: " so react leaves that DOM element alone too.第二个子元素始终是字符串"Clicks: " ,因此 react 也只保留了该 DOM 元素。

If this.state.click has changed since the last render, then the third child will be a different string, maybe changing from "0" to "1" , so the text node will be replaced in the DOM.如果this.state.click自上次渲染后发生了变化,那么第三个子节点将是一个不同的字符串,可能从"0"变为"1" ,因此文本节点将在 DOM 中被替换。


If Button s render were to return a root element of a different type like so:如果Buttonrender要返回不同类型的根元素,如下所示:

  render() {
    return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
      createElement(Timer, null),
      'Clicks: ',
      this.state.clicks
    );
  }

then in the first step, the a would be compared to the button and because they are different types, the old element and all of its children would be removed from the DOM, unmounted, and destroyed.然后在第一步中,将abutton进行比较,因为它们是不同的类型,旧元素及其所有子元素将从 DOM 中删除、卸载和销毁。 Then the new element would be created with no previous render result, and so a new Timer instance would be created with fresh state, and the timer would be back at 0.然后新元素将在没有先前渲染结果的情况下创建,因此将使用新的 state 创建一个新的Timer实例,并且计时器将返回 0。


Timer matches? Timer匹配? previous tree上一棵树 new tree新树
no match不匹配 <div><Timer /></div> <span><Timer /></span>
match匹配 <div>a <Timer /> a</div> <div>b <Timer /> b</div>
no match不匹配 <div><Timer /></div> <div>first <Timer /></div>
match匹配 <div>{false}<Timer /></div> <div>first <Timer /></div>
match匹配 <div><Timer key="t" /></div> <div>first <Timer key="t" /></div>

Never used Vue, but this is my take.从未使用过 Vue,但这是我的看法。

Does the child component's state persist even though it's props have changed?即使子组件的道具已更改,子组件的 state 是否仍然存在? (In Vue, the state of a component does persist when it's props change) (在 Vue 中,组件的 state 在其 props 更改时确实存在)

This depends on how you handle the props in your child.这取决于您如何处理孩子身上的道具。

This child will re-render every time you change (mutate) your props.每次您更改(变异)道具时,这个孩子都会重新渲染。

const Child = (props) => {
    return <div>{ props.username }</div>;
};

This child will not re-render when props change since the return value is dependent on the local state, and not the props.由于返回值取决于本地 state,而不是道具,因此当道具更改时,此子项不会重新渲染。

const Child = (props) => {
    const [state, setState] = useState(props.username);
    return <div>{ state }</div>;
};

This child will re-render when props change, as local state updates with the new props.当道具更改时,这个孩子将重新渲染,因为本地 state 使用新道具更新。

const Child = (props) => {
    const [state, setState] = useState(props.username);

    useEffect(() => {
        // changing props changes the component's state, causing a re-render
        setState(props.username); 
    }, [props]);

    return <div>{ state }</div>;
};

As seen in the examples above, the programmer is the one in control of whether React triggers a re-render of a child.从上面的例子中可以看出,程序员是控制 React 是否触发子元素的重新渲染的人。

How about lists?清单呢? What happens when an entry in the middle of a list of child components is removed?当子组件列表中间的条目被删除时会发生什么? A change to a list like that would obviously generate very different VDOM nodes, yet the state of the components still persists.对这样的列表进行更改显然会生成非常不同的 VDOM 节点,但组件的 state 仍然存在。

When a list of children is involved (eg. when .map is used) React will require the key parameter, so that React will be aware what was add/removed/changed between parent component re-renders.当涉及到子列表时(例如,当使用.map时)React 将需要key参数,以便 React 知道在父组件重新渲染之间添加/删除/更改了什么。 React requires that the same key be used for the same components to prevent unnecessary re-renders (don't use Math.random() as your key). React 要求对相同的组件使用相同的密钥,以防止不必要的重新渲染(不要使用Math.random()作为密钥)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 React Redux-父状态发生变化,但子组件未重新呈现 - React redux - parent state changing, but child components not re-rendering 尽管 Parent 中的状态发生了变化,但子组件不会重新渲染 - Child Components not re-rendering despite a state-change in Parent 父组件多次重新渲染子组件ReactJS - Parent component re-rendering child components multiple times ReactJS 根据父组件的状态来突变子组件,而无需重新渲染 - Mutating child components based on parent component's state, without re-rendering React子组件不会在父状态更改时重新呈现 - React child component not re-rendering on parent state change 反应子组件不会在更新的父状态上重新渲染 - React child component not re-rendering on updated parent state 更改状态后React不重新呈现,我如何设置子组件的状态? - React not re-rendering after a change in state, how do I setState of child components? React:当只有子组件需要重新渲染时,如何防止父组件重新渲染 onmousemove 状态更改? - React: How can I prevent the parent component re-rendering onmousemove state change when only the child component needs re-rendering? 停止重新渲染父组件。 仅重新渲染子组件 - Stop Re-Rendering of Parent Components. Only Re-Render Child Component React - 当父状态组件发生更改时,如何强制子组件重新呈现? - React - How do i force child components to re render when parent state component changes?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM