簡體   English   中英

在 state 更改后,React 如何更新組件及其子組件?

[英]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,默認情況下不會這樣做!
  • 防止 Child02 渲染的唯一方法是用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方法。

這是我創建的用於挖掘的代碼盒中的調用堆棧。

從 setState 調用堆棧直到子渲染

我們如何在 DOMComponentWrapper 中結束

ComponentmountComponent方法中,我們有:

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.

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