1. React概览

最初听到React而还未深入了解它时,大多数人可能和我的想法一样:难道又是一个新的MVC/MVVM前端framework?深入了解后发现不是这么一回事,React关注的东西很单纯,就是view,并且它也确实解决了前端目前的一些问题,比如view代码的复用,封装组件。应该说React提出了一些新的东西,让前端开发人员有机会重新审视view层的开发策略。

先来快速看一个React的简单例子:

HelloMessage组件:

)

使用HelloMessage组件:

当然别忘了还有组件要挂载到的节点:

效果:

这个例子很好理解,在HelloMessage.react.js中,我们用React创建了一个名叫HelloMessage的UI组件,它的输出是

 <div>Hello, {this.props.name}!</div>

this.props.name就是在使用HelloMessage组件时传入的name属性:

 <HelloMessage name="Tom"/>

于是会显示 "Hello, Tom!" 就很好理解了,很简单吧?

要跑起这个例子我们还需要一些引入一些库和配置一些自动化构建的工作流,这部分比较枯燥,放在最后给出,想马上动手试一试的童鞋可以先跳转到[环境搭建]一节中。

React 的核心思想是:封装组件

简单来说就是,各个组件维护自己的状态(state)和UI,当状态变更时,自动重新渲染整个组件。这种做法的一个好处就在于我们可以得到一个个UI组件,并且它们的状态是自管理的。比如当用户在某个UI组件上触发一个事件时,我们不必编写查找DOM的代码去找到要操作的DOM元素,再施加操作;取而代之的是我们在编写组件时,就已经写好组件能响应什么事件,要如何响应事件。没有没繁琐的DOM查找,代码也变得清晰很多。

React基本上由以下部分组成:

1. 组件
2. JSX
3. Virtual DOM
4. Data Flow

组件

