React学习笔记

React 是 Facebook 开源的一个用于构建用户界面的 Javascript 库,已经 应用于 Facebook 及旗下 Instagram。

和庞大的 AngularJS 不同,React 专注于 MVC 架构中的 V,即视图。 这使得 React 很容易和开发者已有的开发栈进行融合。

React 顺应了 Web 开发组件化的趋势。应用 React 时,应该从 UI 出发抽象出不同的组件,然后像搭积木一样把它们拼装起来。

React简介

这个项目本身也越滚越大,从最早的 UI 引擎变成了一整套前后端通吃的 Web App 解决方案。衍生的 React Native 项目,目标更是宏伟,希望用写 Web App 的方式去写 Native App。如果能够实现,整个互联网行业都会被颠覆,因为同一组人只需要写一次 UI ,就能同时运行在服务器、浏览器和手机。

官网地址

英文网:http://facebook.github.io/react/docs/getting-started.html

中文网:http://reactjs.cn/react/docs/getting-started.html

在线编辑工具 JSFiddle

在线编辑工具,可以方便快速学习 react 基本语法

为什么使用 React

我们创造 React 是为了解决一个问题:构建随着时间数据不断变化的大规模应用程序。为了达到这个目标,React 采用下面两个主要的思想。

1、简单

仅仅只要表达出应用程序在任一个时间点应该长的样子,然后当底层的数据变了,React 会自动处理所有用户界面的更新。

2、声明式 (Declarative)

数据变化后,React 概念上与点击“刷新”按钮类似,但仅会更新变化的部分。

3、构建可组合的组件

React 都是关于构建可复用的组件。事实上,通过 React 唯一要做的事情就是构建组件。得益于其良好的封装性,组件使代码复用、测试和关注分离(separation of concerns)更加简单。

更多原因 http://facebook.github.io/react/blog/2013/06/05/why-react.html

实战案例

http://info.smartstudy.com/
http://www.kongkonghu.com/choice
https://github.com/webpack/react-starter

入门视频

https://www.youtube.com/watch?v=7eLqKgp0eeY
https://www.youtube.com/watch?v=fZKaq623y38&list=PLQDnxXqV213JJFtDaG0aE9vqvp6Wm7nBg
https://www.youtube.com/watch?v=QQK5hpUuOuA&list=PLUAEXpf1UDMkzPOiNJBrlqsUryn7n2cnK

参考资料

https://github.com/dingyiming/learn-Js-react/issues/1

React的四个概念简单介绍

React主要有四个主要概念构成:

Virtual DOM
React 组件
JSX语法
Data Flow(单向数据流)

Virtual DOM

虚拟DOM是React的基石

之所以引入虚拟 DOM,一方面是性能的考虑。Web 应用和网站不同,一个 Web 应用 中通常会在单页内有大量的 DOM 操作,而这些DOM操作很慢。

在 React 中,应用程序在虚拟 DOM 上操作,这让 React 有了优化的机会。简单说,React 在每次需要渲染时,会先比较当前 DOM 内容和待渲染内容的差异,然后再决定如何最优地更新 DOM。这个过程被称为 reconciliation。

除了性能的考虑,React 引入虚拟 DOM 更重要的意义是提供了一种一致的开发方式来开发服务端应用、Web 应用和手机端应用:

虚拟DOM服务端应用、Web应用和手机端应用

因为有了虚拟 DOM 这一层,所以通过配备不同的渲染器,就可以将虚拟 DOM 的内容渲染到不同的平台。而应用开发者,使用 JavaScript 就可以通吃各个平台了。

Virtual DOM速度快的说明

在 Web 开发中,我们总需要将变化的数据实时反应到 UI 上,这时就需要对 DOM 进行操作。而复杂或频繁的 DOM 操作通常是性能瓶颈产生的原因(如何进行高性能的复杂 DOM 操作通常是衡量一个前端开发人员技能的重要指标)。

React 为此引入了虚拟 DOM(Virtual DOM)的机制:在浏览器端用 Javascript 实现了一套 DOM API。基于 React 进行开发时所有的 DOM 构造都是通过虚拟 DOM 进行,每当数据变化时,React 都会重新构建整个 DOM 树,然后 React 将当前整个 DOM 树和上一次的 DOM 树进行对比,得到 DOM 结构的区别。而且 React 能够批处理虚拟 DOM 的刷新,在一个事件循环(Event Loop)内的两次数据变化会被合并。例如连续的先将节点内容从 A 变成 B,然后又从 B 变成 A,React 会认为 UI 不发生任何变化,而如果通过手动控制,这种逻辑通常是极其复杂的。

尽管每一次都需要构造完整的虚拟 DOM 树,但是因为虚拟 DOM 是内存数据,性能是极高的。而对实际 DOM 进行操作的仅仅是 Diff 部分,因而能达到提高性能的目的。这样,在保证性能的同时,开发者将不再需要关注某个数据的变化如何更新到一个或多个具体的 DOM 元素,而只需要 关心在任意一个数据状态下,整个界面是如何 Render 的。

http://blog.csdn.net/yczz/article/details/49585313

React组件

组件化概念

虚拟 DOM(virtual-dom) 不仅带来了简单的 UI 开发逻辑,同时也带来了组件化开发的思想。
所谓组件,即封装起来的具有独立功能的UI部件。

