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中,树的算法其实非常简单,那就是两棵树只会对同一层次的节点进行比较, 也就是进行逐层比较。如下图所示:


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

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

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

    renderA: <Header />
    renderB: <Content />
    => [removeNode <Header />], [insertNode <Content />]

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

   renderA: <div id="before" />
   renderB: <div id="after" />
   => [replaceAttribute id "after"]

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

   renderA: <div style={{color: 'red'}} />
   renderB: <div style={{fontWeight: 'bold'}} />
   => [removeStyle color], [addStyle font-weight 'bold']

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

  • constructor: 构造函数,组件被创建时执行;
  • componentDidMount: 当组件添加到DOM树之后执行;
  • componentWillUnmount: 当组件从DOM树中移除之后执行,在React中可以认为组件被销毁;
  • componentDidUpdate: 当组件更新时执行。

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

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算法完成更新界面。



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



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

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


即将同一层的节点位置进行调整。如果未提供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 ( <Root> <A> <B key="B" /> <C key="C" /> </A> </Root> );},
shape6: function() { return ( <Root> <A> <C key="C" /> <B key="B" /> </A> </Root> );},

那么控制台输出如下:

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类型标签:以小写字母打头的闭合标签;如:<div className="foo" />
    react组件标签:以大写字母打头的闭合标签;如:<MyComponent someProperty={true} />

  • 标签属性
    属性定义:

        //有属性值
        <div myProp="value" />
        //无属性值
        <div nav />    等价于 <div nav={true} />
    

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

    var person = <Person name={window.isLoggedIn ? window.name : ''} />;
    

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

    // 在JSX中,对于禁用按钮这二者是相同的。
    <input type="button" disabled />;
    <input type="button" disabled={true} />;
    
    // 在JSX中,对于不禁用按钮这二者是相同的。
    <input type="button" />;
    <input type="button" disabled={false} />;
    

    自定义属性:
    html本身不存在的属性,react会自动过滤掉,只用data- 前缀的属性保留下来。
    如:<div data-custom-attribute="foo" />

    style属性:

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

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

    使用事件:

     //组件内部事件的调用方式,建议绑定bind()的工作放在构造函数里边,提升效率
    <button onClick={this.checkAndSubmit.bind(this)}>Submit</button>
     //再redux中定义的action事件的绑定案例,后期会说明
    <input type='text' className='product_num' onChange={this.props.actions.handleChange.bind(this,item.id)} maxLength='5' value={item.num}  />
    

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

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

  • 子节点表达式:

// 输入 (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
  • 注释:
    注释方式和javascript类似,只是需要注意的,再注释子节点块的时候,需要使用{} 包含注释部分
    <Nav>
    { /* child comment, 用 {} 包围 */}
      <Person
      /* 多行注释 */
      name={window.isLoggedIn ? window.name : ''} // 行尾注释
      />
    </Nav>
  • html实体
// 错误: 会显示 “First · Second”
<div>{'First · Second'}</div>

//一下几种形式都可解决上边的错误:

//直接用 Unicode 字符。这时要确保文件是 UTF-8 编码且网页也指定为 UTF-8 编码
<div>{'First · Second'}</div>

//安全的做法是先找到 实体的 Unicode 编号,然后在 JavaScript 字符串里使用
<div>{'First \u00b7 Second'}</div>
<div>{'First ' + String.fromCharCode(183) + ' Second'}</div>

//在数组里混合使用字符串和 JSX 元素
<div>{['First ', <span>·</span>, ' Second']}</div>

//直接插入原始HTML
<div dangerouslySetInnerHTML={{'{{'}}__html: 'First · Second'}} />

组件

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

生命周期

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

中间件

异步实现

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,878评论 0 24
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,858评论 1 18
  • 好就没上微薄,看了下感兴趣的各路男神女神最近的生活,看到他们一切安好,心情甚慰。然后就看到了酒店女生遇袭的新闻,去...
    summerlight阅读 258评论 0 0
  • 在路灯的指引下 知鸟来到了我旧日的纱窗上 白炽灯把大地照得如同白昼 知鸟尖锐的发声把 七月滚烫的生活推向了高潮 今...
    闽洞秋沙阅读 417评论 2 4
  • seajs是啥,可以看看这篇前端模块化(CommonJs,AMD和CMD)点我点我--项目源码地址:https:/...
    linwalker阅读 45,329评论 21 54