reactjs + redux系列之基础概念

react.js概念

react.js框架是facebook推出的UI框架,主要解决视图层展示中的一些痛点问题:

  1. 虚拟dom:避免频繁的访问dom,修改dom带来的性能问题,在加上diff算法实现不重复渲染UI组件;
  2. 组件化:对于公司组件库的积累和沉淀,有利于资源复用,提高效率节省成本;
  3. 单向数据流:react提出单向数据流,数据的流向有规律可循;

更想说的是通过对react技术栈的使用,颠覆了许多前端开发的习惯。比以前更少的接触dom,控制dom;转而更多的关心业务逻辑,数据的流动和处理,更像是在写服务端代码。

虚拟dom

在React中,render执行的结果得到的并不是真正的DOM节点,结果仅仅是轻量级的JavaScript对象,我们称之为virtual DOM。

react具有batching(批处理)和高效的Diff算法,这使我们无需估计性能问题,毫无估计的刷新页面;虚拟dom的diff算法会告诉系统哪些dom需要操作,这一过程不需要认为干预,但是进一步了解虚拟dom的实现原理对于开发和优化都是有好处的。

比较innerHTML 和Virtual DOM 的重绘过程如下:
innerHTML: render html string + 重新创建所有 DOM 元素
Virtual DOM: render Virtual DOM + diff + 必要的 DOM 更新

Diff算法

上边也提到了diff算法,在react中不同状态对应不同的页界面,对俩个界面的不同通过对dom树的进行diff算法分析。
Facebook工程师结合Web界面的特点做出了两个简单的假设,使得Diff算法复杂度直接降低到O(n):

  • 两个相同组件产生类似的DOM结构,不同的组件产生不同的DOM结构;
  • 对于同一层次的一组子节点,它们可以通过唯一的id进行区分。

逐层进行节点比较
提到树,相信大多数同学立刻想到的是二叉树,遍历,最短路径等复杂的数据结构算法。而在React中,树的算法其实非常简单,那就是两棵树只会对同一层次的节点进行比较, 也就是进行逐层比较。如下图所示:

reactjs + redux系列之基础概念_第1张图片

React只会对相同颜色方框内的DOM节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。
例如,考虑有下面的DOM结构转换:
reactjs + redux系列之基础概念_第2张图片

A节点被整个移动到D节点下,直观的考虑DOM Diff操作应该是

 A.parent.remove(A); 
 D.append(A);

但因为React只会简单的考虑同层节点的位置变换,对于不同层的节点,只有简单的创建和删除。当根节点发现子节点中A不见了,就会直接销毁A;而当D发现自己多了一个子节点A,则会创建一个新的A作为子节点。因此对于这种结构的转变的实际操作是:

 A.destroy();
 A = new A();
 A.append(new B());
 A.append(new C());
 D.append(A);

可以看到,以A为根节点的树被整个重新创建。

不同节点类型的比较
在React中即比较两个虚拟DOM节点,当两个节点不同时,应该如何处理。这分为两种情况:
(1)节点类型不同 :
React直接删除前面的节点,然后创建并插入新的节点。假设我们在树的同一位置前后两次输出不同类型的节点。

  renderA: 
renderB: => [removeNode
], [insertNode ] 当一个节点从div变成span时,简单的直接删除div节点,并插入一个新的span节点。这符合我们对真实DOM操作的理解。

如果该删除的节点之下有子节点,那么这些子节点也会被完全删除,它们也不会用于后面的比较。这也是算法复杂能够降低到O(n)的原因。
以此类推,不同类型的组件也是相同的比较逻辑:

    renderA: 
renderB: => [removeNode
], [insertNode ]

(2)节点类型相同,但是属性不同。
React会对属性进行重设从而实现节点的转换。例如:

   renderA: 
renderB:
=> [replaceAttribute id "after"]

虚拟DOM的style属性稍有不同,其值为一个对象,因此转换过程如下:

   renderA: 
renderB:
=> [removeStyle color], [addStyle font-weight 'bold']

由DOM Diff算法理解组件的生命周期
React组件的生命周期,其中的每个阶段其实都是和DOM Diff算法息息相关的。例如以下几个方法:

  • constructor: 构造函数,组件被创建时执行;
  • componentDidMount: 当组件添加到DOM树之后执行;
  • componentWillUnmount: 当组件从DOM树中移除之后执行,在React中可以认为组件被销毁;
  • componentDidUpdate: 当组件更新时执行。
