簡單來講,就是virtual dom & react diff。
咱們都知道在前端開發中,js運行很快,dom操做很慢,而react充分利用了這個前提。在react中render的執行結果是樹形結構的javascript對象,當數據(state || props)發生變化時,會生成一個新的樹形結構的javascript對象,這兩個javascript對象咱們能夠稱之爲virtual dom。而後對比兩個virtual dom,找出最小的有變化的點,這個對比的過程咱們稱之爲react diff,將這個變化的部分(patch)加入到一個隊列中,最終批量更新這些patch到dom中。javascript
關於提高性能,不少人說virtual dom能夠提高性能,這一說法其實是很片面的。由於咱們知道,直接操做dom是很是耗費性能的,可是即便咱們用了react,最終依然要去操做真實的dom。而react幫咱們作的事情就是儘可能用最佳的方式有操做dom。若是是首次渲染,virtual dom不具備任何優點,甚至它要進行更多的計算,消耗更多的內存。
react自己的優點在於react diff算法和批處理策略。react在頁面更新以前,提早計算好了如何進行更新和渲染DOM,實際上,這個計算過程咱們在直接操做DOM時,也是能夠本身判斷和實現的,可是必定會耗費很是多的精力和時間,並且每每咱們本身作的是不如React好的。因此,在這個過程當中React幫助咱們"提高了性能"。
因此,我更傾向於說,virtual dom幫助咱們提升了開發效率,在重複渲染時它幫助咱們計算如何更高效的更新,而不是它比DOM操做更快。html
咱們在實現一個React組件時能夠選擇兩種編碼方式,第一種是使用JSX編寫,第二種是直接使用React.createElement編寫。實際上,上面兩種寫法是等價的,jsx只是爲React.createElemen方法的語法糖,最終全部的jsx都會被babel轉換成React.createElement。
可是請注意,babel在編譯時會判斷jsx中組件的首字母,當首字母爲小寫時,其被認定爲原生dom標籤,createElement的第一個變量被編譯爲字符串。當首字母爲大寫時,其被認定爲自定義組件,createElement的第一個變量被編譯爲對象。前端
在react16中,廢棄了三個will屬性componentWillMount,componentWillReceiveProps,comonentWillUpdate,可是目前還未刪除,react17計劃會刪除,同時經過UNSAFF_前綴向前兼容。
在 React 中,咱們能夠將其生命週期分爲三個階段。java
react diff會幫助咱們計算出virtual dom中真正變化的部分,並只針對該部分進行實際dom操做,而非從新渲染整個頁面,從而保證了每次操做更新後頁面的高效渲染。傳統diff算法經過循環遞歸對節點進行依次對比,效率低下,算法複雜度達到 O(n^3)。react diff基於一下三個策略實現了O(n)的算法複雜度。react
基於以上三個前提策略,React分別對tree diff、component diff以及element diff 進行算法優化,事實也證實這三個前提策略是合理且準確的,它保證了總體界面構建的性能。git
首先說一下element diff的過程。好比有老的集合(A,B,C,D)和新的集合(B,A,D,C),咱們考慮在不增長空間複雜度的狀況下如何以O(n)的時間複雜度找出老集合中須要移動的元素。
在react裏的思路是這樣的,遍歷新集合,初始化lastIndex=0(表明訪問過的老集合中最右側的位置),表達式爲max(prev.mountIndex, lastIndex),若是當前節點在老集合中的位置即(prev.mountIndex)比lastIndex大說明當前訪問節點在老集合中就比上一個節點位置靠後則該節點不會影響其餘節點的位置,所以不用添加到差別隊列中,即不執行移動操做,只有當訪問的節點比 lastIndex 小時,才須要進行移動操做。
部分源碼爲github
var lastIndex = 0; var nextIndex = 0; for (name in nextChildren) { var prevChild = prevChildren && prevChildren[name]; // 老節點 var nextChild = nextChildren[name]; // 新節點 if (prevChild === nextChild) { // 若是新節點存在老節點集合裏 // 移動節點 this.moveChild(prevChild, nextIndex, lastIndex); lastIndex = Math.max(prevChild._mountIndex, lastIndex); prevChild._mountIndex = nextIndex; } else { if (prevChild) { // 若是不存在在 lastIndex = Math.max(prevChild._mountIndex, lastIndex); // 刪除節點 this._unmountChild(prevChild); } // 初始化並建立節點 this._mountChildAtIndex( nextChild, nextIndex, transaction, context ); } nextIndex++; } // 移動節點 moveChild: function(child, toIndex, lastIndex) { if (child._mountIndex < lastIndex) { this.prepareToManageChildren(); enqueueMove(this, child._mountIndex, toIndex); } }
React Fiber是React對核心算法的一次從新實現。
在協調階段階段,之前因爲是採用的遞歸的遍歷方式,這種也被稱爲Stack Reconciler,主要是爲了區別Fiber Reconciler取的一個名字。這種方式有一個特色: 一旦任務開始進行,就沒法中斷,那麼js將一直佔用主線程,一直要等到整棵virtual dom樹計算完成以後,才能把執行權交給渲染引擎,那麼這就會致使一些用戶交互、動畫等任務沒法當即獲得處理,就會有卡頓,很是的影響用戶體驗。
頁面是一幀一幀繪製出來的,當每秒繪製的幀數(FPS)達到60時,頁面是流暢的,小於這個值時,用戶會感受到卡頓。1秒60幀,因此每一幀分到的時間是1000/60 ≈ 16ms。因此咱們書寫代碼時力求不讓一幀的工做量超過 16ms。若是任意一個步驟所佔用的時間過長,超過16ms了以後,用戶就能看到卡頓。web
簡單來講就是時間分片 + 鏈表結構。而fiber就是維護每個分片的數據結構。
Fiber利用分片的思想,把一個耗時長的任務分紅不少小片,每個小片的運行時間很短,在每一個小片執行完以後,就把控制權交還給React負責任務協調的模塊,若是有緊急任務就去優先處理,若是沒有就繼續更新,這樣就給其餘任務一個執行的機會,惟一的線程就不會一直被獨佔。
所以,在組件更新時有可能一個更新任務尚未完成,就被另外一個更高優先級的更新過程打斷,優先級高的更新任務會優先處理完,而低優先級更新任務所作的工做則會徹底做廢,而後等待機會重頭再來。因此 React Fiber把一個更新過程分爲兩個階段:算法
高階組件(HOC)是React中用於複用組件邏輯的一種高級技巧。HOC自身不是React API的一部分,它是一種基於 React 的組合特性而造成的設計模式。具體而言,高階組件是參數爲組件,返回值爲新組件的函數。
請注意,HOC 不會修改傳入的組件,也不會使用繼承來複制其行爲。相反,HOC 經過將組件包裝在容器組件中來組成新組件。HOC 是純函數,沒有反作用。
我理解的高階組件是,將組件以參數的方式傳遞給另一個函數,在該函數中,對組件進行包裝,封裝了一些公用的組件邏輯,實現組件的邏輯複用,該函數被稱爲高階組件。可是請注意,高階組件不該修改傳入的組件行爲。
屬性代理segmentfault
function ppHOC(WrappedComponent) { return class PP extends React.Component { render() { const newProps = { user: currentLoggedInUser } return <WrappedComponent {...this.props} {...newProps}/> } } }
反向繼承
function hoc(ComponentClass) { return class HOC extends ComponentClass { render() { if (this.state.success) { return super.render() } return <div>Loading...</div> } } } export default class ComponentClass extends Component { state = { success: false, data: null }; async componentDidMount() { const result = await fetch(...請求); this.setState({ success: true, data: result.data }); } render() { return <div>主要內容</div> } }
術語 「render prop」 是指一種技術,用於使用一個值爲函數的 prop 在 React 組件之間的代碼共享。
帶有渲染屬性(Render Props)的組件須要一個返回 React 元素並調用它的函數,而不是實現本身的渲染邏輯。
我理解的渲染屬性是,提供渲染頁面的props給子組件,共享能夠共享子組件的狀態,複用子組件的狀態,並告訴子組件如何進行渲染。
import React from 'react' import ReactDOM from 'react-dom' import PropTypes from 'prop-types' // 與 HOC 不一樣,咱們可使用具備 render prop 的普通組件來共享代碼 class Mouse extends React.Component { static propTypes = { render: PropTypes.func.isRequired } state = { x: 0, y: 0 } handleMouseMove = (event) => { this.setState({ x: event.clientX, y: event.clientY }) } render() { return ( <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}> {this.props.render(this.state)} </div> ) } } const App = React.createClass({ render() { return ( <div style={{ height: '100%' }}> <Mouse render={({ x, y }) => ( // render prop 給了咱們所須要的 state 來渲染咱們想要的 <h1>The mouse position is ({x}, {y})</h1> )}/> </div> ) } }) ReactDOM.render(<App/>, document.getElementById('app'))
React Hooks 是 React 16.7.0-alpha 版本推出的新特性,它可讓你在不編寫class的狀況下使用state以及其餘的 React特性。React Hooks要解決的問題是狀態共享,是繼render-props和hoc以後的第三種狀態共享方案,不會產生JSX嵌套地獄問題。這個狀態指的是狀態邏輯,因此稱爲狀態邏輯複用會更恰當,由於只共享數據處理邏輯,不會共享數據自己。
let memoizedState = []; // hooks 存放在這個數組 let cursor = 0; // 當前 memoizedState 下標 function useState(initialValue) { memoizedState[cursor] = memoizedState[cursor] || initialValue; const currentCursor = cursor; function setState(newState) { memoizedState[currentCursor] = newState; render(); } return [memoizedState[cursor++], setState]; // 返回當前 state,並把 cursor 加 1 } function useEffect(callback, depArray) { const hasNoDeps = !depArray; const deps = memoizedState[cursor]; const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true; if (hasNoDeps || hasChangedDeps) { callback(); memoizedState[cursor] = depArray; } cursor++; }
super表明父類的構造函數,javascript規定若是子類不調用super是不容許在子類中使用this的,這不是React的限制,而是javaScript的限制,同時你也必須給super傳入props,不然React.Component就無法初始化this.props
在 React 的類組件中,當咱們把事件處理函數引用做爲回調傳遞過去,事件處理程序方法會丟失其隱式綁定的上下文。當事件被觸發而且處理程序被調用時,this的值會回退到默認綁定,即值爲 undefined,這是由於類聲明和原型方法是以嚴格模式運行。
SyntheticEvent是react合成事件的基類,定義了合成事件的基礎公共屬性和方法。react會根據當前的事件類型來使用不一樣的合成事件對象,好比鼠標單機事件 - SyntheticMouseEvent,焦點事件-SyntheticFocusEvent等,可是都是繼承自SyntheticEvent。在合成事件中主要作了如下三件事情。
組件掛載階段,根據組件內的聲明的事件類型-onclick,onchange等,給document上添加事件addEventListener,並指定統一的事件處理程序dispatchEvent。
經過virtual dom的props屬性拿到要註冊的事件名,回調函數,經過listenTo方法使用原生的addEventListener進行事件綁定。
事件存儲,就是把react組件內的全部事件統一的存放到一個二級map對象裏,緩存起來,爲了在觸發事件的時候能夠查找到對應的方法去執行。先查找事件名,而後找對對應的組件id相對應的事件。以下圖:
由執行機制看,setState自己並非異步的,而是在調用setState時,若是react正處於更新過程,當前更新會被暫存,等上一次更新執行後再執行,這個過程給人一種異步的假象。
ReactComponent.prototype.setState = function(partialState, callback) { // 將setState事務放進隊列中 this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } }; enqueueSetState: function (publicInstance, partialState) { // 獲取當前組件的instance var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState'); // 將要更新的state放入一個數組裏 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); queue.push(partialState); // 將要更新的component instance也放在一個隊列裏 enqueueUpdate(internalInstance); } function enqueueUpdate(component) { // 若是沒有處於批量建立/更新組件的階段,則處理update state事務 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 若是正處於批量建立/更新組件的過程,將當前的組件放在dirtyComponents數組中 dirtyComponents.push(component); }
這裏的partialState能夠傳object,也能夠傳function,它會產生新的state以一種Object.assgine()的方式跟舊的state進行合併。
由這段代碼能夠看到,當前若是正處於建立/更新組件的過程,就不會馬上去更新組件,而是先把當前的組件放在dirtyComponent裏,因此不是每一次的setState都會更新組件。這段代碼就解釋了咱們常據說的:setState是一個異步的過程,它會集齊一批須要更新的組件而後一塊兒更新。而batchingStrategy 又是個什麼東西呢?
ReactDefaultBatchingStrategy.js
var ReactDefaultBatchingStrategy = { // 用於標記當前是否出於批量更新 isBatchingUpdates: false, // 當調用這個方法時,正式開始批量更新 batchedUpdates: function (callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // 若是當前事務正在更新過程在中,則調用callback,既enqueueUpdate if (alreadyBatchingUpdates) { return callback(a, b, c, d, e); } else { // 不然執行更新事務 return transaction.perform(callback, null, a, b, c, d, e); } } };
前端路由的原理思路大體上都是相同的,即實如今無刷新頁面的條件下切換顯示不一樣的頁面。而前端路由的本質就是頁面的URL發生改變時,頁面的顯示結果能夠根據URL的變化而變化,可是頁面不會刷新。目前實現前端路由有兩種方式:
路徑中hash值改變,並不會引發頁面刷新,同時咱們能夠經過hashchange事件,監聽hash的變化,從而實現咱們根據不一樣的hash值展現和隱藏不一樣UI顯示的功能,進而實現前端路由。
HTML5的History接口,History對象是一個底層接口,不繼承於任何的接口。History接口容許咱們操做瀏覽器會話歷史記錄。
而history的pushState和repalce方法能夠實現改變當前頁面顯示的url,但都不會刷新頁面。
未完待續~
參考文檔:
react生命週期詳解
React diff
react 16新特性
react fiber1
react fiber2
react hooks
react 事件機制
setState機制1
setState機制2
react-router原理
集合