React.js生态系统概览 [译]

Posted 开发者

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React.js生态系统概览 [译]相关的知识,希望对你有一定的参考价值。

开发者( KaiFaX
面向开发者、程序员的专业平台!


javascript领域发展速度很快,甚至有人认为这已经引起了负效应。一个前端库从早期开发的小玩具,到流行,再到过时,可能也就几个月时间。判断一个工具能否在几年内依然保持活力都快成了一门艺术了。

React.js在两年前发布时,我刚开始学Angular,React在我看来只是又一个模板库而已。这两年间,Angular得到了JavaScript开发者的认同,它几乎成了现代前端开发的代名词。我还看到一些很保守的团队都在用它,这让我觉得Angular好像就是未来。

但突然发生了件奇怪的事,Angular好像成了奥斯本效应的受害者,或者说它被提前宣布了死亡。Angular团队宣布,Angular 2将会完全不同,基本没有从Angular 1升级迁移的东西,而且Angular 2在接下来的一年里还用不了。这告诉了那些想开发新Web项目的人:你想用一个马上要被淘汰了的框架写项目吗?

开发者们的忧虑影响到了正在建立的React社区,但React总标榜它只是MVC中的视图层(V),让一些依赖完整MVC框架做开发的人感觉有点失望。如何补充其他部分的功能?自己写吗?还是用别的三方库?要是的话该选哪一个呢?

果然,Facebook(React.js的创始)出了另一个杀手锏:Flux工作流,它声称要填补模型层(M)和控制层(C )的功能。Facebook还称Flux只是一种“模式”,不是个框架,他们的Flux实现只是这个模式的一个例子。就像他们所说,这个实现过于简单,但还是要写很多代码和一堆重复的模板才跑得起来。

这时开源社区发力了,一年后便有了各种Flux实现库,甚至都出来比较他们的元项目了。Facebook激起了社区的兴趣,不是给出现成的东西,而是鼓励大家提出自己的解决方案,这点很不错。

当你要结合各种库开发一个完整架构时,摆脱了框架的束缚,独立的库还可以在很多地方重用,在自己构建架构的过程中,这个优点很明显。

这便是为什么React相关的东西这么有意思。它们可以很容易地在其他JavaScript环境中实现重用。就算你不打算用React,看看它的生态系统都能受到启发。可以试试强大又容易配置的模块化打包工具Webpack来简化构建系统,或者用Babel转译器马上开始用ECMAScript 6甚至ECMAScript 7来写代码。

在这篇文章里我会给你概览一遍这些有意思的库和特性,来探索下React整个生态系统吧。


构建系统

创建一个新的Web项目时,首先要考虑的可能就是构建系统了。它不只是做为个运行脚本的工具,还能优化你的项目结构。一个构建系统必须能包括下面几个最主要功能:

  • 管理内部与外部依赖

  • 运行编译器和预处理器(例如CoffeeScript与SASS)

  • 为生产环境优化资源(例如Uglify)

  • 运行开发环境的Web Server,文件监控,浏览器自动刷新

最近几年,Yeoman,Bower与Grunt被誉为现代前端开发的三剑客。他们解决了生成基础模板,包管理和各种通用任务问题,后面也很多人从Grunt换到了Gulp。

在React的生态系统里,基本上可以丢掉这些东西了,不是说你用不到他们,而是说可以用更先进的Webpack与NPM。怎么做到的呢?Webpack是一个模块化打包工具,用它可以在浏览器环境下使用Node.js中常用的CommonJS模块语法。其实它要更简单点,因为你不用为了前端另外学一种包管理方案。只需要用NPM,就可以做到服务端与前端模块的共用。也不用处理JS文件按顺序加载的问题,因为它能够从每个文件的import语法中推测出依赖关系,整个串联成一个可以在浏览器中加载的脚本。

React.js生态系统概览 [译](一)

Webpack


更强大的是Webpack不只像同类工具Browserify,它还可以处理其他类型的资源。例如用加载器,可以将任何资源文件转换成JavaScript函数,去内联或加载引用到的文件。用不着手工预处理还有从html中引用资源了,只要在JavaScript中requireCSS/SASS/LESS文件就好了,Webpack会根据配置文件的描述去搞定一切。它还提供了个开发环境的Web Server,文件监控器,你可以在package.json中使用scripts域去定义一个任务:

{
"name": "react-example-filmdb",
"version": "0.0.1",
"description": "Isomorphic React + Flux film database example",
"main": "server/index.js",
"scripts": {
"build": "./node_modules/.bin/webpack --progress --stats --config ./webpack/prod.config.js",
"dev": "node --harmony ./webpack/dev-server.js",
"prod": "NODE_ENV=production node server/index.js",
"test": "./node_modules/.bin/karma start --single-run",
"postinstall": "npm run build"
}
...
}

这些东西就可以代替Gulp与Bower了。当然,还是可以继续用Yeoman去生成应用基础模板的。要是Yeoman生成不了你需要的东西时(其实大多时候也都需要删掉那些用不到的库),还可以从Github上Clone一个基础模板,然后再改改。

马上试试新的ECMAScript

JavaScript在这几年有很大改善,移除糟粕稳定语言后,我们看到了很多新特性,ECMAScript 6(ES6)草案已经定稿。ECMAScript 7也已纳入标准化日程中,它们的特性也都已经被很多库采用了。

React.js生态系统概览 [译](一)

ECMAScript 7


可能你觉得到IE支持之前都用不上这些JS新特性,但实际我们不需要等浏览器完全支持,ES转译器已经广泛应用了。目前最好的ES转译器是Babel,它能够把ES6+代码转换成ES5,所以你马上就能用上新ES特性了(指已经在Babel中实现的那些,其实一般新出的特性也会很快被支持)。

Babel


新的JavaScript特性在所有前端框架里都可以用,更新的React能很好的在ES6与ES7下运行。这些新特性可以解决在用React开发时遇到的一些问题。来看下这些改善吧,它们对React项目很有用。稍后我们再看看怎样利用这些语法,来搭配使用React的工具和库。

ES6 Classes

面向对象编程是一种强大又广泛适用的范式,但在JavaScript里感觉有点不一样。Backbone,Ember,Angular,或React等大多数前端框架都有它们自己的定义类和创建对象的方式。在ES6中,有原生的类支持了,它简洁清晰而不用我们自己实现,例如:

React.createClass({

displayName: 'HelloMessage',

render() {

return <div>Hello {this.props.name}</div>;

}

})

使用ES6就可以写成:

class HelloMessage extends React.Component {

render() {

return <div>Hello {this.props.name}</div>;

}

}

再看个详细的例子:

React.createClass({
displayName: 'Counter',
getDefaultProps: function(){
return {initialCount: 0};
},
getInitialState: function() {
return {count: this.props.initialCount}
},
propTypes: {initialCount: React.PropTypes.number},
tick() {
this.setState({count: this.state.count + 1});
},
render() {
return (
<div onClick={this.tick}>
Clicks: {this.state.count}
</div>
);
}
});

ES6可以写成:

class Counter extends React.Component {

static propTypes = {initialCount: React.PropTypes.number};

static defaultProps = {initialCount: 0};


constructor(props) {

super(props);

this.state = {count: props.initialCount};

}


state = {count: this.props.initialCount};

tick() {

this.setState({count: this.state.count + 1});

}


render() {

return (

<div onClick={this.tick.bind(this)}>

Clicks: {this.state.count}

</div>

);

}

}

在这里不用再写getDefaultPropsgetInitialState这两个React的生命周期函数了。getDefaultProps改为了类的静态变量defaultProps,初始state也只需要定义在构造函数中。这种方式唯一的缺点是,在JSX中使用的方法的上下文不会再自动绑定到类实例了,必须用bind来指定。

装饰器

装饰器是ES7中的特性。通过一个包装函数,来增强函数或类的行为。例如想为一些组件使用同一个change handler, 而又不想inheritance antipattern,则可以用类的装饰器去实现。定义一个装饰器:

addChangeHandler: function(target) {

target.prototype.changeHandler = function(key, attr, event) {

var state = {};

state[key] = this.state[key] || {};

state[key][attr] = event.currentTarget.value;

this.setState(state);

};

return target;

}

在这里,函数addChangeHandler添加了changeHandler方法到目标类target的实例上。

要应用装饰器,只需要:

MyClass = addChangeHandler(MyClass)

或者用更优雅的ES7写法:

@addChangeHandler

class MyClass {

...

}

因为React没有双向数据绑定,在用到input之类的控件时,代码就会比较冗余,changeHandler函数则把它简化了。第一个参数表示在state对象中使用的key,它将存储input的一个数据对象。第二个参数是属性,它表示input的值。这两个参数在JSX中被传入:

@addChangeHandler
class LoginInput extends React.Component {
constructor(props) {
super(props);
this.state = {
login: {}
};
}
render() {
return (
<input
type='text'
value={this.state.login.username}
onChange={this.changeHandler.bind(this, 'login', 'username')} />
<input
type='password'
value={this.state.login.password}
onChange={this.changeHandler.bind(this, 'login', 'password')} />
)
}
}

用户名输入框发生改变时,输入框的值会被直接存到this.state.login.username中,不需要一个个去写handler了。

箭头函数

JavaScript的动态上下文this不太直观,成了开发者的常痛了。比如在类里,包装函数中的this都被指向了全局变量。要fix这个问题,通常是把this存到外部作用域下(例如_this),然后在内部函数中用它:

class DirectorsStore {
onFetch(directors) {
var _this = this;
this.directorsHash = {};
directors.forEach(function(x){
_this.directorsHash[x._id] = x;
})
}
}

在ES6中,函数function(x) {可以写成(x) => {。这种箭头方式的函数定义不仅将内部的this绑定到了外部作用域,而且看起来很简洁,在写大量异步代码时很有用:

onFetch(directors) {
this.directorsHash = {};
directors.forEach((x) => {
this.directorsHash[x._id] = x;
})
}

解构赋值

ES6中的解构赋值允许在赋值表达式左边写个复合对象:

var o = {p: 42, q: true};

var {p, q} = o;


console.log(p); // 42

console.log(q); // true

在React中有什么实际用处吗?看下面这个例子:

function makeRequest(url, method, params) {

var config = {

url: url,

method: method,

params: params

};

...

}

用解构方式,可以一次给几个键赋值。url, method, params这些值会被自动赋给有着同名键的对象中。这让代码更不易出错:

function makeRequest(url, method, params) {
var config = {url, method, params};
...
}

解构赋值也可以用来加载一个模块的子集:

const {clone, assign} = require('lodash');

function output(data, optional) {

var payload = clone(data);

assign(payload, optional);

}

函数的默认,剩余,扩展参数

在ES6中函数传参更强大了,可以为函数设置默认参数:

function http(endpoint, method='GET') {

console.log(method)

...

}


http('/api') // GET

觉得arguments用起来很麻烦?可以把剩余参数写成一个数组:

function networkAction(context, method, ...rest) {

// rest is an array

return method.apply(context, rest);

}

要是不想调用apply()方法,可以把数组扩展成函数的参数:

myArguments = ['foo', 'bar', 123];

myFunction(...myArguments);

Generator与Async函数

Generator是一种可以暂停执行,保存状态并稍后恢复执行的函数,每次遇到yield关键字时,就会暂停执行。Generator写法:

function* sequence(from, to) {

console.log('Ready!');

while(from <= to) {

yield from++;

}

}

调用Generator函数:

> var cursor = sequence(1,3)
Ready!
> cursor.next()
{ value: 1, done: false }
> cursor.next()
{ value: 2, done: false }
> cursor.next()
{ value: 3, done: false }
> cursor.next()
{ value: undefined, done: true }

当调用Generator函数时,会立即执行到第一次遇到的yield关键字处暂停。在调用next()函数后,返回yield后的表达式的值(value),然后Generator里再继续执行之后的代码。每次遇到yield都返回一个值,在第三次调用next()后,Generator函数终止,最后调用的next()将返回{ value: undefined, done: true }

当然咯,Generator不只能用来创建数字序列。它能够暂停和恢复函数的执行,这便可以不需要回调函数就完成异步流的控制。

我们用异步函数来证明这一点。一般我们会做些I/O操作,这里为了简单起见,用setTimeout模拟。这个异步函数立即返回一个promise。(ES6有原生的promise):

function asyncDouble(x) {
var deferred = Promise.defer();
setTimeout(function(){
deferred.resolve(x*2);
}, 1000);
return deferred.promise;
}

然后写一个消费者函数:

function consumer(generator){
var cursor = generator();
var value;
function loop() {
var data = cursor.next(value);
if (data.done) {
return;
} else {
data.value.then(x => {
value = x;
loop();
})
}
}
loop();
}

这个函数接受Generator函数作为参数,只要yield有值,就会继续调用next()方法。这个例子中,yield的值是promise,所以必须等待promise调用resolve后,再递归调用loop()循环。

resolve会调用then()方法,把resolve的结果赋值给value,value被定义在函数外部,它会被传给下一个next(value)。这次next的调用为yield产生了一个返回值(即value)。这样可以不用回调函数来写异步调用了:

function* myGenerator(){
const data1 = yield asyncDouble(1);
console.log(`Double 1 = ${data1}`);
const data2 = yield asyncDouble(2);
console.log(`Double 2 = ${data2}`);
const data3 = yield asyncDouble(3);
console.log(`Double 3 = ${data3}`);
}

consumer(myGenerator);

Generator函数myGenerator将在每次遇到yield时暂停,等待消费者函数的promise去resolve。在控制台每隔1秒的输出:

Double 1 = 2
Double 2 = 4
Double 3 = 6

上面的示例代码不推荐在生产环境中用,可以用更完善的co库,能很简单地用yield处理异步,还包含了错误处理:

co(function *(){
var a = yield Promise.resolve(1);
console.log(a);
var b = yield Promise.resolve(2);
console.log(b);
var c = yield Promise.resolve(3);
console.log(c);
}).catch(function(err){
console.error(err.stack);
});

在ES7中,将异步处理的改进更近了一步,增加了asyncawait关键字,无需使用Generator。上面的例子可以写成:

async function (){
try {
var a = await Promise.resolve(1);
console.log(a);
var b = await Promise.resolve(2);
console.log(b);
var c = await Promise.resolve(3);
console.log(c);
} catch (err) {
console.error(err.stack);
}
};

有了这些特性,不会觉得JavaScript写异步代码麻烦了,而且在任何地方都可以用。

Generator不仅简洁明了,还能做些用回调很难实现的事。比如Node.js中koaWeb框架的中间件。koa的目标是替换掉Express,它有个很不错的特性:中间件的上下游都可以对服务端响应做进一步修改。看看这段koa服务器代码:

// Response time logger middleware
app.use(function *(next){
// Downstream
var start = new Date;
yield next;
// Upstream
this.body += ' World';
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
});

// Response handler
app.use(function *(){
this.body = 'Hello';
});

app.listen(3000);



来源:http://www.inkpaper.io/blog/post/2015/10/18/navigating-the-react-ecosystem.html


1. 回复“m”可以查看历史记录;

2. 回复“h”或者“帮助”,查看帮助;

开发者已开通多个微信群交流学习,请加若飞微信:13511421494 进群


开发者: KaiFaX
面向开发者、程序员的专业平台!



以上是关于React.js生态系统概览 [译]的主要内容,如果未能解决你的问题,请参考以下文章

从传统的 Laravel/Rails 应用迁移到 React.js 生态系统。需要在前端进行数据管理

译React.js的diff算法

[译] 我多希望在我学习 React.js 之前就已经知晓这些小窍门

Web前端开发:React.js与web前端是什么关系?

Web前端开发:React.js与web前端是什么关系?

聊聊MassTransit——实现Saga模式概览(译)