本文概述
JavaScript Land的创新速度如此之高, 以至于有些人甚至认为它适得其反。在几个月的时间里, 图书馆可能会从早期采用者的玩具到最新技术, 再到过时。能够识别出至少可以保持一年的相关性的工具本身正在成为一门艺术。
两年前发布React.js时, 我只是在学习Angular, 而我很快就把React视作一些晦涩却又模板化的图书馆。在那两年中, Angular确实在JavaScript开发人员中占据了立足点, 并且几乎成为现代JS开发的代名词。我什至开始在非常保守的公司环境中看到Angular, 我认为这是光明的未来。
但是突然, 一件奇怪的事情发生了。 Angular似乎成为了奥斯本效应或”预先宣布死亡”的受害者。团队宣布, 首先, Angular 2将会完全不同, 没有从Angular 1明确的移植途径, 其次, Angular 2将在一年左右的时间内不再可用。这会告诉想要启动新网络项目的人吗?你是否要在一个新版本发布将使它过时的框架中编写新项目?
开发人员之间的这种焦虑影响了React, React正在寻求在社区中建立自己的地位。但是React总是将自己称为” MVC”中的” V”。这使习惯于使用完整框架的Web开发人员感到有些沮丧。如何填写缺失的部分?我应该自己写吗?我应该只使用现有的库吗?如果是这样, 哪一个?
果然, Facebook(React.js的创建者)还有一个强项:Flux工作流, 该工作流承诺填充缺少的” M”和” C”功能。为了使事情变得更加有趣, Facebook表示Flux是一种”模式”, 而不是框架, 它们对Flux的实现只是该模式的一个示例。忠实于他们的话, 他们的实现确实很简单, 并且涉及编写很多冗长而重复的样板来使事情顺利进行。
开源社区进行了抢救, 一年后, 我们有了数十个Flux库, 甚至还有一些旨在比较它们的元项目。这是一件好事。 Facebook并未发布某些现成的公司框架, 而是设法引起了社区的兴趣, 并鼓励人们提出自己的解决方案。
这种方法有一个有趣的副作用:当你必须组合许多不同的库以获取完整的框架时, 就可以有效地逃避供应商的锁定, 并且可以轻松地重用自己的框架构建过程中出现的创新。别处。
这就是为什么围绕React的新事物如此有趣的原因;大部分可以在其他JavaScript环境中轻松重用。即使你不打算使用React, 看看它的生态系统也是鼓舞人心的。你可能想使用功能强大但相对易于配置的模块捆绑器Webpack简化构建系统, 或者立即开始使用Babel编译器编写ECMAScript 6甚至ECMAScript 7。
在本文中, 我将介绍一些有趣的功能和库。因此, 让我们探索React生态系统!
建立系统
可以说, 构建系统是你在创建新的Web应用程序时应该关心的第一件事。构建系统不仅是运行脚本的工具, 而且在JavaScript世界中, 它通常会影响应用程序的总体结构。构建系统必须涵盖的最重要的任务如下:
- 管理外部和内部依赖性
- 运行编译器和预处理器
- 优化生产资产
- 运行开发Web服务器, 文件观察器和浏览器重新加载器
近年来, 与Bower和Grunt共同开发的Yeoman工作流被视为现代前端开发的三位一体, 分别解决了样板生成, 程序包管理和日常任务运行的问题, 近来人们越来越多地从Grunt切换到Gulp。
在React环境中, 你可以放心地忘记这些。并不是说你不能使用它们, 而是有可能你可以摆脱使用Webpack和老式NPM的束缚。那怎么可能? Webpack是一个模块打包程序, 它在浏览器中也实现Node.js世界中常见的CommonJS模块语法。实际上, 它使事情变得更简单, 因为你不需要为前端学习另一个软件包管理器。你只需使用NPM并在服务器和前端之间共享依赖项。你也不需要处理按正确顺序加载JS文件的问题, 因为它是从每个文件中指定的依赖项导入推断出来的, 并且整个群集都正确地连接到一个可加载脚本中。
Webpack
为了使事情更具吸引力, 与以前的表弟Browserify不同, Webpack还可以处理其他资产类型。例如, 使用加载程序, 你可以将任何资产文件转换为内联或加载引用文件的JavaScript函数。因此, 无需手动处理和引用HTML中的资产。只需从JavaScript中获取CSS / SASS / LESS文件, Webpack就会通过一个简单的配置文件来处理其余的工作。 Webpack还包括一个开发Web服务器和一个文件监视程序。另外, 你可以使用package.json中的”脚本”键来定义shell一线式:
{
"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生成器时, 不要气disc(最前沿的库通常没有一个)。你仍然可以从GitHub克隆一些样板, 然后破解。
相关:React组件如何使UI测试变得容易
ECMA明天的脚本, 今天
近年来, JavaScript语言的发展速度大大提高, 经过一段时间的消除古怪和稳定语言的发展, 我们现在看到了强大的新功能。ECMAScript 6(ES6)规范草案已经完成, 即使尚未被正式宣布, 已经被广泛采用。关于ECMAScript 7(ES7)的工作正在进行中, 但是更前沿的库已经采用了其许多功能。
ECMAScript 7
这怎么可能?也许你认为你无法利用这些闪亮的JavaScript新功能, 除非Internet Explorer支持它们, 然后再考虑一下。 ES编译器已经无处不在, 即使没有适当的浏览器支持, 我们也可以做到。目前最好的ES编译器是Babel:它将使用你最新的ES6 +代码, 并将其转换为原始ES5, 因此, 你可以在发明任何ES新功能后立即使用它(并在Babel中实现, 通常会很快)。
巴别塔
最新的JavaScript功能在所有前端框架中都很有用, 并且React最近已更新为可以与ES6和ES7规范很好地配合使用。这些新功能应该消除使用React开发时的许多麻烦。让我们看一些最有用的添加, 以及它们如何使React项目受益。稍后, 我们将了解如何在利用此改进的语法的同时将一些有用的工具和库与React结合使用。
ES6课程
面向对象的程序设计是一种功能强大且被广泛采用的范例, 但是JavaScript对此的看法有些奇怪。因此, 大多数前端框架(无论是Backbone, Ember, Angular还是React)都采用了自己专有的定义类和创建对象的方式。但是有了ES6, 我们现在有了JavaScript中的传统类, 使用它们而不是编写我们自己的实现就很有意义。因此, 代替:
React.createClass({
displayName: 'HelloMessage', render() {
return <div>Hello {this.props.name}</div>;
}
})
我们可以这样写:
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>
);
}
}
在这里, React生命周期方法getDefaultProps和getInitialState不再需要。 getDefaultProps成为静态类变量defaultProps, 并且初始状态仅在构造函数中定义。唯一的缺点是方法不再自动绑定, 因此从JSX调用处理程序时必须使用bind。
装饰工
装饰器是ES7的一项有用功能。它们允许你通过将函数或类包装在另一个函数中来增强其行为。例如, 假设你要在某些组件上使用相同的更改处理程序, 但又不想提交继承反模式。你可以改用类装饰器。让我们如下定义装饰器:
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函数添加到目标类的原型。
要应用装饰器, 我们可以编写:
MyClass = changeHandler(MyClass)
或更优雅地使用ES7语法:
@addChangeHandler
class MyClass {
...
}
至于changeHandler函数本身的内容, 由于React没有双向数据绑定, 因此在React中处理输入可能很乏味。 changeHandler函数试图使其变得更容易。第一个参数指定状态对象上的键, 该键将用作输入的数据对象。第二个参数是属性, 输入字段中的值将保存到该属性。这两个参数是使用bind关键字从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, 而无需定义更多自定义处理程序。
箭头功能
JavaScript的动态这种上下文一直困扰着开发人员, 因为有点不直观, 嵌套函数的这种上下文甚至在类内部也重置为全局。要解决此问题, 通常需要将其保存到某个外部范围变量(通常为_this), 并在内部函数中使用它:
class DirectorsStore {
onFetch(directors) {
var _this = this;
this.directorsHash = {};
directors.forEach(function(x){
_this.directorsHash[x._id] = x;
})
}
}
使用新的ES6语法, 可以将函数(x){重写为(x)=> {。这种”箭头”方法定义不仅将其正确地绑定到外部范围, 而且还相当短, 这在编写大量异步代码时绝对重要。
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
厌倦了笨拙的论证对象?使用新的规范, 你可以将其余参数作为数组获取:
function networkAction(context, method, ...rest) {
// rest is an array
return method.apply(context, rest);
}
而且, 如果你不喜欢调用apply(), 则可以将数组扩展为函数参数:
myArguments = ['foo', 'bar', 123];
myFunction(...myArguments);
生成器和异步函数
ES6引入了JavaScript生成器。生成器本质上是一个JavaScript函数, 可以暂停执行, 然后在稍后继续执行, 记住其状态。每次遇到yield关键字时, 都会暂停执行, 并将yield参数传递回调用对象:
function* sequence(from, to) {
console.log('Ready!');
while(from <= to) {
yield from++;
}
}
这是此生成器正在运行的示例:
> 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都返回另一个值, 但是在第三个调用之后, 生成器函数终止, 并且对next()的每个后续调用将返回{value:undefined, done:true}。
当然, 生成器的目的不是创建复杂的数字序列。令人兴奋的部分是它们停止和恢复函数执行的能力, 可用于控制异步程序流并最终摆脱那些令人讨厌的回调函数。
为了证明这个想法, 我们首先需要一个异步函数。通常, 我们会进行一些I / O操作, 但为简单起见, 我们只使用setTimeout并返回一个Promise。 (请注意, ES6还向JavaScript引入了本机承诺。)
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();
}
此函数将任何生成器作为参数, 只要有要产生的值, 就继续在其上调用next()。在这种情况下, 产生的值是promise, 因此有必要等待promise解析, 然后对loop()使用递归来实现嵌套函数之间的循环。
返回值在then()处理函数中解析, 并传递给在外部作用域中定义的value, 该值将传递给next(value)调用。该调用使该值成为相应的yield表达式的结果。这意味着我们现在可以异步编写而根本没有任何回调:
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);
生成器myGenerator将在每种收益率上暂停, 等待消费者交付已解决的承诺。实际上, 我们将看到计算的数字以一秒钟的间隔出现在控制台中。
Double 1 = 2
Double 2 = 4
Double 3 = 6
这说明了基本概念, 但是, 建议你不要在生产中使用此代码。而是选择一个经过良好测试的库, 例如co。这将使你轻松编写带有收益的异步代码, 包括错误处理:
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);
});
因此, 此示例说明如何使用ES6生成器编写不带回调的异步代码。 ES7通过引入async和await关键字, 并完全消除了对生成器库的需求, 将这一方法又向前迈进了一步。有了此功能, 上一个示例将如下所示:
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中使用异步代码的麻烦。不仅在React中, 而且在其他地方也是如此。
生成器不仅更简洁明了, 而且使我们能够使用很难通过回调实现的技术。生成器良好性的一个突出示例是Node.js的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);
拜托
响应中间件向下游传递到响应处理程序中, 该响应处理程序设置响应主体, 并在上游流中(在yield表达式之后)允许对此.body以及其他功能(如时间记录)的进一步修改, 这是因为上游和下游共享相同的功能上下文。这比Express强大得多, 在Express中, 尝试完成同一件事的尝试将像这样结束:
var start;
// Downstream middleware
app.use(function(req, res, next) {
start = new Date;
next();
// Already returned, cannot continue here
});
// Response
app.use(function (req, res, next){
res.send('Hello World')
next();
});
// Upstream middleware
app.use(function(req, res, next) {
// res already sent, cannot modify
var ms = new Date - start;
console.log('%s %s - %s', this.method, this.url, ms);
next();
});
app.listen(3000);
你可能已经在这里发现了问题所在;使用”全局”开始变量将导致竞争状态, 并发请求并发废话。解决方案是一些不太明显的解决方法, 你可以忘记在上游流程中修改响应。
另外, 使用koa时, 你将免费获得生成器异步工作流程:
app.use(function *(){
try {
const part1 = yield fs.readFile(this.request.query.file1, 'utf8');
const part2 = yield fs.readFile(this.request.query.file2, 'utf8');
this.body = part1 + part2;
} catch (err) {
this.status = 404
this.body = err;
}
});
app.listen(3000);
你可以想象在Express中重新创建此小示例时涉及到的Promise和回调。
Node.js的所有对话都与React有什么关系?好吧, 在为React考虑合适的后端时, Node是首选。由于Node也使用JavaScript编写, 因此它支持后端和前端之间的代码共享, 从而使我们能够构建同构的React Web应用程序。但是, 稍后会对此进行更多介绍。
Flux库
React非常擅长创建可组合的视图组件, 但是我们需要某种方式来管理整个应用程序中的数据和状态。几乎所有人都同意, React最好由Flux应用程序体系结构来补充。如果你完全不熟悉Flux, 建议你快速刷新。
尚未达成普遍共识的是从许多Flux实现中选择哪个。 Facebook Flux将是显而易见的选择, 但是对于大多数人来说, 它太冗长了。替代实现主要集中在通过约定优于配置的方法来减少所需的样板数量, 并且还具有一些用于高级组件, 服务器端呈现等的便利功能。在这里可以看到一些具有各种受欢迎程度指标的顶级竞争者。我研究了Alt, 回流, Flummox, Fluxxor和Marty.js。
我选择合适的库的方法绝不是目标, 但无论如何它可能会有所帮助。 Fluxxor是我检出的第一批库之一, 但现在看起来有些陈旧。 Marty.js很有趣, 并且具有很多功能, 但是仍然涉及很多样板, 并且某些功能似乎是多余的。 Reflux看起来不错, 具有一定的吸引力, 但对于初学者来说有点难, 并且缺少适当的文档资料。 Flummox和Alt非常相似, 但是Alt似乎没有更多样板, 非常活跃的开发, 最新文档和有用的Slack社区, 因此我选择了Alt。
旧助焊剂
旧助焊剂
使用Alt, Flux的工作流程变得更加简单, 而不会失去任何功能。 Facebook的Flux文档中有很多关于调度程序的内容, 但是我们可以自由地忽略这一点, 因为在Alt中, 调度程序是按照约定隐式地连接到动作的, 通常不需要任何自定义代码。剩下的只是商店, 动作和组件。这三层的使用方式可以很好地映射到MVC思想模型中:存储是模型, 动作是控制器, 组件是视图。主要区别是Flux模式中心的单向数据流, 这意味着控制器(动作)不能直接修改视图(组件), 而只能触发视图(组件)的修改, 而视图被被动地绑定到该模型(存储)。对于某些开明的Angular开发人员来说, 这已经是最佳实践。
工作流程如下:
- 组件启动动作。
- 商店监听动作并更新数据。
- 组件绑定到存储, 并在更新数据时重新呈现。
动作
使用Alt Flux库时, 动作通常有两种形式:自动和手动。自动动作是使用generateActions函数创建的, 它们直接进入调度程序。手动方法被定义为你的操作类的方法, 它们可以与其他有效负载一起进入调度程序。自动操作最常见的用例是通知存储有关应用程序中某些事件的信息。除其他事项外, 手动操作是处理服务器交互的首选方法。
因此, REST API调用属于动作。完整的工作流程如下:
- 组件触发一个动作。
- 动作创建者运行一个异步服务器请求, 结果作为有效负载进入调度程序。
- 商店侦听该动作, 相应的动作处理程序将结果作为参数接收, 并且商店相应地更新其状态。
对于AJAX请求, 我们可以使用axios库, 该库尤其可以无缝处理JSON数据和标头。可以使用ES7异步/等待模式来代替承诺或回调。如果POST响应状态不是2XX, 则将引发错误, 并且我们将调度返回的数据或收到的错误。
让我们看一下登录页面, 以获取Alt工作流程的简单示例。注销操作不需要做任何特殊的事情, 只需通知商店, 我们就可以自动生成它。登录操作是手动的, 并且期望登录数据作为操作创建者的参数。从服务器获得响应后, 我们将调度成功数据, 或者如果抛出错误, 则调度收到的错误。
class LoginActions {
constructor() {
// Automatic action
this.generateActions('logout');
}
// Manual action
async login(data) {
try {
const response = await axios.post('/auth/login', data);
this.dispatch({ok: true, user: response.data});
} catch (err) {
console.error(err);
this.dispatch({ok: false, error: err.data});
}
}
}
module.exports = (alt.createActions(LoginActions));
专卖店
Flux存储有两个用途:它具有动作处理程序并带有状态。让我们继续我们的登录页面示例, 以了解其工作原理。
让我们创建具有两个状态属性的LoginStore:用户(用于当前登录的用户)和错误(用于当前与登录相关的错误)。本着减少样板的精神, Alt允许我们使用单个函数bindActions绑定到一个类的所有动作。
class LoginStore {
constructor() {
this.bindActions(LoginActions);
this.user = null;
this.error = null;
}
...
处理程序名称由约定定义, 并在相应的动作名称之前。因此, 登录操作由onLogin等处理。请注意, 动作名称的首字母将以驼峰式大写。在我们的LoginStore中, 我们有以下处理程序, 由相应的操作调用:
...
onLogin(data) {
if (data.ok) {
this.user = data.user;
this.error = null;
router.transitionTo('home');
} else {
this.user = null;
this.error = data.error
}
}
onLogout() {
this.user = null;
this.error = null;
}
}
组件
将存储绑定到组件的通常方法是使用某种React Mixin。但是由于mixin已过时, 因此需要其他方法。新方法之一是使用高阶组件。我们将组件放入包装器组件中, 该组件将用于监听存储并调用重新渲染。我们的组件将以道具的形式接收商店的状态。这种方法还有助于将我们的代码组织为智能和笨拙的组件, 这些组件最近变得很流行。对于Alt, 组件包装器由AltContainer实现:
export default class Login extends React.Component {
render() {
return (
<AltContainer stores={{LoginStore: LoginStore}}>
<LoginPage/>
</AltContainer>
)}
}
我们的LoginPage组件还使用了前面介绍的changeHandler装饰器。如果登录失败, 则使用LoginStore中的数据显示错误, 并且AltContainer负责重新呈现。单击登录按钮将执行登录操作, 从而完成Alt Flux工作流程:
@changeHandler
export default class LoginPage extends React.Component {
constructor(props) {
super(props);
this.state = {
loginForm: {}
};
}
login() {
LoginActions.login(this.state.loginForm)
}
render() {
return (
<Alert>{{this.props.LoginStore.error}}</Alert>
<Input
label='Username'
type='text'
value={this.state.login.username}
onChange={this.changeHandler.bind(this, 'loginForm', 'username')} />
<Input
label='Password'
type='password'
value={this.state.login.password}
onChange={this.changeHandler.bind(this, 'loginForm', 'password')} />
<Button onClick={this.login.bind(this)}>Login</Button>
)}
}
同构渲染
同构Web应用程序是当今热门的话题, 因为它们解决了传统单页应用程序最大的麻烦。在那些应用程序中, 标记是由JavaScript在浏览器中动态创建的。结果是, 关闭JavaScript的客户端无法使用该内容, 特别是搜索引擎Web搜寻器。这意味着你的网页未建立索引, 也不会出现在搜索结果中。有多种方法可以解决此问题, 但远非最佳。同构方法尝试通过在服务器上预呈现单个页面应用程序的请求URL来解决此问题。使用Node.js, 你可以在服务器上使用JavaScript, 这意味着React也可以在服务器端运行。这不应该太难了吧?
一个障碍是某些Flux库, 尤其是那些使用单例的库, 在服务器端渲染方面存在困难。当你具有单例Flux存储并且对服务器有多个并发请求时, 数据将变得混乱。一些库通过使用Flux实例来解决此问题, 但这还有其他缺点, 特别是需要在代码中传递这些实例。 Alt也提供了Flux实例, 但是它也解决了单例服务器端渲染的问题。它会在每个请求之后刷新存储, 以便每个并发请求都以干净的状态开始。
服务器端渲染功能的核心由React.renderToString提供。整个React前端应用程序也运行在服务器上。这样, 我们无需等待客户端JavaScript创建标记;它是在服务器上针对访问的URL预先构建的, 并以HTML格式发送到浏览器。客户端JavaScript运行时, 它将在服务器停止运行的地方继续工作。为此, 我们可以使用Iso库, 该库应与Alt一起使用。
首先, 我们使用alt.bootstrap在服务器上初始化Flux。可以用渲染数据预填充Flux存储。还必须决定为哪个URL呈现哪个组件, 这是客户端路由器的功能。我们使用的是alt的单例版本, 因此在每次渲染后, 我们都需要对alt.flush()商店进行清理, 以用于其他请求。使用iso附加组件, Flux的状态被序列化为HTML标记, 以便客户端知道从哪里提取:
// We use react-router to run the URL that is provided in routes.jsx
var getHandler = function(routes, url) {
var deferred = Promise.defer();
Router.run(routes, url, function (Handler) {
deferred.resolve(Handler);
});
return deferred.promise;
};
app.use(function *(next) {
yield next;
// We seed our stores with data
alt.bootstrap(JSON.stringify(this.locals.data || {}));
var iso = new Iso();
const handler = yield getHandler(reactRoutes, this.request.url);
const node = React.renderToString(React.createElement(handler));
iso.add(node, alt.flush());
this.render('layout', {html: iso.render()});
});
在客户端, 我们获取服务器状态, 并使用数据引导alt。然后, 我们在目标容器上运行Router和React.render, 这将根据需要更新服务器生成的标记。
Iso.bootstrap(function (state, _, container) {
// Bootstrap the state from the server
alt.bootstrap(state)
Router.run(routes, Router.HistoryLocation, function (Handler, req) {
let node = React.createElement(Handler)
React.render(node, container)
})
})
可爱!
有用的前端库
没有提到一些在React上特别有用的前端库, React生态系统指南是不完整的。这些库处理几乎在每个Web应用程序中发现的最常见的任务:CSS布局和容器, 样式化的表单和按钮, 验证, 日期选择等。当这些问题已经解决时, 没有必要重新发明轮子。
React引导
Twitter的Bootstrap框架已变得司空见惯, 因为它为每个不想花大量时间在CSS中工作的Web开发人员提供了巨大的帮助。特别是在原型设计阶段, Bootstrap是必不可少的。要在React应用程序中利用引导功能, 最好将它与React-Bootstrap一起使用, 它具有出色的React语法, 并使用本机React组件重新实现Bootstrap的jQuery插件。生成的代码简洁易懂:
<Navbar brand='React-Bootstrap'>
<Nav>
<NavItem eventKey={1} href='#'>Link</NavItem>
<NavItem eventKey={2} href='#'>Link</NavItem>
<DropdownButton eventKey={3} title='Dropdown'>
<MenuItem eventKey='1'>Action</MenuItem>
<MenuItem eventKey='2'>Another action</MenuItem>
<MenuItem eventKey='3'>Something else here</MenuItem>
<MenuItem divider />
<MenuItem eventKey='4'>Separated link</MenuItem>
</DropdownButton>
</Nav>
</Navbar>
就我个人而言, 我无法逃脱这种感觉, 那就是HTML应该一直是这样。
如果要将Bootstrap源与Webpack一起使用, 请考虑使用less-loader或bootstrap-sass-loader, 具体取决于你喜欢的预处理器。它可以让你轻松自定义要包括的Bootstrap组件, 还可以在CSS代码中使用LESS或SASS全局变量。
React路由器
React Router已成为React中事实上的路由标准。它允许嵌套路由, 对重定向的支持, 与同构渲染很好地配合, 并且具有基于JSX的简单语法:
<Router history={new BrowserHistory}>
<Route path="/" component={App}>
<Route path="about" name="about" component={About}/>
<Route path="users" name="users" component={Users} indexComponent={RecentUsers}>
<Route path="/user/:userId" name="user" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
React Router还提供一个Link组件, 可用于在应用程序中进行导航, 仅指定路由名称:
<nav>
<Link to="about">About</Link>
<Link to="users">Users</Link>
</nav>
甚至还有一个用于与React-Bootstrap集成的库, 因此, 如果你使用的是Bootstrap的组件, 并且不想一直手动对其设置活动类, 则可以使用react-router-bootstrap并编写如下代码:
<Nav>
<NavItemLink to="about">About</NavItemLink>
<NavItemLink to="users">Users</NavItemLink>
</Nav>
无需其他设置。活动链接会照顾好自己。
Formsy-React
处理表单可能很乏味, 因此让我们从formy-react库中获取一些帮助, 该库将帮助我们管理验证和数据模型。奇怪的是, Formsy-React库不包含实际的表单输入, 因为鼓励用户编写自己的表单(很棒)。但是, 如果你对常见的内容感到满意, 则只需使用formy-react-components。 Bootstrap类包括在内:
import Formsy from 'formsy-react';
import {Input} from 'formsy-react-components';
export default class FormsyForm extends React.Component {
enableButton() {
this.setState({canSubmit: true});
}
disableButton() {
this.setState({canSubmit: true});
}
submit(model) {
FormActions.saveEmail(model.email);
}
render() {
return (
<Formsy.Form onValidSubmit={this.submit} onValid={this.enableButton} onInvalid={this.disableButton}>
<Input
name="email"
validations="isEmail"
validationError="This is not a valid email"
required/>
<button type="submit" disabled={!this.state.canSubmit}>Submit</button>
</Formsy.Form>
)}
}
日历和提前输入
日历和预输入功能为每个UI工具包锦上添花。不幸的是, 这两个组件已从Bootstrap 3中删除, 可能是因为它们对于通用CSS框架过于专业化。幸运的是, 我已经能够在react-pikaday和react-select中找到有价值的替代品。我已经测试了10多个库, 而这两个库是最好的。它们也很容易使用:
import Pikaday from 'react-pikaday';
import Select from 'react-select';
export default class CalendarAndTypeahead extends React.Component {
constructor(props){
super(props);
this.options = [
{ value: 'one', label: 'One' }, { value: 'two', label: 'Two' }
];
}
dateChange(date) {
this.setState({date: date});
}, selectChange(selected) {
this.setState({selected: selected});
}, render() {
return (
<Pikaday
value={this.state.date}
onChange={this.dateChange} />
<Select
name="form-field-name"
value={this.state.selected}
options={this.options}
onChange={selectChange} />
)}
}
当涉及到React时, 那里就是一片丛林!这是一张可以帮助你找到路的地图。
鸣叫
结论-React.JS
在本文中, 我介绍了一些库和技术, 它们被认为是当前Web开发中效率最高的。其中一些是特定于React的, 但是由于React的开放性, 它们中的许多也可以在其他环境中使用。有时, 由于担心最新的技术而阻碍了技术的进步, 因此, 我希望本文能够消除对React, Flux和ECMAScript中的最新功能的疑虑。
如果你有兴趣, 可以看一下使用这些技术构建的示例应用程序。源代码可在GitHub上获得。
谢谢阅读!
相关:srcmini开发人员的React.js最佳实践和技巧
评论前必须登录!
注册