React入门
@React入门与实战
@@React入门与实战
@@@第一章 React简介
@@@@React由来
###React由来
<p>
<img src="img/one.png" style="border-radius:50%">
<div>相信对于React大家或多或少都知道一些关于它的消息吧,确实React在这最近的<em>2-3</em>年时间里面迅速的流传开来。不管是作为web端开发的同学,还是作为App端开发的同学,都有必要要去了解、学习它。因为它已经成为一种既可以跨平台、而且又可以快速开发、性能特别棒的宠儿了。下面列出几条React的由来:</div><br />
<li>React是一个用于构建用户界面的<em>JAVASCRIPT</em>库</li>
<li>React主要用于构建UI,很多人认为React是MVC中的V(视图)</li>
<li>React起源于Facebook的内部项目,用于<em>构建Instagram的网站</em>,并于2013年5月开源</li>
<li>React拥有较高的性能,代码逻辑非常简单,越来越多的人已开始关注和使用它</li><br />
<div>相信看完上面列出来的特点,有些同学会有疑问了,比如什么是声明式设计、什么是单向响应的数据流,且先别着急,让我们先把好奇心先放放,跟着课程的进度,相信你能够一一了解到。</div><br />
**React学习需具备的知识**
<br />
<div>在开始学习React之前,您需要具备以下基础知识:</div>
<li><em>HTML5</em></li>
<li><em>CSS</em></li>
<li><em>JavaScript</em></li>
<br />
**练习**
<div>
在这里给大家留道练习题,目前市场上面,还有哪些流行的前端框架,它们与React项目有哪些区别呢?
</div>
<br />
</p>
@@@@React下载
###React下载
<p>
<div>由于React主要是提供一套JS框架,所以在使用它之前我们首先需要得到一份React的js库,目前主流的四种方法:</div>
<li>通过<em>npm</em>进行下载,这种下载方式需要你本地首先配置好npm环境,然后通过npm指令从远程仓库里面下载一份JS库</li>
<li>通过他人已经下载好的js得到</li>
<li>通过<em>CDN服务器</em>直接进行引用,这种方式我们不需要事先下载React js库,而是提供一个远程的CDN服务器的链接,当代码进行链接编译的时候,将会自动从远程下载JS库。优点是我们不需要随着JS库的升级更新本地JS库,缺点就是必须保证远程CDN服务器的稳定可用</li>
<li>4.由于React是开源的,所以我们可以通过源码自己编译得到</li>
<div>由于篇幅限制,所以这一小节我们就不详细介绍npm配置下载了,有兴趣的同学可以自行研究,相信也不会太难。以后的课程里面,我们将采取第三条方法,通过cdn引用的方法,这种方法也是正规项目里面最常采用的方法了。</div><br />
**React JS CDN**
<li><code><script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script></code></li>
<li><code><script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script></code></li>
<li><code><script src="http://static.runoob.com/assets/react/browser.min.js"></script></code></li>
<div>下面大致讲一讲上面三个JS库的各自作用,三个库都带有.min后缀,相信具有web开发经验的同学应该知道js库是经过压缩的。<em>react.min.js是React核心js库</em>,<em>react-dom.min.js则是React与dom操作相关的js库<em>,<em>browser.min.js的作用是将JSX语法转换为JavaScript语法</em>,当然这一步是很耗时间的。</div><br />
<img src="img/img14.png" width="600" style="border-radius:50%">
<br />
**练习**
<div>
通过本节学习相信你已经对React有一定的了解了,最起码能够知道它大概应该是怎样使用的。本节最后给大家留一个练习题吧,这个练习题我觉得还是很有必要试一下,因为对你以后学习其他新技术还是很有帮助的,比如<em>nodejs</em>。大家可以自己在本地电脑上面搭建一下npm环境,同时用这种方法下载一下React JS库。
</div>
</p>
@@@@特点与Hello World
###特点与Hello World
<p>
<img src="img/seven.jpg">
**React的特点**
<div>作为一款知名度这么高的前端JS框架,何以能够有如此魅力,下面我们详细列出React的几条主要特点:</div><br />
<li>声明式设计-React采用申明范式,可以轻松描述应用。</li>
<li>高效-React通过对<em>DOM的模拟</em>,最大限度地减少与DOM的交互。</li>
<li>灵活-React可以与已知的库或框架很好地配合。</li>
<li><em>JSX-JSX是javascript语法的扩展</em>,React开发不一定使用JSX,但我们推荐使用它。</li>
<li>组件-通过React构建组件,使得代码更加容易得到复用,能够很好地应用在大项目的开发中。</li>
<li>单向响应的数据流-React实现了单向响应的数据流,从而减少了重复代码,这也是它为什么比传统数据绑定更简单。</li><br />
**Hello World**
<div>在这一节里面,让我们庸俗一把,通过一个Hello World的demo来快速了解一下React的代码规范与结构。</div>
<pre>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</pre>
<div>
下面我们来大致讲一下React代码结构,从上面的代码可以看到首先需要加载三个JS库,所以我们将script标签放置在head里面。在主体的body部分我们简单的定义了一个名为"example"的div,然后通过script标记定义了一个js代码块,其中的type特别重要,这也是react所独有并且能够识别的。通过<em>type="text/babel"来告诉react的解析库</em>,下面这段代码是JSX了,请帮我解析成正规的javascript代码吧。最后在script里面,使用了<em>render函数</em>,该函数是React的最基本方法,用于将模板转为HTML语言,并插入指定的DOM节点。上面代码将一个h1标题,插入example节点。
</div><br />
<div class='cw-exe'>
同学可以写个demo,将自己的名字输出到屏幕上面。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@JSX语法
###JSX语法
<p>
<img src="img/three.png" height="300" width="600">
<div>好了,进入这一节,请大家一定要仔细阅读,同时尽可能的完成后面的练习题。因为这一节是React学习中的重中之重,你只有学好了JSX语法才能游刃有余的用好React,就像<em>java/c#</em>一样,只有了解了基本语法,才能构建上层的繁华。通过上一节的代码,其实大家已经一窥JSX语法了,只是还不够全面而已。HTML语言直接写在JavaScript语言之中,不加任何引号,这就是<a href="https://facebook.github.io/react/docs/rendering-elements.html">JSX的语法</a>,它允许HTML与JavaScript的混写。</div>
<pre>
<div id="example"></div>
<script type="text/babel">
var names = ['Alice', 'Emily', 'Kate'];
ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);
</script>
</pre>
<div>
上面代码体现了JSX的基本语法规则:<em>遇到HTML标签(以<开头)</em>,就用HTML规则解析;<em>遇到代码块(以{开头)</em>,就用JavaScript规则解析。同时JSX允许直接在模板插入JavaScript变量。如果这个变量是个数组,则会展开这个数组的所有成员,请看下面的代码片段:
<pre>
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
ReactDOM.render(
<div>{arr}</div>,
document.getElementById('example')
);
</pre>
我们发现通过JSX语法,Facebook给我们提供很灵活的js和html交互方式。相信有过java web开发经验的同学对于SSH框架一定不陌生吧,但是它里面的struts框架在js与html操作的过程显得特别的笨拙,用起来经常会显得束手束脚。
</div><br />
**其他JSX语法**
<li>
<div>
render an element into the dom
<pre>
<div id="root"></div>
const element = <h1>Hello, world</h1>;
ReactDOM.render(
element,
document.getElementById('root')
);
</pre>
</div>
</li>
<li>
<div>
updating the rendered element
<pre>
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
ReactDOM.render(
element,
document.getElementById('root')
);
}
setInterval(tick, 1000);
</pre>
</div>
</li><br />
<div class="cw-exe">
通过上面的学习,同学可以试着自己写一个根据用户输入款的内容更新下面的label的内容,即两者是同步更新的。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var names=['xiao','cai','hello'];
ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@第二章 组件简介
@@@@组件事件
###组件事件
<p>
<img src="img/eight.png" width="600">
<div>
进入本章之后,我们将开始学习React里面的精华知识,相对于第一章的内容,相对来说难一点。但是如果你有一定的语言基础,相信理解起来也不会有太大的难度。好了,让我们进去正题。首先就是为什么我们需要组件,第一章的入门篇,我们就提到过React具有:高效、灵活、组件等特点。当我们在项目里面需要多次使用同一个UI片段的时候,我们就可以考虑将其以组件的形式独立出来了。这样将可以很好地实现代码复用,便于日后的维护,同时很好的符合设计里面内聚性。
</div><br />
<div>
React允许将代码封装成<em>组件(Component)</em>,然后像插入普通HTML标签一样,在网页中插入这个组件,React.createClass方法就用于生成一个组件类。<br />
<pre>
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.name}</h1>;
}
});
ReactDOM.render(
<HelloMessage name="John" />,
document.getElementById('example')
);
</pre>
</div><br />
<div>
上面代码我们就定义了一个HelloMessage组件,但是同学可能会有几个地方觉得很陌生。首先就是this.props.name这个东西是什么意思?还有render:function这种写法是不是必须的呢?模板插入<HelloMessage />时,会自动生成HelloMessage的一个实例(下文的“组件”都指组件类的实例)。所有组件类都必须有自己的render方法,用于输出组件。
</div><br />
**注意(tip)**
<li>
组件类的<em>第一个字母必须大写</em>,否则会报错,比如HelloMessage不能写成helloMessage。
</li>
<li>
组件类只能包含一个顶层标签,否则也会报错。
</li>
<pre>
var HelloMessage = React.createClass({
render: function() {
return <h1>
Hello {this.props.name}
</h1><p>
some text
</p>;
}
});
</pre>
<div>
上面的代码就会报错,<em>因为包含两个顶层标签:h1和p</em>。
</div><br />
<div class="cw-exe">
学完这一小节,是不是有种想创建自己组件的冲动呢?同学可以试着创建一个以自己命名的组件。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var HelloMessage = React.createClass({
render: function() {
return <h1>Hello {this.props.className}</h1>;
}
});
ReactDOM.render(
<HelloMessage className="John" />,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@组件属性
###组件属性
<div>组件的用法与原生的HTML标签完全一致,可以任意加入属性,比如<HelloMessage name="cai">,就是HelloMessage组件加入一个name属性,值为cai。组件的属性可以在组件类的 <em>this.props</em> 对象上获取,比如 name 属性就可以通过 this.props.name 读取。
</div>
<br />
**注意**
<li>添加组件属性,有一个地方需要注意,就是 <em>class 属性需要写成 className</em> ,<em>for 属性需要写成 htmlFor</em> ,这是因为 class 和 for 是 JavaScript 的保留字。</li><br />
**this.props.children**
<div>
this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点:<br />
<pre>
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
);
</pre>
</div>
<div>
上面代码的 NoteList 组件有两个 span 子节点,它们都可以通过 <em>this.props.children</em> 读取,但是这里需要注意,this.props.children的值有三种情况:
<li>如果当前组件没有子节点,它就是 undefined;</li>
<li>如果有一个子节点,数据类型是 object;</li>
<li>如果有多个子节点,数据类型就是 array;</li>
所以,处理 this.props.children 的时候要小心。<br />
React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。
</div>
<br />
**PropTypes**
<div>
组件的属性可以接受任意值,字符串、对象、函数等等都可以。有时,我们需要一种机制,验证别人使用组件时,提供的参数是否符合要求。组件类的PropTypes属性,就是用来验证组件实例的属性是否符合要求:<br />
<pre>
var MyTitle = React.createClass({
propTypes: {
title: React.PropTypes.string.isRequired,
},
render: function() {
return <h1> {this.props.title} </h1>;
}
});</pre><br />
上面的Mytitle组件有一个title属性。PropTypes 告诉 React,这个 title 属性是必须的,而且它的值必须是字符串。<br />
<pre>
var data = 123;
ReactDOM.render(
<MyTitle title={data} />,
document.body
);</pre><br />
这样一来,title属性就通不过验证了。<br />
此外,<em>getDefaultProps</em> 方法可以用来设置组件属性的默认值。<br />
<pre>
var MyTitle = React.createClass({
getDefaultProps : function () {
return {
title : 'Hello World'
};
},
render: function() {
return <h1> {this.props.title} </h1>;
}
});
ReactDOM.render(
<MyTitle />,
document.body
);
</pre>
</div>
<div class='cw-exe'>
这一节的内容比较多,这里留个同学一个比较完整的练习,写一个完整的登录页面,要求有完整的验证流程(用户名6-20位字母数字,密码智能为字母,且都不能为空)。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
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.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@组件状态
###组件状态
<p>
<img src="img/six.jpg" height="220" width="600">
<div>组件免不了要与用户互动,React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI。</div>
<br />
**Demo实例**
<pre>
<body>
<div id="example"></div>
<script type="text/babel">
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
</script>
</pre>
<div>
上面代码是一个 LikeButton 组件,它的 getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过 this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用 this.render 方法,再次渲染组件。<br />
由于 <em>this.props 和 this.state </em>都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。
</div><br />
<div class='cw-exe'>
同学们可以写一个,每隔3秒刷新页面button透明度的demo。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? 'like' : 'haven\'t liked';
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@组件生命周期
###组件生命周期
<p>
<img src="img/nine.png" height="300" width="600">
<div>首先我想来解释一下,什么是生命周期。相信如果同学有一定的App端开发经验的话,那么对于生命周期这个词一定不会觉得陌生。不过不熟悉也没关系,且听笔者慢慢道来。在App开发过程中,对于每一个页面都会有一个生命周期,比如当页面在创建的时候,我们可以加载一些资源。当页面销毁的时候,我们可以清理一些大文件数据等。同理拿到React里面,它也会有对应的三个状态:
<li>Mounting:已插入<em>真实 DOM</em>;</li>
<li>Updating:正在被重新渲染;</li>
<li>Unmounting:已移出真实 DOM;</li>
</div>
<br />
**React对应的生命周期处理函数**
<div>
React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。
<li>componentWillMount():将插入(加载)页面;</li>
<li>componentDidMount():已经加载(渲染)完成页面;</li>
<li>componentWillUpdate(object nextProps, object nextState):将更新DOM对象;</li>
<li>componentDidUpdate(object prevProps, object prevState):已经更新DOM对象;</li>
<li>componentWillUnmount():将销毁DOM对象;</li>
<div>此外,React 还提供两种特殊状态的处理函数:</div>
<li>componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用;</li>
<li>shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用;</li>
</div>
<br />
**Demo实例**
<pre>
<div id="example"></div>
<script type="text/babel">
var Hello = React.createClass({
getInitialState: function () {
return {
opacity: 1.0
};
},
componentDidMount: function () {
this.timer = setInterval(function () {
var opacity = this.state.opacity;
opacity -= .05;
if (opacity < 0.1) {
opacity = 1.0;
}
this.setState({
opacity: opacity
});
}.bind(this), 100);
},
render: function () {
return (
<div style={{opacity: this.state.opacity}}>
Hello {this.props.name}
</div>
);
}
});
ReactDOM.render(
<Hello name="world"/>,
document.getElementById('example')
);
</script>
</pre>
上面代码在hello组件加载以后,通过 <em>componentDidMount 方法设置一个定时器</em>,每隔100毫秒,就重新设置组件的透明度,从而引发重新渲染。
<div class='cw-exe'>
同学可以加载一张图片,在加载之前提示“正在加载...”,然后在加载完成之后提示“加载完成...”。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var Hello = React.createClass({
getInitialState: function () {
return {
opacity: 1.0
};
},
componentDidMount: function () {
this.timer = setInterval(function () {
var opacity = this.state.opacity;
opacity -= .05;
if (opacity < 0.1) {
opacity = 1.0;
}
this.setState({
opacity: opacity
});
}.bind(this), 100);
},
render: function () {
return (
<div style={{opacity: this.state.opacity}}>
Hello {this.props.name}
</div>
);
}
});
ReactDOM.render(
<Hello name="world"/>,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@第三章 React其他功能
@@@@访问DOM
###访问DOM
<p>
<img src="img/ten.png" height="300" width="600">
<div>组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做<em>虚拟 DOM (virtual DOM)</em>。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM 上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 <a href="http://calendar.perfplanet.com/2013/diff/">DOM diff</a> ,它可以极大提高网页的性能表现。</div><br />
<div>但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 ref 属性。</div>
<br />
**Demo实例**
<pre>
<div id="example"></div>
<script type="text/babel">
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
</script>
</pre>
<div>
上面代码中,组件 MyComponent 的子节点有一个文本输入框,用于获取用户的输入。这时就必须获取真实的 DOM 节点,虚拟 DOM 是拿不到用户输入的。为了做到这一点,文本输入框必须有一个 ref 属性,然后 this.refs.[refName] 就会返回这个真实的 DOM 节点。<br />
需要注意的是,由于 <em>this.refs.[refName] 属性获取的是真实 DOM</em> ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个属性,否则会报错。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会读取 this.refs.[refName] 属性。<br />
</div>
<div class='cw-exe'>
同学可以将上面的demo改造一下,当按钮点击的时候,获取用户输入的内容。进而将上面的登录页面完善,验证用户名和密码不为空。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var MyComponent = React.createClass({
handleClick: function() {
this.refs.myTextInput.focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
ReactDOM.render(
<MyComponent />,
document.getElementById('example')
);
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@使用表单
###使用表单
<p>
<div>如果同学有了解java web开发的话,那么对于表单这个词一定不会觉得陌生。因为在jsp里面专门有个form标签用于表单的定义,然后在form标签里面有个“method”属性用于定义不同的方法提交表单。当然这样做的结果就导致表单用起来非常的麻烦,必须严格的按照form标签的方式进行提交表单。但是在React里面就将表单设计的非常灵活,可以以一种动态绑定的方式来获取表单里面的数据。用户在表单填入的内容,属于用户跟组件的互动,所以不能用 this.props 读取。</div><br />
**Demo实例**
<div>
<pre>
<div id="example"></div>
<script type="text/babel">
var Input = React.createClass({
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function () {
var value = this.state.value;
return (
<div>
<input type="text" value={value} onChange={this.handleChange} />
<p>{value}</p>
</div>
);
}
});
ReactDOM.render(<Input/>, document.getElementById('example'));
</script>
</pre>
上面代码中,文本输入框的值,不能用 this.props.value 读取,而要定义一个 onChange 事件的回调函数,通过<em>event.target.value</em> 读取用户输入的值。textarea 元素、select元素、radio元素都属于这种情况
</div>
<div class='cw-exe'>
同学可以利用表单的方式,获取一下select元素的值。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var Input = React.createClass({
getInitialState: function() {
return {value: 'Hello!'};
},
handleChange: function(event) {
this.setState({value: event.target.value});
},
render: function () {
var value = this.state.value;
return (
<div>
<input type="text" value={value} onChange={this.handleChange} />
<p>{value}</p>
</div>
);
}
});
ReactDOM.render(<Input/>, document.getElementById('example'));
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@使用上下文对象
###使用上下文对象
<p>
<div>context 是在 react @ 0.14 版本以后发布的一个高级且实验性的功能,有可能在未来做出更改。不推荐频繁使用,如果使用的话尽量保持在小范围内并且避免直接使用 context 的 API,为了以后升级框架更加容易。</div><br />
**使用 Context 的原因**
<div>
为了有时候想传递数据通过组件树,但是不想给每一层级的组件手动传递属性,那么 context 就能帮你 <em>"越级" 传递数据</em>到组件树中你想传递到的深层次组件。<br />
有时候 A组件 为了给 B组件 中的 C组件 传递一个 prop ,而需要把参数在组件中传递两次才能最终将 A组件 中的 prop 传递给 C组件 。请看下面的官方实例代码,使用原来的两层方式传递:
<pre>
var Button = React.createClass({
render: function() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div>
{this.props.text} <Button color={this.props.color}>Delete</Button>
</div>
);
}
});
var MessageList = React.createClass({
render: function() {
var color = "purple";
var children = this.props.messages.map(function(message) {
return <Message text={message.text} color={color} />;
});
return <div>{children}</div>;
}
});
</pre>
现在我们使用 context 来完成参数的传递试试。
<pre>
var Button = React.createClass({
// 必须指定context的数据类型
contextTypes: {
color: React.PropTypes.string
},
render: function() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
});
var MessageList = React.createClass({
//
childContextTypes: {
color: React.PropTypes.string
},
getChildContext: function() {
return {color: "purple"};
},
render: function() {
var children = this.props.messages.map(function(message) {
return <Message text={message.text} />;
});
return <div>{children}</div>;
}
});
</pre>
示例代码中通过添加 childContextTypes 和 getChildContext() 到 MessageList ( context 的提供者),<em>React 自动向下传递数据然后在组件树中的任意组件</em>(也就是说任意子组件,在此示例代码中也就是 Button )都能通过定义 contextTypes 访问 context 中的数据。
</div>
<div class='cw-exe'>
同学们可以再好好比较一下这两种方式传递的优缺点,同时做一下这样的demo:利用Context的方式,拿到ul列表标签里面的自列表li里面的Text值。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/babel">
var Button = React.createClass({
// 必须指定context的数据类型
contextTypes: {
color: React.PropTypes.string
},
render: function() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div>
{this.props.text} <Button>Delete</Button>
</div>
);
}
});
var MessageList = React.createClass({
//
childContextTypes: {
color: React.PropTypes.string
},
getChildContext: function() {
return {color: "purple"};
},
render: function() {
var children = this.props.messages.map(function(message) {
return <Message text={message.text} />;
});
return <div>{children}</div>;
}
});
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@动画效果
###动画效果
<p>
<img src="img/img11.gif" height="300" width="600">
<div>
React为动画提供一个ReactTransitonGroup插件组件作为一个底层的API,一个ReactCSSTransitionGroup来简单地实现基本的CSS动画和过渡。</div>
<br />
**高级API:ReactCSSTransitionGroup**
<div>
ReactCSSTransitionGroup是基于ReactTransitionGroup的,在React组件进入或者离开DOM的时候,它是一种简单地执行CSS过渡和动画的方式。这个的灵感来自于优秀的<em>ng-animate库</em>。<br />
ReactCSSTransitionGroup是ReactTransitions的接口。这是一个简单的元素,包含了所有你对其动画感兴趣的组件。这里是一个例子,例子中我们让列表项淡入淡出。
<pre>
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var TodoList = React.createClass({
getInitialState: function() {
return {items: ['hello', 'world', 'click', 'me']};
},
handleAdd: function() {
var newItems =
this.state.items.concat([prompt('Enter some text')]);
this.setState({items: newItems});
},
handleRemove: function(i) {
var newItems = this.state.items;
newItems.splice(i, 1);
this.setState({items: newItems});
},
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
{item}
</div>
);
}.bind(this));
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
<ReactCSSTransitionGroup transitionName="example">
{items}
</ReactCSSTransitionGroup>
</div>
);
}
});
</pre>
<div>在这个组件当中,当一个新的项被添加到ReactCSSTransitionGroup,它将会被添加example-enter类,然后在下一时刻被添加example-enter-active CSS类。这是一个基于<em>transitionName prop的约定</em>。</div><br />
<div>为了能够给它的子级应用过渡效果,ReactCSSTransitionGroup必须已经挂载到了DOM。下面的例子不会生效,因为ReactCSSTransitionGroup被挂载到新项,而不是新项被挂载到ReactCSSTransitionGroup里。将这个与上面的快速开始部分比较一下,看看有什么差异。
<pre>
render: function() {
var items = this.state.items.map(function(item, i) {
return (
<div key={item} onClick={this.handleRemove.bind(this, i)}>
<ReactCSSTransitionGroup transitionName="example">
{item}
</ReactCSSTransitionGroup>
</div>
);
}, this);
return (
<div>
<button onClick={this.handleAdd}>Add Item</button>
{items}
</div>
);
}
</pre>
</div>
</div>
<br />
**禁用动画**
<div>
如果你想,你可以禁用入场或者出场动画。例如,有些时候,你可能想要一个入场动画,不要出场动画,但是ReactCSSTransitionGroup会在移除DOM节点之前等待一个动画完成。你可以给ReactCSSTransitionGroup添加transitionEnter={false}或者transitionLeave={false} props来禁用这些动画。<br />
当使用ReactCSSTransitionGroup的时候,没有办法通知你在过渡效果结束或者在执行动画的时候做一些复杂的运算。如果你想要更多细粒度的控制,你可以使用底层的ReactTransitionGroup API,该API提供了你自定义过渡效果所需要的函数。
</div>
<br />
**ReactTransitionGroup生命周期**
<div>ReactTransitionGroup是动画的基础。它可以通过<em>React.addons.TransitionGroup得到</em>。当子级被添加或者从其中移除(就像上面的例子)的时候,特殊的生命周期函数就会在它们上面被调用。</div>
<li>
<pre>
componentWillEnter(callback)
在组件被添加到已有的TransitionGroup中的时候,该函数和componentDidMount()被同时调用。这将会阻塞其它动画触发,直到callback被调用。该函数不会在TransitionGroup初始化渲染的时候调用。
</pre>
</li>
<li>
<pre>
componentDidEnter()
该函数在传给componentWillEnter的callback函数被调用之后调用。
</pre>
</li>
<li>
<pre>
componentWillLeave(callback)
该函数在子级从ReactTransitionGroup中移除的时候调用。虽然子级被移除了,ReactTransitionGroup将会使它继续在DOM中,直到callback被调用。
</pre>
</li>
<li>
<pre>
componentDidLeave()
该函数在willLeave callback被调用的时候调用(与componentWillUnmount是同一时间)。
</pre>
</li>
<div class='cw-exe'>
同学们可以利用本节的动画内容,做一个简单的图片轮播demo。
</div>
</p>
<hr>
~@ $v.event.type == "scene.init"
var html = function(){/*
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react.min.js"></script>
<script src="http://static.runoob.com/assets/react/react-0.14.7/build/react-dom.min.js"></script>
<script src="http://static.runoob.com/assets/react/browser.min.js"></script>
</head>
<body>
<script type="text/babel">
var ReactCSSTransitionGroup = require('react-addons-css-transition-group');
var React = require('react');
var ReactDOM = require('react-dom');
var data = ['one','two','three'];
var Control = React.createClass({
getInitialState:function(){
return {
'items':this.props.data
}
},
addItem:function(){
var newItems = this.state.items.concat('four');
this.setState({
'items':newItems
});
},
removeItem:function(i){
var newItems = this.state.items;
newItems.splice(i,1);
this.setState({
'items':newItems
});
},
render:function(){
var $this = this;
var List = this.state.items.map(function(value,index){
return <div key={value} onClick = {$this.removeItem.bind($this,index)}> { value}</div>
});
return (
<div>
<button onClick={this.addItem}>add Item</button>
<ReactCSSTransitionGroup
transitionName='example'
transitionEnterTimeout={500}
transitionLeaveTimeout={300}>
{List}
</ReactCSSTransitionGroup>
</div>
)
}
});
ReactDOM.render(<Control data={data}/> ,document.getElementById('content'));
</script>
</body>
</html>
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@@性能优化
###性能优化
<p>
<div>当大家考虑在项目中使用 React 的时候,第一个问题往往是他们的应用的速度和响应是否能和非 React 版一样,每当状态改变的时候就重新渲染组件的整个子树,让大家怀疑这会不会对性能造成负面影响。React 用了一些黑科技来<em>减少 UI </em>更新需要的花费较大的 DOM 操作。</div>
<br />
**使用 production 版本**
<div>
如果你在你的 React app 中进行性能测试或在寻找性能问题,一定要确定你在使用 <em>minified production build</em>。开发者版本包括额外的警告信息,这对你在开发你的 app 的时候很有用,但是因为要进行额外的处理,所以它也会比较慢。
</div>
<br />
**避免更新 DOM**
<div>
React 使用虚拟 DOM,它是在浏览器中的 DOM 子树的渲染描述,这个平行的描述让 React 避免创建和操作 DOM 节点,这些远比操作一个 JavaScript 对象慢。当一个组件的 props 或 state 改变,React 会构造一个新的虚拟 DOM 和旧的进行对比来决定真实 DOM 更新的必要性,只有在它们不相等的时候,React 才会使用尽量少的改动更新 DOM。
<br />
在此之上,React 提供了生命周期函数 shouldComponentUpdate,在重新渲染机制回路(虚拟 DOM 对比和 DOM 更新)之前会被触发,赋予开发者跳过这个过程的能力。这个函数默认返回 true,让 React 执行更新。
<pre>
shouldComponentUpdate: function(nextProps, nextState) {
return true;
}
</pre>
一定要记住,React 会非常频繁的调用这个函数,所以要确保它的执行速度够快。
假如你有个带有多个对话的消息应用,如果只有一个对话发生改变,如果我们在 ChatThread 组件执行 shouldComponentUpdate,React 可以跳过其他对话的重新渲染步骤。
<pre>
shouldComponentUpdate: function(nextProps, nextState) {
// TODO: return whether or not current chat thread is
// different to former one.
}
</pre>
因此,总的说,React 通过让用户使用 <em>shouldComponentUpdate 减短重新渲染回路</em>,避免进行昂贵的更新 DOM 子树的操作,而且这些必要的更新,需要对比虚拟 DOM。
</div>
<br />
**Immutable-js 来救赎**
<div>
Immutable-js 是 Lee Byron 写的 JavaScript 集合类型的库,最近被 Facebook 开源,它通过结构共享提供不可变持久化集合类型。一起看下这些特性的含义:
<li>Immutable: 一旦创建,集合就不能再改变;</li>
<li>Persistent: 新的集合类型可以通过之前的集合创建,比如 set 产生改变的集合。创建新的集合之后源集合仍然有效;</li>
<li>Structural Sharing: 新的集合会使用尽量多的源集合的结构,减少复制来节省空间和性能友好。如果新的集合和源集合相等,一般会返回源结构;</li>
不可变让跟踪改变非常简单,每次改变都是产生新的对象,所以我们仅需要对象的引用是否改变,比如这段简单的 JavaScript 代码:
<pre>
var x = { foo: "bar" };
var y = x;
y.foo = "baz";
x === y; // true
</pre>
尽管 y 被改变,因为它和 x 引用的是同一个对象,这个对比返回 true。然而,这个代码可以使用 immutable-js 改写如下:
<pre>
var SomeRecord = Immutable.Record({ foo: null });
var x = new SomeRecord({ foo: 'bar' });
var y = x.set('foo', 'baz');
x === y; // false
</pre>
这个例子中,因为改变 x 的时候返回了新的引用,我们就可以安全的认为 x 已经改变。
<br />
脏检测可以作为另外的可行的方式追踪改变,给 setters 一个标示。这个方法的问题是,它强制你使用 setters,而且要写很多额外的代码,影响你的类。或者你可以在改变之前深拷贝对象,然后进行深对比来确定是不是发生了改变。这个方法的问题是,深拷贝和深对比都是很花性能的操作。
<br />
因此,不可变数据结构给你提供了一个高效、简洁的方式来跟踪对象的改变,而<em>跟踪改变</em>是实现 shouldComponentUpdate 的关键。所以,如果我们使用 immutable-js 提供的抽象创建 props 和 state 模型,我们就可以使用 PureRenderMixin,而且能够获得很好的性能增强。
</div>
<br />
**练习**
对比一下Immutable-js 和 Flux,看看它们之间有什么异同点?
</p>
~@ $v.event.type == "scene.init"
var html = function(){/*
*/}.tpl();
var css = function(){/*
body{background:black;color:red;}
h1{font:90px Courier;}
*/}.tpl();
var js = "";
$v.lab.setValue({html:html,css:css,js:js,active:"html",jstype:"text/typescript",jslabel:"TypeScript"});
~@ $v.event.type=="input"
$v.vtutor.pass();
@@@第四章 Redux
@@@@Redux介绍
###Redux介绍
<p>
<img src="img/img13.png" height="300" width="600">
<br />
**为什么使用Redux**
<div>
首先明确一点,Redux 是一个有用的架构,但不是非用不可。事实上,大多数情况,你可以不用它,只用 React 就够了。当满足下面这些情况的时候,你可以考虑使用Redux:
<li>用户的使用方式复杂;</li>
<li>不同身份的用户有不同的使用方式(比如普通用户和管理员);</li>
<li>多个用户之间可以协作;</li>
<li>与服务器大量交互,或者使用了WebSocket;</li>
<li>View要从多个来源获取数据;</li>
上面这些情况才是 Redux 的适用场景:多交互、多数据源。<br />
从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux:
<li>某个组件的状态,需要共享;</li>
<li>某个状态需要在任何地方都可以拿到;</li>
<li>一个组件需要改变全局状态;</li>
<li>一个组件需要改变另一个组件的状态;</li>
发生上面情况时,如果不使用 Redux 或者其他状态管理工具,不按照一定规律处理状态的读写,代码很快就会变成一团乱麻。你需要一种机制,可以在同一个地方查询状态、改变状态、传播状态的变化。<br />
总之,<em>不要把 Redux 当作万灵丹,如果你的应用没那么复杂,就没必要用它</em>。另一方面,Redux 只是 Web 架构的一种解决方案,也可以选择其他方案。
</div>
<br />
**设计思想**
<br />
<div>
<li>(1)Web 应用是一个状态机,视图与状态是一一对应的;</li>
<li>(2)所有的状态,保存在一个对象里面;</li>
</div>
<br />
**练习**
<div>有兴趣的同学,可以查阅更多的资料,深入理解Redux的设计思想。</div>
</p>
@@@@使用Redux管理视图模型
###使用Redux管理视图模型
<p>
<img src="img/four.png" height="300" width="600">
**Store**
<div>
Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。<br>
Redux 提供<em>createStore</em>这个函数,用来生成 Store。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);
</pre>
<div>
上面代码中,createStore函数接受另一个函数作为参数,返回新生成的 Store 对象。
</div>
<br />
**State**
<br />
<div>
Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。
当前时刻的 State,可以通过<em>store.getState()</em>拿到。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);
const state = store.getState();
</pre>
<div>
Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。你知道 State,就知道 View 是什么样,反之亦然。
</div>
<br />
**Action**
<div>
State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。<br />
Action 是一个对象。其中的type属性是必须的,表示 Action 的名称。其他属性可以自由设置,社区有一个规范可以参考。
</div>
<pre>
const action = {
type: 'ADD_TODO',
payload: 'Learn Redux'
};
</pre>
<div>
上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux。<br />
可以这样理解,Action 描述当前发生的事情。改变 State 的唯一办法,就是使用 Action。它会运送数据到 Store。
</div>
<br />
**store.dispatch()**
<div>
store.dispatch()是 View 发出 Action 的唯一方法。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(fn);
store.dispatch({
type: 'ADD_TODO',
payload: 'Learn Redux'
});
</pre>
上面代码中,store.dispatch接受一个 Action 对象作为参数,将它发送出去。
结合 Action Creator,这段代码可以改写如下:
<pre>
store.dispatch(addTodo('Learn Redux'));
</pre>
<br />
**store.subscribe()**
<div>
Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
</div>
<pre>
import { createStore } from 'redux';
const store = createStore(reducer);
store.subscribe(listener);
</pre>
显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。<br />
store.subscribe方法返回一个函数,调用这个函数就可以解除监听:
<pre>
let unsubscribe = store.subscribe(() =>
console.log(store.getState())
);
unsubscribe();
</pre>
<br/>
**Store 的实现**
<div>
上一节介绍了 Redux 涉及的基本概念,可以发现 Store 提供了三个方法:
<li><code>store.getState()</code></li>
<li><code>store.dispatch()</code></li>
<li><code>store.subscribe()</code></li>
</div>
<pre>
import { createStore } from 'redux';
let { subscribe, dispatch, getState } = createStore(reducer);
</pre>
createStore方法还可以接受第二个参数,表示 State 的最初状态。这通常是服务器给出的。
<pre>
let store = createStore(todoApp, window.STATE_FROM_SERVER)
</pre>
上面代码中,<em>window.STATE_FROM_SERVER</em>就是整个应用的状态初始值。注意,如果提供了这个参数,它会覆盖 Reducer 函数的默认初始值。<br />
下面是createStore方法的一个简单实现,可以了解一下 Store 是怎么生成的。
<pre>
const createStore = (reducer) => {
let state;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
}
};
dispatch({});
return { getState, dispatch, subscribe };
};
</pre>
<br />
**练习**
<div>
同学们可以利用上面讲解到Redux知识点,写一个HelloWorld,从加强理解Action、State、Store概念。
</div>
<br />
</p>
@@@@Reducer 的拆分
###Reducer 的拆分
<p>
<img src="img/five.jpg" height="300" width="600">
<br />
Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大,请看下面的例子。
<pre>
const chatReducer = (state = defaultState, action = {}) => {
const { type, payload } = action;
switch (type) {
case ADD_CHAT:
return Object.assign({}, state, {
chatLog: state.chatLog.concat(payload)
});
case CHANGE_STATUS:
return Object.assign({}, state, {
statusMessage: payload
});
case CHANGE_USERNAME:
return Object.assign({}, state, {
userName: payload
});
default: return state;
}
};
</pre>
上面代码中,三种 Action 分别改变 State 的三个属性。
<li><em>ADD_CHAT:chatLog属性</em></li>
<li><em>CHANGE_STATUS:statusMessage属性</em></li>
<li><em>CHANGE_USERNAME:userName属性</em></li>
这三个属性之间没有联系,这提示我们可以把 Reducer 函数拆分。不同的函数负责处理不同属性,最终把它们合并成一个大的 Reducer 即可。
<pre>
const chatReducer = (state = defaultState, action = {}) => {
return {
chatLog: chatLog(state.chatLog, action),
statusMessage: statusMessage(state.statusMessage, action),
userName: userName(state.userName, action)
}
};
</pre>
上面代码中,Reducer 函数被拆成了三个小函数,每一个负责生成对应的属性,这样一拆,Reducer 就易读易写多了。而且,这种拆分与 React 应用的结构相吻合:一个 React 根组件由很多子组件构成。这就是说,子组件与子 Reducer 完全可以对应,Redux 提供了一个<em>combineReducers</em>方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。
<pre>
import { combineReducers } from 'redux';
const chatReducer = combineReducers({
chatLog,
statusMessage,
userName
})
export default todoApp;
</pre>
上面的代码通过combineReducers方法将三个子 Reducer 合并成一个大的函数。
这种写法有一个前提,就是 State 的属性名必须与子 Reducer 同名。如果不同名,就要采用下面的写法。
<pre>
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
// 等同于
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
</pre>
总之,<em>combineReducers()</em>做的就是产生一个整体的 Reducer 函数。该函数根据 <em>State 的 key</em> 去执行相应的子 Reducer,并将返回结果合并成一个大的 State 对象,下面是combineReducer的简单实现。
<pre>
const combineReducers = reducers => {
return (state = {}, action) => {
return Object.keys(reducers).reduce(
(nextState, key) => {
nextState[key] = reducers[key](state[key], action);
return nextState;
},
{}
);
};
};
</pre>
<div>
你可以把所有子 Reducer 放在一个文件里面,然后统一引入。
</div>
<br />
**练习**
<br />
<div>
同学们可以试着做一个计算器,完整的示例
<a href="https://github.com/reactjs/redux/blob/master/examples/counter/public/index.html">代码</a>。
</div>
</p>
@@@@Redux的React绑定(一)
###Redux的React绑定(一)
<p>
<img src="img/img12.jpg" height="300" width="600">
**UI 组件**
<div>
React-Redux 将所有组件分成两大类:UI 组件(presentational component)和容器组件(container component)。
UI 组件有以下几个特征:
<li>只负责 UI 的呈现,不带有任何业务逻辑;</li>
<li>没有状态(即不使用this.state这个变量);</li>
<li>所有数据都由参数(this.props)提供;</li>
<li>不使用任何 Redux 的 API;</li>
下面就是一个 UI 组件的例子。
<pre>
const Title =
value => <h1>{value}</h1>;
</pre>
因为不含有状态,<em>UI 组件又称为"纯组件"</em>,即它纯函数一样,纯粹由参数决定它的值。
</div>
<br />
**容器组件**
<div>
容器组件的特征恰恰相反:
<li>负责管理数据和业务逻辑,不负责 UI 的呈现;</li>
<li>带有内部状态;</li>
<li>使用 Redux 的 API;</li>
总之,只要记住一句话就可以了:<em>UI 组件负责 UI 的呈现,容器组件负责管理数据和逻辑</em>。<br />
你可能会问,如果一个组件既有 UI 又有业务逻辑,那怎么办?回答是,将它拆分成下面的结构:外面是一个容器组件,里面包了一个UI 组件。前者负责与外部的通信,将数据传给后者,由后者渲染出视图。<br />
React-Redux 规定,所有的 <em>UI 组件都由用户提供</em>,容器组件则是由 React-Redux 自动生成。也就是说,用户负责视觉层,状态管理则是全部交给它。
</div>
<br />
**mapStateToProps()**
<div>
mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系,作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。请看下面的例子。
<pre>
const mapStateToProps = (state) => {
return {
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}</pre>
上面代码中,mapStateToProps是一个函数,它接受state作为参数,返回一个对象。这个对象有一个todos属性,代表 UI 组件的同名参数,后面的getVisibleTodos也是一个函数,可以从state算出 todos 的值,下面就是getVisibleTodos的一个例子,用来算出todos。
<pre>
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
default:
throw new Error('Unknown filter: ' + filter)
}
}
</pre>
<em>mapStateToProps会订阅 Store</em>,每当state更新的时候,就会自动执行,重新计算 UI 组件的参数,从而触发 UI 组件的重新渲染,mapStateToProps的第一个参数总是state对象,还可以使用第二个参数,代表容器组件的props对象。
<pre>
// 容器组件的代码
// <FilterLink filter="SHOW_ALL">
// All
// </FilterLink>
const mapStateToProps = (state, ownProps) => {
return {
active: ownProps.filter === state.visibilityFilter
}
}
</pre>
使用ownProps作为参数后,如果容器组件的参数发生变化,也会引发 UI 组件重新渲染。<br />
connect方法可以省略mapStateToProps参数,那样的话,UI 组件就不会订阅Store,就是说 Store 的更新不会引起 UI 组件的更新。
</div>
</p>
@@@@Redux的React绑定(二)
###Redux的React绑定(二)
<p>
**mapDispatchToProps()**
<div>
mapDispatchToProps是connect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。<br />
如果mapDispatchToProps是一个函数,会得到dispatch和ownProps(容器组件的props对象)两个参数。
<pre>
const mapDispatchToProps = (
dispatch,
ownProps
) => {
return {
onClick: () => {
dispatch({
type: 'SET_VISIBILITY_FILTER',
filter: ownProps.filter
});
}
};
}
</pre>
从上面代码可以看到,mapDispatchToProps作为函数,应该返回一个对象,该对象的每个键值对都是一个映射,定义了 UI 组件的参数怎样发出 Action。<br />
如果mapDispatchToProps是一个对象,它的每个键名也是对应 UI 组件的同名参数,键值应该是一个函数,会被当作 Action creator ,返回的 Action 会由 Redux 自动发出。举例来说,上面的mapDispatchToProps写成对象就是下面这样。
<pre>
const mapDispatchToProps = {
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
</pre>
</div>
<br />
**connect()**
<div>
React-Redux 提供connect方法,用于从 UI 组件生成容器组件。<em>connect的意思,就是将这两种组件连起来</em>。
<pre>
import { connect } from 'react-redux'
const VisibleTodoList = connect()(TodoList);
</pre>
上面代码中,TodoList是 UI 组件,<em>VisibleTodoList</em>就是由 React-Redux 通过connect方法自动生成的容器组件。
但是,因为没有定义业务逻辑,上面这个容器组件毫无意义,只是 UI 组件的一个单纯的包装层。为了定义业务逻辑,需要给出下面两方面的信息。
<li>(1)输入逻辑:外部的数据(即state对象)如何转换为 UI 组件的参数;</li>
<li>(2)输出逻辑:用户发出的动作如何变为 Action 对象,从 UI 组件传出去;</li>
因此,connect方法的完整 API 如下。
<pre>
import { connect } from 'react-redux'
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
</pre>
上面代码中,connect方法接受两个参数:<em>mapStateToProps</em>和<em>mapDispatchToProps</em>。它们定义了 UI 组件的业务逻辑。前者负责输入逻辑,即将state映射到 UI 组件的参数(props),后者负责输出逻辑,即将用户对 UI 组件的操作映射成 Action。
</div>
<br />
**Provider组件**
<div>
connect方法生成容器组件以后,需要让容器组件拿到state对象,才能生成 UI 组件的参数。
一种解决方法是将state对象作为参数,传入容器组件。但是,这样做比较麻烦,尤其是容器组件可能在很深的层级,一级级将state传下去就很麻烦。<br />
React-Redux 提供Provider组件,可以让容器组件拿到state。
<pre>
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
</pre>
上面代码中,Provider在根组件外面包了一层,这样一来,App的所有子组件就默认都可以拿到state了。
它的原理是React组件的context属性,请看源码。
<pre>
class Provider extends Component {
getChildContext() {
return {
store: this.props.store
};
}
render() {
return this.props.children;
}
}
Provider.childContextTypes = {
store: React.PropTypes.object
}
</pre>
上面代码中,store放在了上下文对象context上面。然后,子组件就可以从<em>context拿到store</em>,代码大致如下。
<pre>
class VisibleTodoList extends Component {
componentDidMount() {
const { store } = this.context;
this.unsubscribe = store.subscribe(() =>
this.forceUpdate()
);
}
render() {
const props = this.props;
const { store } = this.context;
const state = store.getState();
// ...
}
}
VisibleTodoList.contextTypes = {
store: React.PropTypes.object
}
</pre>
React-Redux自动生成的容器组件的代码,就类似上面这样,从而拿到store。
</div>
<br />
**练习**
<div>
同学们学到这里,可以试着写一个登录页面demo,要求有登录相关的验证流程。
</div>
</p>
@@@@Redux中间件介绍
###Redux中间件介绍
<p>
<img src="img/img15.jpg" height="300" width="600">
**为什么要使用中间件**
<div>
一个关键问题没有解决:异步操作怎么办?Action 发出以后,Reducer 立即算出 State,这叫做同步;Action 发出以后,过一段时间再执行 Reducer,这就是异步。<br />
怎么才能 Reducer 在异步操作结束后自动执行呢?这就要用到新的工具:中间件(middleware)。
</div>
<br />
**中间件的概念**
<br />
<div>
为了理解中间件,让我们站在框架作者的角度思考问题:如果要添加功能,你会在哪个环节添加?
<li>Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作;
</li>
<li>View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能;</li>
<li>Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作;</li>
想来想去,只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。举例来说,要添加日志功能,把 Action 和 State 打印出来,可以对store.dispatch进行如下改造。
<pre>
let next = store.dispatch;
store.dispatch = function dispatchAndLog(action) {
console.log('dispatching', action);
next(action);
console.log('next state', store.getState());
}
</pre>
上面代码中,对store.dispatch进行了重定义,在发送 Action 前后添加了打印功能。这就是中间件的雏形。
中间件就是一个函数,对store.dispatch方法进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
</div>
<br />
**中间件的用法**
<br />
<div>
本教程不涉及如何编写中间件,因为常用的中间件都有现成的,只要引用别人写好的模块即可。比如,上一节的日志中间件,就有现成的<em>redux-logger模块</em>。这里只介绍怎么使用中间件。
<pre>
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(logger)
);
</pre>
上面代码中,redux-logger提供一个生成器<em>createLogger</em>,可以生成日志中间件logger。然后,将它放在<em>applyMiddleware</em>方法之中,传入createStore方法,就完成了store.dispatch()的功能增强。
这里有两点需要注意:
<li>createStore方法可以接受整个应用的初始状态作为参数,那样的话,applyMiddleware就是第三个参数了。
<pre>
const store = createStore(
reducer,
initial_state,
applyMiddleware(logger)
);</pre></li>
<li>中间件的次序有讲究。
<pre>
const store = createStore(
reducer,
applyMiddleware(thunk, promise, logger)
);</pre></li>
上面代码中,applyMiddleware方法的三个参数,就是三个中间件。有的中间件有次序要求,使用前要查一下文档。比如,logger就一定要放在最后,否则输出结果会不正确。
</div>
</p>
@@@@Redux中间件高级
###Redux中间件高级
<p>
**applyMiddlewares()**
<div>
看到这里,你可能会问,applyMiddlewares这个方法到底是干什么的?
它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。下面是它的源码。
<pre>
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer);
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {...store, dispatch}
}
}
</pre>
上面代码中,所有中间件被放进了一个数组chain,然后嵌套执行,最后执行store.dispatch。可以看到,中间件内部(middlewareAPI)可以拿到getState和dispatch这两个方法。
</div>
<br />
**redux-thunk 中间件**
<br />
<div>
异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?奥妙就在 Action Creator 之中。
<pre>
class AsyncApp extends Component {
componentDidMount() {
const { dispatch, selectedPost } = this.props
dispatch(fetchPosts(selectedPost))
}
// ...
</pre>
上面代码是一个异步组件的例子。加载成功后(componentDidMount方法),它送出了(dispatch方法)一个 Action,向服务器要求数据 <em>fetchPosts(selectedSubreddit)</em>。这里的fetchPosts就是 Action Creator。
下面就是fetchPosts的代码,关键之处就在里面。
<pre>
const fetchPosts = postTitle => (dispatch, getState) => {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(postTitle, json)));
};
};
// 使用方法一
store.dispatch(fetchPosts('reactjs'));
// 使用方法二
store.dispatch(fetchPosts('reactjs')).then(() =>
console.log(store.getState())
);
</pre>
上面代码中,<em>fetchPosts是一个Action Creator(动作生成器)</em>,返回一个函数。这个函数执行后,先发出一个Action(requestPosts(postTitle)),然后进行异步操作。拿到结果后,先将结果转成 JSON 格式,然后再发出一个 Action( receivePosts(postTitle, json))。
上面代码中,有几个地方需要注意。
<li>fetchPosts返回了一个函数,而普通的 Action Creator 默认返回一个对象;</li>
<li>返回的函数的参数是<em>dispatch和getState</em>这两个 Redux 方法,普通的 Action Creator 的参数是 Action 的内容;</li>
<li>在返回的函数之中,先发出一个 <em>Action(requestPosts(postTitle))</em>,表示操作开始;</li>
<li>异步操作结束之后,再发出一个 <em>Action(receivePosts(postTitle, json))</em>,表示操作结束;</li>
这样的处理,就解决了自动发送第二个 Action 的问题。但是,又带来了一个新的问题,Action 是由store.dispatch方法发送的。而store.dispatch方法正常情况下,参数只能是对象,不能是函数,这时,就要使用中间件redux-thunk。
<pre>
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
// Note: this API requires redux@>=3.1.0
const store = createStore(
reducer,
applyMiddleware(thunk)
);
</pre>
上面代码使用<em>redux-thunk中间件</em>,改造store.dispatch,使得后者可以接受函数作为参数。因此,异步操作的第一种解决方案就是,写出一个返回函数的 Action Creator,然后使用redux-thunk中间件改造store.dispatch。
</div>
<br/>
**练习**
<br />
<div>
同学们可以利用这一节学习的知识,模拟一下http异步请求,更新UI界面。
</div>
</p>
@@@第五章 Router
@@@@使用Router管理多组件
###React Router安装
<p>
<img src="img/img16.jpg" height="300" width="600">
<div>
本章开始介绍 React 体系的一个重要部分:<em>路由库React-Router</em>。它是官方维护的,事实上也是唯一可选的路由库。它通过管理 URL,实现组件的切换和状态的变化,开发复杂的应用几乎肯定会用到。
<br />
**基本用法**
<br />
<div>
React Router 安装命令如下。
</div>
<pre>
$ npm install -S react-router
</pre>
使用时,路由器Router就是React的一个组件。
<pre>
import { Router } from 'react-router';
render(<Router/>, document.getElementById('app'));
</pre>
Router组件本身只是一个容器,真正的路由要通过Route组件定义。
<pre>
import { Router, Route, hashHistory } from 'react-router';
render((
<Router history={hashHistory}>
<Route path="/" component={App}/>
</Router>
), document.getElementById('app'));
</pre>
上面代码中,用户访问根路由/(比如<pre>http://www.example.com/</pre>),组件APP就会加载到document.getElementById('app')。
你可能还注意到,Router组件有一个参数history,它的值hashHistory表示,路由的切换由URL的hash变化决定,即URL的#部分发生变化。举例来说,用户访问<pre>http://www.example.com/</pre>,实际会看到的是<pre>http://www.example.com/#/</pre>。
<em>Route组件定义了URL路径与组件的对应关系</em>。你可以同时使用多个Route组件。
<pre>
<Router history={hashHistory}>
<Route path="/" component={App}/>
<Route path="/repos" component={Repos}/>
<Route path="/about" component={About}/>
</Router>
</pre>
上面代码中,用户访问/repos(比如<pre>http://localhost:8080/#/repos</pre>)时,加载Repos组件;访问/about(<pre>http://localhost:8080/#/about</pre>)时,加载About组件。
</div>
<br />
</p>
@@@@React Router属性
###React Router属性
<p>
**Link**
<br />
<div>
Link组件用于取代<code><a></code>元素,生成一个链接,允许用户点击后跳转到另一个路由。它基本上就是<code><a></code>元素的React 版本,可以接收Router的状态。
<pre>
render() {
return <div>
<ul role="nav">
<li><Link to="/about">About</Link></li>
<li><Link to="/repos">Repos</Link></li>
</ul>
</div>
}
</pre>
如果希望当前的路由与其他路由有不同样式,这时可以使用Link组件的activeStyle属性。
<pre>
<Link to="/about" activeStyle={{color: 'red'}}>About</Link>
<Link to="/repos" activeStyle={{color: 'red'}}>Repos</Link>
</pre>
上面代码中,当前页面的链接会红色显示。
另一种做法是,使用activeClassName指定当前路由的Class。
<pre>
<Link to="/about" activeClassName="active">About</Link>
<Link to="/repos" activeClassName="active">Repos</Link>
</pre>
上面代码中,当前页面的链接的<em>class会包含active</em>。
在Router组件之外,导航到路由页面,可以使用浏览器的History API,像下面这样写。
<pre>
import { browserHistory } from 'react-router';
browserHistory.push('/some/path');
</pre>
</div>
<br />
**path 属性**
<br />
<div>
Route组件的path属性指定路由的匹配规则。这个属性是可以省略的,这样的话,不管路径是否匹配,总是会加载指定组件。
请看下面的例子。
<pre>
<Route path="inbox" component={Inbox}>
<Route path="messages/:id" component={Message} />
</Route>
</pre>
上面代码中,当用户访问<em><code>/inbox/messages/:id</code></em>时,会加载下面的组件。
<pre>
<Inbox>
<Message/>
</Inbox>
</pre>
如果省略外层Route的path参数,写成下面的样子。
<pre>
<Route component={Inbox}>
<Route path="inbox/messages/:id" component={Message} />
</Route>
</pre>
现在用户访问/inbox/messages/:id时,组件加载还是原来的样子。
<pre>
<Inbox>
<Message/>
</Inbox>
</pre>
</div>
<br />
**练习**
<br />
<div>
同学可以自己找一找<em>Restful Api</em>的文档看看,结合两者知识再理解一下<em>Link/to</em>属性,path属性,下一节我们讲解的History属性多多少少也是有联系的。
</div>
</p>
@@@@路由器历史策略
###路由器历史策略
<p>
<img src="img/img18.jpg" height="300" width="600">
<div>详细大家对于浏览器的历史记录都非常的熟悉吧,其实呢?Router里面的历史记录跟这个概念是差不多的。</div>
<br />
**histroy 属性**
<br />
<div>
Router组件的history属性,用来监听浏览器地址栏的变化,并将URL解析成一个地址对象,供 React Router 匹配。
history属性,一共可以设置三种值:
<li><em>browserHistory</em></li>
<li>hashHistory</li>
<li>createMemoryHistory</li>
如果设为hashHistory,路由将通过URL的hash部分(#)切换,URL的形式类似example.com/#/some/path。
<pre>
import { hashHistory } from 'react-router'
render(
<Router history={hashHistory} routes={routes} />,
document.getElementById('app')
)
</pre>
如果设为browserHistory,浏览器的路由就不再通过Hash完成了,而显示正常的路径<code>example.com/some/path</code>,背后调用的是浏览器的History API。
<pre>
import { browserHistory } from 'react-router'
render(
<Router history={browserHistory} routes={routes} />,
document.getElementById('app')
)
</pre>
但是,这种情况需要对服务器改造。否则用户直接向服务器请求某个子路由,会显示网页找不到的404错误。
如果开发服务器使用的是<em>webpack-dev-server</em>,加上--history-api-fallback参数就可以了。
<br />
$ webpack-dev-server --inline --content-base . --history-api-fallback
createMemoryHistory主要用于服务器渲染。它创建一个内存中的history对象,不与浏览器URL互动。
<pre>
const history = createMemoryHistory(location)</pre>
</div>
<br />
**练习**
<br />
<div>
这节的知识点比较少,同学们可以再复习一下上面提到的三种历史记录的异同点。
</div>
</p>
@@@@路由匹配
###路由匹配
<p>
**通配符**
<br />
<div>
path属性可以使用通配符。
<pre>
<Route path="/hello/:name">
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/hello(/:name)">
// 匹配 /hello
// 匹配 /hello/michael
// 匹配 /hello/ryan
<Route path="/files/*.*">
// 匹配 /files/hello.jpg
// 匹配 /files/hello.html
<Route path="/files/*">
// 匹配 /files/
// 匹配 /files/a
// 匹配 /files/a/b
<Route path="/**/*.jpg">
// 匹配 /files/hello.jpg
// 匹配 /files/path/to/file.jpg
</pre>
通配符的规则如下:
<li>:paramName
:paramName匹配URL的一个部分,直到遇到下一个/、?、#为止。这个路径参数可以通过this.props.params.paramName取出;</li>
<li>()
()表示URL的这个部分是可选的;</li>
<li>*
*匹配任意字符,直到模式里面的下一个字符为止。匹配方式是非贪婪模式;</li>
<li>**
** 匹配任意字符,直到下一个/、?、#为止。匹配方式是贪婪模式;</li>
path属性也可以使用相对路径(不以/开头),匹配时就会相对于父组件的路径,可以参考上一节的例子。嵌套路由如果想摆脱这个规则,可以使用绝对路由。路由匹配规则是从上到下执行,一旦发现匹配,就不再其余的规则了。
<pre>
<Route path="/comments" ... />
<Route path="/comments" ... />
</pre>
上面代码中,路径/comments同时匹配两个规则,第二个规则不会生效。
设置路径参数时,需要特别小心这一点。
<pre>
<Router>
<Route path="/:userName/:id" component={UserPage}/>
<Route path="/about/me" component={About}/>
</Router>
</pre>
上面代码中,用户访问/about/me时,不会触发第二个路由规则,因为它会匹配/:userName/:id这个规则。因此,带参数的路径一般要写在路由规则的底部。
此外,URL的查询字符串/foo?bar=baz,可以用<em>this.props.location.query.bar</em>获取。
</div>
<br />
**练习**
<br />
<div>
同学可以自己找一些关于正则表达式的文档看看,结合本节的知识相信,理解起来更加容易些。
</div>
</p>
@@@@服务器渲染
###服务器渲染
<p>
<img src="img/img19.jpg" height="300" width="600">
<div>
想让搜索引擎抓取到你的站点,服务端渲染这一步不可或缺,服务端渲染还可以提升站点的性能,因为在加载JavaScript脚本的同时,浏览器就可以进行页面渲染。<br />
React的虚拟DOM是其可被用于服务端渲染的关键。首先每个ReactComponent 在虚拟DOM中完成渲染,然后React通过虚拟DOM来更新浏览器DOM中产生变化的那一部分,虚拟DOM作为内存中的DOM表现,为React在Node.js这类非浏览器环境下的吮吸给你提供了可能,React可以从<em>虚拟DoM中生成一个字符串</em>。而不是跟新真正的DOM,这使得我们可以在客户端和服务端使用同一个<em>React Component</em>。<br />
React 提供了两个可用于服务端渲染组件的函数:<em>React.renderToString</em> 和<em>React.render-ToStaticMarkup</em>。 在设计用于服务端渲染的ReactComponent时需要有预见性,考虑以下方面:
<li>选取最优的渲染函数;</li>
<li>如何支持组件的异步状态;</li>
<li>如何将应用的初始化状态传递到客户端;</li>
<li>哪些生命周期函数可以用于服务端的渲染;</li>
<li>如何为应用提供同构路由支持;</li>
<li>单例、实例以及上下文的用法;</li>
<br />
**React.renderToString**
<br />
<div>
React.renderToString是两个服务端渲染函数中的一个,也是开发主要使用的一个函数,和React.render不同,该函数去掉了用于表示渲染位置的参数。取而代之,该函数只返回一个字符串,这是一个快速的同步(阻塞式)函数,非常快。
</div>
<pre>
var MyComponent = React.createClass({
render:fucniton(){
return <div> Hello World!</div>;
}
});
var world= React.renderToString (<MyComponent/>);
//这个示例返回一个单行并且格式化的输出
<divdata-reactid=".fgvrzhg2yo"data-ract-checksum="-1663559667">
Hello World!
</div>
</pre>
你会注意到,React为这个<div>元素添加了两个data前缀的属性。在浏览器环境下,React使用data-reactid区分DOM节点。这也是每当组件的state及props发生变化时,React都可以精准的跟新制定DOM节点的原因。
<br />
<div>
data-react-checksum仅仅存在于服务端。顾名思义,它是已创建<em>DOM和校验和</em>。这准许React在客户端服用与服务端结构上相同点的DOM结构。该属性只会添加到跟元素上。
</div>
<br />
**React.renderToStaticMarkup**
<br />
<div>
React.renderToStaticMarkup是第二个服务端渲染函数,除了不会包含React的data属性外,它和React.renderToString没有区别。
</div>
<pre>
varMyComponent=React.createClass({
render:function(){
return<div>Hello World!</div>;
}
});
varworld= React.renderToStaticMarkup(<MyCompoent/>);
//单行输出
<div>HelloWorld!</div>
</pre>
<br />
**用React.renderToString还是React.renderToStaticMarkup**
<br />
<div>
每个渲染函数都有自己的用途,所以你必须明确自己的需求,再去决定使用哪个渲染函数。当且仅当你不打算在客户端渲染这个React Component时,才应该选择使用React.renderToStaticMarkup函数。
</div>
<br />
下面有一些示例:
<li><em>生成HTML电子邮件;</em></li>
<li><em>通过HTML到PDF的转化来生成PDF;</em></li>
<li><em>组件测试;</em></li>
<br />
大多数情况下,我们都会选择使用<em>React.renderToString</em>。这将准许React使用<em>data-react-checksum</em>在客户端跟迅速的初始化同一个React Component因为React可以重用服务端提供的
DOM,所以它可以跳过生成DOM节点以及把他们挂载到文档中这两个昂贵的进程,对于复杂些的站点,这样做就会显著的减少加载时间,用户可以更快的与站点进行交互。
确保React Component能够在服务端和客户端准确的渲染出一致的结构是很重要的。如果data-react-checksum不匹配,React会舍弃服务端提供的DOM,然后生成新的DOM节点,并且将它们更新到文档中。此时,Ract也不胡iyongyou服务端渲染带来的各种性能上的优势。
</div>
</p>