当我拿到一个前后端实现todolist的学习任务时,我应该怎么样

标签:至少1个,最多5个
在了解了 Vue 的一些基本概念之后,就可以写一个最简单的小项目了 --- TodoList。麻雀虽小,五张俱全。虽然是一个小 demo,但也涉及到了组件化、双向绑定、自定义事件的触发与监听、计算属性等概念。接下来从这个小项目中,对这些基本概念进行实践,从而加深理解。
本文的所有代码在 。
最终实现效果如下:
接下来就一一实现。
初始化项目
同样使用 vue-cli 初始化项目,直接回车就好了。
$ vue init webpack todoListDemo
$ cd todoListDemo
$ npm install
$ npm run dev
启动之后,浏览器就会自动现默认的页面。
在进行编码之前,首先要考虑组件怎么设计。在本文中,组件结构如下。
+-----------------------+
+-----------------+
+-----------------+
+-----------------+
|+---------------+|
|+---------------+|
|+---------------+|
|+---------------+|
|+---------------+|
|+---------------+|
+-----------------+
+-----------------------+
其中主要包括两个大的组件
TodoAdd 添加 Todo 的一个输入框
TodoList Todo 列表,里面有每一个 Todo Item
添加 TodoList 组件
在 src/components 目录下新建一个名为 TodoList.vue 的文件,并添加如下代码:
&template&
&div id="todoList"&
&h1&Todo List&/h1&
&ul class="todos"&
&li v-for="todo, index in todos" class="todo"&
type="checkbox"
:checked="todo.isCompleted"
:class="todo.isCompleted ? 'completed' : ''"
&em&{{ index + 1 }}.&/em&{{ todo.text }}
&/template&
export default {
name: 'TodoList',
data: () =& ({
text: '吃饭',
isCompleted: false
text: '睡觉',
isCompleted: false
&style scoped&
#todoList {
max-width: 350
.todos li {
list-style:
text-align:
.completed {
text-decoration: line-
在 TodoList 中,使用 todos 数组来保存所有的 todo list。其中每一个 todo 都是对象,对象里面有两个属性,分别是 todo 的内容,和 todo 是否完成的标志。默认给数组添加了两个 todo,主要用于演示。
src/components/Hello.vue 在本项目中没什么用,可以随意删除。
然后修改 src/router/index.js:
import Vue from 'vue'
import Router from 'vue-router'
import TodoList from '@/components/TodoList'
Vue.use(Router)
export default new Router({
path: '/',
name: 'todoList',
component: TodoList
修改完之后,vue 会自动重新编译并刷新页面,这时浏览器的页面如下:
添加完成 Todo 的方法
在该 demo 中,当点击 todo item 或者前面的复选框的时候,就完成 todo。所以现在需要添加完成 todo 的方法,并设置 todo item 的点击事件。
像下面这样修改 src/components/TodoList.vue 中的 template 部分:
type="checkbox"
:checked="todo.isCompleted"
@click="completed(index)"
:class="todo.isCompleted ? 'completed' : ''"
@click="completed(index)"
&em&{{ index }}.&/em&{{ todo.text }}
然后在组件里面添加对应的 completed 方法:
export default {
// 其他现有代码
name: 'TodoList',
methods: {
completed(index) {
this.todos[index].isCompleted = !this.todos[index].isCompleted
当点击 check box 或 span 的时候,就调用 completed 方法并传入被点击的 todo item 的索引。在 completed 方法里面,更新数据对象 data 里面对应的 todo item 的 isCompleted 属性。这样就实现了完成 todo 和取消完成 todo 的功能。点击之后如图:
TodoAdd 组件
接下来就需要完成添加新的 todo 的功能了。
新建一个文件 src/components/TodoAdd.vue,添加如下代码:
&template&
&div id="addTodo"&
type="text"
class="input"
v-model="todo"
@keyup.enter="addTodo"
type="button"
name="button"
@click="addTodo"
&/template&
export default {
name: 'addTodo',
data: () =& ({
methods: {
addTodo () {
if (this.todo) {
this.$emit('add', this.todo)
this.todo = ''
alert('内容不能为空')
&style scoped&
min-width: 200
首先在组件的数据对象 data 里面有一个 todo 属性,用来存储用户输入的内容。然后在 template 的 input 输入框里,使用
实现双向绑定。
当用户按下回车(@keyup.enter="addTodo",详见 )或者点击添加按钮(@click="addTodo")的时候,就调用 methods 里面的 addTodo 方法。
addTodo 方法通过
触发了一个 add 事件,并将用户输入的内容(即 this.todo)作为参数传递。事件触发之后,将输入框中的内容清空。
接下来就需要监听 add 事件了。监听事件需要在使用组件的模板里面,通过 v-on 来实现。详见 。
完成添加 Todo 功能
在 src/components/TodoList.vue 中使用 AddTodo 这个子组件:
&h1&Todo List&/h1&
&!-- 调用子组件,并使用 v-on 监听 add 方法 --&
&!-- 当 add 事件触发时,就调用当前组件 addTodo 这个方法 --&
&todo-add v-on:add="addTodo"&&/todo-add&
&ul class="todos"&
&!-- // 调用子组件 --&
// 引入子组件
import TodoAdd from './TodoAdd.vue'
export default {
name: 'TodoList',
components: {
methods: {
// 添加新的 todo
addTodo() {
this.todos.push({
text: todo,
isCompleted: false
到此,添加 todo 和完成 todo 功能就实现了。
接下来还可以做点别的事情,比如显示总共的 todo 数目,以及完成和未完成的数目。
要实现此功能,方法有很多种。最简单的一种是直接在模板中加入 JS 表达式,来显示总共的数目,比如:
&p&总共有 &strong&{{ this.todos.lengt }}&/strong& 个待办事项。&/p&
对于简单的逻辑可以很方便用表达式写出来,但如果是比较复杂的逻辑,比如统计未完成数目(当然这个也可以用一个表达式搞定),可能一个表达式看起来就不太清晰。这个时候就可以用。
修改 src/components/TodoList.vue:
&!-- // ... --&
&ul class="todos"&
&!-- // ... --&
&p v-show="todos.length === 0"&
恭喜!所有的事情都已完成!
&p v-show="todos.length !== 0"&
共 &strong&{{ todos.length }}&/strong& 个待办事项。{{ completedCounts }} 个已完成,{{ notCompletedCounts }} 个未完成。
export default {
name: 'TodoList',
computed: {
completedCounts () {
return this.todos.filter(item =& item.isCompleted).length
notCompletedCounts () {
return this.todos.filter(item =& !item.isCompleted).length
上述代码中通过 completedCounts 和 notCompletedCounts 两个计算属性,来计算出已完成和未完成的 todo。虽然这两个表达式可以直接放在模板中,但表达式比较复杂,看起来也不是很清晰,所以很多时候就可以用计算属性来计算出一个最终值,然后在模板中使用。
到此,基于 Vue 的 Todo List 就完成了。在该项目中,对组件化、双向绑定、自定义事件的触发与监听、计算属性等概念进行了实践。当然,最重要的不是完成这个 Todo List 的代码,而是从实现功能的过程中举一反三,通过简单的 demo 实现,去思考如何用 vue 开发一个更大更完整的项目。
0 收藏&&|&&4
你可能感兴趣的文章
23 收藏,2.6k
15 收藏,890
55 收藏,5.4k
分享到微博?
我要该,理由是:说到React,我从一年之前就开始试着了解并且看了相关的入门教程,而且还买过一本《React:引领未来的用户界面开发框架&》拜读。React的轻量组件化的思想及其virtual-dom的这种技术创新,也算是早就有了初步了解。一来没有学的太深入,二来后来在工作中和业余项目中都没有用到,因此慢慢的就更加生疏了。
近期,因为我想把自己的开源项目能放在React、angular和vuejs中使用。先从react开始,顺手自己也重试一下React的基础知识,顺便再做一个小demo,体验一下React写程序的一些提倡的思路。经过几天的回顾学习,我也写了一个在React中集成的简单demo:
不得不说一下,React风靡世界、功能强大、适应性强,但是入门起来相当简单。反观angularjs,学习成本就比较高,我还没弄明白1.x呢,2.0已经出来了。纵然我非常努力,但是某些方面还是摆脱不了out的命运(见笑...)。
2. 基础入门
想入门React,首先你得有比较扎实的javascript语法基础以及前端开发的基础知识,否则我下面推荐的教程讲的再好,你也咂摸不出啥滋味来。所以如果你是初学者,不要被现在前端这些琳琅满目的框架、库迷惑了眼睛,以为学会了这个那个就行了&&基础不行学啥都白搭。
闲话不多扯。前人栽树后人乘凉,给大家推荐两个我看过的非常好的React入门教程,一个文字一个视频。
阮一峰老师的
这两篇教程的篇幅都不长 ,阅读加练习的话,两个晚上(正常下班吃完饭之后的剩余时间)绝对能搞定的。当然你如果具备程序员优质的熬夜技能,一晚上搞定也说不定啊,创造奇迹的同时照顾一下身体,哈哈。看完这两篇教程,你能基本了解react的设计思想,技术特点,使用的语法和技巧。做个hello word什么的,完全没啥问题的。
3. 入门之后
记得几年前上大学乃至刚毕业那会儿,无论是学java还是php还是.net的,会了语法、会写个hello world肯定不能算是入门的。当时跟hello world齐名还有一个东西叫做『留言板』。师生之间经常有这样的对话。
老师:xxx技术会用了吗?
学生:会了
老师:那写个留言板系统吧,能留言、查看、删除、回复
学生:不会
老师:....
上述的『留言板』也随着几年之前流行的bbs、论坛、校内网等没几年就河了西了(《大宅门》学的),目前用来做demo的一般都是todolist,例如backbone的官方demo就是一个todolist。
无论是『留言板』还是『todolist』,我们需要用它来表达的就是&&我们如何通过这项技术去实现基本的『增删改查』 这种能力,因为一个系统其他所有的业务逻辑操作,都是『增删改查』这几个功能的拼接。所以,我们在刚刚接触一个新东西的时候,就用它来做一个简单的todolist吧。
4. todolist
做出来大约是这样子的,很简单很丑,too 羊 too 森破 sometime native 。不过没关系,虽然它很丑,但是很温柔啊。我们只是抛开了其他内容,专注于这项技术实现的本身而已。如果你想漂亮一点,自己写一个css样式喽。
下面我将一步一步讲解如何使用React来制作出一个简单的todolist,不过还是需要你耐心把文章读完,我也尽量写的可读性强一些,不至于太乏味。
4.1 整体分析
React最大的卖点是轻量组件化。我们分析一下以上截图中的页面,如果要分组件的话,我们大约可以分成一个总组件和两个子组件。一个输入内容的组件,一个显示内容列表(带删除功能)的组件,外面再用一个总组件将两个子组件包括起来。
因此,我们的代码的整体结构大约是这么写的:
1 // TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
2 // 该组件用于将『新增』和『列表』两个组件集成起来
3 var TodoList = React.createClass({
render: function () {
&ListTodo /&
14 // TypeNew 组件用于新增数据,
15 var TypeNew = React.createClass({
render: function () {
&input type="text" placeholder="typing a newthing todo" autoComplete="off" /&
25 // ListTodo 组件用于展示列表,并可以删除某一项内容,
26 var ListTodo = React.createClass({
render: function () {
&ul id="todo-list"&
{/* 其中显示数据列表 */}
36 // 将 TodoList 组件渲染到页面
37 React.render(&TodoList /&, document.getElementById('container'));
4.2. 显示数据
下面,我们要把todolist的数据,显示到列表中,并且每个数据项后面都显示一个『删除』按钮,就像这样:
既然是展示数据,首先要考虑数据存储在哪里,来自于哪里。现在这里放一句话&&React提倡所有的数据都是由父组件来管理,通过props的形式传递给子组件来处理&&先记住,接下来再解释这句话。
上文提到,做一个todolist页面需要一个父组件,两个子组件。父组件当然就是todolist的『总指挥』,两个子组件分别用来add和show、delete。用通俗的方式讲来,父组件就是领导,两个子组件就是协助领导开展工作的,一切的资源和调动资源的权利,都在领导层级,子组件配合领导工作,需要资源或者调动资源,只能申请领导的批准。
这么说来就明白了吧。数据完全由父组件来管理和控制,子组件用来显示、操作数据,得经过父组件的批准,即&&父组件通过props的形式将数据传递给子组件,子组件拿到父组件传递过来的数据,再进行展示。
另外,根据React开发的规范,组件内部的数据由state控制,外部对内部传递数据时使用 props 。这么看来,针对父组件来说,要存储todolist的数据,那就是内部信息(本身就是自己可控的资源,而不是『领导』控制的资源),用state来存储即可。而父组件要将todolist数据传递给子组件,对子组件来说,那就是传递进来的外部信息(是『领导』的资源,交付给你来处理),需要使用props。
好了,我们再修改一下代码,用代码表述一下这个问题:
1 // TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
2 // 该组件用于将『新增』和『列表』两个组件集成起来
3 var TodoList = React.createClass({
// 初始化数据,todolist的数据由state来控制
getInitialState: function () {
todolist: []
render: function () {
集成 ListTodo 组件
todo - 将todolist的数据传入到组件,用于组件展示数据
&ListTodo todo={this.state.todolist} /&
24 // TypeNew 组件用于新增数据,
25 var TypeNew = React.createClass({
// 此处省略 ... 字
29 // ListTodo 组件用于展示列表,并可以删除某一项内容,
30 var ListTodo = React.createClass({
render: function () {
&ul id="todo-list"&
// this.props.todo 获取父组件传递过来的数据
// {/* 遍历数据 */}
this.props.todo.map(function (item, i) {
&label&{item}&/label&
&button&delete&/button&
51 // 将 TodoList 组件渲染到页面
52 React.render(&TodoList /&, document.getElementById('container'));
4.3 新增数据
刚才都把数据展示讲完了,但是想展示一下,目前还没有数据呢,那就新增一个吧。如下图:
根据刚才的拐弯抹角、高谈阔论、旁征博引的那几句话,我们知道,子组件得到数据后,就需要将新数据添加到todolist的数据中。而todolist的数据是由父组件来管理的,子组件不能说改就改呀,得申请父组件的允许和同意呀。因此,我们需要让父组件开放一个可以修改数据的接口,然后将这个接口作为props传递给子组件,让其能修改数据。
另外,子组件调用父组件的接口对todolist数据进行修改了之后,相当于修改了React对象的state数据,此时就会触发React的自动更新(就是通过virtual-dom对比,然后更新真实的dom那一套),React会将UI实时随着数据更新,就不用我们操心了,这也是React比较强大的地方之一。
因此,代码将改为:
1 // TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
2 // 该组件用于将『新增』和『列表』两个组件集成起来
3 var TodoList = React.createClass({
// 初始化数据,todolist的数据由state来控制
getInitialState: function () {
todolist: []
// 接收一个传入的数据,并将它实时更新到组件的 state 中,以便组件根据数据重新render
// 只要改变了 state ,react自动执行 reader 计算
handleChange: function (rows) {
this.setState({
todolist: rows
render: function () {
集成 TypeNews 组件,传入两个属性 onAdd 和 todo
todo - 将todolist的数据传入到组件,当新增时,更新todolist数据
将 handleChange 函数传入到组件,新增时,用它来处理最新的todolist数据
&TypeNew onAdd={this.handleChange} todo={this.state.todolist} /&
集成 ListTodo 组件
todo - 将todolist的数据传入到组件,用于组件展示数据
&ListTodo todo={this.state.todolist} /&
36 // TypeNew 组件用于新增数据,它需要 todo 和 onAdd 两个属性,上文已经提到过
37 // 基本逻辑是:当从 input 中获取数据时,将新数据 push 到todo中,
38 // 然后使用 onAdd 调用 TodoList 的 handleChange 来更新state,然后react自动render
39 var TypeNew = React.createClass({
handleAdd: function (e) {
e.preventDefault();
// 通过 refs 获取dom元素,然后获取输入的内容
var inputDom = this.refs.inputnew.getDOMNode();
var newthing = inputDom.value.trim();
// 获取传入的todolist数据
var rows = this.props.
if (newthing !== '') {
// 更新数据,并使用 onAdd 更新到 TodoList 组件的 state 中
rows.push(newthing);
this.props.onAdd(rows);
inputDom.value = '';
render: function () {
// form submit 时,触发 handleAdd 事件
&form onSubmit={this.handleAdd}&
&input type="text" ref="inputnew" id="todo-new" placeholder="typing a newthing todo" autoComplete="off" /&
64 // ListTodo 组件用于展示列表,并可以删除某一项内容,
65 var ListTodo = React.createClass({
render: function () {
&ul id="todo-list"&
// this.props.todo 获取父组件传递过来的数据
// {/* 遍历数据 */}
this.props.todo.map(function (item, i) {
&label&{item}&/label&
&button&delete&/button&
86 // 将 TodoList 组件渲染到页面
87 React.render(&TodoList /&, document.getElementById('container'));
4.4 删除数据
删除数据和新增数据,逻辑上是一样的,都是需要父组件提供一个修改数据的接口,通过props形式传递给子组件,然后让子组件来调用。就不再赘述了,直接上代码,注意看注释:
// TodoList 组件是一个整体的组件,最终的React渲染也将只渲染这一个组件
// 该组件用于将『新增』和『列表』两个组件集成起来,并且存储 todolist 的数据
var TodoList = React.createClass({
// 初始化数据
getInitialState: function () {
todolist: []
// 接收一个传入的数据,并将它实时更新到组件的 state 中,以便组件根据数据重新render
// 只要改变了 state ,react自动执行 reader 计算
handleChange: function (rows) {
this.setState({
todolist: rows
render: function () {
集成 TypeNews 组件,传入两个属性 onAdd 和 todo
todo - 将todolist的数据传入到组件,当新增时,更新todolist数据
将 handleChange 函数传入到组件,新增时,用它来处理最新的todolist数据
&TypeNew onAdd={this.handleChange} todo={this.state.todolist} /&
集成 ListTodo 组件,传入两个属性 onDel 和 todo
todo - 将todolist的数据传入到组件,当删除时,更新todolist数据
onDel - 将 handleChange 函数传入到组件,删除时,用它来处理最新的todolist数据
&ListTodo onDel={this.handleChange} todo={this.state.todolist} /&
// TypeNew 组件用于新增数据,它需要 todo 和 onAdd 两个属性,上文已经提到过
// 基本逻辑是:当从 input 中获取数据时,将新数据 push 到todo中,
// 然后使用 onAdd 调用 TodoList 的 handleChange 来更新state,然后react自动render
var TypeNew = React.createClass({
handleAdd: function (e) {
e.preventDefault();
// 通过 refs 获取dom元素,然后获取输入的内容
var inputDom = this.refs.inputnew.getDOMNode();
var newthing = inputDom.value.trim();
// 获取传入的todolist数据
var rows = this.props.
if (newthing !== '') {
// 更新数据,并使用 onAdd 更新到 TodoList 组件的 state 中
rows.push(newthing);
this.props.onAdd(rows);
inputDom.value = '';
render: function () {
// form submit 时,触发 handleAdd 事件
&form onSubmit={this.handleAdd}&
&input type="text" ref="inputnew" id="todo-new" placeholder="typing a newthing todo" autoComplete="off" /&
// ListTodo 组件用于展示列表,并可以删除某一项内容,它有 noDel todo 两个属性,上文已经提到过
// 它的基本逻辑是:遍历 todo 的内容,生成数据列表和删除按钮
// 对某一项执行删除时,想将 todo 中的数据删除,
// 然后通过 onDel 事件调用 TodoList 的 handleChange 来更新state,然后react自动render
var ListTodo = React.createClass({
handleDel: function (e) {
var delIndex = e.target.getAttribute('data-key');
// 更新数据,并使用 onDel 更新到 TodoList 的 state 中,以便 React自动render
this.props.todo.splice(delIndex, 1);
this.props.onDel(this.props.todo);
render: function () {
&ul id="todo-list"&
// {/* 遍历数据 */}
this.props.todo.map(function (item, i) {
&label&{item}&/label&
&button className="destroy" onClick={this.handleDel} data-key={i}&delete&/button&
}.bind(this)) // {/* 绑定函数的执行this - 以便 this.handleDel */}
// 将 TodoList 组件渲染到页面
React.render(&TodoList /&, document.getElementById('container'));
入门React的基本语法和使用比较简单,但是想要了解它的工作过程和基本的设计思想,还是需要一点时间的。接下来,在大型系统中使用React肯定又需要更多的时间,你可能还会遇到很多坑,等着你去填。
但是无论现在用还是不用,咱们都不能落伍,该学的还是得掌握一些比较好。大家共勉。
最后,此文章参考了& 感谢本文作者
-------------------------------------------------------------------------------------------------------------
欢迎关注我的教程:
《》《》《》
------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------
阅读(...) 评论()登录注册涉及哪些功能?&br&&ol&&li&基础 mvc 生成页面&br&&/li&&li&处理登录,注册表单&/li&&li&登录状态&/li&&li&用户数据存储&/li&&li&找回密码&/li&&/ol&对下来要用到的模块:&br&&ol&&li&&a href=&///?target=https%3A///koajs/ejs& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&koajs/ejs · GitHub&i class=&icon-external&&&/i&&/a&、&a href=&///?target=https%3A///alexmingoia/koa-router& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&alexmingoia/koa-router · GitHub&i class=&icon-external&&&/i&&/a& 实现 mvc&br&&/li&&li&&a href=&///?target=https%3A///koajs/bodyparser& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&koajs/bodyparser · GitHub&i class=&icon-external&&&/i&&/a& 用来处理登录注册的 post 上来的 HTTP body 中的数据&br&&/li&&li&&a href=&///?target=https%3A///expressjs/cookie-parser& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&expressjs/cookie-parser · GitHub&i class=&icon-external&&&/i&&/a&&a href=&///?target=https%3A///koajs/session& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&koajs/session · GitHub&i class=&icon-external&&&/i&&/a& cookie 解析以及基于 cookie 的 session 管理,用来保存用户的登录状态,也可以使用 &a href=&///?target=https%3A///Chilledheart/koa-session-redis& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Chilledheart/koa-session-redis · GitHub&i class=&icon-external&&&/i&&/a& 来把 session 保存在 redis 中,等等&br&&/li&&li&&a href=&///?target=https%3A///Automattic/mongoose& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Automattic/mongoose · GitHub&i class=&icon-external&&&/i&&/a& 数据库,保存用户信息&br&&/li&&li&&a href=&///?target=https%3A///andris9/Nodemailer& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&andris9/Nodemailer · GitHub&i class=&icon-external&&&/i&&/a& 发邮件&br&&/li&&li&整个应用的文件划分可参考:&a href=&///?target=https%3A///gusnips/node-koa-mvc& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&gusnips/node-koa-mvc · GitHub&i class=&icon-external&&&/i&&/a&&/li&&/ol&
登录注册涉及哪些功能? 基础 mvc 生成页面 处理登录,注册表单登录状态用户数据存储找回密码对下来要用到的模块: 、 实现 mvc
用来处理登录注册的 post 上来的 HTTP body…
&figure&&img src=&/50/v2-f68f2fe7647ebd627f5ce_b.jpg& data-rawwidth=&894& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&894& data-original=&/50/v2-f68f2fe7647ebd627f5ce_r.jpg&&&/figure&&p&选择 React 这条路,于我它简单好用,于团队它活跃的生态圈与层出不穷的优秀解决方案给予进步。我们一直在坚持在这条路上作探索与学习于今。&/p&&br&&p&专栏写作近一载,积累了 24 篇沉淀及分享,非常感谢朋友们的支持。此时,我即将出版这一年对此的总结,就是题图上的这本书 -《深入 React 技术栈》。&/p&&p&非常幸运,请到了在 React 实践方面的先驱 &a href=&/people/85dedf29b4249b91cfd5& data-hash=&85dedf29b4249b91cfd5& class=&member_mention& data-editable=&true& data-title=&@郭达峰& data-hovercard=&p$b$85dedf29b4249b91cfd5&&@郭达峰&/a& 写这本书的序,也请到了 &a href=&/people/20fdd386a6e59d178b8fe14e2863cb40& data-hash=&20fdd386a6e59d178b8fe14e2863cb40& class=&member_mention& data-editable=&true& data-title=&@张克军& data-hovercard=&p$b$20fdd386a6e59d178b8fe14e2863cb40&&@张克军&/a&&a href=&/people/0d9b98af12015c94cff646a6fc0773b5& data-hash=&0d9b98af12015c94cff646a6fc0773b5& class=&member_mention& data-editable=&true& data-title=&@寸志& data-hovercard=&p$b$0d9b98af12015c94cff646a6fc0773b5&&@寸志&/a& 和阮一峰这三位大牛推荐本书,当然还有来自不同地方的一些朋友在百忙之中审阅,一并表示感谢。&/p&&p&下面我谈下本书出版的目的,主要有哪些内容和后续。&/p&&br&&h2&1. 本书出版目的&/h2&&p&摘录前言中的一段文字:&br&&/p&&blockquote&目前,不论在国内,还是在国外,已经有一些入门的 React 图书,它们大多在介绍基本概念,那些内容可以让你方便地进入 React 世界。但本书除了详细阐述基本概念外,还会帮助你从了解 React 到熟悉其原理,从探索 Flux 应用架构的思想到精通 Redux 应用架构,帮助你思考 React 给前端界带来的价值,React 今天是一种思想,希望通过解读它,能够让读者有&b&自学&/b&的能力。&/blockquote&&p&本书内容一部分是从『专栏』文章的整理提炼与总结,你会看到在目录中有好些章节出现过在专栏中,但在内容上进行了纠错与升级,还有很多因为需要前后关联去深入探索的内容都是重新写的,旨在系统的讲述 React 与其技术栈的使用及其原理。&br&&/p&&p&React、Flux、Redux 本身都不复杂,还在于了解它们的运行的原理并学以自用。因此,本书不同于实践类中有大量的篇幅来讲例子,而总是先来讲述这个特性的来源或是原理,究其所以然。自然,你不论是不是在学 React,总可以从中了解些编程思想。对前端初学者来说会有一定的学习成本,无妨,我相信从一开始阅读还是可以深入下去的。&/p&&br&&p&如果你需要一个可以跑得起来的 project,那么你会接触到 babel、webpack、eslint、karma 等一系列的工具,对于初学者这是一个恶梦。我不建议一头扎进这个漩涡里,在 Github 上有大量的 starterkit 可以跑,对于初学,不要把时间浪费在配置上。等你深入应用的开发,自然都会慢慢了解。&/p&&h2&2. 专栏内容的改进与升级&/h2&&p&专栏的写作由小伙伴们专研或实践总结写成,主题分散,书中内容对此都作了细致的整理与串连,还有大量的新写的内容:&/p&&p&1. 方方面面地从讲解 React 的&b&基础知识到高阶知识&/b&,包括大家关心的组件间通信、组件间抽象、性能优化等内容。尤其是第一二章通过 Tabs 组件例子述说组件化的一些方法,有纯粹的知识体系,也有实践的内容。&/p&&p&2. 源码分析一章升级了内容,所有示例都基于 &b&React 15.0&/b& 而写,当然全书都是。这算是一个比较大的更新,对源码感兴趣的同学可以认真阅读。&/p&&br&&p&3. 结合完整的示例讲解&b& Flux、Redux&/b& 的使用,并分析到源码层面,帮助读者可以理解他们的原理,并真正的运用好它们。&/p&&p&4. React 结合&b&可视化&/b&的内容丰富的使用示例,尤其是讲到了怎么去封装像 Recharts 的可视库(PS. Recharts 相关问题请请教作者 &a href=&/people/1f108db216b303ecc4d39e8bef9f7f03& data-hash=&1f108db216b303ecc4d39e8bef9f7f03& class=&member_mention& data-title=&@琼玖& data-editable=&true& data-hovercard=&p$b$1f108db216b303ecc4d39e8bef9f7f03&&@琼玖&/a&)。可视化与前端领域关系甚密,大多数的原理均在于图像与图形的算法上,框架层面可讲的内容不多。&/p&&p&在审校的过程中,FB 公布了 React VR 的信息,FB 真正想把 React 打造成全端的界面库,对于我们来真是一个重磅新闻。&/p&&br&&h2&3. 遗憾与感想&/h2&&p&本书出版并不是专栏的『终结』,对我们来说只是一个前情总结。前端每天有意思的想法层出不穷,还有很多领域要去玩味。&/p&&p&就目前最流行的移动端方面本书涉及较少,本书还是以 PC 端的组件化为基础在写。索性 React Native 才是正规军,有兴趣的同学可以看 RN 方面的书籍认真学习。&/p&&p&另外,在写作期间 React 15 的小版本有几次小版本的升级,更是有 Fiber 等重大更新,这是无法避免的情况。之间的更新会反馈到专栏上,此外也希望读者能够举一反三,学习到精髓。&/p&&br&&p&关于 Redux 上讲的内容有很多,总体涉及的库会很多,难免会对新手造成学习上的困扰。此外还有很多流行库没有具体涉及到,如 redux-sagas,redux-observable,Mobx 等。&/p&&p&本人阅历尚浅,难免出现疏漏和错误,对内容有所不满或批评意见,欢迎通过知乎或邮件不吝指正。&br&&/p&&p&最后,非常感谢王老师和图灵出版社的支持。&/p&&p&&b&下周会对我进行一个访谈,请有兴趣的朋友上 &a href=&/?target=http%3A//.cn/article/273543& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&图灵社区&i class=&icon-external&&&/i&&/a& 提问。&/b&&/p&&p&附上书已经上架的地址:&/p&&p&&a href=&/?target=https%3A///%25E6%25B7%25B1%25E5%React%25E6%258A%%259C%25AF%25E6%25A0%%E5%25B1%25B9/dp/B01MQIE77V/ref%3Dsr_1_1%3Fie%3DUTF8%26qid%3D%26sr%3D8-1%26keywords%3D%25E6%25B7%25B1%25E5%React%25E6%258A%%259C%25AF%25E6%25A0%2588& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&亚马逊地址&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&/?target=http%3A//product./5007398& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&china-pub 地址&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&/?target=https%3A///.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&京东地址&i class=&icon-external&&&/i&&/a&&/p&&p&&a href=&/?target=http%3A///.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&当当地址&i class=&icon-external&&&/i&&/a&&/p&
选择 React 这条路,于我它简单好用,于团队它活跃的生态圈与层出不穷的优秀解决方案给予进步。我们一直在坚持在这条路上作探索与学习于今。 专栏写作近一载,积累了 24 篇沉淀及分享,非常感谢朋友们的支持。此时,我即将出版这一年对此的总结,就是题图上…
&p&我们(凡普信贷)的移动端页面正在使用 vue2.0 重构,在基于 vue-cli 脚手架生成项目模板基础上做了些改动,加入了 vue-router ,vuex 等配套设施,本地 dev server 中加入了接口 mock 功能,还增加一个 build server 来预览 build 结果页面,前后端通过 spa 的方式实现分离,并相应做了分离后的联调,部署方案。在这里俺也对整个过程简单做个介绍吧。&/p&&p&&b&目录结构&/b&&/p&&div class=&highlight&&&pre&&code class=&language-text&&
├── index.html
├── build
构建脚本目录
├── build-server.js
运行本地构建服务器,可以访问构建后的页面
├── build.js
生产环境构建脚本
├── dev-client.js
开发服务器热重载脚本,主要用来实现开发阶段的页面自动刷新
├── dev-server.js
运行本地开发服务器
├── utils.js
构建相关工具方法
├── webpack.base.conf.js
wabpack基础配置
├── webpack.dev.conf.js
wabpack开发环境配置
└── webpack.prod.conf.js
wabpack生产环境配置
├── config
├── dev.env.js
开发环境变量
├── index.js
项目配置文件
├── prod.env.js
生产环境变量
└── test.env.js
测试环境变量
├── mock
mock数据目录
└── hello.js
├── package.json
npm包配置文件,里面定义了项目的npm脚本,依赖包等信息
├── src
项目源码目录
├── main.js
入口js文件
├── app.vue
├── components
公共组件目录
└── title.vue
├── assets
资源目录,这里的资源会被wabpack构建
└── images
└── logo.png
├── routes
└── index.js
├── store
应用级数据(state)
└── index.js
└── views
├── hello.vue
└── notfound.vue
├── static
纯静态资源,不会被wabpack构建。
└── test
测试文件目录(unit&e2e)
└── unit
├── index.js
├── karma.conf.js
karma配置文件
└── specs
单测case目录
└── Hello.spec.js
&/code&&/pre&&/div&&br&&p&&b&快速开始&/b&&/p&&div class=&highlight&&&pre&&code class=&language-text&&git clone /hanan198501/vue-spa-template.git
cd vue-spa-template
cnpm install
npm run dev
&/code&&/pre&&/div&&br&&p&&b&命令列表&/b&&/p&&div class=&highlight&&&pre&&code class=&language-text&&#开启本地开发服务器,监控项目文件的变化,实时构建并自动刷新浏览器,浏览器访问 http://localhost:8081
npm run dev
#使用生产环境配置构建项目,构建好的文件会输出到 &dist& 目录,
npm run build
#运行构建服务器,可以查看构建的页面
npm run build-server
#运行单元测试
npm run unit
&/code&&/pre&&/div&&br&&p&&b&前后端分离&/b&&/p&&p&项目基于 spa 方式实现前后端分离,后端将所有 url 都返回到同一个 jsp 页面(由前端提供),此 jsp 页面也是前端的入口页面。然后路由由前端控制(基于vue-router),根据不同的 url 加载相应数据和组件进行渲染。&/p&&p&&b&接口 mock&/b&&/p&&p&前后端分离后,开发前需要和后端同学定义好接口信息(请求地址,参数,返回信息等),前端通过 mock 的方式,即可开始编码,无需等待后端接口 ready。项目的本地开发服务器是基于
express 搭建的,通过 express 的中间件机制,我们可以很方便的添加接口 mock 功能:&/p&&p&在 build/dev-server.js 中新增接口 mock 处理:&/p&&div class=&highlight&&&pre&&code class=&language-text&&// mock api requests
var mockDir = path.resolve(__dirname, '../mock');
fs.readdirSync(mockDir).forEach(function (file) {
var mock = require(path.resolve(mockDir, file));
app.use(mock.api, mock.response);
&/code&&/pre&&/div&&br&&p&其中,mock 目录下可能有个文件内容如下,描述了一个接口的数据信息:&/p&&div class=&highlight&&&pre&&code class=&language-text&&module.exports = {
// 接口地址
api: '/api/hello',
// 返回数据
response: function (req, res) {
res.send(`
&p&hello vue!&/p&
&/code&&/pre&&/div&&br&&p&&b&组件化&/b&&/p&&p&整个应用通过 vue 组件的方式搭建起来,通过 vue-router 控制相应组件的展现,组件树结构如下:&/p&&div class=&highlight&&&pre&&code class=&language-text&&
根组件(整个应用只有一个)
├──view1.vue
页面级组件,放在 views 目录里面,有子组件时,可以建立子目录
├──component1.vue
功能组件,公用的放在 components 目录,否则放在 views 子目录
├──component2.vue
└──component3.vue
├──view2.vue
├──component1.vue
└──component4.vue
└──view3.vue
├──component5.vue
&/code&&/pre&&/div&&br&&p&&b&单元测试&/b&&/p&&p&可以为每个组件编写单元测试,放在 test/unit/specs 目录下面, 单元测试用例的目录结构建议和测试的文件保持一致(相对于src),每个测试用例文件名以 .spec.js 结尾。
执行 npm run unit 时会遍历所有的 spec.js 文件,产出测试报告在 test/unit/coverage 目录。&/p&&p&&b&前后端联调&/b&&/p&&p&前后端分离后,由于服务端和前端的开发环境处于2台不同的机器上,整个联调过程,入口页面需要引用前端机器的静态资源,又要调用后端机器的异步接口。根据入口页面的位置,我们可以使用不同的联调方案:&/p&&p&1. 入口页面在前端机器:&/p&&p&通过在本地 dev-server 中使用 &a href=&///?target=https%3A///chimurai/http-proxy-middleware& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/chimurai/htt&/span&&span class=&invisible&&p-proxy-middleware&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& 中间件把接口请求代理到后端机器,vue-cli 生成的 dev-server 中已经自带了这个功能:&/p&&div class=&highlight&&&pre&&code class=&language-text&&// proxy api requests
Object.keys(proxyTable).forEach(function (context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = { target: options }
app.use(proxyMiddleware(context, options))
&/code&&/pre&&/div&&p&最好通过启动 dev-server 时传入一个参数来控制是否打开代理功能,这样可以避免开发阶段覆盖掉我们的 mock 配置。&/p&&p&2. 入口页面在后端机器:
后端工程里面的入口 jsp 中引用的 js 文件地址需要指向前端环境中的地址,联调时才能显示最新的修改。主要有2种实现方式:
1) jsp 文件引用一个固定域名(如 debughost)的 js 文件, 后端机器上通过修改此域名的ip指向前端机器,达到引入前端环境 js 的目的。
2) jsp 文件通过获取一个 url 参数(如 debughost)的值,这个值为前端机器的 ip 地址,再动态的插入一个 script 标签引入这个 ip 的前端 js 文件。&/p&&p&举个例子,假设前端机器的 ip 为 172.16.36.90,需要加载前端的js文件地址为:&a href=&///?target=http%3A//172.16.36.90%3A8081/main.js& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&172.16.36.90:8081/main.&/span&&span class=&invisible&&js&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&, 那么后端同学的机器中需要在 host 文件加一条记录:&/p&&div class=&highlight&&&pre&&code class=&language-text&&172.16.36.90 debughost
&/code&&/pre&&/div&&br&&p&而入口 jsp 页面中则通过以下代码开加载前端js: &/p&&div class=&highlight&&&pre&&code class=&language-text&&
var debughost = 'debughost';
location.search.substr(1).split('&').forEach(function (item) {
var arr = item.split('=');
var key = arr[0];
var value = arr[1];
if (key === 'debughost') {
debughost =
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'http://' + debughost + ':8081/main.js?' + (new Date()).getTime();
document.head.appendChild(script);
&/code&&/pre&&/div&&p&这样,jsp 页面默认会加载 &a href=&///?target=http%3A//debughost%3A8081/main.js& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&http://&/span&&span class=&visible&&debughost:8081/main.js&/span&&span class=&invisible&&&/span&&i class=&icon-external&&&/i&&/a&这个文件。
此外,如果不想用 debughost 这个域名的 js 文件,访问 jsp 时候还可以通过 url 带入 debughost 参数来指定前端 ip 。&/p&&p&&b&部署方案
分离后前后端代码会存放在2个单独的 git 仓库中,构建过程也是分开的。后端构建时,需要依赖前端的构建结果。具体流程如下:&/p&&p&1. 拉取前端项目代码
2. 构建前端(构建结果放在dist目录)
3. 拉取后端代码
4. 将前端的构建结果(dist目录里的文件)复制到后端工程中
5. 构建后端&/p&&p&此过程可以借助 jenkins 配置,或者,让运维同学配合修改部署脚本。&/p&&p&最终的项目模板会是这样:&a href=&///?target=https%3A///annnhan/vue-spa-template& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&annnhan/vue-spa-template&i class=&icon-external&&&/i&&/a&&/p&&p&==================&/p&&p&vue交流群:&/p&
我们(凡普信贷)的移动端页面正在使用 vue2.0 重构,在基于 vue-cli 脚手架生成项目模板基础上做了些改动,加入了 vue-router ,vuex 等配套设施,本地 dev server 中加入了接口 mock 功能,还增加一个 build server 来预览 build 结果页面,前后端通过 sp…
&figure&&img src=&/50/v2-b18e5b739adc135aa20591_b.jpg& data-rawwidth=&1728& data-rawheight=&1080& class=&origin_image zh-lightbox-thumb& width=&1728& data-original=&/50/v2-b18e5b739adc135aa20591_r.jpg&&&/figure&&h2&前言&/h2&&p&前端生态日新月异, flux 已经过时(恩然而我都还没有来得及学),redux 成了状态管理的标配,每一个前端开发者都应该学习。然而因为 redux 的多种概念着实让新手费解,很多人沉浸在 react 全家桶的配置无法自拔。所以我试着丢开 react,理清楚 redux 本身的几个主要概念,并争取循序渐进的讲解 redux 各种概念的用法和意义。&/p&&h2&从零开始&/h2&&p&一次性引入过多的新东西会让人不知所措,所以我们尽可能的减轻依赖。同时为了避免新人掉进环境配置(npm install)的大坑,我们直接采用&a href=&/?target=https%3A//jsfiddle.net/& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Create a new fiddle&i class=&icon-external&&&/i&&/a& 来实践。&/p&&p&新建一个 jsfiddle,将语言设为 Babel,在左侧的 External Resources 直接引入 redux: &a href=&/?target=https%3A///ajax/libs/redux/3.6.0/redux.min.js& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/aj&/span&&span class=&invisible&&ax/libs/redux/3.6.0/redux.min.js&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&。记得打开开发者工具,方便看到报错和后面的输出。&/p&&p&目前为止这么多足以,不需要引入 react 或者任何其他东西。&/p&&h2&最简单的例子&/h2&&p&很多教程喜欢一开始就把 redux 里的几个概念拆开讲,每个概念又贴上大段大段的代码,弄得人一头雾水。而我们因为暂时丢掉了 react,得以用一个足够简单却完整的例子讲解 redux 的工作流程。&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&// 这里是因为直接引入了 js 才用这种写法,当本地使用 webpack 时我们应该使用 import { createStore, applyMiddleware
} from 'redux'
const { createStore, applyMiddleware } = Redux
const reducer = (state, action) =& {
let result = state
switch (action.type) {
case &INC&:
result += action.payload
case &DEC&:
result -= action.payload
return result
const logger = store =& next =& action =& {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
const store = createStore(reducer, 0, applyMiddleware(logger))
store.dispatch({type: 'INC', payload: 1})
store.dispatch({type: 'INC', payload: 2})
store.dispatch({type: 'DEC', payload: 5})
&/code&&/pre&&/div&&p&观察控制台,我们得到的输出是:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&dispatching&/span& &span class=&nb&&Object&/span& &span class=&p&&{&/span&&span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s2&&&INC&&/span&&span class=&p&&,&/span& &span class=&nx&&payload&/span&&span class=&o&&:&/span& &span class=&mi&&1&/span&&span class=&p&&}&/span&
&span class=&nx&&next&/span& &span class=&nx&&state&/span& &span class=&mi&&1&/span&
&span class=&nx&&dispatching&/span& &span class=&nb&&Object&/span& &span class=&p&&{&/span&&span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s2&&&INC&&/span&&span class=&p&&,&/span& &span class=&nx&&payload&/span&&span class=&o&&:&/span& &span class=&mi&&2&/span&&span class=&p&&}&/span&
&span class=&nx&&next&/span& &span class=&nx&&state&/span& &span class=&mi&&3&/span&
&span class=&nx&&dispatching&/span& &span class=&nb&&Object&/span& &span class=&p&&{&/span&&span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s2&&&DEC&&/span&&span class=&p&&,&/span& &span class=&nx&&payload&/span&&span class=&o&&:&/span& &span class=&mi&&5&/span&&span class=&p&&}&/span&
&span class=&nx&&next&/span& &span class=&nx&&state&/span& &span class=&o&&-&/span&&span class=&mi&&2&/span&
&/code&&/pre&&/div&&p&这个短小的例子已经用到了我们要讲的几个主要概念,reducer、store、middleware、action。&/p&&p&store 负责状态的存储,需要注意的是,在 redux 中只有一个 store,而如何在一个 store 中维护众多的状态,我们后面会提到。&/p&&p&action 则类似触发事件,使用 store.dispatch 发送出去,必须要有一个 type 属性。同时 action 也可以附带其他数据,例如上面代码里的 payload。&/p&&p&reducer 在每次有新的 action 时被触发,则只做一件事情,就是收到当前的 state 和 action,并且返回一个全新的 state。&/p&&p&middleware 则发生在每次 action dispatch 后,reducer 触发前,在 middleware 中调用 next(action) 才会将 action 传递给 reducer 并且返回新的结果。这种设计可以让我们很灵活的实现一些针对所有 action 的功能,例如 log 或者针对特定类型的 action 做一些处理等。&/p&&p&这样,整个 redux 就相当于一个状态机,新的 action 被触发,经过 middleware,由 reducer 产生新的状态。&/p&&p&我特意没有把他们画成一个循环,因为 state 是不应该改变的,只是由 reducer 返回新的 state。也正以为这个原因,我们在使用 redux 开发应用时,可以很轻松地跟踪到状态的变化,将状态直接 revert 到某个点,撤销这一类的功能非常容易实现。&/p&&figure&&img src=&/v2-763da8efa_b.png& data-rawwidth=&436& data-rawheight=&513& class=&origin_image zh-lightbox-thumb& width=&436& data-original=&/v2-763da8efa_r.png&&&/figure&&br&&h2&immutable&/h2&&p&上面我们提到,state 不会改变,而是由 reducer 返回新的 state。这样我们称 state 是 immutable 的。但是 JavaScript 本身没有提供这个特性,我们需要使用一些和平时不同的写法来达到这个目的。在更详细地介绍 redux 的各个概念前,我们先来了解一下 immutable。&/p&&p&immutable 和 const 不是一个含义,const 指的是标识符对应的值不可被改变,例如&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&const&/span& &span class=&nx&&a&/span& &span class=&o&&=&/span& &span class=&mi&&3&/span&
&span class=&nx&&a&/span& &span class=&o&&=&/span& &span class=&mi&&4&/span&
&/code&&/pre&&/div&&p&会抛出一个异常,表示 a 不能被修改。&/p&&p&而 immutable 则指的是值本身不能被修改,例如在 JavaScript 中的 string 和 number 等基本类型都是不能被修改的,但 object 和 array 则不是。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&a&/span& &span class=&o&&=&/span& &span class=&s1&&'I am string'&/span&
&span class=&nx&&b&/span& &span class=&o&&=&/span& &span class=&nx&&a&/span&
&span class=&nx&&b&/span& &span class=&o&&=&/span& &span class=&s1&&'Oh yes'&/span&
&span class=&nx&&c&/span& &span class=&o&&=&/span& &span class=&p&&{&/span&&span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&s2&&&Li&&/span&&span class=&p&&}&/span&
&span class=&nx&&d&/span& &span class=&o&&=&/span& &span class=&nx&&c&/span&
&span class=&nx&&d&/span&&span class=&p&&.&/span&&span class=&nx&&name&/span& &span class=&o&&=&/span& &span class=&s1&&'K'&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&a&/span&&span class=&p&&)&/span& &span class=&c1&&// &I am string&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&b&/span&&span class=&p&&)&/span& &span class=&c1&&// &Oh yes&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&c&/span&&span class=&p&&)&/span& &span class=&c1&&// {name: &K&}
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&d&/span&&span class=&p&&)&/span& &span class=&c1&&// {name: &K&}
&/code&&/pre&&/div&&p&可以看到对象的内容被改变了。所以在处理对象和数组的时候,我们需要注意一些,只使用没有副作用(side effect)的操作:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&c&/span& &span class=&o&&=&/span& &span class=&p&&{&/span&&span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&s2&&&Li&&/span&&span class=&p&&,&/span& &span class=&nx&&age&/span&&span class=&o&&:&/span& &span class=&mi&&17&/span&&span class=&p&&}&/span&
&span class=&nx&&d&/span& &span class=&o&&=&/span& &span class=&p&&{...&/span&&span class=&nx&&c&/span&&span class=&p&&,&/span& &span class=&nx&&name&/span&&span class=&o&&:&/span& &span class=&s2&&&K&&/span&&span class=&p&&}&/span&
&span class=&nx&&e&/span& &span class=&o&&=&/span& &span class=&p&&[&/span&&span class=&mi&&2&/span&&span class=&p&&,&/span& &span class=&mi&&4&/span&&span class=&p&&,&/span& &span class=&mi&&6&/span&&span class=&p&&]&/span&
&span class=&nx&&f&/span& &span class=&o&&=&/span& &span class=&nx&&e&/span&&span class=&p&&.&/span&&span class=&nx&&slice&/span&&span class=&p&&(&/span&&span class=&mi&&0&/span&&span class=&p&&,&/span&&span class=&mi&&1&/span&&span class=&p&&).&/span&&span class=&nx&&concat&/span&&span class=&p&&(&/span&&span class=&nx&&e&/span&&span class=&p&&.&/span&&span class=&nx&&slice&/span&&span class=&p&&(&/span&&span class=&mi&&2&/span&&span class=&p&&))&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&c&/span&&span class=&p&&)&/span& &span class=&c1&&// {name: &Li&, age: 17}
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&d&/span&&span class=&p&&)&/span& &span class=&c1&&// {name: &K&, age: 17}
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&e&/span&&span class=&p&&)&/span& &span class=&c1&&// [2, 4, 6]
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&nx&&f&/span&&span class=&p&&)&/span& &span class=&c1&&// [2, 6]
&/code&&/pre&&/div&&p&当然,我们也可以使用 immutable.js 等库来解决这个问题。&/p&&h2&一步步改进&/h2&&p&了解了 immutable 后我们可以学习更加复杂的 reducer。上面的例子中整个 state 只是一个数字,而这次我们存储更加复杂的数据。以 github user API 为例( &a href=&/?target=https%3A///users/codefalling& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/users/co&/span&&span class=&invisible&&defalling&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& ),我们来写一段代码来获取指定用户头像。&/p&&h3&初始化 store&/h3&&p&我们将初始化 store 的地方改为:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&const&/span& &span class=&nx&&store&/span& &span class=&o&&=&/span& &span class=&nx&&createStoreWithMiddleware&/span&&span class=&p&&(&/span&&span class=&nx&&reducer&/span&&span class=&p&&,&/span& &span class=&p&&{&/span&
&span class=&nx&&username&/span&&span class=&o&&:&/span& &span class=&kc&&null&/span&&span class=&p&&,&/span&
&span class=&nx&&info&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&fetching&/span&&span class=&o&&:&/span& &span class=&kc&&null&/span&&span class=&p&&,&/span&
&span class=&nx&&fetched&/span&&span class=&o&&:&/span& &span class=&kc&&null&/span&&span class=&p&&,&/span&
&span class=&p&&}&/span&
&span class=&p&&},&/span&
&span class=&nx&&applyMiddleware&/span&&span class=&p&&(&/span&&span class=&nx&&logger&/span&&span class=&p&&))&/span&
&/code&&/pre&&/div&&h3&异步 action&/h3&&p&看起来我们只需要一个获取头像的 action,但其实我们应该还需要一个获取成功的 action,因为获取头像不会马上得到数据,而请求返回后应该更新 state。&/p&&p&在这里我们使用 redux-thunk, (因为 cdn 上找不到 redux-promise 之类的。。)。&/p&&p&同样的,在左侧的 External Resources 中引入&a href=&/?target=https%3A///ajax/libs/redux-thunk/2.1.0/redux-thunk.min.js& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/aj&/span&&span class=&invisible&&ax/libs/redux-thunk/2.1.0/redux-thunk.min.js&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a& 。&/p&&p&然后只要将 applyMiddleware(logger) 改为 applyMiddleware(logger, ReduxThunk.default)。&/p&&p&然后我们就能写出类似&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&dispatch&/span&&span class=&p&&(&/span&&span class=&nx&&dispatch&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&nx&&dispatch&/span&&span class=&p&&({&/span& &span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s1&&'FETCH_START'&/span& &span class=&p&&})&/span&
&span class=&nx&&fetch&/span&&span class=&p&&(&/span&&span class=&s1&&'/users/codefalling'&/span&&span class=&p&&).&/span&&span class=&nx&&then&/span&&span class=&p&&(&/span&&span class=&nx&&res&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&res&/span&&span class=&p&&.&/span&&span class=&nx&&text&/span&&span class=&p&&()&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&then&/span&&span class=&p&&(&/span&&span class=&nx&&data&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&kr&&const&/span& &span class=&nx&&obj&/span& &span class=&o&&=&/span& &span class=&nx&&JSON&/span&&span class=&p&&.&/span&&span class=&nx&&parse&/span&&span class=&p&&(&/span&&span class=&nx&&data&/span&&span class=&p&&)&/span&
&span class=&kr&&const&/span& &span class=&nx&&avatarUrl&/span& &span class=&o&&=&/span& &span class=&nx&&obj&/span&&span class=&p&&[&/span&&span class=&s1&&'avatar_url'&/span&&span class=&p&&]&/span&
&span class=&kr&&const&/span& &span class=&nx&&blog&/span& &span class=&o&&=&/span& &span class=&nx&&obj&/span&&span class=&p&&.&/span&&span class=&nx&&blog&/span&
&span class=&nx&&dispatch&/span&&span class=&p&&({&/span& &span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s1&&'FETCH_END'&/span&&span class=&p&&,&/span& &span class=&nx&&payload&/span&&span class=&o&&:&/span& &span class=&p&&{&/span& &span class=&nx&&avatarUrl&/span&&span class=&p&&,&/span& &span class=&nx&&blog&/span& &span class=&p&&}&/span& &span class=&p&&})&/span&
&span class=&p&&})&/span&
&span class=&p&&})&/span&
&/code&&/pre&&/div&&p&然后会先触发 FETCH&em&START,完成后带着数据触发 FETCH&/em&END。&/p&&p&很容易可以写出对应的 reducer,FETCH&em&START 时把 fetching 设为 true,FETCH&/em&END 时设置 avatarUrl 并且将 fetched 设为 true,fetching 设为 false.&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&const&/span& &span class=&nx&&reducer&/span& &span class=&o&&=&/span& &span class=&p&&(&/span&&span class=&nx&&state&/span&&span class=&p&&,&/span& &span class=&nx&&action&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&switch&/span& &span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&type&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&case&/span& &span class=&s2&&&FETCH_START&&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span&
&span class=&nx&&fetching&/span&&span class=&o&&:&/span& &span class=&kc&&true&/span&&span class=&p&&,&/span&
&span class=&p&&}&/span&
&span class=&k&&case&/span& &span class=&s2&&&FETCH_END&&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span&
&span class=&nx&&fetching&/span&&span class=&o&&:&/span& &span class=&kc&&false&/span&&span class=&p&&,&/span&
&span class=&nx&&fetched&/span&&span class=&o&&:&/span& &span class=&kc&&true&/span&&span class=&p&&,&/span&
&span class=&nx&&info&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&avatarUrl&/span&&span class=&o&&:&/span& &span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&payload&/span&&span class=&p&&.&/span&&span class=&nx&&avatarUrl&/span&&span class=&p&&,&/span&
&span class=&nx&&blog&/span&&span class=&o&&:&/span& &span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&payload&/span&&span class=&p&&.&/span&&span class=&nx&&blog&/span&&span class=&p&&,&/span&
&span class=&p&&},&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&h2&拆分 reducer&/h2&&p&前面曾经提到,redux 只有一个 store,当应用变得越来越复杂时,reducer 和 store 中的数据都越来越多。而 reducer 可能并不关心和自己无关的 state,把 reducer 全部写在一个大 switch case 中也不是个好主意,所以我们要将 reducer 拆分开。redux 提供一个函数,combineReducers,用于合并多个 reducer。&/p&&p&以上面的代码为例,假设我们有一个和其没有关系的其他 reducer,我们可以写成:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&const&/span& &span class=&p&&{&/span& &span class=&nx&&createStore&/span&&span class=&p&&,&/span& &span class=&nx&&applyMiddleware&/span&&span class=&p&&,&/span& &span class=&nx&&combineReducers&/span& &span class=&p&&}&/span& &span class=&o&&=&/span& &span class=&nx&&Redux&/span&
&span class=&kr&&const&/span& &span class=&nx&&fetchReducer&/span& &span class=&o&&=&/span& &span class=&p&&(&/span&&span class=&nx&&state&/span& &span class=&o&&=&/span& &span class=&p&&{},&/span& &span class=&nx&&action&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&switch&/span& &span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&type&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&case&/span& &span class=&s2&&&FETCH_START&&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&&span class=&nx&&state&/span&&span class=&p&&,&/span&
&span class=&nx&&fetching&/span&&span class=&o&&:&/span& &span class=&kc&&true&/span&&span class=&p&&,&/span&
&span class=&p&&}&/span&
&span class=&k&&case&/span& &span class=&s2&&&FETCH_END&&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span&
&span class=&p&&...&/span&&span class=&nx&&state&/span&&span class=&p&&,&/span&
&span class=&nx&&fetching&/span&&span class=&o&&:&/span& &span class=&kc&&false&/span&&span class=&p&&,&/span&
&span class=&nx&&fetched&/span&&span class=&o&&:&/span& &span class=&kc&&true&/span&&span class=&p&&,&/span&
&span class=&nx&&info&/span&&span class=&o&&:&/span& &span class=&p&&{&/span&
&span class=&nx&&avatarUrl&/span&&span class=&o&&:&/span& &span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&payload&/span&&span class=&p&&.&/span&&span class=&nx&&avatarUrl&/span&&span class=&p&&,&/span&
&span class=&nx&&blog&/span&&span class=&o&&:&/span& &span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&payload&/span&&span class=&p&&.&/span&&span class=&nx&&blog&/span&&span class=&p&&,&/span&
&span class=&p&&},&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&nx&&state&/span&
&span class=&p&&}&/span&
&span class=&kr&&const&/span& &span class=&nx&&helloReducer&/span& &span class=&o&&=&/span& &span class=&p&&(&/span&&span class=&nx&&state&/span& &span class=&o&&=&/span& &span class=&p&&{},&/span& &span class=&nx&&action&/span&&span class=&p&&)&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&type&/span& &span class=&o&&===&/span& &span class=&s1&&'HELLO'&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&nx&&state&/span& &span class=&o&&=&/span& &span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&payload&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&nx&&state&/span&
&span class=&p&&}&/span&
&span class=&kr&&const&/span& &span class=&nx&&reducer&/span& &span class=&o&&=&/span& &span class=&nx&&combineReducers&/span&&span class=&p&&({&/span&
&span class=&nx&&fetch&/span&&span class=&o&&:&/span& &span class=&nx&&fetchReducer&/span&&span class=&p&&,&/span&
&span class=&nx&&hello&/span&&span class=&o&&:&/span& &span class=&nx&&helloReducer&/span&&span class=&p&&,&/span&
&span class=&p&&})&/span&
&span class=&kr&&const&/span& &span class=&nx&&logger&/span& &span class=&o&&=&/span& &span class=&nx&&store&/span& &span class=&o&&=&&/span& &span class=&nx&&next&/span& &span class=&o&&=&&/span& &span class=&nx&&action&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'dispatching'&/span&&span class=&p&&,&/span& &span class=&nx&&action&/span&&span class=&p&&)&/span&
&span class=&kd&&let&/span& &span class=&nx&&result&/span& &span class=&o&&=&/span& &span class=&nx&&next&/span&&span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&)&/span&
&span class=&nx&&console&/span&&span class=&p&&.&/span&&span class=&nx&&log&/span&&span class=&p&&(&/span&&span class=&s1&&'next state'&/span&&span class=&p&&,&/span& &span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&getState&/span&&span class=&p&&())&/span&
&span class=&k&&return&/span& &span class=&nx&&result&/span&
&span class=&p&&}&/span&
&span class=&kr&&const&/span& &span class=&nx&&store&/span& &span class=&o&&=&/span& &span class=&nx&&createStore&/span&&span class=&p&&(&/span&&span class=&nx&&reducer&/span&&span class=&p&&,&/span& &span class=&p&&{},&/span& &span class=&nx&&applyMiddleware&/span&&span class=&p&&(&/span&&span class=&nx&&logger&/span&&span class=&p&&,&/span& &span class=&nx&&ReduxThunk&/span&&span class=&p&&.&/span&&span class=&k&&default&/span&&span class=&p&&))&/span&
&span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&dispatch&/span&&span class=&p&&(&/span&&span class=&nx&&dispatch&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&nx&&dispatch&/span&&span class=&p&&({&/span& &span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s1&&'FETCH_START'&/span& &span class=&p&&})&/span&
&span class=&nx&&fetch&/span&&span class=&p&&(&/span&&span class=&s1&&'/users/codefalling'&/span&&span class=&p&&).&/span&&span class=&nx&&then&/span&&span class=&p&&(&/span&&span class=&nx&&res&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&res&/span&&span class=&p&&.&/span&&span class=&nx&&text&/span&&span class=&p&&()&/span&
&span class=&p&&})&/span&
&span class=&p&&.&/span&&span class=&nx&&then&/span&&span class=&p&&(&/span&&span class=&nx&&data&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&kr&&const&/span& &span class=&nx&&avatarUrl&/span& &span class=&o&&=&/span& &span class=&nx&&JSON&/span&&span class=&p&&.&/span&&span class=&nx&&parse&/span&&span class=&p&&(&/span&&span class=&nx&&data&/span&&span class=&p&&)[&/span&&span class=&s1&&'avatar_url'&/span&&span class=&p&&]&/span&
&span class=&nx&&dispatch&/span&&span class=&p&&({&/span& &span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s1&&'FETCH_END'&/span&&span class=&p&&,&/span& &span class=&nx&&payload&/span&&span class=&o&&:&/span& &span class=&nx&&avatarUrl&/span& &span class=&p&&})&/span&
&span class=&p&&})&/span&
&span class=&p&&})&/span&
&/code&&/pre&&/div&&p&初始化不再在 store 中完成,而是直接写在缺省参数里。拆分开的 reducer 不能返回 undefined,如果什么都没发生,就原样返回即可。&/p&&p&我们可以看到,多个 reducer 被拆分开,各自独立,state 也不会互相影响。&/p&&h2&更多&/h2&&p&到这里介绍完了 redux 中比较重要的概念,本文为了简单和易于理解没有引入 react,先搞清楚 redux 本身的工作方式,避免陷入茫茫多的细节和概念。将会在未来的文章中介绍 react 和 redux 共同工作的方式。&/p&&h2&&b&其他&/b&&/h2&&p&原文地址: &a href=&/?target=https%3A////learn-redux-from-zero/& class=& external& target=&_blank& rel=&nofollow noreferrer&&&span class=&invisible&&https://&/span&&span class=&visible&&/2016/10&/span&&span class=&invisible&&/05/learn-redux-from-zero/&/span&&span class=&ellipsis&&&/span&&i class=&icon-external&&&/i&&/a&&/p&&p&感觉自己已经葛优躺了好几个礼拜。。几乎是个废前端了。。&/p&
前言前端生态日新月异, flux 已经过时(恩然而我都还没有来得及学),redux 成了状态管理的标配,每一个前端开发者都应该学习。然而因为 redux 的多种概念着实让新手费解,很多人沉浸在 react 全家桶的配置无法自拔。所以我试着丢开 react,理清楚 redux 本…
&figure&&img src=&/50/b4e55f928a88e8a17759_b.png& data-rawwidth=&444& data-rawheight=&120& class=&origin_image zh-lightbox-thumb& width=&444& data-original=&/50/b4e55f928a88e8a17759_r.png&&&/figure&&p&Redux 的第一次代码提交是在 2015 年 5 月底(也就是一年多前的样子),那个时候 React 的最佳实践还不是明晰,作为一个 View 层,有人会用 backbone 甚至是 angular 和它搭配,也有人觉得这层 View 功能已经足够强大,简单地搭配一些 utils 就直接上。后来便有了 FLUX 的演讲,React 社区开始注意到这种新的类似函数式编程的理念,Redux 也作为 FLUX 的一种变体开始受到关注,再后来顺理成章地得到 React 的『钦点』,作者也加入了 Facebook 从事 React 的开发。生态圈经过了这一年的成熟,现在很多第三方库已经非常完善,所以这里想介绍一下目前 Redux 的一些最佳实践。&/p&&br&&br&&h2&一、复习一下 Redux 的基本概念&/h2&&p&首先我们复习一下 Redux 的基本概念,&b&如果你已经很熟悉了,就直接跳过这一章吧。&/b&&/p&&p&Redux 把界面视为一种状态机,界面里的所有状态、数据都可以由一个状态树来描述。所以对于界面的任何变更都简化成了状态机的变化:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&(State, Input) =& NewState
&/code&&/pre&&/div&&p&这其中切分成了三个阶段:&/p&&ol&&li& action&br&&/li&&li& reducer&br&&/li&&li& store&/li&&/ol&&p&所谓的 action,就是用一个对象描述发生了什么,Redux 中一般使用一个纯函数,即 &b&actionCreator&/b& 来生成 action 对象。&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&// actionCreator =& action&/span&
&span class=&c1&&// 这是一个纯函数,只是简单地返回 action&/span&
&span class=&kd&&function&/span& &span class=&nx&&somethingHappened&/span&&span class=&p&&(&/span&&span class=&nx&&data&/span&&span class=&p&&){&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span&
&span class=&nx&&type&/span&&span class=&o&&:&/span& &span class=&s1&&'foo'&/span&&span class=&p&&,&/span&
&span class=&nx&&data&/span&&span class=&o&&:&/span& &span class=&nx&&data&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&随后这个 action 对象和当前的状态树 state 会被传入到 reducer 中,产生一个新的 state&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//reducer(action, state) =& newState&/span&
&span class=&kd&&function&/span& &span class=&nx&&reducer&/span&&span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&,&/span& &span class=&nx&&state&/span&&span class=&p&&){&/span&
&span class=&k&&switch&/span&&span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&.&/span&&span class=&nx&&type&/span&&span class=&p&&){&/span&
&span class=&k&&case&/span& &span class=&s1&&'foo'&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&p&&{&/span& &span class=&nx&&data&/span&&span class=&o&&:&/span& &span class=&nx&&data&/span& &span class=&p&&};&/span&
&span class=&k&&default&/span&&span class=&o&&:&/span&
&span class=&k&&return&/span& &span class=&nx&&state&/span&&span class=&p&&;&/span&
&span class=&p&&}&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&store 的作用就是储存 state,并且监听其变化。&/p&&p&简单地说就是你可以这样产生一个 store :&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kr&&import&/span& &span class=&p&&{&/span& &span class=&nx&&createStore&/span& &span class=&p&&}&/span& &span class=&nx&&from&/span& &span class=&s1&&'redux'&/span&
&span class=&c1&&//这里的 reducer 就是刚才的 Reducer 函数&/span&
&span class=&kd&&let&/span& &span class=&nx&&store&/span& &span class=&o&&=&/span& &span class=&nx&&createStore&/span&&span class=&p&&(&/span&&span class=&nx&&reducer&/span&&span class=&p&&);&/span&
&/code&&/pre&&/div&&p&然后你可以通过 dispatch 一个 action 来让它改变状态:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&getState&/span&&span class=&p&&();&/span& &span class=&c1&&// {}&/span&
&span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&dispatch&/span&&span class=&p&&(&/span&&span class=&nx&&somethingHappened&/span&&span class=&p&&(&/span&&span class=&s1&&'aaa'&/span&&span class=&p&&));&/span&
&span class=&nx&&store&/span&&span class=&p&&.&/span&&span class=&nx&&getState&/span&&span class=&p&&();&/span& &span class=&c1&&// { data: 'aaa'}&/span&
&/code&&/pre&&/div&&p&好了,这就是 Redux 的全部功能。对的,它就是如此简单,以至于它本体只有 3KB 左右的代码,因为它只是实现了一个简单的状态机而已,任何稍微有点编程能力的人都能很快写出这个东西。至于和 React 的结合,则需要 &a href=&/?target=https%3A///reactjs/react-redux& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&react-redux&i class=&icon-external&&&/i&&/a& 这个库,这里我们就不讲怎么用了。&/p&&br&&br&&h2&二、Redux 的一些痛点&/h2&&p&大体上,Redux 的数据流是这样的:&/p&&div class=&highlight&&&pre&&code class=&language-text&&&span&&/span&界面 =& action =& reducer =& store =& react =& virtual dom =& 界面
&/code&&/pre&&/div&&p&每一步都很纯净,看起来很美好对吧?对于一些小小的尝试性质的 DEMO 来说确实很美好。但其实当应用变得越来越大的时候,这其中存在诸多问题:&/p&&ol&&li& 如何优雅地写异步代码?(从简单的数据请求到复杂的异步逻辑)&br&&/li&&li& 状态树的结构应该怎么设计?&br&&/li&&li& 如何避免重复冗余的 actionCreator?&br&&/li&&li& 状态树中的状态越来越多,结构越来越复杂的时候,和 react 的组件映射如何避免混乱?&br&&/li&&li& 每次状态的细微变化都会生成全新的 state 对象,其中大部分无变化的数据是不用重新克隆的,这里如何提高性能?&/li&&/ol&&p&你以为我会在下面一一介绍这些问题是怎么解决的?还真不是,这里大部分问题的回答都可以在官方文档中看到:&a href=&/?target=http%3A//cn.redux.js.org/docs/recipes/index.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&技巧 | Redux 中文文档&i class=&icon-external&&&/i&&/a&,文档里讲得已经足够详细(有些甚至详细得有些啰嗦了)。所以下面只挑 Redux 生态圈里几个比较成熟且流行的组件来讲讲。&/p&&br&&br&&h2&三、Redux 异步控制&/h2&&p&官方文档里介绍了一种很朴素的异步控制中间件 &a href=&/?target=https%3A///gaearon/redux-thunk& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&redux-thunk&i class=&icon-external&&&/i&&/a&(如果你还不了解中间件的话请看 &a href=&/?target=http%3A//cn.redux.js.org/docs/advanced/Middleware.html& class=& wrap external& target=&_blank& rel=&nofollow noreferrer&&Middleware | Redux 中文文档&i class=&icon-external&&&/i&&/a&,事实上 redux-thunk 的代码很简单,简单到只有几行代码:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&kd&&function&/span& &span class=&nx&&createThunkMiddleware&/span&&span class=&p&&(&/span&&span class=&nx&&extraArgument&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&p&&({&/span& &span class=&nx&&dispatch&/span&&span class=&p&&,&/span& &span class=&nx&&getState&/span& &span class=&p&&})&/span& &span class=&o&&=&&/span& &span class=&nx&&next&/span& &span class=&o&&=&&/span& &span class=&nx&&action&/span& &span class=&o&&=&&/span& &span class=&p&&{&/span&
&span class=&k&&if&/span& &span class=&p&&(&/span&&span class=&k&&typeof&/span& &span class=&nx&&action&/span& &span class=&o&&===&/span& &span class=&s1&&'function'&/span&&span class=&p&&)&/span& &span class=&p&&{&/span&
&span class=&k&&return&/span& &span class=&nx&&action&/span&&span class=&p&&(&/span&&span class=&nx&&dispatch&/span&&span class=&p&&,&/span& &span class=&nx&&getState&/span&&span class=&p&&,&/span& &span class=&nx&&extraArgument&/span&&span class=&p&&);&/span&
&span class=&p&&}&/span&
&span class=&k&&return&/span& &span class=&nx&&next&/span&&span class=&p&&(&/span&&span class=&nx&&action&/span&&span class=&p&&);&/span&
&span class=&p&&};&/span&
&span class=&p&&}&/span&
&/code&&/pre&&/div&&p&它其实只干了一件事情,判断 actionCreator 返回的是不是一个函数,如果不是的话,就很普通地传给下一个中间件(或者 reducer);如果是的话,那么把 &b&dispatch&/b&、&b&getState&/b&、&b&extraArgument&/b& 作为参数传入这个函数里,实现异步控制。&/p&&p&比如我们可以这样写:&/p&&div class=&highlight&&&pre&&code class=&language-js&&&span&&/span&&span class=&c1&&//普通action&/span&
&span class=&kd&&function&/span& &span class=&nx&&foo&/span&&span class=&p&&(){&/span&
&span class=&k&&return&/span& &span clas}

我要回帖

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信