React - 基礎筆記梳理

備戰秋招,複習基礎。若有錯誤,歡迎批評指正,共同進步!javascript

基本概念

聲明式:建立用戶交互界面,高效渲染頁面。聲明式編寫UI方便調試。html

組件化:使用組件傳遞數據,將應用狀態和DOM拆分開。當內部狀態(this.state)改變時,組件會調用方法(render())從新渲染。java

箭頭函數var func = (x,y) => {return x+y;};react

class → className for → htmlFor算法

樣式 style須要{{}}express

JSX:很像XML的js語法擴展。可用{js表達式} → 沒有if else,只有三元表達式redux

Key:每次構建動態列表的時候,指定一個合適的key。在同一級元素之間保證惟一!segmentfault

組件API:promise

  • setState 設置狀態 每次調用時會自動更新子組件,但不是當即生效!
  • replaceState 替換狀態
  • setProps 設置屬性
  • replaceProps 替換屬性
  • forceUpdate 強制更新
  • findDOMNode 獲取DOM節點
  • isMounted 判斷組件掛載狀態

實現原理

參考資料:react基本原理及性能優化性能優化

react只負責解決view層的渲染

虛擬DOM

參考資料:淺談React的最大亮點——虛擬DOM

虛擬DOM是在DOM的基礎上創建了一個抽象層,是一個js對象。對數據和狀態所作的任何改動,都會被自動且高效的同步到虛擬DOM,最後再批量同步到DOM中。

虛擬DOM能夠確保只對界面上真正變化的部分進行實際的DOM操做。

真實的DOM tree結構:

<div id="container">
    <p>Real DOM</p>
    <ul>
        <li class="item">Item 1</li>
        <li class="item">Item 2</li>
        <li class="item">Item 3</li>
    </ul>
</div>
複製代碼

虛擬DOM結構:

let virtualDomTree = CreateElement('div', { id: 'container' }, [
    CreateElement('p', {}, ['Virtual DOM']),
    CreateElement('ul', {}, [
        CreateElement('li', { class: 'item' }, ['Item 1']),
        CreateElement('li', { class: 'item' }, ['Item 2']),
        CreateElement('li', { class: 'item' }, ['Item 3']),
    ]),
]);

let root = virtualDomTree.render();   //轉換爲一個真正的dom結構或者dom fragment
document.getElementById('virtualDom').appendChild(root);


function CreateElement(tagName, props, children) {
    if (!(this instanceof CreateElement)) {
        return new CreateElement(tagName, props, children);
    }

    this.tagName = tagName;
    this.props = props || {};
    this.children = children || [];
    this.key = props ? props.key : undefined;
}


CreateElement.prototype.render = function() {
    let el = document.createElement(this.tagName);
    let props = this.props;

    for (let propName in props) {
        setAttr(el, propName, props[propName]);
    }

    this.children.forEach((child) => {
        let childEl = (child instanceof Element) ? child.render() : document.createTextNode(child);
        el.appendChild(childEl);
    });

    return el;
};
複製代碼

diff算法

參考資料:React之diff算法

計算出Virtual DOM中真正變化的部分,並只針對該部分進行原生DOM操做,而非從新渲染整個頁面。

  • react diff算法制定了三條策略,將算法複雜度從 O(n3)下降到O(n)。
  1. WebUI中DOM節點跨節點的操做特別少,能夠忽略不計。
  2. 擁有相同類的組件會擁有類似的DOM結構。擁有不一樣類的組件會生成不一樣的DOM結構。
  3. 同一層級的子節點,能夠根據惟一的ID來區分。
  • react diff實施的具體策略是:
  1. diff對樹進行分層比較,只對比兩棵樹同級別的節點。跨層級移動節點,將會致使節點刪除,從新插入,沒法複用。

  2. diff對組件進行類比較,類相同的遞歸diff子節點,不一樣的直接銷燬重建。diff對同一層級的子節點進行處理時,會根據key進行簡要的複用。兩棵樹中存在相同key的節點時,只會移動節點。

  3. 在對比同一層級的子節點時,diff算法會以新樹的第一個子節點做爲起點遍歷新樹,尋找舊樹中與之相同的節點。若是節點存在,則移動位置。若是不存在,則新建一個節點。 在這過程當中,維護了一個字段lastIndex,這個字段表示已遍歷的全部新樹子節點在舊樹中最大的index。在移動操做時,只有舊index小於lastIndex的纔會移動。 這個順序優化方案其實是基於一個假設,大部分的列表操做應該是保證列表基本有序的。 能夠推倒倒序的狀況下,子節點列表diff的算法複雜度爲O(n2)

