React as a UI Runtime(三、協調)
1.協調
如果我們在同一個容器中使用兩次ReactDOM.render()
會發生什麼?
ReactDOM.render( <button className="blue" />, document.getElementById('container') ); // ... later ... //應該替換掉 button 宿主例項嗎? // 還是在已有的 button 上更新屬性? ReactDOM.render( <button className="red" />, document.getElementById('container') );
再次說明,React的工作是使宿主樹和提供的React元素的樹一致。確定宿主樹怎麼樣來響應新的資訊的這個過程被稱為協調。
協調有兩種方法。React的簡單的版本是拋棄已經存在的樹,重新建立新的樹:
let domContainer = document.getElementById('container'); // 清除樹 domContainer.innerHTML = ''; // 建立新的宿主樹 let domNode = document.createElement('button'); domNode.className = 'red'; domContainer.appendChild(domNode);
但是在DOM中,這是低效的,並且會丟失一些重要的資訊像聚焦狀態,選中狀態,滾動狀態等等。所以我們希望React像下面一樣工作:
let domNode = domContainer.firstChild; // Update existing host instance domNode.className = 'red';
換句話說,React需要決定那時候需要更新一個已經存在的宿主例項來響應新的React元素,那時候需要新建一個宿主例項。
這就提出了關於分別的問題,React的元素可能一直在變化,那理論上那時候引用同一個宿主例項呢?
在我們的例子上是很簡單的。我們已經建立了一個<button>
作為第一個(也是唯一一個)子元素,並且我們希望在同一個地方再次渲染一個<button>
。我們已經有一個<button>
的宿主例項,我們就不需要再建立新的,再次使用它就好了。
這個已經與React的思想非常接近了。
如果元素的種類在樹的同一個地方之前一次的渲染和接下來的渲染是相同的,React會再次使用已經存在的宿主例項。
下面是React帶有備註的大致實現過程:
// let domNode = document.createElement('button'); // domNode.className = 'blue'; // domContainer.appendChild(domNode); ReactDOM.render( <button className="blue" />, document.getElementById('container') ); // 可以再次使用嗎? Yes! (button → button) // domNode.className = 'red'; ReactDOM.render( <button className="red" />, document.getElementById('container') ); // 可以再次使用嗎? No! (button → p) // domContainer.removeChild(domNode); // domNode = document.createElement('p'); // domNode.textContent = 'Hello'; // domContainer.appendChild(domNode); ReactDOM.render( <p>Hello</p>, document.getElementById('container') ); // 可以再次使用嗎? Yes! (p → p) // domNode.textContent = 'Goodbye'; ReactDOM.render( <p>Goodbye</p>, document.getElementById('container') );
這套規則對子樹也同樣適用。例如,當我們在更新有兩個<button>
子元件時的<dialog>
,React首先決定是否重用<dialog>
,
然後再對每一個子元件進行相同的過程。