react 使用diff算法,使用key來作同級比對。若是使用數組下標做爲key,有如下狀況:
在數組頭部或中部插入或刪除元素: 全部key對應的節點的值發生更改,進行從新渲染。形成性能損耗
而若是使用數組中惟一值來做爲key:無論是在何處插入或刪除節點,其餘key對應的節點的值未發生更改,只需插入或刪除操做的數組節點。javascript
使用shouldComponentUpdate()以讓React知道當前狀態或屬性的改變是否不影響組件的輸出,默認返回ture,返回false時不會重寫render,並且該方法並不會在初始化渲染或當使用forceUpdate()時被調用.css
若是在組件樹的根節點發生更新則全部子節點都會發生更新,這時對全部子節點使用shouldComponentUpdate來減小子節點的渲染,無疑會增長不少代碼。咱們能夠選擇使用PureComponent來處理。html
PureComponent 和 Component的區別是:Component須要手動實現 shouldComponentUpdate,而PureComponent經過淺對比默認實現了shouldComponentUpdate方法前端
淺比較
(shallowEqual),即react源碼中的一個函數,而後根據下面的方法進行是否是PureComponent
的判斷,幫咱們作了原本應該咱們在shouldComponentUpdate
中作的事情vue
if (this._compositeType === CompositeTypes.PureClass) { shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState); }
注意: 淺比較只比較了第一層,複雜數據結構可能會致使更新問題java
總結: PureComponent不只會影響自己,並且會影響子組件,因此PureComponent最佳狀況是展現組件react
React.memo爲高階組件。它與 React.PureComponent 很是類似,但它適用於函數組件,但不適用於 class 組件。
若是你的函數組件在給定相同 props 的狀況下渲染相同的結果,那麼你能夠經過將其包裝在 React.memo 中調用,以此經過記憶組件渲染結果的方式來提升組件的性能表現。這意味着在這種狀況下,React 將跳過渲染組件的操做並直接複用最近一次渲染的結果。webpack
React.memo 依然是淺比較(默認)。在React.memo
中能夠自定義其比較方法的實現。web
function MyComponent(props) { /* render using props */ } function areEqual(prevProps, nextProps) { /* return true if passing nextProps to render would return the same result as passing prevProps to render, otherwise return false */ } export default React.memo(MyComponent, areEqual);
和PureComponent的區別:算法
Immutable Data 就是一旦建立,就不能再被更改的數據。對 Immutable 對象的任何修改或添加刪除操做都會返回一個新的 Immutable 對象。Immutable 實現的原理是Persistent Data Structure(持久化數據結構),也就是使用舊數據建立新數據時,要保證舊數據同時可用且不變。同時爲了不 deepCopy 把全部節點都複製一遍帶來的性能損耗,Immutable 使用了Structural Sharing(結構共享),即若是對象樹中一個節點發生變化,只修改這個節點和受它影響的父節點,其它節點則進行共享。
由於使用Immutable 任何修改或添加刪除操做都會返回新的Immutable 對象,因此只需簡單比較便可,可參考以下代碼
// 使用 immutable.js 後 let map1 = Immutable.Map({a:1, b:1, c:1}); let map2 = Immutable.Map({a:1, b:1, c:1}); map1 === map2; // false // 爲了直接比較對象的值,immutable.js 提供了 \`Immutable.is\` 來作『值比較』 Immutable.is(map1, map2); // true
流行的 Immutable 庫有兩個:
// 原來的寫法 let foo = {a: {b: 1}}; let bar = foo; bar.a.b = 2; console.log(foo.a.b); // 打印 2 console.log(foo === bar); // 打印 true // 使用 immutable.js 後 import Immutable from 'immutable'; foo = Immutable.fromJS({a: {b: 1}}); bar = foo.setIn(['a', 'b'], 2); // 使用 setIn 賦值 console.log(foo.getIn(['a', 'b'])); // 使用 getIn 取值,打印 1 console.log(foo === bar); // 打印 false // 使用 seamless-immutable.js 後 import SImmutable from 'seamless-immutable'; foo = SImmutable({a: {b: 1}}) bar = foo.merge({a: { b: 2}}) // 使用 merge 賦值 console.log(foo.a.b); // 像原生 Object 同樣取值,打印 1 console.log(foo === bar); // 打印 false
Immutable 優勢:
一、Immutable 下降了 Mutable 帶來的複雜度
二、節省內存(Immutable.js 使用了 Structure Sharing 會盡可能複用內存。沒有被引用的對象會被垃圾回收。)
import { Map} from 'immutable'; let a = Map({ select: 'users', filter: Map({ name: 'Cam' }) }) let b = a.set('select', 'people'); a === b; // false a.get('filter') === b.get('filter'); // true a 和 b 共享了沒有變化的 \`filter\` 節點
三、擁抱函數式編程
Immutable 自己就是函數式編程中的概念,純函數式編程比面向對象更適用於前端開發。由於只要輸入一致,輸出必然一致,這樣開發的組件更易於調試和組裝。
四、Undo/Redo,Copy/Paste,甚至時間旅行這些功能作起來小菜一碟
由於每次數據都是不同的,只要把這些數據放到一個數組裏儲存起來,想回退到哪裏就拿出對應數據便可,很容易開發出撤銷重作這種功能。
五、併發安全
傳統的併發很是難作,由於要處理各類數據不一致問題,所以『聰明人』發明了各類鎖來解決。但使用了 Immutable 以後,數據天生是不可變的,併發鎖就不須要了。
Immutable 缺點:
一、須要學習新的 API
二、增長了資源文件大小
三、容易與原生對象混淆
高階組件是參數爲組件,返回值爲新組件的函數。HOC 是純函數,沒有反作用。HOC 在 React 的第三方庫中很常見,例如 Redux 的 connect
高階組件的做用:
高階組件的實現:
代碼層面:
return null
而不是CSS的display:none
來控制節點的顯示隱藏。保證同一時間頁面的DOM節點儘量的少。props
和state
的數據儘量簡單明瞭,扁平化。render
裏面儘可能減小新建變量和bind
函數,傳遞參數是儘可能減小傳遞參數的數量。代碼體積優化:
性能檢測工具:
react 父子組件通訊方式:
一、 父子組件,父->子直接用Props,子->父用callback回調
二、 非父子組件,用發佈訂閱模式的Event模塊
三、 項目複雜的話用Redux、Mobx等全局狀態管理管庫
四、 Context Api context 會使組件複用性變差
Context 提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法.若是你只是想避免層層傳遞一些屬性,組件組合(component composition)有時候是一個比 context 更好的解決方案。
組件組合缺點:會使高層組件變得複雜
vue 父子組件通訊方式:
一、 父->子組件用Props通訊.子->父 事件和回調函數
二、 非父子組件用Event Bus通訊
三、 若是項目夠複雜,可能須要Vuex等全局狀態管理庫通訊
參考: React/Vue 通訊
Context 提供了一個無需爲每層組件手動添加 props,就能在組件樹間進行數據傳遞的方法.若是你只是想避免層層傳遞一些屬性,組件組合(component composition)有時候是一個比 context 更好的解決方案。
props
和state
,React的Context
能夠實現跨層級的組件通訊。childContextTypes
聲明,而後經過實例方法getChildContext()
建立Context
對象。消費者一方,經過組件靜態屬性contextTypes
申請要用到的Context
屬性,而後經過實例的context
訪問Context
的屬性。Context
須要多一些思考,不建議在App中使用Context
,但若是開發組件過程當中能夠確保組件的內聚性,可控可維護,不破壞組件樹的依賴關係,影響範圍小,能夠考慮使用Context
解決一些問題。Context
暴露API或許在必定程度上給解決一些問題帶來便利,但我的認爲不是一個很好的實踐,須要慎重。Context
的更新須要依賴setState()
,是不可靠的,不過這個問題在新版的API中得以解決。Context
當作組件的做用域來看待,可是須要關注Context
的可控性和影響範圍,使用以前,先分析是否真的有必要使用,避免過分使用所帶來的一些反作用。Context
當作媒介,進行App級或者組件級的數據共享。Context
或許能夠更加優雅。[參考](https://juejin.im/post/5a90e0...)
一、在組件之間複用狀態邏輯很難: 在大型的工做項目中用react,你會發現你的項目中實際上不少react組件冗長且難以複用。尤爲是那些寫成class的組件,它們自己包含了狀態(state),因此複用這類組件就變得很麻煩。官方推薦渲染屬性(Render Props)和高階組件(Higher-Order Components)。可是這兩種方案會生成不少DOM層級嵌套
二、複雜組件變得難以理解(咱們常常維護一些組件,組件起初很簡單,可是逐漸會被狀態邏輯和反作用充斥。每一個生命週期經常包含一些不相關的邏輯。)
三、難以理解的class: class建立組件會遇到this指向的問題。
Hook 是一些可讓你在函數組件裏「鉤入」 React state 及生命週期等特性的函數。
hook 的使用規則:
useState
是react自帶的一個hook函數,它的做用就是用來聲明狀態變量。useState
這個函數接收的參數是咱們的狀態初始值(initial state),它返回了一個數組,這個數組的第[0]
項是當前當前的狀態值,第[1]
項是能夠改變狀態值的方法函數。
注意:
_Effect Hook_可讓你在函數組件中執行反作用操做。
在 React 組件中有兩種常見反作用操做:須要清除的和不須要清除的。
經過使用這個 Hook,你能夠告訴 React 組件須要在渲染後執行某些操做。React 會保存你傳遞的函數(咱們將它稱之爲 「effect」),而且在執行 DOM 更新以後調用它(初始化DOM加載後和更新DOM後)。
function Example() { const [count, setCount] = useState(0); useEffect(() => { document.title = `You clicked ${count} times`; }); return count }
注意:
有一些反作用是須要清除的。例如訂閱外部數據源。這種狀況下,清除工做是很是重要的,能夠防止引發內存泄露。(不用hook只能用DidMount訂閱和WillUnmount清除)
useEffect
能夠在組件渲染後實現各類不一樣的反作用。有些反作用可能須要清除,因此須要返回一個清除函數
清除函數在何時執行:effect 的清除階段在每次從新渲染時都會執行,而不是隻在卸載組件的時候執行一次。
componentDidMount() { ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentDidUpdate(prevProps) { // 取消訂閱以前的 friend.id ChatAPI.unsubscribeFromFriendStatus( prevProps.friend.id, this.handleStatusChange ); // 訂閱新的 friend.id ChatAPI.subscribeToFriendStatus( this.props.friend.id, this.handleStatusChange ); } componentWillUnmount() { ChatAPI.unsubscribeFromFriendStatus( this.props.friend.id, this.handleStatusChange ); }
若未添加DidUpdate函數時:當組件已經顯示在屏幕上時,friend
prop 發生變化時會發生什麼?咱們的組件將繼續展現原來的好友狀態。這是一個 bug。並且咱們還會由於取消訂閱時使用錯誤的好友 ID 致使內存泄露或崩潰的問題。
忘記正確地處理 componentDidUpdate 是 React 應用中常見的 bug 來源
使用useEffect則不會出現 componentDidUpdate 未正確處理時出現的狀況
在某些狀況下,每次渲染後都執行清理或者執行 effect 可能會致使性能問題。
若是某些特定值在兩次重渲染之間沒有發生變化,你能夠通知 React跳過對 effect 的調用,只要傳遞數組做爲useEffect
的第二個可選參數便可:
useEffect(() => { document.title = `You clicked ${count} times`; }, [count]); // 僅在 count 更改時更新
對於有清除操做的 effect 一樣適用:
將Virtual DOM樹轉換成actual DOM樹的最少操做的過程 稱爲 協調(Reconciliaton)。
舊版 React 經過遞歸的方式進行渲染,使用的是 JS 引擎自身的函數調用棧,它會一直執行到棧空爲止。
缺點:在頁面元素不少,且須要頻繁刷新的場景下,React 15 會出現掉幀的現象。
緣由:是大量的同步計算任務阻塞了瀏覽器的 UI 渲染。默認狀況下,JS 運算、頁面佈局和頁面繪製都是運行在瀏覽器的主線程當中,他們之間是互斥的關係。若是 JS 運算持續佔用主線程,頁面就無法獲得及時的更新。當咱們調用setState
更新頁面的時候,React 會遍歷應用的全部節點,計算出差別,而後再更新 UI。整個過程是一鼓作氣,不能被打斷的。若是頁面元素不少,整個過程佔用的時機就可能超過 16 毫秒,就容易出現掉幀的現象
Fiber
實現了本身的組件調用棧,它以鏈表的形式遍歷組件樹,能夠靈活的暫停、繼續和丟棄執行的任務。實現方式是使用了瀏覽器的requestIdleCallback
這一 API。官方的解釋是這樣的:
window.requestIdleCallback()會在瀏覽器空閒時期依次調用函數,這就能夠 讓開發者在主事件循環中執行後臺或低優先級的任務,並且不會對像動畫和用戶 交互這些延遲觸發但關鍵的事件產生影響。函數通常會按先進先調用的順序執 行,除 非函數在瀏覽器調用它以前就到了它的超時時間。
React 框架內部的運做能夠分爲 3 層:
Fiber 其實指的是一種數據結構,它能夠用一個純 JS 對象來表示:
const fiber = { stateNode, // 節點實例 child, // 子節點 sibling, // 兄弟節點 return, // 父節點 }
Fiber Reconciler 每執行一段時間,都會將控制權交回給瀏覽器,能夠分段執行:
爲了達到這種效果,就須要有一個調度器 (Scheduler) 來進行任務分配。任務的優先級有六種:
優先級高的任務(如鍵盤輸入)能夠打斷優先級低的任務(如Diff)的執行,從而更快的生效。
Fiber Reconciler 在執行過程當中,會分爲 2 個階段。
生命週期函數也被分爲2個階段了:
// 第1階段 render/reconciliation componentWillMount componentWillReceiveProps shouldComponentUpdate componentWillUpdate // 第2階段 commit componentDidMount componentDidUpdate componentWillUnmount
第1階段的生命週期函數可能會被屢次調用,默認以low優先級 執行,被高優先級任務打斷的話,稍後從新執行。
預廢棄的三個生命週期函數都發生在虛擬dom的構建期間,也就是render以前。在未來的React 17中,在dom真正render以前,React中的調度機制可能會不按期的去查看有沒有更高優先級的任務,若是有,就打斷當前的週期執行函數(哪怕已經執行了一半),等高優先級任務完成,再回來從新執行以前被打斷的周期函數。這種新機制對現存周期函數的影響就是它們的調用時機變的複雜而不可預測,這也就是爲何」UNSAFE」。
參考:React Fiber 原理介紹
參考:淺談React Fiber
這個方法是用來告訴react組件數據有更新,有可能須要從新渲染。它是異步的,react一般會集齊一批須要更新的組件,而後一次性更新來保證渲染的性能,因此這就給咱們埋了一個坑:
那就是在使用setState
改變狀態以後,馬上經過this.state
去拿最新的狀態每每是拿不到的。
設想有一個需求,須要在在onClick裏累加兩次,以下
onClick = () => { this.setState({ index: this.state.index + 1 }); this.setState({ index: this.state.index + 1 }); } 複製代碼
在react眼中,這個方法最終會變成
Object.assign( previousState, {index: state.index+ 1}, {index: state.index+ 1}, ... ) 複製代碼
因爲後面的數據會覆蓋前面的更改,因此最終只加了一次.因此若是是下一個state依賴前一個state的話,推薦給setState傳function
onClick = () => { this.setState((prevState, props) => { return {quantity: prevState.quantity + 1}; }); this.setState((prevState, props) => { return {quantity: prevState.quantity + 1}; }); }
數據流
react主張函數式編程,因此推崇純組件,數據不可變,單向數據流, vue的思想是響應式的,也就是基因而數據可變的,經過對每個屬性創建Watcher來監聽,當屬性變化的時候,響應式的更新對應的虛擬dom。
監聽數據變化的實現原理不一樣
組件通訊的區別
jsx和模板
react的思路是all in js,經過js來生成html,因此設計了jsx,還有經過js來操做css,社區的styled-component、jss等。 vue是把html,css,js組合到一塊兒,用各自的處理方式,vue有單文件組件, 能夠把html、css、js寫到一個文件中,html提供了模板引擎來處理。
HoC 和 mixins
在 Vue 中咱們組合不一樣功能的方式是經過 mixin,而在React中咱們經過 HoC (高階組件)。
React 最先也是使用 mixins 的,不事後來他們以爲這種方式對組件侵入太強會致使不少問題,就棄用了 mixinx 轉而使用 HoC. 高階組件本質就是高階函數,React 的組件是一個純粹的函數,因此高階函數對React來講很是簡單。 可是Vue就不行了,Vue中組件是一個被包裝的函數,並不簡單的就是咱們定義組件的時候傳入的對象或者函數。好比咱們定義的模板怎麼被編譯的?好比聲明的props怎麼接收到的?這些都是vue建立組件實例的時候隱式乾的事。因爲vue默默幫咱們作了這麼多事,因此咱們本身若是直接把組件的聲明包裝一下,返回一個高階組件,那麼這個被包裝的組件就沒法正常工做了。
寫法
react: 類的寫法 vue: 聲明式的寫法
性能優化
react: shouldcomponent vue: vue中的每一個組件內部自動實現了`shouldComponentUpdate`的優化,在vue裏面因爲依賴追蹤系統的存在,當任意數據變更的時,Vue的每個組件都精確地知道本身是否須要重繪,因此並不須要手動優化。用vue渲染這些組件的時候,數據變了,對應的組件基本上去除了手動優化的必要性。而在react中咱們須要手動去優化其性能,可是當數據特別多的時候vue中的watcher也會特別多,從而形成頁面卡頓,因此通常數據比較多的大型項目會傾向於使用react。
爲何須要Redux: React做爲一個組件化開發框架,組件之間存在大量通訊,有時這些通訊跨越多個組件,或者多個組件之間共享一套數據,簡單的父子組件間傳值不能知足咱們的需求,天然而然地,咱們須要有一個地方存取和操做這些公共狀態。而redux就爲咱們提供了一種管理公共狀態的方案。咱們但願公共狀態既可以被全局訪問到,又是私有的不能被直接修改。
redux的三個API:
redux三大原則:
參考
Vuex、Flux、Redux、Redux-saga、Dva、MobX
redux 官網
不變性能夠爲您的應用帶來更高的性能,並致使更簡單的編程和調試,由於永遠不會改變的數據比在您的應用中隨意更改的數據更容易推理。
Redux 和 React-Redux 都採用淺層平等檢查。尤爲是:
combineReducers
實用程序淺顯地檢查由它調用的reducer引發的引用更改。connect
方法生成的組件會淺顯地檢查對根狀態的引用更改,並從mapStateToProps
函數返回值以查看被包裝的組件是否實際須要從新呈現。這種淺層檢查須要不變性才能正常工做。參考:Redux FAQ:不可變數據(Immutable Data)
爲何須要react-redux: 一個組件若是想從store存取公用狀態,須要進行四步操做:import引入store、getState獲取狀態、dispatch修改狀態、subscribe訂閱更新,代碼相對冗餘,咱們想要合併一些重複的操做,而react-redux就提供了一種合併操做的方案
react-redux提供Provider
和connect
兩個API,Provider將store放進this.context裏,省去了import這一步,connect將getState、dispatch合併進了this.props,並自動訂閱更新
從實現原理上來講,最大的區別是兩點: