摘要:是事件回調,有時機執行邏輯,所以為,,是合并的,結束之后被重新設置為。沒有控制權的情況有很多回調,網絡回調等等。的說明從函數名上理解強制更新。所以可以簡單的理解為,只不過這個是不調用自己的聲明周期的。
setState同步異步問題,React批量更新一直是一個比較模糊的問題,本文希望從框架設計的角度說明一下這個問題。
React有個UI = f(data) 公式:UI是由data推導出來的,所以在寫應用的時候,我們只需要關心數據的改變,只需data ---> data", 那么UI ---> UI",在這個過程中,我們其實并不關心UI是怎么變化到UI‘的(即DOM的變化),這部分工作是React替我們處理了。
那么React是如何知道當數據變化的時候,需要修改哪些DOM的呢?最簡單暴力的是,每次都重新構建整個DOM樹。實際上,React使用的是一種叫virtual-dom的技術:用JS對象來表示DOM結構,通過比較前后JS對象的差異,來獲得DOM樹的增量修改。virtual-dom通過暴力的js計算,大大減少了DOM操作,讓UI = f(data)這種模型性能不是那么的慢,當然你用原生JS/jquery直接操作DOM永遠是最快的。
setState 批量更新除了virtual-dom的優化,減少數據更新的頻率是另外一種手段,也就是React的批量更新。 比如:
g() { this.setState({ age: 18 }) this.setState({ color: "black‘ }) } f() { this.setState({ name: "yank" }) this.g() }
會被React合成為一次setState調用
f() { this.setState({ name: "yank", age: 18, color: "black" }) }
我們通過偽碼大概看一下setState是如何合并的。
setState實現
setState(newState) { if (this.canMerge) { this.updateQueue.push(newState) return } // 下面是真正的更新: dom-diff, lifeCycle... ... }
然后f方法調用
g() { this.setState({ age: 18 }) this.setState({ color: "black‘ }) } f() { this.canMerge = true this.setState({ name: "yank" }) this.g() this.canMerge = false // 通過this.updateQueue合并出finalState const finalState = ... // 此時canMerge 已經為false 故而走入時機更新邏輯 this.setState(finaleState) }
可以看出 setState首先會判斷是否可以合并,如果可以合并,就直接返回了。
不過有同學會問:在使用React的時候,我并沒有設置this.canMerge呀?我們的確沒有,是React隱式的幫我們設置了!事件處理函數,聲明周期,這些函數的執行是發生在React內部的,React對它們有完全的控制權。
class A extends React.Component { componentDidMount() { console.log("...") } render() { return ({ console.log("hi") }}>} }
在執行componentDidMount前后,React會執行canMerge邏輯,事件處理函數也是一樣,React委托代理了所有的事件,在執行你的處理函數函數之前,會執行React邏輯,這樣React也是有時機執行canMerge邏輯的。
批量更新是極好滴!我們當然希望任何setState都可以被批量,關鍵點在于React是否有時機執行canMerge邏輯,也就是React對目標函數有沒有控制權。如果沒有控制權,一旦setState提前返回了,就再也沒有機會應用這次更新了。
class A extends React.Component { handleClick = () => { this.setState({x: 1}) this.setState({x: 2}) this.setState({x: 3}) setTimeout(() => { this.setState({x: 4}) this.setState({x: 5}) this.setState({x: 6}) }, 0) } render() { return ( } }
handleClick 是事件回調,React有時機執行canMerge邏輯,所以x為1,2,3是合并的,handleClick結束之后canMerge被重新設置為false。注意這里有一個setTimeout(fn, 0)。 這個fn會在handleClick之后調用,而React對setTimeout并沒有控制權,React無法在setTimeout前后執行canMerge邏輯,所以x為4,5,6是無法合并的,所以fn這里會存在3次dom-diff。React沒有控制權的情況有很多: Promise.then(fn), fetch回調,xhr網絡回調等等。
unstable_batchedUpdates 手動合并那x為4,5,6有辦法合并嗎?是可以的,需要用unstable_batchedUpdates這個API,如下:
class A extends React.Component { handleClick = () => { this.setState({x: 1}) this.setState({x: 2}) this.setState({x: 3}) setTimeout(() => { ReactDOM.unstable_batchedUpdates(() => { this.setState({x: 4}) this.setState({x: 5}) this.setState({x: 6}) }) }, 0) } render() { return ( } }
這個API,不用解釋太多,我們看一下它的偽碼就很清楚了
function unstable_batchedUpdates(fn) { this.canMerge = true fn() this.canMerge = false const finalState = ... //通過this.updateQueue合并出finalState this.setState(finaleState) }
so, unstable_batchedUpdates 里面的setState也是會合并的。
forceUpdate的說明forceUpdate從函數名上理解:“強制更新”。 既然是“強制更新”有兩個問題容易引起誤解:
forceUpdate 是同步的嗎?“強制”會保證調用然后直接dom-diff嗎?
“強制”更新整個組件樹嗎?包括自己,子孫后代組件嗎?
這兩個問題官方文檔都沒有明確說明。
class A extends React.Component{ handleClick = () => { this.forceUpdate() this.forceUpdate() this.forceUpdate() this.forceUpdate() } shouldComponentUpdate() { return false } render() { return () } }// 一個組件
對于第一個問題:forceUpdate在批量與否的表現上,和setState是一樣的。在React有控制權的函數里,是批量的。
對于第二個問題:forceUpdate只會強制本身組件的更新,即不調用“shouldComponentUpdate”直接更新,對于子孫后代組件還是要調用自己的“shouldComponentUpdate”來決定的。
所以forceUpdate 可以簡單的理解為 this.setState({}),只不過這個setState 是不調用自己的“shouldComponentUpdate”聲明周期的。
Fiber 的想象顯示的讓開發者調用unstable_batchedUpdates是不優雅的,開發者不應該被框架的實現細節影響。但是正如前文所說,React沒有控制權的函數,unstable_batchedUpdates好像是不可避免的。 不過 React16.x的fiber架構,可能有所改變。我們看下fiber下的更新
setState(newState){ this.updateQueue.push(newState) requestIdleCallback(performWork) }
requestIdleCallback 會在瀏覽器空閑時期調用函數,是一個低優先級的函數。
現在我們再考慮一下:
handleClick = () => { this.setState({x: 1}) this.setState({x: 2}) this.setState({x: 3}) setTimeout(() => { this.setState({x: 4}) this.setState({x: 5}) this.setState({x: 6}) }, 0) }
當x為1,2,3,4,5,6時 都會進入更新隊列,而當瀏覽器空閑的時候requestIdleCallback會負責來執行統一的更新。
由于fiber的調度比較復雜,這里只是簡單的說明,具體能不能合并,跟優先級還有其他都有關系。不過fiber的架構的確可以更加優雅的實現批量更新,而且不需要開發者顯示的調用unstable_batchedUpdates
廣告時間最后,廣告一下我們開源的RN轉小程序引擎alita,alita區別于現有的社區編譯時方案,采用的是運行時處理JSX的方式,詳見這篇文章。
所以alita內置了一個mini-react,這個mini-react同樣提供了合成setState/forceUpdate更新的功能,并對外提供了unstable_batchedUpdates接口。如果你讀react源碼無從下手,可以看一下alita minil-react的實現,這是一個適配小程序的react實現, 且小,代碼在https://github.com/areslabs/alita/tree/master/packages/wx-react。
alita地址:https://github.com/areslabs/alita。 歡迎star & pr & issue
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/105505.html
摘要:會自動觸發函數內回調函數的執行。因此利用并將依賴置為使代碼在所有渲染周期內,只在初始化執行一次。同時代碼里還對等公共方法進行了包裝,讓這些回調函數中自帶效果。前端精讀幫你篩選靠譜的內容。 1. 引言 react-easy-state 是個比較有趣的庫,利用 Proxy 創建了一個非常易用的全局數據流管理方式。 import React from react; import { stor...
摘要:而在第二個參數中我們輸出了改變后的即第五行輸出,表明我們的更改生效了。而在的回調內,我們還調用了一個定義于內的事件函數,但是該事件函數內的也是同步的形式。 在react中,setState是用以改變class組件狀態的函數,它有兩種用法:一 傳入一個updater函數,該函數有兩個參數,一個是當前的state,還有一個是當前的props。該函數的返回值需要是一個更改的state值的對象...
摘要:的作用在文檔中是這么說的給下級組件中的提供可用的的對象。這個文件里的主要是被方法引入,并傳給的,算是一個默認的。表示當前的名稱。這個值表示在里面的值。便于控制,同時某些不需要渲染的,也不會造成渲染。 注:這篇文章只是講解React Redux這一層,并不包含Redux部分。Redux有計劃去學習,等以后學習了Redux源碼以后再做分析注:代碼基于現在(2016.12.29)React ...
react組件 參考:https://facebook.github.io/re... react的組件是其核心思想部分,react允許將整個ui設計分割稱為獨立的、可復用的隔離模塊,react的組件是一個抽象的類,直接使用reacy.component是沒有很大意義的,所以一般使用的方法就是定義一個 class 來繼承這個component,并且需要實現方法 render();就像下面一樣: ...
react組件 參考:https://facebook.github.io/re... react的組件是其核心思想部分,react允許將整個ui設計分割稱為獨立的、可復用的隔離模塊,react的組件是一個抽象的類,直接使用reacy.component是沒有很大意義的,所以一般使用的方法就是定義一個 class 來繼承這個component,并且需要實現方法 render();就像下面一樣: ...
閱讀 2524·2021-11-18 10:02
閱讀 1997·2021-11-09 09:45
閱讀 2449·2021-09-26 09:47
閱讀 1038·2021-07-23 10:26
閱讀 1079·2019-08-30 15:47
閱讀 3368·2019-08-30 15:44
閱讀 987·2019-08-30 15:43
閱讀 893·2019-08-29 13:50