对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,因此关于这个方面没(水)有(平)讲(不)到(够),同时哪里有错误也请你们指出。

相关文章
相关标签/搜索