生命週期

react生命週期

  1. 初始化
  • getDefaultProps() 設置默認的props
  • getInitialState() 定義初始this.state
  • componentWillMount() 修改state的最後一次機會
  • render()建立一個虛擬DOM,表示組件的輸出(惟一必須的方法)
  • componentDidMount()訪問真實DOM,進行數據監聽、數據請求
  1. 更新
  • componentWillReceiveProps() 可更新state,觸發render
  • shouldComponentUpdate() 決定是否須要從新渲染,優化渲染性能
  • componentWillUpdate() 從新渲染前調用
  • componentDidUpdate() 訪問並修改DOM,監聽props或state變化。這裏放setState必須加條件,不然會致使死循環。
  1. 銷燬
  • componentWillUnmount() 將組件從DOM中卸載並銷燬`

setState

  • 理想狀況:

    setState是「異步」的,調用setState只會提交一次state修改到隊列中,不會直接修改this.state。 等到知足必定條件時,react會合並隊列中的全部修改,觸發一次update流程,更新this.state。

    所以setState機制減小了update流程的觸發次數,從而提升了性能。

    因爲setState會觸發update過程,所以在update過程當中必經的生命週期中調用setState會存在循環調用的風險。

    另外用於監聽state更新完成,可使用setState方法的第二個參數,回調函數。在這個回調中讀取this.state就是已經批量更新後的結果。

  • 特殊狀況:在實際開發中,setState的表現有時會不一樣於理想狀況。主要是如下兩種。

    1. 在mount流程中調用setState:不會進入update流程,隊列在mount時合併修改並render。

    2. 在setTimeout/Promise回調中調用setState:將不會進行隊列的批更新,而是直接觸發一次update流程。 這是因爲setState的兩種更新機制致使的,只有在批量更新模式中,纔會是「異步」的。

  • setState會引起一次組件的更新過程,從而引起頁面的從新繪製。主要會涉及如下幾個生命週期函數:

    1. shouldComponentUpdate(被調用時this.state沒有更新;若是返回了false,生命週期被中斷,雖然不調用以後的函數了,可是state仍然會被更新)
    2. componentWillUpdate(被調用時this.state沒有更新)
    3. render(被調用時this.state獲得更新)
    4. componentDidUpdate

性能優化

減小diff算法觸發

  1. 減小setState:主要在於非批更新階段中(timeout/Promise回調),減小setState的觸發次數。
  2. 父組件的render:會觸發子組件進入update階段(不管props是否更新)。此時最經常使用的優化方案即爲shouldComponentUpdate方法。最多見的方式爲進行this.props和this.state的淺比較來判斷組件是否須要更新。或者直接使用PureComponent,原理一致。

合理diff

  1. 不使用跨層級移動節點的操做。
  2. 對於條件渲染多個節點時,儘可能採用隱藏等方式切換節點,而不是替換節點。
  3. 儘可能避免將後面的子節點移動到前面的操做,當節點數量較多時,會產生必定的性能問題。

PureComponent

資料參考:詳解在React.js中使用PureComponent的重要性和使用方式

資料參考:React 的 PureComponent Vs Component

資料參考:什麼時候使用Component仍是PureComponent?

基本概念

PureComponent改變了生命週期方法 shouldComponentupdate,而且會自動檢查組件是否須要從新渲染。這時,只有PureComponent檢測到 state 或者 props 發生變化(引用類型的引用變化,或基本類型string number值變化)時,PureComponent纔會調用 render 方法。

淺比較源碼:

if (this._compositeType === CompositeTypes.PureClass) {
  shouldUpdate = !shallowEqual(prevProps, nextProps) || ! shallowEqual(inst.state, nextState);
}
複製代碼

使用pureComponent至關於在component中優化檢測:

-----這是component中的優化 在purecomponent中默認包含-----
shouldComponentUpdate(nextProps, nextState) {
    return (nextState.person !== this.state.person);
  }
複製代碼

使用

class MyComponent extends PureComponent {...}
複製代碼

若是一個純組件(PureComponent)的 state 或 props 引用了一個新對象,那麼這個組件就會被從新渲染(re-render)。

handleClick() {
 this.setState(prevState => ({
  words: prevState.items.concat(['new-item'])  ← 引用新對象 淺比較不一樣
 }));
}
複製代碼

使用PureComponent的最佳狀況就是展現組件,它既沒有子組件,也沒有依賴應用的全局狀態。

  • 父組件繼承PureComponent,子組件繼承Component時,若是渲染被父組件攔截,子組件也不會從新渲染。
  • 父組件繼承Component,子組件繼承PureComponent時:父組件會從新渲染,子組件不必定會從新渲染。

若是prop和state每次都會變,那麼PureComponent的效率還不如Component!(由於還要淺比較)

不要在PureComponent中使用shouldComponentUpdate,由於根本沒有必要~

一些操做

事件監聽

<button onClick = { (e) => this.deleteRow(id,e) } Delete </button>
複製代碼

等價於

<button onClick = { this.deleteRow.bind(this,id) } Delete </button>
複製代碼

具體方法 調用中 e/this → e

deleteRow (id, e){...}
複製代碼

判斷執行

true && expression
複製代碼

爲真才執行,取代if條件渲染

React-router

參考資料:react-router的實現原理

react-router就是控制不一樣的url渲染不一樣的組件。react-router在history庫的基礎上,實現了URL與UI的同步。

  • 原理:DOM渲染完成以後,給window添加onhashchange事件監聽頁面hash的變化,而且在state屬性中添加了route屬性,表明當前頁面的路由。

  • 具體步驟:

    1 當點擊連接,頁面hash改變時,觸發綁定在 window 上的 onhashchange 事件;

    2 在 onhashchange 事件中改變組件的 state中的route屬性,react組件的state屬性改變時,自動從新渲染頁面;

    3 頁面隨着 state 中的route屬性改變,自動根據不一樣的hash給Child變量賦值不一樣的組件,進行渲染。

Redux

數據管理中心:單一數據源(全部state維護在一個根級store);狀態只讀(沒法直接修改,嚴控修改執行);純函數(只能用reducer描述修改)

Store:全局單例。

  • 方法:

    getState:獲取state;

    dispatch:觸發action,更新state;

    subcribe:訂閱數據變動,註冊監聽器。

  • const store = createStroe(Reducer, initStore);

action:行爲載體,映射響應Reducer。可成爲數據載體,是store惟一數據源。

Reducer:修改行爲的實質,用於描述如何修改數據的純函數。

純函數:對於相同的輸入,永遠會獲得相同的輸出,並且沒有任何可觀察的反作用,也不依賴外部環境的狀態。
緣由:Redux只經過比較新舊兩個對象的存儲位置來比較新舊兩個對象是否相同(淺比較)。若是你在reducer內部直接修改舊的state對象的屬性值,那麼新的state和舊的state將都指向同一個對象。所以Redux認爲沒有任何改變,返回的state將爲舊的state。兩個state相同的話,頁面就不會從新渲染了。由於比較兩個Javascript對象全部的屬性是否相同的的惟一方法是對它們進行深比較。可是深比較在真實的應用當中代價昂貴,由於一般js的對象都很大,同時須要比較的次數不少。所以一個有效的解決方法是做出一個規定:不管什麼時候發生變化時,開發者都要建立一個新的對象,而後將新對象傳遞出去。同時,當沒有任何變化發生時,開發者發送回舊的對象。也就是說,新的對象表明新的state。
複製代碼

處理異步:引入Redux-thunk或者redux-promise這種中間件,延遲事件的派發。

!!!!!!!!!!!!!寫一個!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

React Hook

在函數定義組件中使用React特性

引入React到html中

layout.ftl 最初渲染頁面

<html ...>
    <body>
        <div id = "app-container"></div>
        <script type = "text/javascript" ... ></script
    </body>
</html>
複製代碼

client.js 配置redux的頁面

在此引入 store  router
...
ReactDom.render(
    ...引入其餘設置及組件
    document.getElementById('app-container');
);
複製代碼
相關文章
相關標籤/搜索