React试点开发总结

  汐辰

前段时间需要开发一个流程化接口管理的chrome插件,用来对线上和线下mock数据进行管理,劫持和替换,同时需要对过滤的请求状态进行实时的刷新,之前有了解到react在视图层的渲染上有着不错的效率表现,于是趁着这个项目,利用react+socket.io进行了试点开发,上周刚好完成了第一个版本,于是决定结合这个项目跟大家分享一下利用react开发的实际体验以及踩过的坑.

首先,什么是react?官方称React更加倾向于做传统MVC的View层,本质上,可以把react看成是一个”状态机”,它帮助开发者管理复杂的随时间而变化的状态,而开发者只需要根据不同的状态来写每个组件对应的逻辑就可以了,而react最关心的只有两件事:

  • 更新DOM
  • 响应事件

但是react对于视图的刷新又是非常高效的,主要依赖两件大杀器:组件驱动开发和虚拟DOM,下面将对react里边的几个核心的东西分开进行介绍.

组件驱动开发

前端组件化其实很早就已经出来了,而react则是把它做到了极致,对react应用而言,尽量把一个页面的东西拆得越碎通常是越容易维护及理解的,而且react的设计初衷似乎也不是奔着写出最精简优美的代码去的,比如写同样一个应用用react和用avalon或者vuejs来写通常来说react的代码量会比后者多,但是用react写出来的却是最容易维护的,因为它把页面上的东西拆得很细独立成了一个组件,做到了充分的解耦。在我写的这个chrome插件里边,单一个popup页面我也是把他拆分成了4个组件,如下图:

单向数据流

既然说到了拆分成多个组件,那么react的组件间通信又是怎么样的呢?react并没有做成双向通信那么自由,而是单向的,既从父节点传到子节点(top-down rendering)有人可能会说:啊,太low了吧,现在都流行MVVM了!但是我想了一下其实做出这个限制也是有原因的,通常对一个新手来说,一个框架太过于自由,往往是hold不住的,不知道从何入手,不知道怎么写才是最优的,而像如果之前用angular做过大型应用的老手来说这些限制其实是不必要的,因为就算你不给出规范和限制,他们同样知道中间的流程是怎么样的.但是我觉得react中的限制做的刚刚好,既没有太严格,同时保留了最基本的.

构成单向数据流主要依赖两个属性props和state,props是从父级传到子级的属性,通常来说是只读的数据和事件处理器什么的,而state则是组件内部的状态属性,这些状态只能在组件内部进行修改,state改变了之后就会刷新这个state改变的组件及其子组件的视图,中间的流转就是一层一层往下走.所以通常来说我们可以通过使用props传递数据配置以及向子级传递事件处理器进行通信.如下述代码中的handleClick和unmockData就是传给子级用的事件处理器.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var HomePage = React.createClass({
mixins: [StatustoMocksMixin],
getInitialState: function() {
var self = this;
var mockStatus = {};
feed.getMockStatus(undefined,function(mockStatus) {
self.setState({mockStatus: mockStatus});
});
return {mockStatus: mockStatus};
},
unmockData: function(data) {
var self = this;
feed.unmock(data,function(mockStatus){
self.setState({mockStatus: mockStatus});
});
},
handleClick: function(ev) {
chrome.tabs.create({
url: chrome.extension.getURL("options.html")
});
return;
},
render: function(){...}

})

Virtual DOM

在上一章提到只要state改变,该组件及子组件及其子组件就会进行刷新,既调用他们的render方法,那么如果在根节点调用setState方法,整个react应用都会被重新渲染,这个听起来似乎性能像是很低的样子,但是实际上它不会碰触到真实的DOM,而且通常来说我们有数据更新的时候通常不是在根节点调用setState,而是在子组件中得某个节点调用,如下图,react只会更新渲染子树,而不会渲染其父级,所有的更新都只会出现在与用户产生交互的局部,基本不会产生性能问题,想要对react的diff算法有深入了解的可以参考这篇文章.像我这个项目就需要实时显示proxy过滤的请求,proxy server每秒钟会给我发个数据包,如果数据包发生变化了,我也只会在变化的局部去更新真实的dom而不是整块重新渲染.

Mixin

对,react自带mixin,是解决代码复用的强大工具之一.既然把页面拆得这么碎,可想而知,肯定会有不少代码片段是重复的,而mixin就是解决这个的,为此我在每个大模块下新建了一个mixin的文件,专门放不同组件复用的代码片段.同时react官方也提供了几个mixin插件,比如很多用户抱怨没有双向绑定,就可以用LinkedStateMixin得到妥善的解决,当然它的原理还是利用onChange进行实现的,而不是现在主流MVVM的那种解决方案.