React 推荐以组件的方式去重新思考 UI 构成,将 UI 上每一个功能相对独立的模块定义成组件,然后将小的组件通过组合或者嵌套的方式构成大的组件,最终完成整体UI的构建。
例如,Facebook 的 instagram.com 整站都采用了 React 来开发,整个页面就是一个大的组件,其中包含了嵌套的大量其它组件。

如果说 MVC 的思想是做到视图-数据-控制器的分离,那么组件化的思考方式则是带来了 UI 功能模块之间的分离。

通过一个典型的 Blog 评论界面来看 MVC 和组件化开发思路的区别:

对于 MVC 开发模式来说,开发者将三者定义成不同的类,实现了表现,数据,控制的分离。开发者更多的是从技术的角度来对UI进行拆分,实现松耦合。

对于 React 而言,则完全是一个新的思路,开发者从功能的角度出发,将 UI 分成不同的组件,每个组件都独立封装。在 React 中,按照界面模块自然划分的方式来组织和编写代码,

对于评论界面而言,整个 UI 是一个通过小组件构成的大组件,每个组件只关心自己部分的逻辑,彼此独立。

组件的特点

组件化开发特性

React认为一个组件应该具有如下特征:

(1)、可组合(Composeable):一个组件易于和其它组件一起使用,或者嵌套在另一个组件内部。如果一个组件内部创建了另一个组件,那么说父组件拥有(own)它创建的子组件,通过这个特性,一个复杂的UI可以拆分成多个简单的UI组件;
(2)、可重用(Reusable):每个组件都是具有独立功能的,它可以被使用在多个UI场景;
(3)、可维护(Maintainable):每个小的组件仅仅包含自身的逻辑,更容易被理解和维护;
(4)、可测试(Testable):因为每个组件都是独立的,那么对于各个组件分别测试显然要比对于整个UI进行测试容易的多。

组件定义

在 React 中定义一个组件也是相当的容易,组件就是一个实现预定义接口的 JavaScript 类:

1、组件渲染

ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。

1
2
3
4
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);

而这个方法, 必须而且只能返回一个有效的 React 元素。这意味着,如果你的组件是由多个元素构成的,那么你必须在外边包一个顶层元素,然后返回这个顶层元素。比如创建一个布局组件:

1
2
3
4
5
6
7
8
render:function(){
return React.createElement(
"div",null,
React.createElement("div",null,"header"),
React.createElement("div",null,"content"),
React.createElement("div",null,"footer")
);
}

2、ES5方式定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
"use strict";
var HelloMessage = React.createClass({
displayName: "HelloMessage",
render: function render() {
return React.createElement(
"div",
null,
"Hello ",
this.props.name
);
}
});
ReactDOM.render(React.createElement(HelloMessage, { name: "John" }), mountNode);

3、JSX 中定义组件

1
2
3
4
5
6
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="John" />, mountNode);

4、ES6中定义组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import './Hello.css';
import './Hello.scss';
import React, {Component} from 'react';
// 内联样式
let style = {
backgroundColor:'blue'
}
export default class Hello extends Component {
constructor(props) {
super(props);
this.state = { count: 'es6'};
}
render() {
return (
<div>
<h1 style={style}>Hello world{this.state.count}</h1>
<br/>
<image/>
</div>
)
}
}

5、注意事项

React 组件名称的首字母应当大写,关于大小写的差异你会在后面发现。

div 元素的样式类是用 className 而不是 class 声明的,这是因为 class 是 JavaScript 的保留字,渲染后,真实的 DOM 还会是:<div class="ez-led">Hello, React!</div>

JSX语法

什么是 JSX

在用 React 写组件的时候,通常会用到 JSX 语法,像是在 Javascript 代码里直接写起了 XML 标签,每一个 XML 标签都会被 JSX 转换工具转换成纯 Javascript 代码,直接使用纯 Javascript 代码写也是可以的,只是利用 JSX,组件的结构和组件之间的关系看上去更加清晰

JSX 语法使用

HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写。

1
2
3
4
5
6
7
8
9
10
11
var names = ['Alice', 'Emily', 'Kate'];
ReactDOM.render(
<div>
{
names.map(function (name) {
return <div> Hello, {name}! </div>
})
}
</div>,
document.getElementById('example')
);

上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。

JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员

1
2
3
4
5
6
7
8
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);

上面代码的 arr 变量是一个数组,结果 JSX 会把它的所有成员,添加到模板,运行结果如下:

运行结果

Data Flow(单向数据流)

传统的 MVC

传统的MVC

到了 Flux 当中, 除了名字改变了, 重要的是大量的 Model 归到了 Store, View 也统一了,从而得到了所谓单向的数据流, 就是 Model 和 View 之间关系非常清晰了。这样需要人为管理的状态就一下少了很多, 结果体现在开发应用的效率当中。

Flux

1、学习地址:https://hulufei.gitbooks.io/react-tutorial/content/flux.html

2、React 标榜自己是 MVC 里面 V 的部分,那么 Flux 就相当于添加 M 和 C 的部分,Flux 是 Facebook 使用的一套前端应用的架构模式。

3、一个 Flux 应用主要包含四个部分:

(1)、dispatcher 处理动作分发,维护 Store 之间的依赖关系

(2)、stores 数据和逻辑部分

(3)、views React 组件,这一层可以看作 controller-views,作为视图同时响应用户交互

