![](/img/trans.png)
[英]Why does my React grandchild component not update after receiving a change in props from its parent? Or: do I need to use state?
[英]How does React update a component and its children after a state change?
我正在看Paul O Shannessy - 從零開始構建 React
而且我非常了解安裝過程,但我很難理解 React 如何更新組件及其子組件
reconciler 通過這種方法控制更新過程:
function receiveComponent(component, element) {
let prevElement = component._currentElement;
if (prevElement === element) {
return;
}
component.receiveComponent(element);
}
Component.receiveComponent
receiveComponent(nextElement) {
this.updateComponent(this._currentElement, nextElement);
}
這是Component.updateComponent
方法:
updateComponent(prevElement, nextElement) {
if (prevElement !== nextElement) {
// React would call componentWillReceiveProps here
}
// React would call componentWillUpdate here
// Update instance data
this._currentElement = nextElement;
this.props = nextElement.props;
this.state = this._pendingState;
this._pendingState = null;
let prevRenderedElement = this._renderedComponent._currentElement;
let nextRenderedElement = this.render();
if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
}
}
這是在 state 更改后更新組件的代碼部分,我認為它也應該更新孩子,但我不明白這段代碼是如何實現的,在安裝過程中反應實例化組件以深入了解樹,但這不會在這里發生,我們需要找到第一個 HTML 元素,然后我們可以更改策略並在代碼的另一個位置更新 HTML 元素,我找不到任何方法來找到任何 Z4C4AD5FCA2E7A3F74DBB1CED0038 元素.
找到第一個 HTML 是停止這種無休止遞歸的方法,從邏輯上講,這就是我對代碼的期望,在掛載過程中以相同的方式停止遞歸,但是在掛載時,這需要組件實例化,因此我們可以委托給協調器會發現我們正在處理 HTML 元素的包裝器實例,而不是自定義組件的包裝器實例,然后 React 可以將該 HTML 元素放在 DOM 中。
我不明白代碼在更新過程中是如何工作的。 我看到的這段代碼不會深入樹中,我認為不會更新子元素,也不能讓 React 找到第一個 HTML 元素,所以 React 可以更新 DOM 元素,不是嗎?
這是Github上的代碼倉庫
我認為 React 不會先重新渲染父組件,而是先重新渲染子組件。
示例:A(父)-> B(子)-> C(B 的子)當 A 更新 state C(重新渲染)-> B
React 完全復制實際 DOM 並在 javascript 中創建虛擬 DOM。 在我們的應用程序中,每當我們更新任何最終在組件中呈現的數據時,React 都不會重新呈現整個 DOM。 它只影響重要的事情。 所以 react 實際上再次復制了虛擬 DOM。 這次它將更改應用於已更新的數據。
它將在紅色組件中進行更改,然后將這個虛擬 DOM 與舊 DOM 進行比較。 它將看到不同的部分。 然后它將僅將 DOM 更改應用於該不同的組件。
如果 props 或 state 發生更改,則更新階段開始。 如果頂層數據發生變化:
如果它將數據傳遞給它的孩子,所有孩子都將被重新渲染。 如果中級組件的state發生變化:
這次只有它的孩子會被重新渲染。 React 將重新渲染該節點下方的樹的任何部分。 因為生成子組件視圖的數據實際上位於父組件(中級組件)。 但是任何高於它的東西,父母或兄弟姐妹都不會重新渲染。 因為數據不會影響他們。 這個概念稱為Unidirectional Data Flow
。
您可以在 chrome 瀏覽器中查看實際操作。 選擇渲染,然后啟用painting flushing
選項
如果您在頁面上進行任何更改,您將看到更新的組件將閃爍。
Hey Consider using a Tree data structure for your need, ReactJs follows a unidirectional manner of Updating the state ie As soon as the there is a Change in the parent state then all the children which are passed on the props that are residing in the Parent Component一勞永逸地更新! 考慮使用稱為深度優先搜索的算法選項,它將找到連接到父節點的節點,一旦到達該節點,檢查 state 以及是否與 state 共享的變量存在偏差父母你可以更新他們!
注意:這可能看起來有點理論,但如果你可以遠程做一些接近這個東西的事情,你將創建一種更新組件的方法,就像 react 一樣!
我通過實驗發現 React 只會在必要時重新渲染元素,除了{children}
和React.memo()
之外,總是如此。
正確使用孩子,以及批量 dom 更新,可以提供非常高效和流暢的用戶體驗。
考慮這種情況:
function App() {
return <div>
<Parent>
<Child01/>
<Child01/>
</Parent>
<Child03/>
</div>
}
function Parent({children}) {
const [state, setState] = useState(0);
return <div>
<button onClick={x => x+1)>click</button>
<Child02 />
{children}
</div>
}
單擊按鈕時,您將獲得以下信息:
- button click
- setState(...), add Parent to dirty list
- start re-rendering all dirty nodes
- Parent rerenders
- Child02 rerenders
- DONE
注意
app
) 和兄弟節點 ( Child03
) 不會被重新渲染,否則您最終會得到重新渲染遞歸。Parent
被重新渲染是因為它的state已經改變,所以它的output必須重新計算。{children}
沒有受到此更改的影響,因此保持不變。 (除非涉及上下文,但這是一種不同的機制)。<Child02 />
已被標記為臟,因為虛擬 dom 的那部分已被觸及。 雖然我們很容易看到它沒有受到影響,但 React 可以驗證它的唯一方法是比較 props,默認情況下不會這樣做!React.memo
包裝它,這可能比重新渲染要慢。這是我創建的代碼框
這是我打開調試器並查看調用堆棧的簡短記錄。
從你離開的地方開始,Component.updateComponent:
updateComponent(prevElement, nextElement) {
//...
if (shouldUpdateComponent(prevRenderedElement, nextRenderedElement)) {
Reconciler.receiveComponent(this._renderedComponent, nextRenderedElement);
//...
在Component.updateComponent
方法中調用Reconciler.receiveComponent
調用component.receiveComponent(element);
現在,這個component
引用this._renderedComponent
並且不是Component
的實例,而是DOMComponentWrapper
這是 DOMComponentWrapper 的DOMComponentWrapper
方法:
receiveComponent(nextElement) {
this.updateComponent(this._currentElement, nextElement);
}
updateComponent(prevElement, nextElement) {
// debugger;
this._currentElement = nextElement;
this._updateDOMProperties(prevElement.props, nextElement.props);
this._updateDOMChildren(prevElement.props, nextElement.props);
}
然后_updateDOMChildren
最終調用子render
方法。
這是我創建的用於挖掘的代碼盒中的調用堆棧。
在Component
的mountComponent
方法中,我們有:
let renderedComponent = instantiateComponent(renderedElement);
this._renderedComponent = renderedComponent;
在instantiateComponent
中,我們有:
let type = element.type;
let wrapperInstance;
if (typeof type === 'string') {
wrapperInstance = HostComponent.construct(element);
} else if (typeof type === 'function') {
wrapperInstance = new element.type(element.props);
wrapperInstance._construct(element);
} else if (typeof element === 'string' || typeof element === 'number') {
wrapperInstance = HostComponent.constructTextComponent(element);
}
return wrapperInstance;
HostComponent 在dilithium.js
主文件中被DOMComponentWrapper
注入:
HostComponent.inject(DOMComponentWrapper);
HostComponent 只是一種代理,旨在反轉控制並允許 React 中的不同主機。
這是inject
方法:
function inject(impl) {
implementation = impl;
}
和construct
方法:
function construct(element) {
assert(implementation);
return new implementation(element);
}
另一個答案可能是 Fiber 樹的結構。 在執行期間,react 將ReactComponent
渲染為由ReactNode
和 props 組成的 object。 這些ReactNode
被組裝成一個FiberNode
樹(可能是 memory 中虛擬 dom 的表示?)。
在FiberNode
樹中,根據遍歷算法(孩子優先,兄弟優先等),React 總是有一個“下一個”節點繼續。 因此,React 將深入到樹中,並隨着它的進行更新FiberNode
。
如果我們舉同樣的例子,
function App() {
return <div>
<Parent>
<Child01/>
<Child01/>
</Parent>
<Child03/>
</div>
}
function Parent({children}) {
const [state, setState] = useState(0);
return <div>
<button onClick={x => x+1)>click</button>
<Child02 />
{children}
</div>
}
哪個 React 將轉換成這個 FiberNode 樹:
node01 = { type: App, return: null, child: node02, sibling: null }
node02 = { type: 'div', return: node01, child: node03, sibling: null }
node03 = { type: Parent, return: node02, child: node05(?), sibling: node04 }
node04 = { type: Child03, return: node02, child: null, sibling: null }
node05 = { type: Child01, return: node03, child: null, sibling: node06 }
node06 = { type: Child01, return: node03, child: null, sibling: null }
// Parent will spawn its own FiberTree,
node10 = { type: 'div', return: node02, child: node11, sibling: null }
node11 = { type: 'button', return: node10, child: null, sibling: node12 }
node12 = { type: Child02, return: node10, child: null, sibling: node05 }
我可能錯過了一些東西(即 node03 的子節點可能是 node10),但想法是這樣的——React 在遍歷纖維樹時總是有一個節點(“下一個”節點)要渲染。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.