![](/img/trans.png)
[英]React redux - parent state changing, but child components not re-rendering
[英]How does React re-use child components / keep the state of child components when re-rendering the parent component?
在 React 中,每次渲染/重新渲染組件時,它都會使用createElement
重新生成它的所有子節點/組件。 React 如何知道何時在重新渲染之間保留組件 state?
例如,考慮以下代碼:
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'));
您可以在此處使用 Preact REPL 嘗試此代碼。
請注意,當按下按鈕並更新 clicks 值時, Timer
組件的 state 保持不變,不會被替換。 React 如何知道重用組件實例?
雖然一開始這似乎是一個簡單的問題,但當您考慮更改傳遞給子組件的道具或子組件列表等事情時,它會變得更加復雜。 React 如何處理更改子組件的 props? 即使子組件的道具已更改,子組件的 state 是否仍然存在? (在 Vue 中,組件的 state 在其 props 更改時確實存在)列表怎么樣? 當子組件列表中間的條目被刪除時會發生什么? 對這樣的列表進行更改顯然會生成非常不同的 VDOM 節點,但組件的 state 仍然存在。
createElement
vs render
vs 掛載當渲染一個 React 組件(例如您的Button
)時,會使用createElement
創建許多子組件。 createElement(Timer, props, children)
不會創建Timer
組件的實例,甚至不會渲染它,它只會創建一個“React 元素”,它表示應該渲染組件的事實。
當您的Button
被渲染時, react 會將結果與之前的結果進行協調,以決定需要對每個子元素執行什么操作:
Button
時,所有的孩子都將是新的(因為沒有以前的結果可以匹配)。如果 React 比較它們並且它們具有相同的類型,則一個元素“匹配”另一個元素。
React 比較子元素的默認方式是簡單地同時遍歷兩個子元素列表,將第一個元素相互比較,然后再比較第二個,等等。
如果孩子有key
s,則將新列表中的每個孩子與舊列表中具有相同密鑰的孩子進行比較。
有關更詳細的說明,請參閱React Reconciliation Docs 。
你的Button
總是只返回一個元素:一個button
。 因此,當您的Button
重新渲染時,該button
匹配,並且其 DOM 元素被重新使用,然后比較該button
的子項。
第一個孩子總是一個Timer
,所以類型匹配並且組件實例被重用。 Timer
props 沒有改變,所以 React 可能會重新渲染它(在具有相同狀態的實例上調用render
),或者它可能不會重新渲染它,從而保持樹的那部分保持不變。 這兩種情況都會在你的情況下產生相同的結果——因為你在render
中沒有副作用——並且 React 故意將何時重新渲染的決定作為實現細節。
第二個子元素始終是字符串"Clicks: "
,因此 react 也只保留了該 DOM 元素。
如果this.state.click
自上次渲染后發生了變化,那么第三個子節點將是一個不同的字符串,可能從"0"
變為"1"
,因此文本節點將在 DOM 中被替換。
如果Button
的render
要返回不同類型的根元素,如下所示:
render() {
return createElement(this.state.clicks % 2 ? 'button' : 'a', { onClick: () => this.click() },
createElement(Timer, null),
'Clicks: ',
this.state.clicks
);
}
然后在第一步中,將a
與button
進行比較,因為它們是不同的類型,舊元素及其所有子元素將從 DOM 中刪除、卸載和銷毀。 然后新元素將在沒有先前渲染結果的情況下創建,因此將使用新的 state 創建一個新的Timer
實例,並且計時器將返回 0。
Timer 匹配? |
上一棵樹 | 新樹 |
---|---|---|
不匹配 | <div><Timer /></div> |
<span><Timer /></span> |
匹配 | <div>a <Timer /> a</div> |
<div>b <Timer /> b</div> |
不匹配 | <div><Timer /></div> |
<div>first <Timer /></div> |
匹配 | <div>{false}<Timer /></div> |
<div>first <Timer /></div> |
匹配 | <div><Timer key="t" /></div> |
<div>first <Timer key="t" /></div> |
從未使用過 Vue,但這是我的看法。
即使子組件的道具已更改,子組件的 state 是否仍然存在? (在 Vue 中,組件的 state 在其 props 更改時確實存在)
這取決於您如何處理孩子身上的道具。
每次您更改(變異)道具時,這個孩子都會重新渲染。
const Child = (props) => {
return <div>{ props.username }</div>;
};
由於返回值取決於本地 state,而不是道具,因此當道具更改時,此子項不會重新渲染。
const Child = (props) => {
const [state, setState] = useState(props.username);
return <div>{ state }</div>;
};
當道具更改時,這個孩子將重新渲染,因為本地 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>;
};
從上面的例子中可以看出,程序員是控制 React 是否觸發子元素的重新渲染的人。
清單呢? 當子組件列表中間的條目被刪除時會發生什么? 對這樣的列表進行更改顯然會生成非常不同的 VDOM 節點,但組件的 state 仍然存在。
當涉及到子列表時(例如,當使用.map
時)React 將需要key參數,以便 React 知道在父組件重新渲染之間添加/刪除/更改了什么。 React 要求對相同的組件使用相同的密鑰,以防止不必要的重新渲染(不要使用Math.random()
作為密鑰)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.