(4)、actions 提供给 dispatcher 传递数据给 store

单向数据流

Flux 的核心“单向数据流“怎么运作的:

Action -> Dispatcher -> Store -> View

更多时候 View 会通过用户交互触发 Action,所以一个简单完整的数据流类似这样:

一个简单完整的数据流

整个流程如下:

首先要有 action,通过定义一些 action creator 方法根据需要创建 Action 提供给 dispatcher
View 层通过用户交互(比如 onClick)会触发 Action
Dispatcher 会分发触发的 Action 给所有注册的 Store 的回调函数
Store 回调函数根据接收的 Action 更新自身数据之后会触发一个 change 事件通知 View 数据更改了
View 会监听这个 change 事件,拿到对应的新数据并调用 setState 更新组件 UI

所有的状态都由 Store 来维护,通过 Action 传递数据,构成了如上所述的单向数据流循环,所以应用中的各部分分工就相当明确,高度解耦了。这种单向数据流使得整个系统都是透明可预测的。

Redux

Redux 官方中文文档:http://camsong.github.io/redux-in-chinese/index.

其他

Reflux:https://segmentfault.com/a/1190000002793786?utm_source=tuicool

React快速开始

创建项目文件夹

初始化npm配置文件

1
$ npm init -y

依赖环境

在项目根目录下打开命令窗口下载 react 和 react-dom 依赖

1
$ npm install react react-dom --save

创建目录结构

Hello World

英文官网的:http://facebook.github.io/react/index.html

1
2
3
4
5
6
7
8
Var React = require('react');
Var ReactDOM = require('react-dom');
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
ReactDOM.render(<HelloMessage name="John" />, mountNode);

代码编译方式(语法转换)

因为现在都是使用 jsx 和 es6,所以我们需要对 js 代码进行编译。

编译转换有分为浏览器中转换和离线转换,但是基本上不会用在浏览器中引入转换 js 转换,所以只介绍离线转换。

react-tools转换

这是 react 自己提供的,而且是老版本的,因为中文官网还是老版本的 api,所以介绍的是这种方式。

首先安装依赖
用命令进行转换,有兴趣的大家自己看一下jsx -h

1
2
$ npm install -g react-tools
jsx --watch src/ build/

参考地址:http://reactjs.cn/react/docs/getting-started.html

babel转换

英文官网的文档比较新,已经推荐使用 babel 来进行转换

1、下载依赖

安装babel、babel转换jsx的包、babel转化ES6的包

1
2
3
npm install --global babel-cli
npm install babel-preset-react -dev-save
npm install babel-preset-es2015 -dev-save

注意:加了 -dev 之后,运行 npm install 不会下载开发依赖,需要运行 npm install –dev

2、运行命令进行编译

1
babel --presets react src --watch --out-dir build

3、将编译之后的js文件在index.html文件中引入

Gulp-react

https://github.com/sindresorhus/gulp-react

Webpack

请查看 webpack 的文档

开发工具

开发工具

主要知识

视图相关概念 !!!

Props(属性,就是 element 上的 attrss)
State(写过 view 组件的基本都会知道,按钮有三态,Normal,Highlight,Selected,包括 extjs,jquery 里的大部分 ui 框架都是有状态的。)
Event(其实还应该算一个就是 dom 事件)

了解了上面这些,就可以写代码了,因为:

属性,解决了view的定义问题,即语义描述
状态,是view的有穷状态机,根据状态决定组件 ui 和行为
事件,是view里元素的行为

有穷状态机:http://baike.baidu.com/view/115336.htm

jsx语法详解

HTML 转义

React 会将所有要显示到 DOM 的字符串转义,防止 XSS。
所以如果 JSX 中含有转义后的实体字符比如 © (©) 最后显示到 DOM 中不会正确显示,因为 React 自动把 © 中的特殊字符转义了。
有几种解决办法:

  • 直接使用 UTF-8 字符 ©
  • 使用对应字符的 Unicode 编码
  • 使用数组组装 <div>{['cc ', <span>&copy;</span>, ' 2015']}</div>
  • 直接插入原始的 HTML

<div dangerouslySetInnerHTML=\{\{__html: 'cc &copy; 2015'\}\} />

dangerouslySetInnerHTML 参考文档: http://reactjs.cn/react/tips/dangerously-set-inner-html.html

自定义 HTML 属性

如果在 JSX 中使用的属性不存在于 HTML 的规范中,这个属性会被忽略。如果要使用自定义属性,可以用 data- 前缀。可访问性属性的前缀 aria- 也是支持的。

与dom的区别文档:http://reactjs.cn/react/docs/dom-differences.html

支持的标签和属性

如果你要使用的某些标签或属性不在这些支持列表里面就可能被 React 忽略,必须要使用的话可以提 issue,或者用前面提到的 dangerouslySetInnerHTML。

支持列表:http://reactjs.cn/react/docs/tags-and-attributes.html

1、并不是所有的 html 标签和属性都能在 jsx 语法中使用

2、基本上你能用到的标签的属性,jsx 语法都支持

3、有些特殊的属性需要注意,必须 class 属性要变为 className 属性

所有的属性都是驼峰命名的,class 属性和 for 属性分别改为 className 和 htmlFor,来符合 DOM API 规范。

属性扩散

