前端手记 TodoMVC 之 React 篇
保持专注越来越难了,感觉我的注意力也上了年纪,变的花心多了,很容易被打断。能有心情、有时间静坐下来,学学 Todo list 的内容,感觉是最幸福的了。 前端的框架很多,我先从 React 开始。
知识筹备
- ECMAScript 6.0 (ECMAScript 2015)
JavaScript (js) 是前端工程师绕不过的编程语言,非常灵活,目前 js 已经打通前后端,成为程序员必备技能之一。最近 微信小程序 又让 js 火了一把。ECMAScript 6.0 (简称 ES6)是 js 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标是,使得 js 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
熟读 ES6 feature 可以帮我们写出更现代的代码,这里有两本书必须精读:es6features 和 《ECMAScript 6入门》。如果还不够的话,读读标准吧。
除了 ES6,你还需要了解以下工具:
- webpack: 模块打包工具,并且可以将某些语言编译成 js。
- JSX: 支持 js 和 html 混写
- babel: js 降级
- …
后端 node.js v4.0+ 已经支持大部分 ES6 feature。但是前端,各浏览器对 ES6 响应相对较慢,想用 ES6 的话,你需要 babel,将新 feature 编译成可以在浏览器中运行的原生 js 代码。我配置了一个 ES6 + React 的开发环境(2100ad886643344af77c103c995dfb5360a6e492)。 你可以这样运行它:
webpack -w
用 chrome 打开 react/index.html
, 如果能看到 Hello, React! hahah ...
说明环境搭建完成。当然,也可以运行:
node server.js # 另打开一个终端
访问 http://localhost:3000/
, 查看默认界面。接下来就可以专心写 React 啦。
React
首先,你需要熟读 官方文档,其中有一篇 Thinking in react 写的非常好,需要仔细研读。本文使用 Thinking in react 的步骤来实现 TodoMVC demo。
- 回顾一下 UI
在 前端手记 TodoMVC 之 CSS 篇 我们按步骤实现了 TodoMVC 的前端 UI,如下图所示:
假设 JSON API 返回的数据如下:
[
{id: "1", title: "learn css first", completed: false, date: "2016-09-07T22:01:45Z"},
{id: "2", title: "edit todo", completed: false, date: "2016-09-07T22:02:45Z"},
{id: "3", title: "learn react", completed: true, date: "2016-09-07T22:03:45Z"}
]
- 第一步,根据 UI 划分 component 层级结构
React 将网页上的元素划分成不同模块的 component,每个 component 独立完成自己的显示和逻辑。component 可以重用。
划分组件时使用单一职责原则,保证每个组件只做一件事情。下图是我的一个划分:
划分了 7 个 component:
- TodoApp (red): 包含 TodoMVC App 的全部
- ToggleAll (green): 全选和反选
- NewTodo (orange): 添加一个新的 Todo
- TodoItem (blue): 操作(状态切换 / 删除)一个 Todo
- FilterTodo (purple): 过滤 Todo
- Footer (yellow): 包含 TodoMVC App 的脚注
- MainSection (black): 包括 TodoMVC 的主要 components
对应的层级结构如下:
* TodoApp
* NewTodo
* MainSection
* ToggleAll
* TodoItem
* FilterTodo
* Footer
- 第二步,用 React 实现静态页面
这是加上元素的效果,是不是和用 html 写的一样呢?
其中 style 的 class 和 js 有冲突,react 中用 className 定义样式,加上样式的 TodoMVC App 如下:
只做了个微小的调整,感觉还不错,之前的 css 真的是没白写呀。
- 第三步, 确定 UI 上的状态
React 使用状态机来定义页面上的交互,组件在不同状态下显示不同的形式和内容。对于页面上的数据,React 把它分成两类: props 和 state。props 从父组件传入子组件,state 处理交互引起变化的数据。
想想 Todo list 上的数据:
* 所有的 todo lists
* 用户输入的新 todo 事项
* Toggle/ToggleAll checkbox 状态
* 剩余 item 个数
React 写在 props 上的数据从上层向下传递,并且在传递的过程中,值保持不变。写在 state 上的值发生变化时 (setState), 会引起 component 的重绘,一个应用的 state 数据越少越简单。那么如何区分数据应该用 props 还是 state 呢?有篇文章写这个事情: Interactivity and Dynamic UIs。
state 是那些组件的事件响应会改变,并触发 UI 更新的数据。可以简单的问三个问题来确定一个数据是不是 state:
* 是不是通过父组件的 props 传递下来? 如果是,它应该就不是 state
* 是不是随时间变化?如果是,它应该就不是 state
* 可以通过其它 props 和 state 计算得到吗?如果可以,它应该就不是 state
所有的 todo lists 从父组件向下传递,所以不是 state。用户输入和 checkbox 值无法通过父组件传递,也没办法通过计算得到,需要根据用户的输入随时改变 UI 效果,所以是 state。剩余的 item 个数可以通过 props 上的数据计算得到,所以不是 state。那么最终看来,我们的 state 数据有:
* 用户输入的新 todo 事项
* Toggle/ToggleAll checkbox 状态
- 第四步, 确定 state 所在的组件
通过上一步,我们确定了最小的 state 数据集合,这一步我们确定这些 state 应该被哪些 components 持有。
React 是单向数据流,数据从父组件层层传递给子组件,究竟谁持有 state 会让程序更加的简单方便,对初学者来说是个难题。可以通过以下步骤来确定:
* 确定哪些组件要根据 state 来绘制
* 找到一个共同的父组件
* 要么是共同父组件,要么是其他在层级里更高级的组件应该拥有这个state
* 如果你不能找到一个组件让其可以有意义地拥有这个 state,可以简单地创建一个新的组件 hold 住这个 state,并把它添加到比共同父组件更高的层级上。
现在用这个策略梳理一遍我们的应用
* MainSection 需要通过 state 更新 TodoItem。MainSection 需要通过 NewTodo 输入的值刷新 MainSection。
* 共同的父组件是 TodoApp
* 添加和删除 Todo 存在于 TodoApp,从概念上讲是有意义的。
好了,根据分析,我把所有 state 都写在组件 TodoApp 里,其它组件全部用 props 传递数据。
- 第五步,添加反响数据流
在实际应用中,子组件的交互通常会改变父组件的值,可以使用回调函数的方法,把子组件的操作更新到父组件上。子组件再通过 props 值获取更新的数据。这种单方向的数据更新,能减少应用的复杂性。
和神作对比
眼里过千遍不如手里过一遍,本例中有很多地方,在具体实现时,才可能会考虑到。所有代码都托管在 zddhub/todomvc 上。依然可以根据图片名字的 commit id checkout 源码,查看具体的实现。
开源的 TodoMVC 程序大都结合了各种框架,找到一个纯 js 实现的例子 tastejs/todomvc,大家可以比较一下和自己实现的区别,相互学习。
总结一下
- React 只是 MVC 中的 view
- 页面由 component 组成,每个 component 包括数据展示和交互响应
- React 中的数据主要包括 props 和 state 两类,尽量使用 props,少用 state
- 尽可能的保持数据单向流动
如果你喜欢这篇文章,欢迎赞赏作者以示鼓励