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

中间件

异步实现

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容

  • 3. JSX JSX是对JavaScript语言的一个扩展语法, 用于生产React“元素”,建议在描述UI的时候...
    pixels阅读 2,824评论 0 24
  • 按照惯例,先给ReactJS背书 React是一个Facebook开发的UI库,于2013年5月开源,并迅速的从最...
    艾伦先生阅读 3,234评论 1 12
  • JSX 知识准备 JSX 并不是一门全新的语言,仅仅是一个语法糖,允许开发者在javascript中编写XML语言...
    艾伦先生阅读 4,504评论 4 20
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,834评论 1 18
  • 好就没上微薄,看了下感兴趣的各路男神女神最近的生活,看到他们一切安好,心情甚慰。然后就看到了酒店女生遇袭的新闻,去...
    summerlight阅读 255评论 0 0