有时候你需要给组件设置多个属性,你不想一个个写下这些属性,或者有时候你甚至不知道这些属性的名称,这时候 spread attributes 的功能就很有用了。

1
2
3
4
var props = {};
props.foo = x;
props.bar = y;
var component = <Component {...props} />;

属性也可以被覆盖,写在后面的属性值会覆盖前面的属性。

1
2
3
var props = { foo: 'default' };
var component = <Component {...props} foo={'override'} />;
console.log(component.props.foo); // 'override'

自闭合标签

如果只有一个组件,就用单闭合标签形式,如果有多个组件嵌套就用双闭合标签形式

http://reactjs.cn/react/tips/self-closing-tag.html

注释

在 JSX 里使用注释也很简单,就是沿用 JavaScript,唯一要注意的是在一个组件的子元素位置使用注释要用 {} 包起来

1
2
3
4
5
6
7
8
9
10
11
var content = (
<Nav>
{/* child comment, put {} around */}
<Person
/* multi
line
comment */
name={window.isLoggedIn ? window.name : ''} // end of line comment
/>
</Nav>
);

React 的 API

顶层 API

http://facebook.github.io/react/docs/top-level-api.html

组件 API

http://facebook.github.io/react/docs/component-api.html

组件的生命周期(特别重要)

组件的生命周期,另外的名字是状态回调,和上面讲的状态的唯一差别,上面的状态是它里面的元素,而组件的生命周期是它自己。

https://hulufei.gitbooks.io/react-tutorial/content/component-lifecycle.html

组件的生命周期分成三个状态:

Mounting:已插入真实 DOM
Updating:正在被重新渲染
Unmounting:已移出真实 DOM

处理函数

React 为每个状态都提供了两种处理函数,will函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()

此外,React 还提供两种特殊状态的处理函数。

componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用

函数调用顺序图

函数调用顺序图

从上图中我们可以看出来,组件再初始化一次之后就不会再运行上图运行中文字以上的方法,反而里面会有事件监听,从而执行 shouleComponentUpdate 事件。

代码使用

ES5写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var Hello = React.createClass({
getInitialState() {
return { liked: false };
},
render: function() {
console.log(this.state.liked);
return(
<div>
<h1 style={style}>Hello world</h1>
<br/>
<image/>
</div>
)
}
});
module.exports = Hello;

ES6写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export default class Hello extends Component {
constructor(props) {
super(props);
this.state = { count: 'es6'};
}
render() {
return (
<div>
<h1 style={style}>Hello world{this.state.count}</h1>
<br/>
<image/>
</div>
)
}
}

参考文章

http://www.cnblogs.com/CHONGCHONG2008/p/5099483.html
http://pinggod.com/2015/React-%E7%BB%84%E4%BB%B6%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F/
http://reactjs.cn/react/docs/component-specs.html

注意点

在 ES6 中用 ES5 的写法会报错.

ES5/ES6 最新写法对照表

React 的:

http://www.tuicool.com/articles/equ2my

ReactNative 的:

http://bbs.reactnative.cn/topic/15/react-react-native-%E7%9A%84es5-es6%E5%86%99%E6%B3%95%E5%AF%B9%E7%85%A7%E8%A1%A8/2

生命周期代码 ES5 和 ES6 的区别

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
//语法规定
//1、定义模块 class Life extends Component
//2、导出模块 export default
//3、引入模块 import
//4、默认属性
/*
static defaultProps = {
autoPlay: false,
maxLoops: 10,
}; // 注意这里有分号
*/
//5、默认state
/*
constructor(props){
super(props);
console.log("构造函数");
// 初始化了我们的state,这是被推荐的写法
this.state = {
props1:"初始化state"
};
}
*/
//6、定义方法
/*
(1)生命周期的方法
componentWillMount(){
console.log("componentWillMount");
}
(2)自定义的方法
click1=()=>{
console.log("点击了单击事件");
this.setState({
props1:"改变state的值"
})
console.log("点击了单击事件结束");
}
*/
// ES6语法定义的组件生命周期
import React, {Component} from 'react';
export default class Life extends Component {
// getDefaultProps,getInitialState在es6的写法中不被支持
constructor(props){
super(props);
console.log("构造函数");
// 初始化了我们的state,这是被推荐的写法
this.state = {
props1: "初始化state"
};
}
// 组件将要被渲染到真实的dom节点中
componentWillMount(){
console.log("componentWillMount");
}
// 组件已经插入到真实的dom节点中
componentDidMount(){
console.log("componentDidMount");
}
// 组件是否要被重新渲染
shouldComponentUpdate(){
console.log("shouldCompontentUpdate");
return true;
}
// 组件将要被重新渲染
componentWillUpdate(){
console.log("conpontentWillUpdate");
}
// 组件已经被重新渲染
componentDidUpdate(){
console.log("conpontentDidUpdate");
}
// 组件将要接受到新属性
componentWillReceiveProps(){
console.log("compintentWillReceiveProps");
}
click1 = () => {
console.log("点击了单击事件");
this.setState({
props1:"改变state的值"
})
console.log("点击了单击事件结束");
}
render() {
console.log("render");
return (
<div>
<h1 onClick={this.click1}>{this.state.props1}</h1>
</div>
)
}
}
// ES5定义组件的写法
import React, {Component} from 'react';
var Life = React.createClass({
// getDefaultProps,getInitialState在es6的写法中不被支持
// 初始化props属性方法
getDefaultProps(){
console.log("getDefaultProps");
},
// 初始化我们的state属性
getInitialState(){
console.log("getInitialState");
return {
props1: "初始化state的值"
}
},
// 组件将要被渲染到真实的dom节点中
componentWillMount(){
console.log("componentWillMount");
},
// 组件已经插入到真实的dom节点中
componentDidMount(){
console.log("componentDidMount");
},
// 组件是否要被重新渲染
shouldComponentUpdate(){
// 这个方法比较特殊,如果你要重写,你要在这里手动的进行一下state值是否发生改变的判断,因为已经把之前的方法覆盖了
console.log("shouldCompontentUpdate");
return true;
},
// 组件将要被重新渲染
componentWillUpdate(){
console.log("conpontentWillUpdate");
},
// 组件已经被重新渲染
componentDidUpdate(){
console.log("conpontentDidUpdate");
},
// 组件将要接受到新属性
componentWillReceiveProps(){
console.log("compintentWillReceiveProps");
},
click1(){
console.log("点击事件");
this.setState({
props1:"改变state的值"
})
console.log("2");
console.log(this.state.props1);
},
render() {
console.log("render");
return (
<div>
<h1 onClick={this.click1}>{this.state.props1}</h1>
</div>
)
}
})
module.exports = Life;

