對react技術棧的一些理解

目的

本篇文章主要幫助你們瞭解下react技術棧相關的概念,以及爲何咱們須要引入這些,他們能解決什麼問題。vue

React

爲何選擇react,而不是vue2

vue2的優勢

vue1沒有加入虛擬DOM,作服務端渲染很難,因此vue2引入了虛擬DOM的機制,並且因爲vue2的響應式原理,因此自然的就比react的性能好,react的更新是經過頂層組件的state變化觸發整個組件的從新渲染,而vue2因爲其是經過getter/setter來進行數據管理,因此能夠準確的定位到須要從新渲染的節點,避免了無效的re-renderreact

vue2的缺點

native端支持很差,weex很厲害可是目前只有阿里用於生產環境,而react native有着大量的成熟案例,如手機QQ編程

爲何沒有選擇riot

riot的優勢

小,用在移動端很合適redux

riot的缺點

小的缺點不少,可是比較好克服,最大的缺點仍是在於native端,若是想用riot實現native端的話,須要造輪子,寫就是本身寫一套 Native Bridge,來進行jsObjective C通訊,難度太大,須要引入js引擎等高大上的東西(Native Bridge基本上能夠理解爲一個瀏覽器內核,並且確定是C++寫)。瀏覽器

native的基本原理

一個線程爲js引擎,執行打包好的js,主線程負責UI繪製,js須要繪製UI時會向主線程發出一個命令,主線程接收到命令後執行相應的繪製邏輯,Objective C執行的結果會通過層層回調經過js引擎傳回給js緩存

redux

react + redux的缺點

利用redux作數據管理的話,reduxstore會被放置到最頂層組件的state中,也就是react-redux爲咱們提供的Provider組件。這樣就是意味着每次store發生變化就會從新渲染整個應用,也就是觸發全部組件的render方法,每次都會觸發diff,可是這種大可能是無心義的,這也是產生性能瓶頸的地方:服務器

圖片描述

以下面代碼:weex

render() {
    const { child1Data, child2Data } = this.props.store;
    return (
        <div>
            <Child1 data="child1Data" />
            <Child2 data="child2Data" />
        </div>
    );
}

假如只有child1Data發生變化,而child2Data並無發生變化,理論上來講咱們只想觸發Child1render,但事實上咱們同時會觸發Child2render,此次顯然是無心義的,因此須要來解決這個問題。數據結構

引入purerender