JSX

React的另一个重头是JSX,既javascript XML,它和html的语法很相似,但也略有不同,如HTML中的class属性在JSX中为className,还有HTML中得for属性在JSX中则为htmlFor.我们先来看看JSX的几个特点,再来说它的好处:

  • 每一个JSX都对应一个javascript函数
  • JSX不提供也不需要运行时库
  • JSX不改变和添加javascript语义

可以看出用JSX写更加地直观和语义化,并且可以减少不少代码,如果用js来创建元素的话我们可能会这样写:

1
2
3
4
render () {
return React.createElement("div", null, "Hello ",
this.props.name);
}

而用JSX则只需要:

1
2
3
render () {
return <div>Hello {this.props.name}</div>;
}

非常的简洁干净直观,并且可以使用动态值,如果模板不是很复杂的话,前端模板都可以省了。

组件生命周期

组件生命周期没那么多好说的,react在渲染一个组件的时候,有它自己的一套流程,顺序是这样的,下面只是摘录罗列一下,具体的还是去参考官方文档吧.

  • componentWillMount

    • 该方法会在组件render之前执行,并且永远只执行一次。
  • componentDidMount

    • 该方法会在组件加载完毕之后立即执行。此时,组件已经完成了DOM结构的渲染, 并可以通过this.getDOMNode()方法来访问。
  • componentWillReceiveProps

    • 组件接收到一个新的prop时会被执行,且该方法在初始render时不会被调用。
  • shouldComponentUpdate

    • 在组件接收到新的props或state时被执行。
  • componentWillUpdate

    • 在组件接收到新的props或者state但还没有render时被执行。 在初始化时不会被执行。
  • componentDidUpdate

    • 在组件完成更新后立即执行。在初始化时不会被执行。 一般会在组件完成更新后被使用。
  • componentWillUnMount

    • 在组件从DOM中unmount后立即执行。该方法主要用来执行一些必要的清理任务。

打包工具

本次开发也尝试了一下新的打包工具Webpack,说到Webpack不得不先提一下Browserify,因为这两个有非常多的相似之处,而Browserify也早于Webpack出来.那么Browserify当时是为了要解决什么问题呢?在前端模块化的前进道路上有两套规范AMD和CMD,而像node.js则是用了CMD的写法,所以当时就有人想,如果前端也用CMD那么前后端代码不是就能复用了吗, Browserify由此诞生,它允许开发者使用node.js的写法写前端的代码,最后把当前JS模块及所依赖的模块打包成一个文件在浏览器端使用.

而Webpack虽然与Browserify相似,但是功能上却要比Browserify强大的多,比如Browserify只能够用来打包JS文件,而Webpack则是允许如上图这么多格式的打包,并且在开发的过程中不需要安装额外的插件就能做到对文件的实时监测和打包,命令行工具也是放出了足够多的API,比如在本想目中我完成webpack.config.js的编写后运行下方的命令,就可以很方便地协助我进行开发调试:

1
webpack --config config/webpack-dev.config.js --progress --profile --colors --watch --display-error-details

同时Webpack对不同文件打包的支持也是需要不同对应的loader,如下方对于jsx我使用的是babel而对于css则是用了style-loader.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
module: {
loaders: [{
test: /\.jsx?$/,
loader: 'babel',
exclude: [path.join(root, 'node_modules'), path.join(context, 'node_modules')]
},
{
test: /\.jpe?g$|\.gif$|\.png$|\.svg$|\.woff$|\.ttf$/,
loader: 'file'
},
{
test: /\.css$/,
loader: ExtractTextPlugin.extract('style-loader', 'css-loader')
}]
}

还有几个不得不提的惊喜是Webpack不仅支持CMD规范还兼容AMD规范,并且Babel这个loader的时候可以完全使用所有的ES6新语法,而在打包的时候它能将ES6转换成可以在浏览器中运行的代码,这点太赞了,这就意味你不仅可以commonJS写模块化代码还能用ES6来写:

1
2
3
4
5
6
7
var React = require('react');

var MyComponent = React.createClass({
// do something
});

module.exports = MyComponent;
1
2
3
4
5
6
7
import React from 'react';

class MyComponent extends React.Component {
// do something use es6
}

export default MyComponent;

Webpack提供的功能太多了,目前我也只是用到了一部分,想尝试更多的还是去看官方文档吧.

项目结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
├── build
│   ├── css

│   ├── imgs
│   └── js
├── config

├── node_modules
└── src
├── node_modules
├── content
│   ├── component
│   └── mixins
├── options
│   ├── component
│   ├── css
│   └── mixins
└── popup
├── component
├── css
└── mixins
分享到