事件处理

使用

onClick这种进行驼峰命名ES5和ES6的写法不一样,在ES6中要用bind方法绑定this(具体可参照ES5和ES6写法对照表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React, { Component } from 'react';
import { render } from 'react-dom';
export default class LinkButton extends Component {
constructor(props) {
super(props);
this.state = {liked: false};
}
handleClick(e) {
this.setState({ liked: !this.state.liked });
}
render() {
const text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick.bind(this)}>
You {text} this. Click to toggle.
</p>
);
}
}

参数传递

ES6 写法:给事件处理函数传递额外参数的方式:bind(this, arg1, arg2, …)

1
2
3
4
5
6
render: function() {
return <p onClick={this.handleClick.bind(this, param1, param2, param3)}>;
},
handleClick: function(param1,param2,param3, event) {
// handle click
}

React 支持的事件列表

http://reactjs.cn/react/docs/events.html

组件事件的一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// react中的事件支持和使用
import React, {Component} from 'react';
// 直接在js中定义样式,内嵌样式
let style = {
backgroundColor: 'blue'
}
export default class ClickEvent extends Component {
// 设置默认属性和默认状态
constructor(props) {
super(props);
// 初始化state
this.state = {
liked: false
};
}
// 单击事件处理方法
handleClick(pm1,pm2,pm3,e) {
console.log(pm1);
console.log(pm2);
console.log(pm3);
console.log(e);
this.setState({ liked: !this.state.liked });
}
// 用箭头函数去定义自己的方法
handleMouseOver=(str)=>{
console.log(str);
}
render() {
const text = this.state.liked ? 'like' : 'haven\'t liked';
// return;里面是要渲染的html页面
return (
<p onMouseOver={()=>this.handleMouseOver("2016年")} onClick={this.handleClick.bind(this,12,"dfdf",function(){})}>
You {text} this. Click to toggle.
</p>
);
}
}

Dom操作

方法一:findDOMNode()方法

首先我们要了解 ReactDOM.render 组件返回的是对组件的引用也就是组件实例(对于无状态状态组件来说返回 null),注意 JSX 返回的不是组件实例,它只是一个 ReactElement 对象。

当组件加载到页面上之后(mounted),你都可以通过 react-dom 提供的 findDOMNode() 方法拿到组件对应的 DOM 元素。

1
2
3
4
5
import { findDOMNode } from 'react-dom';
// Inside Component class
componentDidMound() {
const el = findDOMNode(this);
}

findDOMNode() 不能用在无状态组件上。

方法二:refs属性

另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名称,然后通过 this.refs.name 来访问对应的 DOM 元素。

如果 ref 是设置在原生 HTML 元素上,它拿到的就是 DOM 元素,如果设置在自定义组件上,它拿到的就是组件实例,这时候就需要通过 findDOMNode 来拿到组件的 DOM 元素。

因为无状态组件没有实例,所以 ref 不能设置在无状态组件上,一般来说这没什么问题,因为无状态组件没有实例方法,不需要 ref 去拿实例调用相关的方法,但是如果想要拿无状态组件的 DOM 元素的时候,就需要用一个状态组件封装一层,然后通过 ref 和 findDOMNode 去获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import React, { Component } from 'react';
export default class MyInputFocus extends Component {
constructor(props) {
super(props);
this.state = { userInput: '' };
}
handleChange(e) {
// 1、通过ref和this.refs的配合使用可以在react中获取dom元素,dom元素上所有的属性方法都可以使用
// 2、通过this.refs获取dom元素是有使用位置限制的,必须在componentDidMount方法里面或者之后的生命周期方法中使用
// 3、react中是不推荐你用document.getElementById的方式去获取dom元素的
console.log(this.refs.theInput.value);
this.setState({ userInput: e.target.value });
}
clearAndFocusInput() {
// 第一个参数是要重新赋值的state,第二个参数一个回调函数
this.setState({ userInput: '' }, () => {
this.refs.theInput.focus();
});
}
render() {
return (
<div>
<div onClick = {this.clearAndFocusInput.bind(this)}>
Click to Focus and Reset
</div>
<input
ref = "theInput"
value = {this.state.userInput}
onChange = {this.handleChange.bind(this)}
/>
</div>
);
}
}
MyInputFocus.defaultProps = {
autoPlay:false,
maxLoops:10,
}
MyInputFocus.propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
}