reactjs + redux系列之基础概念_第3张图片

上边图示的执行采用文字事例描述如下:

C will unmount.
C is created.
B is updated.
A is updated.
C did mount.
D is updated.
R is updated.

可以看到,C节点是完全重建后再添加到D节点之下,而不是将其“移动”过去。

列表节点的比较
那么当它们在同一层时,又是如何处理的呢?这就涉及到列表节点的Diff算法。相信很多使用React的同学大多遇到过这样的警告:


这是React在遇到列表时却又找不到key时提示的警告。虽然无视这条警告大部分界面也会正确工作,但这通常意味

列表节点的操作通常包括添加、删除和排序。例如下图,我们需要往B和C直接插入节点F,在jQuery中我们可能会直接使用$(B).after(F)来实现。而在React中,我们只会告诉React新的界面应该是A-B-F-C-D-E,由Diff算法完成更新界面。


reactjs + redux系列之基础概念_第4张图片

这时如果每个节点都没有唯一的标识,React无法识别每一个节点,那么更新过程会很低效,即,将C更新成F,D更新成C,E更新成D,最后再插入一个E节点。效果如下图所示:


reactjs + redux系列之基础概念_第5张图片

可以看到,React会逐个对节点进行更新,转换到目标节点。而最后插入新的节点E,涉及到的DOM操作非常多。而如果给每个节点唯一的标识(key),那么React能够找到正确的位置去插入新的节点,入下图所示:
reactjs + redux系列之基础概念_第6张图片

对于列表节点顺序的调整其实也类似于插入或删除,我们将树的形态从shape5转换到shape6:


reactjs + redux系列之基础概念_第7张图片

即将同一层的节点位置进行调整。如果未提供key,那么React认为B和C之后的对应位置组件类型不同,因此完全删除后重建,控制台输出如下:

B will unmount.
C will unmount.
C is created.
B is created.
C did mount.
B did mount.
A is updated.
R is updated.

而如果提供了key,如下面的代码:

shape5: function() { return (       );},
shape6: function() { return (       );},

那么控制台输出如下:

C is updated.
B is updated.
A is updated.
R is updated.

可以看到,对于列表节点提供唯一的key属性可以帮助React定位到正确的节点进行比较,从而大幅减少DOM操作次数,提高了性能。

jsx语法

react的虚拟dom是最大的亮点,可以再内存中生成dom树,通过创建虚拟的dom树来减少对真实dom的操作,实现性能的提升。不论是真实dom还是虚拟dom都是用javascript来创建的。

jsx的诞生就是为了实现虚拟dom的创建,采用类似html的语法也是为了人们更容易接受和理解。可以想象当你编写一段jsx代码,并且render到浏览器,中间要经过转换为javascript对象,再转换为真实dom的多个过程,当然这些转换对我们是透明的。

下边介绍一下jsx的一些具体特点:

jsx看起来像xml/html的javascript语法扩展,对于jsx的编写本人认为完全可以按照html的使用方式来写,只是要注意jsx的特别之处,也就是重点掌握这些特点,很快就能完全了解jsx的全貌。

  • 标签类型
    html类型标签:以小写字母打头的闭合标签;如:


    react组件标签:以大写字母打头的闭合标签;如:

  • 标签属性
    属性定义:

        //有属性值
        
    //无属性值
    等价于

    属性冲突:
    一些标识符像 class 和 for 不建议作为 XML 属性名。作为替代,React DOM 使用 className 和 htmlFor 来做对应的属性。
    属性表达式:
    使用 JavaScript 表达式作为属性值,只需把这个表达式用一对大括号 ({}) 包起来,不要用引号 ("")。

    var person = ;
    

    Boolean 属性:
    省略属性的值,jsx会认为属性的值为true,其他情况的值必须用表达式方式,再html中有些表单元素,含有属性如disabled, required, checked 和 readOnly

    // 在JSX中,对于禁用按钮这二者是相同的。
    ;
    ;
    
    // 在JSX中,对于不禁用按钮这二者是相同的。
    ;
    ;
    

    自定义属性:
    html本身不存在的属性,react会自动过滤掉,只用data- 前缀的属性保留下来。
    如:

    style属性:

    //形式一
    
    Hello World.
    //形式二 var style = { color: '#ff0000', fontSize: '14px' }; var node =
    HelloWorld.
    ;

    样式的属性名写法采用驼峰式,例如“background-color”变为“backgroundColor”, “font-size”变为“fontSize”,这和标准的JavaScript操作DOM样式的API是一致的。

    使用事件:

     //组件内部事件的调用方式,建议绑定bind()的工作放在构造函数里边,提升效率
    
     //再redux中定义的action事件的绑定案例,后期会说明
    
    

    上边代码就是jsx绑定事件的形式,比较直观的展现事件和节点的关系。
    绑定原理:
    react不会真正的绑定事件到每个具体元素上,而采用事件代理的模式,再根节点document上为每种事件添加唯一的listener,然后通过事件的target找到真实的出发元素;这样从出发元素到顶层节点之间的所有节点如果有绑定这个事件,react都会触发对应的事件处理函数,这就是react模拟事件系统。

    基于这种系统,用户不用关心什么时机去移除事件绑定,再真实dom节点移除时会自动解除对应事件的绑定。

  • 子节点表达式:

// 输入 (JSX):
var content = {window.isLoggedIn ? 
  • 注释:
    注释方式和javascript类似,只是需要注意的,再注释子节点块的时候,需要使用{} 包含注释部分
    
  • html实体
// 错误: 会显示 “First · Second”
{'First · Second'}
//一下几种形式都可解决上边的错误: //直接用 Unicode 字符。这时要确保文件是 UTF-8 编码且网页也指定为 UTF-8 编码
{'First · Second'}
//安全的做法是先找到 实体的 Unicode 编号,然后在 JavaScript 字符串里使用
{'First \u00b7 Second'}
{'First ' + String.fromCharCode(183) + ' Second'}
//在数组里混合使用字符串和 JSX 元素
{['First ', ·, ' Second']}
//直接插入原始HTML

组件

React将用户界面看做简单的状态机器。当组件处于某个状态时,那么就输出这个状态对应的界面。通过这种方式,就很容易去保证界面的一致性。

在React中,你简单的去更新某个组件的状态,然后输出基于新状态的整个界面。React负责以最高效的方式去比较两个界面并更新DOM树。

这种组件模型简化了我们思考的方式:对组件的管理就是对状态的管理。

组件就像是一个函数,唯一交互窗口props是参数,state更像内部变量,render方法对应return返回客户想要的内容。
组件形式
考虑到实用性和未来的趋势,下边列出组件的俩种写法和他们的用途:

  • class方式:class是es6的新特性,可以想写类一样写组件,里边可以有内部state,周期函数等
  • function方式:对于那种使用一个render()方法的组件,可以用function的方式来简化代码。

组件状态-state
除了props之外,组件还有一个很重要的概念:state。组件规范中定义了setState方法,每次调用时都会更新组件的状态,触发render方法。需要注意,render方法是被异步调用的,这可以保证同步的多个setState方法只会触发一次render,有利于提高性能。和props不同,state是组件的内部状态,除了初始化时可能由props来决定,之后就完全由组件自身去维护。

组件属性-props
当给予的参数一定时,那么输出也是一定的,而React组件通过唯一的props接口避免了逻辑复杂性,让开发测试都更加容易。
React强烈不推荐去修改自身的props,因为这会破坏UI和Model的一致性,props只能够由使用者来决定。

context

生命周期

reactjs + redux系列之基础概念_第8张图片
0702001.png

componentDidMount: 在组件第一次render之后调用,这时组件对应的DOM节点已被加入到浏览器。在这个方法里可以去实现一些初始化逻辑。

componentWillUnmount: 在DOM节点移除之后被调用,这里可以做一些相关的清理工作。

shouldComponentUpdate: 这是一个和性能非常相关的方法,在每一次render方法之前被调用。它提供了一个机会让你决定是否要对组件进行实际的render。例如:

shouldComponentUpdate(nextProps, nextState) {
return nextProps.id !== this.props.id;
}
当此函数返回false时,组件就不会调用render方法从而避免了虚拟DOM的创建和内存中的Diff比较,从而有助于提高性能。当返回true时,则会进行正常的render的逻辑。

组件是React的核心,虽然功能很强大,但是其API和概念却十分简单,以至于你只要实现一个render方法就可以创建一个组件。这大大降低了React学习门槛。

redux概念

action

reducer

store

中间件

异步实现

你可能感兴趣的:(reactjs + redux系列之基础概念)