react剛剛推出的時候会前,講react優(yōu)勢搜索結(jié)果是幾十頁好乐。
現(xiàn)在,react已經(jīng)慢慢退火瓦宜,該用用react技術(shù)棧的已經(jīng)使用上蔚万,填過多少坑,加過多少班临庇,血淚控訴也不下千文反璃。
今天昵慌,再談一遍react優(yōu)勢,WTF淮蜈?
React的收益有哪些斋攀?React的優(yōu)勢是什么?react和vue梧田、angularJS等其它框架對比優(yōu)勢淳蔼?
而作為總結(jié)回顧。react在工程實踐中裁眯,帶來哪些思想上的質(zhì)變肖方?
virtual dom虛擬DOM概念
它并不直接對DOM進(jìn)行操作,引入了一個叫做virtual dom的概念未状,安插在javascript邏輯和實際的DOM之間,好處是減少DOM操作析桥,減少DOM操作的目的是提高瀏覽器的渲染性能司草。
虛擬dom就中小型項目而言,的確從表象上看不出太多的優(yōu)勢泡仗,因為它解決的是底層的dom渲染埋虹,IO開銷問題。但是想想facebook的體量娩怎,不難猜出react的誕生是為了解決更復(fù)雜更大型的項目開發(fā)和管理的搔课。
實際上React和Vue其實也在操作DOM,只是比較高效地在操作DOM而已截亦,虛擬DOM其實最終也會映射到真實DOM爬泥,雖然虛擬DOM只會將變化的部分更新到真實DOM,但實際上直接操作DOM也可以通過某些方式去優(yōu)化崩瓤,那么:
????1袍啡、操作data,不直接操作DOM有什么好處却桶?
???????? 更少的代碼做更多的事境输。
????2、操作data會給DOM操作帶來什么不好的地方嗎颖系?
??????????不會嗅剖,但是不是所有功能“使用操作data”都可以代替的。
????3嘁扼、會不會比直接操作DOM存在什么難度信粮?
???????? 不會有難度,但是思維需要有一些轉(zhuǎn)變偷拔。
JSX雖然做了抽象視圖蒋院,但她是聲明式API亏钩,能夠保證你看一眼就知道組件樹的結(jié)構(gòu),譬如:
這結(jié)構(gòu)還算清楚吧欺旧,基本一眼就知道這個一個面板由輸入框姑丑、列表、摘要組成辞友,而且布局也清楚了栅哀,自上而下。而且称龙,通過查看一個源文件就可以知道你的組件將會如何渲染留拾。這是最大的好處,盡管這和 Angular 模板沒什么不同鲫尊。具體參看:ReactJS For Stupid People
之前寫UI的時候往往為了性能痴柔,要設(shè)計很多DOM的操作邏輯,用了react之后疫向,這些都不給你做了咳蔚,由他的state跟props來傳遞給VDOM,很省事搔驼,更專注于UI層面谈火。
學(xué)會了react以及這個JSX語法,你不光可以通過react寫web舌涨;也可以通過react-native寫ios或者android的應(yīng)用糯耍;甚至可以通過react-blessed寫terminal可視化應(yīng)用;當(dāng)然也可以通過react-native-desktop寫桌面應(yīng)用囊嘉。因為JSX這種聲明式語法實際是在構(gòu)建一個抽象的視圖層温技,這種抽象可以通過不同適配器適配到各種顯示終端,這總夠?qū)虐桑?/p>
unidirectional data flow-單向數(shù)據(jù)流
React倡導(dǎo)使用flux模式來進(jìn)行組件間數(shù)據(jù)傳輸扭粱,這種做法叫unidirectional data flow(單向數(shù)據(jù)流)荒揣,單向數(shù)據(jù)流的好處是與之前angularJS提出的two-way data binding相比較而言,因為單向焊刹,所以各種變化都是可預(yù)計系任、可控制的。不像two-way data binding那樣虐块,變化一但復(fù)雜起來俩滥,大家都互相觸發(fā)變化,到最后一個地方變了贺奠,你根本猜不出來她還會導(dǎo)致其他什么地方跟著一起變霜旧。這個需要大量實踐才能有所感受,如果你初學(xué)儡率,那聽聽就算了挂据,不必死磕以清。
react項目結(jié)構(gòu)更加清晰:
virtual dom、redux崎逃、action掷倔,分部分別存放,就象java寫后臺查數(shù)據(jù)本來用jdbc一條sql就搞定,但分成action service dao分門別類地存放,這樣維護(hù)性好,大公司的代碼需要規(guī)范,這樣出了問題好找原因个绍。
組件化
一切都是component:代碼更加模塊化勒葱,重用代碼更容易,可維護(hù)性高巴柿。
這里就涉及到react的 架構(gòu)凛虽,比如:
smart, dumb component??
把組件分成兩大類?Smart?Components(容器)& Dumb Components(顆粒化組件)
這樣做的好處:
有助理你分離關(guān)注點广恢,這樣的話更有助于理解你的app的業(yè)務(wù)邏輯 和 它的ui
更有助于復(fù)用你的dumb組件凯旋,你可以將你的dumb組件復(fù)用于別的state下,而且這兩個state還完全不同
本質(zhì)上dumb 組件 其實 就是你的app的調(diào)色版钉迷。瓦阐。你可以將它們放到一個頁面上。篷牌。然后讓設(shè)計師除了app的業(yè)務(wù)邏輯,樣式隨便怎么改踏幻,
參看文章:Smart and Dumb Components
高階組件(HOC-higher order component)?
高階組件(HOC)是react中對組件邏輯進(jìn)行重用的高級技術(shù)枷颊。但高階組件本身并不是React API。它只是一種模式该面,這種模式是由react自身的組合性質(zhì)必然產(chǎn)生的夭苗。
具體而言,高階組件就是一個函數(shù)隔缀,且該函數(shù)接受一個組件作為參數(shù)题造,并返回一個新的組件
const?EnhancedComponent?=?higherOrderComponent(WrappedComponent);
對比組件將props屬性轉(zhuǎn)變成UI,高階組件則是將一個組件轉(zhuǎn)換成另一個新組件夷蚊。
好處:使用高階組件(HOC)解決交叉問題
參看文章:高階組件
總結(jié)下粗仓,看看一個人的組件化水準(zhǔn)牺氨,
pure component
functional component
smart, dumb component?
higher order component
hoc render hijacking
會用 props.children React.children cloneElement
提供 instance method
context
并理解react 內(nèi)部實現(xiàn)原理
懂 setState? 是異步的
懂 synthetic event
懂 react-dom 分層和 react 沒有關(guān)系
懂 reconciler
懂 fiber??
具體問題如下:
1. 怎么抽象一個帶搜索,單多選復(fù)合淮悼,有請求的 Selector,區(qū)分 smart 和 dumped揽思。如果我再往上加功能袜腥,比如 autocomplete? 等
2. 怎么實現(xiàn)對表單的抽象,數(shù)據(jù)驗證怎么統(tǒng)一處理
3. 用 react 來實現(xiàn)一個可視化編輯器的引擎钉汗,怎么設(shè)計羹令,怎么抽象與 model 的交互鲤屡,再引入 redux 呢,怎么支持第三方組件熱插拔
4. 用 react 和 redux 模擬多人協(xié)作的 Todo福侈,node 作為后端酒来,怎么設(shè)計
同構(gòu)、純粹的javascrip
因為搜索引擎的爬蟲程序依賴的是服務(wù)端響應(yīng)而不是JavaScript的執(zhí)行癌刽,預(yù)渲染你的應(yīng)用有助于搜索引擎優(yōu)化役首。
react一些常見問題:
setState()函數(shù)在任何情況下都會導(dǎo)致組件重渲染嗎?如果setState()中參數(shù)還是原來沒有發(fā)生任何變化的state呢显拜?
對setState用得深了衡奥,就容易犯錯,所以我們開門見山先把理解setState的關(guān)鍵點列出來远荠。
setState不會立刻改變React組件中state的值矮固;
setState通過引發(fā)一次組件的更新過程來引發(fā)重新繪制;
多次setState函數(shù)調(diào)用產(chǎn)生的效果會合并
setState后譬淳,知道reader時档址,才真正改變state的值
shouldComponentUpdate函數(shù)返回false,因為更新被中斷邻梆,所以不調(diào)用render守伸,但是React不會放棄掉對this.state的更新的,依然會更新this.state
傳入 setState 函數(shù)的第二個參數(shù)的作用是什么浦妄?
該函數(shù)會在setState函數(shù)調(diào)用完成并且組件開始重渲染的時候被調(diào)用尼摹,我們可以用該函數(shù)來監(jiān)聽渲染是否完成(一般沒有什么卵用)
?調(diào)用 setState 之后發(fā)生了什么?
?在代碼中調(diào)用setState函數(shù)之后剂娄,React 會將傳入的參數(shù)對象與組件當(dāng)前的狀態(tài)合并蠢涝,然后觸發(fā)所謂的調(diào)和過程(Reconciliation)。經(jīng)過調(diào)和過程阅懦,React 會以相對高效的方式根據(jù)新的狀態(tài)構(gòu)建 React 元素樹并且著手重新渲染整個UI界面和二。在 React 得到元素樹之后,React 會自動計算出新的樹與老樹的節(jié)點差異耳胎,然后根據(jù)差異對界面進(jìn)行最小化重渲染惯吕。在差異計算算法中,React 能夠相對精確地知道哪些位置發(fā)生了改變以及應(yīng)該如何改變怕午,這就保證了按需更新混埠,而不是全部重新渲染。
用shouldComponentUpdate做優(yōu)化的意義大嗎诗轻?shouldComponentUpdate將帶來可測量和可感知的提升钳宪?
如果不能,那就別用:你可能應(yīng)該避免用它。據(jù)React團隊的說吏颖,shouldComponentUpdate是一個保證性能的緊急出口搔体,意思就是你不到萬不得已就別用它。具體參考:什么時候使用shouldComponentUpdate方法?
一般情況下setState()確立后總是觸發(fā)一次重繪半醉,除非在 shouldComponentUpdate() 中實現(xiàn)了條件渲染邏輯疚俱。如果使用可變的對象,但是又不能在 shouldComponentUpdate() 中實現(xiàn)這種邏輯缩多,僅在新 state 和之前的 state 存在差異的時候調(diào)用 setState() 可以避免不必要的重新渲染呆奕。
react異步數(shù)據(jù)如ajax請求應(yīng)該放在哪個生命周期?
對于同步的狀態(tài)改變衬吆,是可以放在componentWillMount梁钾,對于異步的,最好好放在componentDidMount逊抡。但如果此時有若干細(xì)節(jié)需要處理姆泻,比如你的組件需要渲染子組件,而且子組件取決于父組件的某個屬性冒嫡,那么在子組件的componentDidMount中進(jìn)行處理會有問題:因為此時父組件中對應(yīng)的屬性可能還沒有完整獲取拇勃,因此就讓其在子組件的componentDidUpdate中處理。
具體參考:《react異步數(shù)據(jù)如ajax請求應(yīng)該放在哪個生命周期孝凌?》
React 中的 keys 是什么方咆,為什么它們很重要?
在開發(fā)過程中蟀架,我們需要保證某個元素的 key 在其同級元素中具有唯一性瓣赂。在 React Diff 算法中 React 會借助元素的 Key 值來判斷該元素是新近創(chuàng)建的還是被移動而來的元素,從而減少不必要的元素重渲染辜窑。此外,React 還需要借助 Key 值來判斷元素與本地狀態(tài)的關(guān)聯(lián)關(guān)系寨躁,因此我們絕不可忽視轉(zhuǎn)換函數(shù)中 Key 的重要性穆碎。
keys 是幫助 React 跟蹤哪些項目已更改、添加或從列表中刪除的屬性职恳。
每個keys 在兄弟元素之間是獨一無二的所禀。我們已經(jīng)談過幾次關(guān)于一致化處理(reconciliation)的過程,而且這個一致化處理過程(reconciliation)中的一部分正在執(zhí)行一個新的元素樹與最前一個的差異放钦。keys 使處理列表時更加高效色徘,因為 React 可以使用子元素上的 keys 快速知道元素是新的還是在比較樹時才被移動的。
而且 keys 不僅使這個過程更有效率操禀,而且沒有keys褂策,React 不知道哪個本地狀態(tài)對應(yīng)于移動中的哪個項目。所以當(dāng)你 map 的時候,不要忽略了 keys 斤寂。
受控組件( controlled component )與不受控制的組件( uncontrolled component )有什么區(qū)別耿焊?
React 的很大一部分是這樣的想法,即組件負(fù)責(zé)控制和管理自己的狀態(tài)(任何改變代用setSate處理)
那么不受控組件呢遍搞?組件數(shù)據(jù)不全部是setState來處理罗侯,還有DOM交互,比如refs這玩意來操控真實DOM
雖然不受控制的組件通常更容易實現(xiàn)溪猿,因為您只需使用引用從DOM獲取值钩杰,但是通常建議您通過不受控制的組件來支持受控組件。
主要原因是受控組件支持即時字段驗證诊县,允許您有條件地禁用/啟用按鈕讲弄,強制輸入格式,并且更多的是 『the React way』翎冲。
描述事件在React中的處理方式
為了解決跨瀏覽器兼容性問題垂睬,您的 React 中的事件處理程序?qū)鬟fSyntheticEvent 的實例,它是 React 的瀏覽器本機事件的跨瀏覽器包裝器抗悍。
這些 SyntheticEvent 與您習(xí)慣的原生事件具有相同的接口驹饺,除了它們在所有瀏覽器中都兼容。有趣的是缴渊,React 實際上并沒有將事件附加到子節(jié)點本身赏壹。React 將使用單個事件監(jiān)聽器監(jiān)聽頂層的所有事件。這對于性能是有好處的衔沼,這也意味著在更新DOM時蝌借,React 不需要擔(dān)心跟蹤事件監(jiān)聽器。
在什么情況下你會優(yōu)先選擇使用 Class Component 而不是 Functional Component指蚁?
在組件需要包含內(nèi)部狀態(tài)或者使用到生命周期函數(shù)的時候使用 Class Component 菩佑,否則使用函數(shù)式組件。
簡單介紹下react的diff
計算一棵樹形結(jié)構(gòu)轉(zhuǎn)換成另一棵樹形結(jié)構(gòu)的最少操作凝化,是一個復(fù)雜且值得研究的問題稍坯。傳統(tǒng) diff 算法通過循環(huán)遞歸對節(jié)點進(jìn)行依次對比,效率低下搓劫,算法復(fù)雜度達(dá)到 O(n^3)瞧哟,其中 n 是樹中節(jié)點的總數(shù)。O(n^3) 到底有多可怕枪向,這意味著如果要展示1000個節(jié)點勤揩,就要依次執(zhí)行上十億次的比較。這種指數(shù)型的性能消耗對于前端渲染場景來說代價太高了秘蛔!現(xiàn)今的 CPU 每秒鐘能執(zhí)行大約30億條指令陨亡,即便是最高效的實現(xiàn)傍衡,也不可能在一秒內(nèi)計算出差異情況。数苫。React 通過制定大膽的策略聪舒,將 O(n^3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度的問題。
?react的diff 策略:
?Web UI 中 DOM 節(jié)點跨層級的移動操作特別少虐急,可以忽略不計箱残。
?擁有相同類的兩個組件將會生成相似的樹形結(jié)構(gòu),擁有不同類的兩個組件將會生成不同的樹形結(jié)構(gòu)止吁。?
對于同一層級的一組子節(jié)點被辑,它們可以通過唯一 id 進(jìn)行區(qū)分。?
基于以上三個前提策略敬惦,React 分別對tree diff盼理、component diff以及element diff進(jìn)行算法優(yōu)化,事實也證明這三個前提策略是合理且準(zhǔn)確的俄删,它保證了整體界面構(gòu)建的性能宏怔。
tree diff:
基于策略一,React 對樹的算法進(jìn)行了簡潔明了的優(yōu)化畴椰,即對樹進(jìn)行分層比較臊诊,兩棵樹只會對同一層次的節(jié)點進(jìn)行比較。
既然 DOM 節(jié)點跨層級的移動操作少到可以忽略不計斜脂,針對這一現(xiàn)象抓艳,React 通過 updateDepth 對 Virtual DOM 樹進(jìn)行層級控制,只會對相同顏色方框內(nèi)的 DOM 節(jié)點進(jìn)行比較帚戳,即同一個父節(jié)點下的所有子節(jié)點玷或。當(dāng)發(fā)現(xiàn)節(jié)點已經(jīng)不存在,則該節(jié)點及其子節(jié)點會被完全刪除掉片任,不會用于進(jìn)一步的比較偏友。這樣只需要對樹進(jìn)行一次遍歷,便能完成整個 DOM 樹的比較对供。
updateChildren:?function(nextNestedChildrenElements,?transaction,?context)?{
??updateDepth++;
??var?errorThrown?=?true;
??try?{
????this._updateChildren(nextNestedChildrenElements,?transaction,?context);
????errorThrown?=?false;
??}?finally?{
????updateDepth--;
????if?(!updateDepth)?{
??????if?(errorThrown)?{
????????clearQueue();
??????}?else?{
????????processQueue();
??????}
????}
??}
}
分析至此位他,大部分人可能都存在這樣的疑問:如果出現(xiàn)了 DOM 節(jié)點跨層級的移動操作,React diff 會有怎樣的表現(xiàn)呢犁钟?是的棱诱,對此我也好奇不已泼橘,不如試驗一番涝动。
如下圖,A 節(jié)點(包括其子節(jié)點)整個被移動到 D 節(jié)點下炬灭,由于 React 只會簡單的考慮同層級節(jié)點的位置變換醋粟,而對于不同層級的節(jié)點靡菇,只有創(chuàng)建和刪除操作。當(dāng)根節(jié)點發(fā)現(xiàn)子節(jié)點中 A 消失了米愿,就會直接銷毀 A厦凤;當(dāng) D 發(fā)現(xiàn)多了一個子節(jié)點 A,則會創(chuàng)建新的 A(包括子節(jié)點)作為其子節(jié)點育苟。此時较鼓,React diff 的執(zhí)行情況:create A -> create B -> create C -> delete A。
由此可發(fā)現(xiàn)违柏,當(dāng)出現(xiàn)節(jié)點跨層級移動時博烂,并不會出現(xiàn)想象中的移動操作,而是以 A 為根節(jié)點的樹被整個重新創(chuàng)建漱竖,這是一種影響 React 性能的操作禽篱,因此React 官方建議不要進(jìn)行 DOM 節(jié)點跨層級的操作。
提示:在開發(fā)組件時馍惹,保持穩(wěn)定的 DOM 結(jié)構(gòu)會有助于性能的提升躺率。例如,可以通過 CSS 隱藏或顯示節(jié)點万矾,而不是真的移除或添加 DOM 節(jié)點悼吱。
component diff:
如果是同一類型的組件,按照原策略繼續(xù)比較 virtual DOM tree勤众。
如果不是舆绎,則將該組件判斷為 dirty component,從而替換整個組件下的所有子節(jié)點们颜。對于同一類型的組件吕朵,有可能其 Virtual DOM 沒有任何變化,如果能夠確切的知道這點那可以節(jié)省大量的 diff 運算時間窥突,因此 React 允許用戶shouldComponentUpdate() 來判斷該組件是否需要進(jìn)行 diff努溃。
如下圖,當(dāng) component D 改變?yōu)?component G 時阻问,即使這兩個 component 結(jié)構(gòu)相似梧税,一旦 React 判斷 D 和 G 是不同類型的組件,就不會比較二者的結(jié)構(gòu)称近,而是直接刪除 component D第队,重新創(chuàng)建 component G 以及其子節(jié)點。雖然當(dāng)兩個 component 是不同類型但結(jié)構(gòu)相似時刨秆,React diff 會影響性能凳谦,但正如 React 官方博客所言:不同類型的 component 是很少存在相似 DOM tree 的機會,因此這種極端因素很難在實現(xiàn)開發(fā)過程中造成重大影響的衡未。
element diff:
當(dāng)節(jié)點處于同一層級時尸执,React diff 提供了三種節(jié)點操作家凯,分別為:INSERT_MARKUP(插入)、MOVE_EXISTING(移動)和 REMOVE_NODE(刪除)如失。
INSERT_MARKUP绊诲,新的 component 類型不在老集合里, 即是全新的節(jié)點褪贵,需要對新節(jié)點執(zhí)行插入操作掂之。
MOVE_EXISTING,在老集合有新 component 類型脆丁,且 element 是可更新的類型板惑,generateComponentChildren 已調(diào)用 receiveComponent,這種情況下 prevChild=nextChild偎快,就需要做移動操作冯乘,可以復(fù)用以前的 DOM 節(jié)點。
REMOVE_NODE晒夹,老 component 類型裆馒,在新集合里也有,但對應(yīng)的 element 不同則不能直接復(fù)用和更新丐怯,需要執(zhí)行刪除操作喷好,或者老 component 不在新集合里的,也需要執(zhí)行刪除操作读跷。
如下圖梗搅,老集合中包含節(jié)點:A、B效览、C无切、D,更新后的新集合中包含節(jié)點:B丐枉、A哆键、D、C瘦锹,此時新老集合進(jìn)行 diff 差異化對比籍嘹,發(fā)現(xiàn) B != A,則創(chuàng)建并插入 B 至新集合弯院,刪除老集合 A辱士;以此類推,創(chuàng)建并插入 A听绳、D 和 C颂碘,刪除 B、C 和 D辫红。
React 提出優(yōu)化策略:允許開發(fā)者對同一層級的同組子節(jié)點凭涂,添加唯一 key 進(jìn)行區(qū)分,雖然只是小小的改動贴妻,性能上卻發(fā)生了翻天覆地的變化切油!
總結(jié)
React 通過制定大膽的 diff 策略,將 O(n3) 復(fù)雜度的問題轉(zhuǎn)換成 O(n) 復(fù)雜度的問題名惩;
React 通過分層求異的策略澎胡,對 tree diff 進(jìn)行算法優(yōu)化;
React 通過相同類生成相似樹形結(jié)構(gòu)娩鹉,不同類生成不同樹形結(jié)構(gòu)的策略攻谁,對 component diff 進(jìn)行算法優(yōu)化;
React 通過設(shè)置唯一 key的策略弯予,對 element diff 進(jìn)行算法優(yōu)化戚宦;
建議,在開發(fā)組件時锈嫩,保持穩(wěn)定的 DOM 結(jié)構(gòu)會有助于性能的提升受楼;
建議,在開發(fā)過程中呼寸,盡量減少類似將最后一個節(jié)點移動到列表首部的操作艳汽,當(dāng)節(jié)點數(shù)量過大或更新操作過于頻繁時,在一定程度上會影響 React 的渲染性能对雪。
diff算法作為react的核心河狐,非三言兩語能夠說起道明,建議參看:React 源碼剖析系列 - 不可思議的 react diff?
怎么看待不可變數(shù)據(jù)?
這個暫待完善
ssr (server side render)會有什么性能問題瑟捣,哪些會引起內(nèi)存泄露馋艺,引入 redux 后怎么處理請求的邏輯
參考:從零開始搭建React同構(gòu)應(yīng)用(三):配置SSR
參考文章:
什么時候使用shouldComponentUpdate方法?
轉(zhuǎn)載請注明文章來源:重談react優(yōu)勢--react技術(shù)棧回顧 - ECMAScript,js,javascript - 周陸軍的個人網(wǎng)站:https://www.zhoulujun.cn/html/webfront/ECMAScript/jsBase/2018_0424_8101.html迈套,如有不妥之處丈钙,望告知!