注意事项

可以使用 ref 到的组件定义的任何公共方法,比如 this.refs.myTypeahead.reset()
Refs 是访问到组件内部 DOM 节点唯一可靠的方法
Refs 会自动销毁对子组件的引用(当子组件删除时)
不要在 render 或者 render 之前访问 refs
不要滥用 refs,比如只是用它来按照传统的方式操作界面 UI:找到 DOM -> 更新 DOM

和其他库配合使用

http://reactjs.cn/react/tips/use-react-with-other-libraries.html

组件的 DOM 事件监听

这篇文章是讲如何给 DOM 元素绑定 React 未提供的事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var Box = React.createClass({
getInitialState: function() {
return {windowWidth: window.innerWidth};
},
handleResize: function(e) {
this.setState({windowWidth: window.innerWidth});
},
componentDidMount: function() {
window.addEventListener('resize', this.handleResize);
},
componentWillUnmount: function() {
window.removeEventListener('resize', this.handleResize);
},
render: function() {
return <div>Current window width: {this.state.windowWidth}</div>;
}
});
React.render(<Box />, mountNode);

http://reactjs.cn/react/tips/dom-event-listeners.html

1、注意添加dom事件的位置

2、在组件退出的时候,取消监听事件

组件的 DOM 事件监听的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// react中的 dom 监听事件
import React, { Component } from 'react';
export default class ListenEvent extends Component {
constructor(props) {
super(props);
//this.state={ userInput: '' };
}
// 演示错误添加监听事件
componentWillMount() {
//window.addEventListener('resize', this.handleResize);
}
componentDidMount() {
// 1、在已经插入到真实的dom节点之后,注册窗体改变大小的事件监听
// 3、用ajax发起数据请求的操作也要在componentDidMount方法里面去调用
window.addEventListener('resize', this.handleResize);
}
componentWillUnmount() {
// 2、在组件将要被卸载的时候移除监听事件,防止对其他组件的影响
window.removeEventListener('resize', this.handleResize);
}
handleResize=(e)=>{
console.log(window.innerWidth);
}
render() {
return (
<div>
<div>
dom事件监听
</div>
</div>
);
}
}

数据获取

http://facebook.github.io/react/tips/initial-ajax.html

用 ajax 发起数据请求的操作也要在 componentDidMount 方法里面去调用

表单

表单不同于其他 HTML 元素,因为它要响应用户的交互,显示不同的状态,所以在 React 里面会有点特殊。

状态属性

表单元素有这么几种属于状态的属性:

value,对应 <input><textarea> 所有
checked,对应类型为 checkbox 和 radio 的 <input> 所有
selected,对应 <option> 所有

在 HTML 中 <textarea> 的值可以由子节点(文本)赋值,但是在 React 中,要用 value 来设置。表单元素包含以上任意一种状态属性都支持 onChange 事件监听状态值的更改。针对这些状态属性不同的处理策略,表单元素在 React 里面有两种表现形式。

受控组件

对于设置了上面提到的对应“状态属性“值的表单元素就是受控表单组件,比如:

1
2
3
render: function() {
return <input type="text" value="hello"/>;
}

一个受控的表单组件,它所有状态属性更改涉及 UI 的变更都由 React 来控制(状态属性绑定 UI)。比如上面代码里的 <input> 输入框,用户输入内容,用户输入的内容不会显示(输入框总是显示状态属性 value 的值 hello),所以说这是受控组件,不是原来默认的表单元素了。

如果希望输入的内容反馈到输入框,就要用 onChange 事件改变状态属性 value 的值:

1
2
3
4
5
6
7
8
9
10
getInitialState: function() {
return {value: 'hello'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function() {
var value = this.state.value;
return <input type="text" value={value} onChange={this.handleChange} />;
}

使用这种模式非常容易实现类似对用户输入的验证,或者对用户交互做额外的处理,比如截断最多输入140个字符:

1
2
3
handleChange: function(event) {
this.setState({value: event.target.value.substr(0, 140)});
}

受控组件例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
//dom操作demo
import React, { Component } from 'react';
export default class ControlForm extends Component {
state={
userInput: ''
}
constructor(props) {
super(props);
//this.state={ userInput: '' };
}
handleChange(e) {
// 1、在react中思想是每一个组件的状态都应该为组件本身所控制
// 2、受控表单组件中的value值是要和state属性绑定的,受控表单组件只能通过onChange方法去改变state的值,从而触发页面的从新渲染绑定
// 3、扩展:WebComponent <x-search>
// 方法一:用refs获取属性值
console.log(this.refs.theInput1.value);
let inputStr="";
if(e.target.value.length>=16){
inputStr=e.target.value.slice(0,15)+"...";
}else{
inputStr=e.target.value;
}
// 方法二:用事件参数获取属性值
this.setState({ userInput:inputStr});
}
clearAndFocusInput() {
// 第一个参数是要重新赋值的state,第二个参数一个回调函数
this.setState({ userInput: '' }, () => {
this.refs.theInput.focus();
});
}
render() {
return (
<div>
<div onClick={this.clearAndFocusInput.bind(this)}>
Click to Focus and Reset
</div>
{/*受控表单组件改变输入框的写法*/}
<input
ref="theInput1"
value={this.state.userInput}
onChange={this.handleChange.bind(this)}
/>
<br/>
{/*受控表单组件写死了value值,永远不会改变了*/}
<input
ref="theInput"
value='I'am mhq'
/>
<br/>
{/*非受控表单组件它里面输入框值的改变不被react控制*/}
<input
ref="theInput"
/>
</div>
);
}
}

非受控属性

和受控组件相对,如果表单元素没有设置自己的“状态属性”,或者属性值设置为 null,这时候就是非受控组件。它的表现就符合普通的表单元素,正常响应用户的操作。同样,你也可以绑定 onChange 事件处理交互。如果你想要给“状态属性”设置默认值,就要用 React 提供的特殊属性 defaultValue,对于 checked 会有 defaultChecked,

为什么要有受控组件

引入受控组件不是说它有什么好处,而是因为 React 的 UI 渲染机制,对于表单元素不得不引入这一特殊的处理方式。

在浏览器 DOM 里面是有区分 attribute 和 property 的。

attribute 是在 HTML 里指定的属性,而每个 HTML 元素在 JS 对应是一个 DOM 节点对象,这个对象拥有的属性就是 property(可以在 console 里展开一个 DOM 节点对象看一下,HTML attributes 只是对应其中的一部分属性),attribute 对应的 property 会从 attribute 拿到初始值,有些会有相同的名称,但是有些名称会不一样,比如 attribute class 对应的 property 就是 className。(详细解释:.prop,.prop() vs .attr())。回到 React 里的 <input> 输入框,当用户输入内容的时候,输入框的 value property 会改变,但是 value attribute 依然会是 HTML 上指定的值(attribute 要用 setAttribute 去更改)。

React 组件必须呈现这个组件的状态视图,这个视图 HTML 是由 render 生成,所以对于:

1
2
3
render: function() {
return <input type="text" value="hello"/>;
}

在任意时刻,这个视图总是返回一个显示 hello 的输入框。

处理 select 表单

在 HTML 中 <select> 标签指定选中项都是通过对应 <option> 的 selected 属性来做的,但是在 React 修改成统一使用 value。所以没有一个 selected 的状态属性。

1
2
3
4
5
<select value="B">
<option value="A">Apple</option>
<option value="B">Banana</option>
<option value="C">Cranberry</option>
</select>

可以通过传递一个数组指定多个选中项:<select multiple={true} value={['B', 'C']}>

参数传递的判断

http://facebook.github.io/react/docs/transferring-props.html

组合组件

使用组件的目的就是通过构建模块化的组件,相互组合组件最后组装成一个复杂的应用。

在 React 组件中要包含其他组件作为子组件,只需要把组件当作一个 DOM 元素引入就可以了。

http://reactjs.cn/react/docs/multiple-components.html

循环插入子元素

如果组件中包含通过循环插入的子元素,为了保证重新渲染 UI 的时候能够正确显示这些子元素,每个元素都需要通过一个特殊的 key 属性指定一个唯一值。为了内部 diff 的效率。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//Todolistdemo
import React, {Component} from 'react';
// 循环生成列表组件
class TodoList extends Component{
render() {
let createItem = function(item) {
return <li key={item.id}>{item.text}</li>;
};
return <ul>{this.props.items.map(createItem)}</ul>;
}
}
//var Item = React.createClass({
// render: function() {
// return <li key={item.id} onClick={this.props.deleteItem}>{item.text}</li>;
// }
//});
class TodoApp extends Component{
constructor(props){
super(props);
// 初始化了我们的state,这是被推荐的写法
this.state = {
items: [],// 存我们输入的数据
text: '' // 每次输入文本
};
}
// 输入框change事件
onChange=(e)=> {
this.setState({text: e.target.value});
}
handleSubmit=(e)=> {
e.preventDefault();
let nextItems = this.state.items.concat([{text: this.state.text, id: Date.now()}]);
let nextText = '';
this.setState({items: nextItems, text: nextText});
}
render() {
return (
<div>
<h3>TODO</h3>
<TodoList items={this.state.items}/>
<form onSubmit={this.handleSubmit}>
<input onChange={this.onChange} value={this.state.text} />
<button>{'Add #' + (this.state.items.length + 0)}</button>
</form>
</div>
);
}
}
export default TodoApp;
//moudle.exports=TodoApp;

(1)当 React 校正带有 key 的子级时,它会确保它们被重新排序(而不是破坏)或者删除(而不是重用)。 务必 把 key 添加到子级数组里组件本身上,而不是每个子级内部最外层 HTML 上。

(2)也可以传递 object 来做有 key 的子级。object 的 key 会被当作每个组件的 key。但是一定要牢记 JavaScript 并不总是保证属性的顺序会被保留。实际情况下浏览器一般会保留属性的顺序,除了 使用 32位无符号数字做为 key 的属性。数字型属性会按大小排序并且排在其它属性前面。一旦发生这种情况,React 渲染组件的顺序就是混乱。可能在 key 前面加一个字符串前缀来避免:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
render: function() {
var items = {};
this.props.results.forEach(function(result) {
// 如果 result.id 看起来是一个数字(比如短哈希),那么
// 对象字面量的顺序就得不到保证。这种情况下,需要添加前缀
// 来确保 key 是字符串。
items['result-' + result.id] = <li>{result.text}</li>;
});
return (
<ol>
{items}
</ol>
);
}

子级

组件标签里面包含的子元素会通过父元素的props.children 传递进来。

HTML 元素会作为 React 组件对象、JS 表达式结果是一个文字节点,都会存入 Parent 组件的 props.children。

props.children 通常是一个组件对象的数组,但是当只有一个子元素的时候,props.children 将是这个唯一的子元素,而不是数组了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var NotesList = React.createClass({
render: function() {
return (
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
);
}
});
ReactDOM.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.body
);

上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 this.props.children 读取。

这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是 undefined ;如果有一个子节点,数据类型是 object ;如果有多个子节点,数据类型就是 array 。所以,处理 this.props.children 的时候要小心。React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。更多的 React.Children 的方法,请参考官方文档。

propsType

http://www.reactjs.cn/react/docs/reusable-components.html

做属性校验使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// react中的属性校验
import React, { Component } from 'react';
export default class PropsCheck extends Component {
// 初始化props属性
//static defaultProps={
// autoPlay:false,
// maxLoops:10,
//};
constructor(props) {
super(props);
//this.state={ userInput: '' };
}
// 进行属性校验
static propTypes = {
autoPlay: React.PropTypes.bool.isRequired,
maxLoops: React.PropTypes.number.isRequired,
};
render() {
return (
<div>
属性校验
</div>
);
}
}
//写在外面的写法
//MyInputFocus.defaultProps={
// autoPlay:false,
// maxLoops:10,
//}
//MyInputFocus.propTypes = {
// autoPlay: React.PropTypes.bool.isRequired,
// maxLoops: React.PropTypes.number.isRequired,
//}

Context

http://facebook.github.io/react/docs/context.html

子组件使用父组件的值,通过 context 可以隔代获取值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import React, {Component} from 'react';
var Button = React.createClass({
// 在孙子里面校验祖宗里面的属性
contextTypes: {
color: React.PropTypes.string
},
render: function() {
return (
<div>
<h1>{this.context.age}</h1>
<button style={{background: this.context.color}}>
{this.props.children}
</button>
</div>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
});
var MessageList = React.createClass({
// 通过这个方法去传递属性
getChildContext: function() {
return {
color: "red",
age:12
};
},
// 传递给子孙属性的类型校验
childContextTypes: {
color: React.PropTypes.string,
age: React.PropTypes.number
},
render: function() {
var children = this.props.messages.map(function(message) {
return <Message text={message.text} />;
});
return <div>{children}</div>;
}
});
export default MessageList;

动画

http://facebook.github.io/react/docs/animation.html
http://blog.csdn.net/lihongxun945/article/details/46778723
https://zhuanlan.zhihu.com/p/20419592

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// react中的dom监听事件
import React, { Component } from 'react';
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import './animate.css'
export default class Animate extends React.Component {
constructor(props) {
super(props);
this.state = {items: ['hello', 'world', 'click', 'me']};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd() {
const newItems = this.state.items.concat([
prompt('Enter some text')
]);
this.setState({items: newItems});
}
handleRemove(i) {
let newItems = this.state.items.slice();
newItems.splice(i, 1);
this.setState({items: newItems});
}
render() {
const items = this.state.items.map((item, i) => (
<div key={item} onClick={() => this.handleRemove(i)}>
{item}
</div>
));
return (
<div>
<button onClick={this.handleAdd}>Add Item1</button>
{/*这个组件虽然叫动画,但是它只负责显示隐藏的动画*/}
{/*你想让谁有显示隐藏的动画你就用ReactCSSTransitionGroup包裹谁*/}
{/*后添加进去的元素和删除的元素才有动画效果,同时添加元素和ReactCSSTransitionGroup是没有动画效果的*/}
<ReactCSSTransitionGroup
component="div"
transitionName="example"
transitionAppear={true}
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{items}
</ReactCSSTransitionGroup>
</div>
);
}
}

获取react常用插件的网址

https://js.coach/react/react-infinite
https://react.parts/native

diff算法

http://blog.csdn.net/lihongxun945/article/details/46640503
http://reactjs.cn/react/docs/reconciliation.html
http://blog.csdn.net/yczz/article/details/49585283
http://blog.csdn.net/yczz/article/details/49886061

Web Components

http://www.oschina.net/p/polymer
http://facebook.github.io/react/docs/webcomponents.html

服务器渲染

http://zhuanlan.zhihu.com/p/20669111?from=groupmessage&isappinstalled=0

组件间通信

非父子组件间的通信

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

这种模式在复杂的系统里面可能会变得难以维护,所以看个人权衡是否将组件封装到大的组件,甚至整个页面或者应用就封装到一个组件。

一般来说,对于比较复杂的应用,推荐使用类似 Flux 这种单项数据流架构,参见 Data Flow。Flux 和 redux

数据流Flux

Github地址:https://github.com/facebook/flux

React redux react-redux react-router webpack+gulp ES6 babel
Mocha+chai node
React native Flex fetch 原生 插件

感谢您的支持!