[转载 fakefisk]
ReactJS介绍
引子
当你第一次看到 React 的时候,一定是十分怀疑的,特别是把 HTML 揉进代码里违背了传统的那些约定,看起来真的像是一个 “坏注意”。
但是这就是 React及其类似的框架,他们挑战了那些约定规则,然后用这种更易接受的方式代替这些约定。
作为一个开发者,有时候改变思考方式是你向前走的一个必经之路。这就是 React 对我的影响,它建立在函数式编程上带给了我很多有意思的想法。
基础功能
在你能理解 React 是如何改变 Web 开发之前,这里有一些东西你需要知道的。React 它本身并不能完成所有的事情,它只能解决一些视图上的问题,你仍然需要一些东西去帮助它完成事情。
最伟大的和最差劲的框架总是企图给你建立一个笼子,当你在他们划定的地盘里能够完成它们期望处理的任何需求,一切都是很好的。但是,当你到达边界的时候,就会开始有问题了。
在一个库驱动开发方法内,你是不被限制的,最初你可能是高效地完成需求,但是伴随时间的推进,问题开始变得严峻起来,你需要更多可行的选择。
JSX 的基础
React 为前端开发提供了一个组件为中心的方法,你可以为你的应用设计一个更小的组件,所有组件有各自的目的。甚至极端来说,一个组件可能包含它自身的逻辑,结构和基本的样式。这里有个 JSX 的例子:
...
<TodoItem className='urgent' owner={owner} task='Make a dinner' />
...
你可以看到一个基本功能的 JSX,这里使用的是javascript,我们使用className 替代了class。此外,我们定义了一些自定义属性 owner 和 task,owner 的值是从 JSX 环境中一个变量 owner 注入,而我们为 task 提供了一个固定的值。
在实践中,你可以会更喜欢根据你的数据模型结构来稍微调整其结构,虽然是 React 进阶的知识点了。
我们可以用那些 {} 来混合普通 JavaScript 代码,我们可以用这个点像这样去去渲染一个 TodoItem 列表(ES 语法):
<ul>
{todoItems.map( (todoItem, i) =>
<li key={'todoitem' + i}><TodoItem owner={todoItem.owner} task={todoItem.task} /></li>
)}
</ul>
你可能注意到这里有些特殊的地方。什么是 key 属性?这是告诉 React 这个项的准确顺序。如果你不像这样提供列表项的唯一 Key 的话,React 会警告你:它没办法保证是正确的顺序。
React 实际上要做的是在真实 DOM 上实现了虚拟 DOM(简称 VDOM)。 这是一个 DOM 的子集, React 能够优化它的渲染。这种优化方法的主要优势是它让 React 能够避开困恼我们多年的 DOM 性能损耗。这就是 React 高性能的原因。
整体组件
为了让你更加清楚了解组件是什么样子,让我们拓展 TodoItem 例子来感受一下(ES6 + JSX),我已经搞完了,然后我们来过一遍代码看看:
var React = require('react');
module.exports = React.createClass({
getInitialState() {
return {
// 让我们保持追踪看看我们给项点了多少次赞
likes: 0,
};
},
render() {
var owner = this.props.owner;
var task = this.props.task;
var likes = this.state.likes;
return <div className='TodoItem'>
<span className='TodoItem-owner'>{owner}</span>
<span className='TodoItem-task'>{task}</span>
<span className='TodoItem-likes'>{likes}</span>
<span className='TodoItem-like' onClick={this.like}>Like</span>
</div>;
},
like() {
this.setState({
likes: this.state.likes + 1
});
},
});
在上面你可以看到一些基础的 React 组件的特征。一开始我们创建了一个组件的类–通过React.createClass函数,然后我们在初始化的时候定义一些状态,接着我们定义渲染方法render,最后我们为我们的处理器handler,如果它们存在的话,定制了一些回调。在这个例子中,我决定去实施一个额外的功能,liking方法(点赞)。当前的这个实现只是保持了一个跟踪点赞组件的计数。
在实践中,你需要把点赞计数传输给后端,然后添加一些验证,不过目前这个阶段对于理解 State 在 React 中的是如何使用的已经是一个非常好的开端了。
稍作说明:在react 组件component官方文档中, getInitialState 和 render 是 React 组件的生命周期一部分。组件component也有额外的能够让你去设置加载 jQuery 插件之类的适配器的钩子。
在例子中的 CSS 的命名是根据 Suit CSS 约定处理后的,这样对我来说看起来非常清楚,这只是一种解决方案而已。
处理操作
现在让我们开始修改我们的 TodoItems 的 owner。为了简化这个目的,我们假设 owner 只是一个字符串而且只有一个。基于这个设计,在用户界面里增加一个输入框给用户来修改用户名,修改后是这样的:
var React = require('react');
var TodoItem = require('./TodoItem.jsx');
module.exports = React.createClass({
getInitialState() {
return {
todoItems: [
{
task: 'Learn React',
},
{
task: 'Learn Webpack',
},
{
task: 'Conquer World',
}
],
owner: 'John Doe',
};
},
render() {
var todoItems = this.state.todoItems;
var owner = this.state.owner;
return <div>
<div className='ChangeOwner'>
<input type='text' defaultValue={owner} onChange={this.updateOwner} />
</div>
<div className='TodoItems'>
<ul>{todoItems.map((todoItem, i) =>
<li key={'todoitem' + i}>
<TodoItem owner={owner} task={todoItem.task} />
</li>
)}</ul>
</div>
</div>;
},
updateOwner() {
this.setState({
owner: e.target.value,
});
},
});
在render方法中,我们定义了一个Div和其中的输入框。
当然我们可以把 TodoItems 和 ChangeOwner 分离出去,但是我暂时不这么做了。React 默认提供了单向数据绑定,关于某些其他的设置,这里有很多的说法。 React 提供了 ReactLink 帮助器helper来帮助我们处理这个特殊案例。
尽管双向数据绑定的缺失–如angularJS和backbone等–听起来让人沮丧,但实际上并不是一件坏事,它能够让系统更加轻松的运行。你必须简单地跟踪流。这也是Flux 架构中的精彩部分。认识它的最简单的方式是去想一个无限瀑布流,或者贪吃蛇。这就是 在React 的世界里,通常工作的流程。对比一下,双向绑定方式让人感觉更加杂乱混乱。
使用一个Mixin
如果我们想使用 ReactLink helper来帮助我们包装DOM的话,就像下面这样使用:
// ReactLink 是一个插件,所以我们需要把它引入。
var React = require('react/addons');
...
module.exports = React.createClass({
mixins: [React.addons.LinkedStateMixin],
...
render() {
var todoItems = this.state.todoItems;
return <div>
<div className='ChangeOwner'>
<input type='text' valueLink={this.linkState('owner')} />
</div>
<div className='TodoItems'>
<ul>{todoItems.map( (todoItem, i) =>
<li key={'todoitem' + i}>
<TodoItem owner={owner} task={todoItem.task} />
</li>
)}</ul>
</div>
</div>;
},
});
现在我们可以跳过绑定 onChange 事件了,React.addons.LinkedStateMixin 封装了逻辑。Mixins 给我们提供了一种封装可以共享关注点,并能简单重用的方法。
现在拓展例子已经非常容易了,你现在应该就可以拓展 Todo 列表,或者把抽取不同的部分分离到各种组件,这完全取决于你如何来开发。这里只是对此话题的一个简单介绍。
测试
如果你对这个 Todo 应用是很认真的,那么我推荐 Jest,使用它来进行测试。刚开始让测试运行起来可能有一点挑战,但是在你学习了解了它的一些基础的 API 之后,就会变得简单了。最基本的是你要实例化一个带有一些属性的组件,然后用 Jest 查询 DOM,最后断言DOM中的值恰如你期望的值。
当你站在组件层级之上时,就有了比如类似 Selenium 的工具,你可以在更高层级使用标准的端对端测试工具。
Flux 架构及其变种
就想你在上面看到的,把一些组件放到一起就可以开始创建一个应用,你可以用 props 和 state 做的更多,或者在 getInitialState 中使用 AJAX 加载数据然后传给其他组件。这些使用一段时间之后可能会觉得有些笨拙。为什么?比如,我们的组件们需要知道如何和后端通讯。
所以出现了 Flux 架构和它的一些演化变种,我会介绍 Reflux ,一种简化的 Flux 变种。在你理解这个简化的版本后,还可以浏览这个 understanding Flux 来完整详尽全面了解 Flux。
并且,为了查看刚才我们讨论的试图组件,Reflux 引入了 Actions 和 Stores 的概念。
现在,整个数据流是这样的:组件 -> Actions -> Stores -> 组件。这样你可以在一个组件中控制触发一些 Action 然后执行一些操作(比如 PUT)然后更新 Store 的状态。而状态的更新再被传播给监听 Store 的那些组件们。
在我们的这个 Todo 例子中,我们定义了基本的 TodoActions, 比如创建、更新、删除之类的。我们也有个 TodoStore,它是整个 TodoList 的数据结构中心,组件们会读取那里的数据,然后适当得展现出来。
使用Reflux 的开发,旧不在这里详细讲述了,我只是想说明如何处理从纯 React 扩大的一种可能的方式。你需要去探索各种选择,然后深入理解那些架构。那些想法都差不多,就是细节不太一样,都有各自的一些缺点有待考略。