react的生命週期函數中有一個shouldComponentUpdate,根據其返回的值來決定是否須要來觸發組件的rendershouldComponentUpdate默認返回的是true,也就是不管什麼狀況都會觸發renderpurerender改善的就是這個生命週期,根據傳入的stateprops來進行簡單的判斷,從而決定是否須要進行render,爲何說只是進行了簡單的判斷,來看其判斷部分代碼:
(注:利用connect將組件與redux關聯起來的容器不須要加purerender ,由於這個工做react-redux已經替咱們作好了框架

// is能夠理解爲Object.is()
function shallowEqual(objA, objB) {
    if (is(objA, objB)) {
        return true;
    }
    // 非引用類型,且不相等直接返回
    if (typeof objA !== 'object' || objA === null ||
        typeof objB !== 'object' || objB === null) {
        return false;
    }
    const keysA = Object.keys(objA),
        keysB = Object.keys(objB);

    if (keysA.length !== keysB.length) {
        return false;
    }
    // 問題所在,僅僅是比較了第一層,假設引用沒變,不會觸發更新
    for (let i = 0; i < keysA.length; i++) {
        if (
            !hasOwnProperty.call(objB, keysA[i]) ||
            !is(objA[keysA[i]], objB[keysB][i])
        ) {
            return false;
        }
    }
    return true;
}

注意:使用react時必定注意不要在render函數中進行函數的bind,由於這樣每次props中會有屬性的引用改變,必定會觸發更新

對於通常的狀況來講purerender已經足夠,能夠減小一些re-render,可是不是很完全,好比:

  • 狀況一:

// 以前的數據:
let person = {
    name: 'zp1996',
    age: 21
};
// 改變引用
person = {
    name: 'zp1996',
    age: 21
};

(注:上面場景我作了下簡化,真實中多是從服務器端得到的數據,好比帖子列表這種)
明顯的引用發生了改變,因此會觸發re-render,可是明顯的是個人數據徹底沒有變化,根本不用進行diff

  • 狀況二:

const data = {
    person: {
        students: [{
            name: 'zp1996',
            age: 21
        }]
    }
};
// 加入一個新的學生,數據結構會變成這樣
{
    person: {
        students: [{
            name: 'zp1996',
            age: 20
        }, {
            name: 'zpy',
            age: 21
        }]
    }
}

假如是這種狀況,引用根本沒有發生變化,因此就不會觸發re-render,每次改變一個小的地方,就須要將整個的數據從新生成一個,這樣形成了內存的沒必要要的浪費。

利用immutable解決

很容易想到的是在shouldComponentUpdate中進行深度比較,用遞歸的方式來進行比較,這樣的代價一樣很大,並非一個有效的解決方案。爲了解決這個問題,須要引入另外一個庫——immutable,其思想是強調不可變數據,一個Immutable Data的建立就是一個不可變的,須要變化時不是利用深拷貝,而是僅僅改變這個變化的節點和其父節點,其他節點還是共享內存。一樣的這樣的一個強大的框架也是很是大,壓縮事後仍然有50k

還有問題嗎?

咱們但願的是reducer中保持簡單,從服務端請求回來的數據直接存在store中,看個例子:

// 從服務端拉回來的數據
{
    students: {
        'id_111': {
            name: 'zp1996',
            age: 21
        }
    }
}
// 最終組件但願咱們傳入這樣的數據
{
    students: [{
        name: 'zp1996',
        age: 21,
        id: 'id_111'
    }]
}
// 通常會在connect中對數據進行整理
const mapStateToProp = state => {
    const { students } = state,
        res = [];
    for (let key in students) {
        if (students.hasOwnProperty(key)) {
            let obj = students[key];
            obj[id] = key;
            res.push(obj);
        }
    }  
    return res;  
};
@connect(
    mapStateToProp,   // 被叫作selector 
);

每次store變化後,也就是執行一次dispatch以後都會執行利用subscribe方法註冊的回調(註冊的回調就是connect的第一個參數,也就是selector),這樣就意味着,儘管students並無發生變化仍是會觸發一次數據結構的重整,這種顯然是一種浪費,因此這個過程也須要優化:

clipboard.png

redux強調的是函數式編程,對於函數式編程來講有一個很明顯的特色就是易於緩存,對於一個函數而言,給定相同的輸入確定會獲得相同的輸出,而selector也所有爲純函數,同時connectmapStateToProp參數也支持返回一個函數。reselect庫就是這個思想,先來看看基本用法:

// createSelector的最後一個參數做爲計算函數
const state = { num: 10 },
    selector = state => state.num,
    reSelector = createSelector(
        selector,
        a => {
            console.log('被調用了')
            return a * a;
        }
    );

console.log(reSelector(state));    // 第一次計算
console.log(reSelector(state));    // 拿緩存
console.log(reSelector(state));    // 拿緩存
state.num = 100;
console.log(reSelector(state));    // 值發生改變,計算
console.log(reSelector(state));    // 拿緩存   
console.log(reSelector(state));    // 拿緩存

實現也是很是簡單,就是對傳入的參數進行判斷,若是與以前同樣則直接返回結果

function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
    let lastArgs = null,    
        lastResult = null;
    const isEqualToLastArg = (value, index) => equalityCheck(value, lastArgs[index]);
    return (...args) => {
        // 檢測輸入是否相等,不等或者第一次執行的話執行函數,反之拿之間的結果
        if (
            lastArgs === null ||
            lastArgs.length !== args.length ||
            !args.every(isEqualToLastArg)
        ) {
            lastResult = func(...args);
        }
        lastArgs = args;
        return lastResult;
    };
}

redux太複雜?嘗試mobx

redux會引入不少的概念,同時代碼量也會不少,而mobx要更爲簡單。mobx給個人感受就像是把vue的響應式數據那一套給拿了出來,給了咱們極大的自由度,能夠利用OOP那一套來創建模型,而用redux必須利用redux的那些套路寫代碼;同時在性能上也會有一些提高。可是同時也會帶來很大的問題:state滿天飛是避免不了的,可是提供了一個strict模式,要求數據必須利用action來進行更改,可是我用了以後發現並無什麼做用,並且組件外是能夠隨意更改數據的。 還有一個小問題就是熱更新,根本沒有找到熱更新的解決方案,每次還得手動刷新頁面。

最後

redux還有一塊很重要的部分,那就是異步處理,目前本人只用過redux-thunk,因此關於這個方面沒(水)有(平)講(不)到(夠),同時哪裏有錯誤也請你們指出。

相關文章
相關標籤/搜索