组件是React的核心,刚才的HelloMessage就是一个组件,组件包含两个核心概念,props和state。props是组件的配置属性,是不会变化的,就像刚才我们看到的示例,在声明组件时我们传入了name="Tom",就是指定了HelloMessage这个组件的name属性值为"Tom",可以定义多个不同的组件属性。state是组件的状态,是会变化的,当state变化时组件会自动重新渲染自己。有一篇讨论React的[文章](http://www.infoq.com/cn/articles/react-art-of-simplity/)中谈到组件,以下引用这篇文章的这段话:

所谓组件,就是状态机器

React将用户界面看做简单的状态机器。当组件处于某个状态时,那么就输出这个状态对应的界面。通过这种方式,就很容易去保证界面的一致性。
在React中,你简单的去更新某个组件的状态,然后输出基于新状态的整个界面。React负责以最高效的方式去比较两个界面并更新DOM树。

JSX

可能有人会发现使用React时JS代码的写法有点奇怪,直接把HTML嵌入在JS中,当然JS引擎是无法解释这种语法的,必须由我们把JSX代码编译输出后,才是JS引擎可读的代码。JSX是一种把HTML封装成组件的有效手段,它把组件的数据和UI融合在一起,使组件化成为可能。这里可能有童鞋会表示疑惑,我们在做界面开发的时候不是经常讲要"表现和逻辑分离"吗,为什么React把表现和逻辑整合在一起就没问题呢?好吧,这是个问题,我们放在后面再来讨论。

Virtual DOM

当组件state发生变化的时候,React会调用组件的render方法自动重新渲染组件UI。可以预想到的一种情况是,如果某个组件很大,其中可能还包含了很多其他组件,那一次重新渲染代价将会很大(因为要遍历这个组件的整个DOM结构进行渲染),不过React对此做了优化,React在内部实现了一个Virtual DOM,组件的DOM结构映射到这个Virtual DOM上,React在这个Virtual DOM上实现了一个diff算法,简单来说就是React会使用这个diff算法来判断某个组件是否需要更新,只更新有变化的组件。这个更新会先发生在Virtual DOM上,Virtual DOM是内存中的一个数据结构,因此更新起来很快,最后再真正地去更新真实的DOM。

Data Flow

React推崇一种叫做"单向数据绑定"的模式, 结合Flux这种应用架构构建客户端web应用。

2. JSX

传统的MVC是将模板写成模板文件独立放在某个地方,在需要时去引用这个模板,这样做确实是把表现成单独分离了,但是这又带来一些新的问题:我们要用什么姿势引用这些模板,或者说我要怎么把数据注入模板,模板存放在哪里等问题。也就是说这个模板和代码逻辑看似是分离的(物理上的分离),其实还是耦合在一起的(逻辑上是耦合的)。为了实现组件化,React引入了JSX的概念。

React实现了组件模板和组件逻辑关联,所以才有了JSX这种语法,把HTML模板直接嵌入到JS中,编译输出后可用。可以认为JSX是一种"中间层",或者说"胶水层",它把HTML模板和JS逻辑代码粘合在一起。

JSX是可选的

React会解析JSX代码来生成JS引擎可读的代码,因此最后的输出代码都是JS引擎可读的,也就是我们平常用的JS代码,因此如果有必要可以无视JSX,直接用React提供的DOM方法来构建HTML模板,比如下面这行代码是用JSX写:

 <a href="http://facebook.github.io/react/">Hello!</a>

也可以使用React.createElement来构建DOM树,第一个参数是标签名,第二个参数是属性对象,第三个参数是子元素:

 React.createElement('a', {href: 'http://facebook.github.io/react/'}, 'Hello!')

利用JSX来写HTML模板,可以用原生的HTML标签,也可以像使用原生标签那样引用React组件。React约定通过首字母大小写来区分这两者。原生的HTML标签和平时一样,都是小写的,React组件首字母需要大写。使用HTML标签:

 var render = require('react-dom').render;
var myDivElement = <div className="foo" />;
render(myDivElement, document.body);

使用React组件:

 var render = require('react-dom').render;
var MyComponent = require('./MyComponet');
var myElement = <MyComponent someProperty={true} />;
render(myElement, document.body);

使用JavaScript表达式

可以在属性值和子组件中使用JavaScript表达式,使用{}包裹表达式。稍微改造一下HelloMessage的例子,

在属性中使用JavaScript表达式:

 //HelloMessage.react.js
var React = require('react'); var HelloMessage = React.createClass({
render: function() {
return < div > Hello,
{
this.props.sex === 'm' ? 'Mr.': 'Mrs.'
} {
this.props.fname
} ! </div>;
}
}); module.exports = HelloMessage;/
 //使用HelloMessage
var React = require('react');
var render = require('react-dom').render;
var HelloMessage = require('./components/HelloMessage.react.js'); var num = 1; render( < HelloMessage fname = 'Smith'sex = {
num === 0 ? 'm': 'f'
}
/>,
document.getElementById('helloMessage')
);/

输出:

Hello, Mr. Smith!

在子组件中使用JavaScript表达式:

 //HelloMessage.react.js
var React = require('react'); var HelloMessage = React.createClass({
render: function() {
return < div > Hello,
{
this.props.sex === 'm' ? 'Mr.': 'Mrs.'
} {
this.props.fname
} ! </div>;
}
}); module.exports = HelloMessage;/
 //使用HelloMessage
var React = require('react');
var render = require('react-dom').render;
var HelloMessage = require('./components/HelloMessage.react.js'); render( < HelloMessage fname = 'Smith'sex = 'm' / >, document.getElementById('helloMessage'));

输出:

Hello, Mrs. Smith!

注释

在JSX中使用注释和在JS差不多,唯一要注意的是在一个组件的子元素位置使用注释要用{}包裹起来,看下面这个可以工作的例子:

 var React = require('react');

 var HelloMessage = React.createClass({
render: function() {
return (
/*这里是注释1*/
< div
/*这里

注释2*/
> {
/*这里是注释3*/
}
Hello, {
this.props.sex === 'm' ? 'Mr.': 'Mrs.'
} {
this.props.fname
} ! </div>
);
}
}); module.exports = HelloMessage;/

注意到只有注释3需要被包裹在{}内,因为存在于一个子元素的位置。

属性扩散(ES6支持)

属性扩散使用了ES6起支持的扩展运算符,扩展运算符用三个点号表示,功能是把数组或类数组对象展开成一系列用逗号隔开的值,使用属性扩散可以满足我们懒惰的心理:

 var React = require('react');
var render = require('react-dom').render;
var HelloMessage = require('./components/HelloMessage.react.js'); var props = {
fname: 'Smith',
sex: 'm'
}; render( < HelloMessage {...props
}
/>,
document.getElementById('helloMessage')
); /

输出:

Hello, Mr. Smith!

还可以显式覆盖属性扩散的值:

 var React = require('react');
var render = require('react-dom').render;
var HelloMessage = require('./components/HelloMessage.react.js'); var props = {
fname: 'Smith',
sex: 'm'
}; render( < HelloMessage {...props
}
fname = 'Johnson'sex = 'f' / >, document.getElementById('helloMessage'));

这里后面的fname和sex会覆盖前面的值,输出:

Hello, Mrs. Johnson!

JSX和HTML的差异

1. 在JSX中,class要写成className。

2. JSX中可以自定义标签和属性。

3. JSX支持JavaScript表达式。

3. React组件

React应用是构建在组件之上的。组件的两个核心概念:

1. props
2. state

组件通过props和state生成最终的HTML,需要注意的是,*通过组件生成的HTML结构只能有一个根节点*。

props

props就是一个组件的属性,我们可以认为属性是不变的,一旦通过属性设置传入组件,就不应该去改变props,虽然对于一个JS对象我们可以改变几乎任何东西。

state

state是组件的状态,之前说到组件是一个状态机,根据state改变自己的UI。组件state一旦发生变化,组件会自动调用render重新渲染UI,这个动作会通过this.setState方法触发。

如何划分props和state

应该尽可能保持组件状态的数量越少越好,状态越少组件越容易管理。那些不会变化的数据、变化不必更新UI的数据,都可以归为props;需要变化的、变化需要更新UI的则归为state。

无状态组件

一些很简单的组件可能并不需要state,只需要props即可渲染组件。在ES6中可以用纯函数(没有副作用,无状态)来定义:

 const HelloMessage = (props) => <div> Hello, {props.name}!</div>;
render(<HelloMessage name="Smith" />, mountNode);

组件的生命周期

一些重要函数

1. getInitialState:

初始化this.state的值,只在组件装载之前调用一次。

2. getDefaultProps

只在组件创建时调用一次并缓存返回的对象(即在 React.createClass 之后就会调用)。在组件加载之后,这个方法的返回结果会保证当访问this.props的属性时,就算在JSX中没有设置相应属性,也总是能取到一个默认的值。

3. render

每个组件都必须实现的方法,用来构造组件的HTML结构。可以返回null或者false,此时ReactDOM.findDOMNode(this)会返回null。

生命周期函数:

装载组件触发

1. componentWillMount

只在组件装载之前调用一次,在render之前调用,可以在这里面调用setState改变状态,并且不会导致额外的一次render。

2. componentDidMount

只会在组件装载完成之后调用一次,在render之后调用,从这里开始可以通过ReactDOM.findDOMNode(this)获取到组件的DOM节点。

更新组件触发

这些方法不会在首次render组件的周期调用

1. componentWillReceiveProps
2. shouldComponentUpdate
3. componentWillUpdate
4. componentDidUpdate

DOM操作

1. findDOMNode

当组件加载到页面上以后,就可以用react-dom的findDOMNode方法来获得组件对用的DOM元素了,注意,findDOMNode不能用在无状态组件上。

2. refs

还有一种方法是在要引用的DOM元素上设置一个ref属性,通过this.refs.name可以引用到相应的对象。如果ref是设置在原生HTML元素上,它拿到的就是DOM元素,如果设置在自定义组件上,它拿到的就是组件实例,这时候就需要通过findDOMNode来拿到组件的DOM元素。需要注意的是,刚才提到的无状态组件没有实例,它就是个函数,所以ref属性不能设置在无状态组件上。因为无状态组件没有实例方法,不需要用ref去拿实例然后调用实例方法,如果真的想获得无状态组件的DOM元素的时候,需要用一个有状态组件封装一层,然后用this.refs.name和findDOMNode去获取DOM。

总结

1. 可以使用ref调用组件内子组件的实例方法,比如this.refs.myInput.focus();
2. refs 是访问到组件内部 DOM 节点唯一可靠的方法。
3. refs 会自动销毁对子组件的引用(当子组件删除时)

注意事项

1. 不要在render或者render之前访问refs

2. 不要滥用refs,比如只是用它来按照传统的方式操作界面UI:找到DOM->更新DOM

组件间通信

1. 父子组件间通信

父子组件间通信可以通过props属性来传递,在父组件中给子组件设置props,子组件就可以通过props访问到父组件的属性和方法。

2. 非父子组件间通信

使用全局事件Pub/Sub模式,在componentDidMount里面订阅事件,在componentWillUnmount里面取消订阅,当收到事件触发的时候调用setState更新UI。

react+flux编程实践(一) 基础篇的更多相关文章

  1. Shell高级编程学习笔记(基础篇)

    目录 1.shell脚本的执行方法  2.shell的变量类型  3.shell特殊变量 4.变量子串的常用操作  5.批量修改文件名实践   6.变量替换 7.在shell中计算字符串长度的方法  ...

  2. 【Spark 深入学习 07】RDD编程之旅基础篇03-键值对RDD

    --------------------- 本节内容: · 键值对RDD出现背景 · 键值对RDD转化操作实例 · 键值对RDD行动操作实例 · 键值对RDD数据分区 · 参考资料 --------- ...

  3. 【spark 深入学习 05】RDD编程之旅基础篇-01

    ---------------- 本节内容 1.RDD的工作流程 2.WordCount解说  · shell版本WordCount  · java版本WordCount -------------- ...

  4. 【spark 深入学习 06】RDD编程之旅基础篇02-Spaek shell

    --------------------- 本节内容: · Spark转换 RDD操作实例 · Spark行动 RDD操作实例 · 参考资料 --------------------- 关于学习编程方 ...

  5. RxJava+RxAndroid+MVP入坑实践(基础篇)

    转载请注明出处:http://www.blog.csdn.net/zhyxuexijava/article/details/51597230.com 前段时间看了MVP架构和RxJava,最近也在重构 ...

  6. socket和多线程编程资料汇集-基础篇

    0 基础 CS结构的分析,server端和client的选取. 1 查看端口是否链接 netstat -an|grep portid 2 root用户抓包 tcpdump port -w fn.cap ...

  7. Shell编程-条件测试 | 基础篇

    什么是Shell Shell是一个命令解释器,它会解释并执行命令行提示符下输入的命令.除此之外,Shell还有另一个功能,如果要执行多条命令,它可以将这组命令存放在一个文件中,然后可以像执行Linux ...

  8. Python高级网络编程系列之基础篇

    一.Socket简介 1.不同电脑上的进程如何通信? 进程间通信的首要问题是如何找到目标进程,也就是操作系统是如何唯一标识一个进程的! 在一台电脑上是只通过进程号PID,但在网络中是行不通的,因为每台 ...

  9. Java编程学习笔记(基础篇)

    一.Java中的数据类型 1.基本数据类型:四类 八种 byte(1) boolean(1) short(2) char(2) int(4) float(4) long(8) double(8) 2. ...

随机推荐

  1. js通过Date获取日期

    获取当前系统时间 var myDate = new Date();//获取系统当前时间 获取特定格式日期 myDate.getYear(); //获取当前年份(2位) myDate.getFullYe ...

  2. ArcGisEngine图层操作(随笔,不全)

    1.加载图层: 1.1 object.AddLayer(Layer[,toindex=0]) Layer表示ILayer对象,必选,toIndex参数表示图层索引(长整型),没需求可以忽略. 1.2 ...

  3. Luogu 1962 斐波那契数列(矩阵,递推)

    Luogu 1962 斐波那契数列(矩阵,递推) Description 大家都知道,斐波那契数列是满足如下性质的一个数列: f(1) = 1 f(2) = 1 f(n) = f(n-1) + f(n ...

  4. Linux 学习记录 四(Bash 和 Shell scirpt)

    一.什么是 Shell? 狭义的shell指的是指令列方面的软件,包括基本的Linux操作窗口Bash等,广义的shell则包括 图形接口的软件,因为图形接口其实也可以操作各种驱动程序来呼叫核心进行工 ...

  5. nyoj_239:月老的难题@_@(二分图匹配基础题)

    题目链接 放假回家不知道多少人被父母催着去相亲啊hhhhhhhhhhhhhh @_@ 参考:二分图的最大匹配.完美匹配和匈牙利算法 #include<bits/stdc++.h> usin ...

  6. 前端性能优化--图片懒加载(lazyload image)

    话说前头: 上次写了一篇webpack的学习心得,webpack能做到提升前端的性能,其模块打包最终生成一个或少量的文件能够减少对服务端的请求.除此之外,本次的图片懒加载(当然不仅限于图片,还可以有视 ...

  7. 白话ASP.NET MVC之一:Url 路由

    好久没有写关于ASP.NET MVC的东西了,虽然<ASP.NET MVC4框架揭秘>已经完完整整的看完一遍,但是感觉和一锅粥差不多,没什么可写的,因为我自己不理解,也就写不出来.现在开始 ...

  8. 【原创】09. easyui-tabs 配合 iframe 使用,请求两次等问题

    描述 需要把已经做好的几个设备管理页面.转为子菜单管理:直接使用 easyui-tabs 配合 iframe 是最省时省力的. 存在问题 当点击 "设备管理" 会出现子页面多次加载 ...

  9. C#字符串格式化(摘抄的,留下来用用)

    1.格式化货币(跟系统的环境有关,中文系统默认格式化人民币,英文系统格式化美元) string.Format("{0:C}",0.2) 结果为:¥0.20 (英文操作系统结果:$0 ...

  10. OC和JS的交互

    1.引入类拓展UIWebView+TS_JavaScriptContext, 这个类拓展是能在JSCotext出现的时候就可以拿到. 因为一般情况下JSCotext在webViewDidFinishL ...