作者都是各自领域经过审查的专家,并撰写他们有经验的主题. 我们所有的内容都经过同行评审,并由同一领域的Toptal专家验证.
托马斯·霍拉斯的头像

Tomas Holas

Tomas最初是一个Ruby on Rails爱好者, 但在2010年,他转向了JavaScript,从此更喜欢使用Angular, React, and NodeJS.

Previously At

IBM
Share

浏览反应.js ecosystem

JavaScript领域的创新速度是如此之快,有些人甚至认为它是如此之快 适得其反. 图书馆可以从早期采用者变成玩具, to state-of-art, 几个月后就过时了. 能够识别出一种至少在未来一年内仍然具有相关性的工具本身就是一门艺术.

When React.js was released two years ago, 我刚刚在学习Angular, 我很快就把React当成了另一个晦涩的模板库. 在这两年里,Angular真正在 JavaScript开发人员,几乎成为了现代JS开发的代名词. 我甚至开始在非常保守的企业环境中看到Angular, 我以为光明的未来是理所当然的.

但是突然,一件奇怪的事情发生了. It seems Angular 成为奥斯本效应的受害者或“预先宣布死亡”. 该团队宣布, first, Angular 2将会完全不同, 没有明确的从Angular 1迁移的路径, and, second, Angular 2要再过一年左右才能发布. 这对想要开始一个新网络项目的人来说意味着什么? 您是否希望在一个即将因新版本发布而过时的框架中编写新项目?

开发人员的这种焦虑正中React的下怀, 是想要在社区中建立自己的地位. 但是React总是把自己定位为MVC中的“V”.这在网页开发者中引起了一定程度的挫败感, 谁习惯于使用完整的框架. 我该如何填补缺失的部分? 我应该自己写吗? 我应该使用现有的库吗? If so, which one?

果然,Facebook (React的创造者).js)还有一张王牌:the Flux 工作流,它承诺填补缺失的“M”和“C”功能. 让事情变得更有趣, Facebook称Flux是一种“模式”,” not a framework, 他们对Flux的实现只是这种模式的一个例子. 正如他们所说,他们的实现非常简单,并且需要编写大量 verbose,重复的样板文件使事情顺利进行.

开源社区伸出了援手, and a year later, 我们有几十个Flux库, 甚至一些元项目的目标 comparing them. This is a good thing; instead of releasing some ready-made corporate framework, Facebook成功地激起了社区的兴趣, 并鼓励人们想出自己的解决方案.

这种方法有一个有趣的副作用:当您必须组合许多不同的库以获得完整的框架时, 您正在有效地摆脱供应商锁定, 在您自己的框架构建过程中出现的创新可以很容易地在其他地方重用.

这就是为什么到处都是新东西 React is so interesting; most of it can be readily reused in other JavaScript environments. 即使你不打算使用React,看看它的生态系统也是鼓舞人心的. 您可能希望使用功能强大的, 但相对容易配置, module bundler Webpack,或者现在就开始编写ECMAScript 6,甚至ECMAScript 7 Babel compiler.

在本文中,我将介绍一些有趣的可用特性和库. 那么,让我们来探索一下React生态系统!

反应探险者

Build System

在创建一个新的web应用程序时,构建系统可以说是你应该关心的第一件事. 构建系统不仅仅是运行脚本的工具, 而是在JavaScript的世界里, 它通常决定了应用程序的总体结构. 构建系统必须涵盖的最重要的任务如下:

  • 管理外部和内部依赖关系
  • 运行编译器和预处理器
  • 优化生产资产
  • 运行开发web服务器、文件监视器和浏览器重新加载程序

近年来, Yeoman workflow with Bower and Grunt 被视为现代前端开发的三位一体, 解决了生成样板文件的问题, 包管理, 和常用任务分别运行, 更进步的人从咕噜咕噜变成了 Gulp recently.

在React环境中,您可以放心地忘记这些. 并不是说你不能使用它们,但很有可能你可以使用它们而不受惩罚 Webpack and good old NPM. 这怎么可能?? Webpack是一个模块绑定器,它实现了 CommonJS module 语法,在Node中很常见.Js世界,在浏览器中也是如此. It actually makes things simpler since you don’t need to learn yet another package manager for front end; you just use NPM and share dependencies between server and front end. 您也不需要处理以正确顺序加载JS文件的问题,因为它是从每个文件中指定的依赖项导入推断出来的, 整个集群被正确地连接到一个可加载的脚本.

Webpack Logo
Webpack

更吸引人的是,Webpack不像它的老表弟 Browserify,也可以处理其他资产类型. 例如, loaders, 您可以将任何资产文件转换为内联的JavaScript函数, or loads, 被引用的文件. 因此,忘记手动预处理和从HTML引用资产吧. Just require 你的CSS/SASS/LESS文件从JavaScript,和Webpack照顾剩下的简单 config file. Webpack还包括一个开发web服务器和一个文件监视器. 另外,你可以使用 "scripts" key in package.json 定义shell一行程序:

{
  “名称”:“react-example-filmdb”,
  "version": "0.0.1",
  "description": "同构React + Flux薄膜数据库示例",
  “主要”:“服务器/索引.js",
  "scripts": {
    "build": "./node_modules/.Bin /webpack——progress——stats——config ./webpack/prod.config.js",
    "dev": "node——harmony . ./ webpack dev-server.js",
    "prod": "NODE_ENV=生产节点服务器/索引.js",
    "test": "./node_modules/.Bin /karma启动——单次运行”,
    "postinstall": "npm run build"
  }
  ...
}

这差不多就是你要取代Gulp和Bower的全部了. 当然,您仍然可以使用Yeoman来生成应用程序样板. 当你想要的东西没有Yeoman生成器时,不要气馁(最先进的库通常没有)。. 你仍然可以从GitHub上克隆一些样板文件,然后删掉.

ECMAScript的明天,今天

近年来,JavaScript语言的开发速度大大加快, 经过一段时间的消除怪癖和稳定的语言, 我们现在看到了强大的新功能. ECMAScript 6 (ES6)规范草案已经发布 finalized尽管它还没有正式发布,但它已经被广泛采用. ECMAScript 7 (ES7)的工作正在进行中, 但它的许多功能已经被更先进的图书馆所采用.

ECMAScript 7 Logo
ECMAScript 7

这怎么可能?? 也许您认为在Internet Explorer中不支持这些JavaScript新特性之前,您无法利用它们, but think again. ES转译器已经变得无处不在,我们甚至可以不需要适当的浏览器支持. 目前可用的最好的ES转译器是 Babel它需要你最新的ES6+代码, 并将其转换为香草ES5, so, 您可以使用任何新的ES特性,只要它被发明出来(并在Babel中实现), 这通常发生得很快).

Babel Logo
Babel

最新的JavaScript特性对所有前端框架都很有用, React最近也进行了更新,可以很好地配合ES6和ES7规范. 在使用React进行开发时,这些新特性应该会消除很多令人头疼的问题. 让我们来看看一些最有用的新增功能,以及它们如何使React项目受益. 稍后,我们将看到如何使用一些 useful tools 和React的库,同时利用这种改进的语法.

ES6 classes

面向对象编程是一种强大且被广泛采用的范式, 但是JavaScript对它的处理有点奇怪. 大多数前端框架, be it Backbone, Ember, Angular, or React, 已经采用了他们自己专有的方式来定义类和创建对象. But with ES6, 我们现在有了JavaScript中的传统类, 使用它们而不是编写我们自己的实现是有意义的. So, instead of:

React.createClass({
  displayName:“HelloMessage”,
  render() {
    return 
Hello {this.props.name}
; } })

we can write:

类HelloMessage扩展React.Component {
  render() {
    return 
Hello {this.props.name}
; } }

对于更详细的示例,请考虑以下代码,使用旧语法:

React.createClass({
  displayName:“计数器”,
  getDefaultProps:函数(){
    返回{initialCount: 0};
  },
  getInitialState:函数(){
    返回{count: this.props.initialCount} 
  },
  propTypes: {initialCount: React.PropTypes.number},
  tick() {
    this.设置状态({数:这个.state.count + 1});
  },
  render() {
    return (
      
Clicks: {this.state.count}
); } });

与ES6版本相比:

类计数器扩展React.Component {
  static propTypes = {initialCount: React.PropTypes.number};
  static defaultProps = {initialCount: 0};

  构造函数(道具){
    super(props);
    this.状态={计数:道具.initialCount};
  }

  状态={计数:这个.props.initialCount};
  tick() {
    this.设置状态({数:这个.state.count + 1});
  }

  render() {
    return (
      
Clicks: {this.state.count}
); } }

这里是React生命周期方法 getDefaultProps and getInitialState 都不再需要了. getDefaultProps 成为静态类变量 defaultProps初始状态只是在构造函数中定义的. 唯一的缺点是,方法不再自动绑定,所以您必须使用 bind 调用 JSX.

Decorators

装饰器是ES7的一个有用的特性. 它们允许您通过将函数或类包装在另一个函数中来增强其行为. For example, 让我们假设您希望在某些组件上使用相同的更改处理程序, 但是你不想承诺 继承反模式. 你可以使用类装饰器. 让我们这样定义装饰器:

addChangeHandler:函数(目标){
  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 adds the changeHandler 函数转换为目标类的原型.

要应用装饰器,我们可以这样写:

MyClass = changeHandler(MyClass)

或者更优雅地使用ES7语法:

@addChangeHandler
class MyClass {
  ...
}

至于内容的 changeHandler function itself, 因为React没有双向数据绑定, 在React中处理输入可能很乏味. The changeHandler 函数试图使它更容易. 第一个参数指定a key 状态对象,它将作为输入的数据对象. 第二个参数是属性,输入字段中的值将保存到该属性中. 控件从JSX设置这两个参数 bind keyword.

@addChangeHandler
类LoginInput扩展React.Component {
  构造函数(道具){
    super(props);
    this.state = {
      login: {}
    };
  }
  render() {
    return (
      
      
    )
  }
}

当用户修改username字段时,其值保存为 this.state.login.username,而无需定义更多的自定义处理程序.

Arrow Functions

JavaScript的动态 this 上下文对开发人员来说一直是一个痛苦的问题,因为,有点不直观 this 嵌套函数的上下文被重置为全局,即使在类中也是如此. 要解决这个问题,通常需要保存 this 到某个外部作用域变量(通常是 _this)并在内部函数中使用它:

类DirectorsStore {
  onFetch(董事){
    var _this = this;
    this.directorsHash = {};
    directors.forEach(函数(x) {
      _this.directorsHash[x._id] = x;
    })
  }
}

在新的ES6语法中, function(x){ 可以重写为 (x) => {. 这个“箭头”方法定义不仅正确绑定 this 到外部范围, 但也相当短, 在编写大量异步代码时,哪一个肯定是重要的.

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

解构作业

解构作业, introduced in ES6, 允许你在赋值的左边有一个复合对象:

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

console.log(p); // 42
console.log(q); // true 

这很好,但它如何在React中帮助我们? 考虑下面的例子:

函数makeRequest(url,方法,参数){
  var config = {
    url: url,
    method: method,
    params: params
  };
  ...
}

通过解构,您可以节省一些击键操作. The keys literal {url,方法,参数} 是否自动从范围中分配与键相同名称的值. 这种习惯用法经常被使用,消除重复使代码更不容易出错.

函数makeRequest(url,方法,参数){
  Var配置= {url,方法,参数};
  ...
}

解构还可以帮助你只加载模块的一个子集:

Const {clone, assign} = require('lodash');
函数输出(数据,可选){
  Var payload = clone(data);
  分配(有效载荷,可选);
}

参数:Default、Rest和Spread

函数参数在ES6中更强大. 最后,您可以设置 default argument:

函数http(端点,方法='GET') {
  console.log(method)
  ...
}

http('/api') // GET

厌倦了和那些笨重的东西打交道 arguments object? 有了新的规格,你可以得到 rest 作为数组的参数:

函数networkAction(context, method, context) ...rest) {
  // rest是一个数组
  return method.应用(上下文、其他);
}

如果你不喜欢打电话 apply(), you can just spread 将数组转换为函数参数:

myArguments = ['foo', 'bar', 123];
myFunction(...myArguments);

生成器和异步函数

ES6引入了JavaScript生成器. 生成器基本上是一个JavaScript函数,它的执行可以暂停,然后稍后恢复, 记住它的状态. Each time the 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}

当我们调用生成器函数时,它一直执行到第一次调用 yield, and then stops. After we call next(),它返回第一个值,并继续执行. Each yield 返回另一个值, 但是在第三个电话之后, 生成器函数终止, 以及随后的每一次呼叫 next() will return {value: undefined, done: true}.

当然,生成器的目的不是创建复杂的数字序列. 令人兴奋的部分是它们停止和恢复函数执行的能力, 哪一个可以用来控制异步程序流,最终摆脱那些讨厌的回调函数.

为了演示这个想法,我们首先需要一个异步函数. 通常,我们会有一些I/O操作,但是为了简单起见,我们使用 setTimeout and return a promise. (注意,ES6也引入了 native promises to JavaScript.)

函数asyncDouble(x) {
  var deferred =承诺.defer();
  setTimeout(函数(){
    deferred.resolve(x*2);
  }, 1000);
  return deferred.promise;
}

接下来,我们需要一个消费者函数:

函数消费者(发电机){
  Var cursor = generator();
  var value;
  函数loop() {
    Var data =游标.next(value);
    if (data.done) {
      return;
    } else {
      data.value.then(x => {
        value = x;
        loop();
      })
    }
  }
  loop();
}

这个函数接受任何生成器作为参数,并不断调用 next() 只要它有价值 yield. In this case, 产出的价值是承诺, 因此,有必要等待承诺兑现, 使用递归 loop() 实现跨嵌套函数的循环.

方法解析返回值 then() 处理程序,并传递给 value,它是在外部作用域中定义的,它将被传递到 next(value) call. 这个调用使该值成为相应yield表达式的结果. 这意味着我们现在可以在不需要任何回调的情况下进行异步写入:

函数* 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} ');
}

消费者(myGenerator);

The generator myGenerator 会暂停吗 yield,等待消费者交付已解决的承诺. 实际上,我们将看到计算出的数字每隔一秒出现在控制台中.

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);
}).抓住(函数(err) {
  console.error(err.stack);  
});

因此,这个例子展示了如何使用ES6生成器编写不带回调的异步代码. ES7将这种方法更进一步,引入了 async and await 关键字,并且完全消除了对生成器库的需求. 有了这个功能,前面的例子看起来像这样:

异步函数(){
  try {
    var a =等待承诺.resolve(1);
    console.log(a);
    var b =等待承诺.resolve(2);
    console.log(b);
    var c =等待承诺.resolve(3);
    console.log(c);
  } catch (err) {
    console.error(err.stack);  
  }
};

在我看来,这减少了在JavaScript中处理异步代码的痛苦. 不仅在React中,在其他任何地方也是如此.

生成器不仅更简洁和直接, 但也允许我们使用回调很难实现的技术. 发电机的优点的一个突出的例子是 koa 用于Node的中间件库.js. 它的目标是取代Express, and, 朝着这个目标前进, 它有一个杀手级特性:中间件链不仅流 downstream (应客户要求),但也 upstream,允许对服务器的响应进行进一步修改. 考虑下面的koa服务器示例:

//响应时间记录器中间件
app.使用(函数*(下){
  // Downstream
  var start =新的日期;
  yield next;
  // Upstream
  this.body += ' World';
  var ms = new日期开始;
  console.Log ('%s %s - %s',这个.method, this.url, ms);
});

//响应处理程序
app.使用(函数* (){
  this.body = 'Hello';
});

app.listen(3000);  

Koa Logo
Koa

响应中间件 yieldS下游到响应处理程序, 哪个设置响应体, 并在上游流(后) yield 表达),进一步修改 this.body is allowed, 还有其他功能,比如时间记录, 这是可能的,因为上游和下游共享相同的功能上下文. 这比 Express,在这种情况下,试图完成同样的事情会以这样的方式结束:

var start;
//下游中间件
app.使用(function(req, res, next) {
  start = new Date;
  next();
  //已返回,无法继续
});

// Response
app.使用(function (req, res, next){
  res.发送(“Hello World”)
  next();
});

//上游中间件
app.使用(function(req, res, next) {
  //文件已经发送,不能修改
  var ms = new日期开始;
  console.Log ('%s %s - %s',这个.method, this.url, ms);
  next();
});

app.listen(3000);  

You can probably already spot what’s wrong here; using a “global” start 变量将导致竞争条件,对并发请求返回无意义. 解决方案是一些不明显的变通方法, 你可以忘记修改上游流的响应.

此外,当使用koa时,你将免费获得生成器异步工作流:

app.使用(函数* (){
  try {
    Const part1 = yield fs.readFile(this.request.query.file1, 'utf8');
    Const part2 = yield fs.readFile(this.request.query.file2, 'utf8');
    this.主体= part1 + part2;
  } catch (err) {
    this.status = 404
    this.body = err;
  }
});

app.listen(3000);

您可以想象在Express中重新创建这个小示例所涉及的承诺和回调.

Node.js Logo

所有这些节点是如何运作的.js的谈话与React有关? 当考虑一个合适的React后端时,Node是第一选择. 因为Node也是用JavaScript编写的, 支持后端和前端之间的代码共享, 让我们建立 isomorphic React web应用程序. 但是,稍后会详细介绍这个.

Flux Library

React非常擅长创建可组合的视图组件, 但是我们需要一些方法来管理整个应用程序中的数据和状态. 几乎所有人都同意,React最好与Flux应用架构相辅相成. 如果你完全是Flux的新手,我建议你快速 refresh.

Flux Logo
Flux

在众多的Flux实现中选择哪一种还没有得到普遍认同. Facebook Flux 这是显而易见的选择,但对大多数人来说太啰嗦了. 可选的实现主要侧重于使用约定优于配置的方法来减少所需的样板文件的数量, 还有一些用于高阶分量的方便函数, 服务器端呈现, and so on. 我们可以看到一些具有不同人气指标的热门竞争者 here. 我调查了Alt, Reflux, flumox, Fluxxor和Marty.js.

我选择合适的图书馆的方式绝不是客观的,但它可能会有所帮助. Fluxxor 是我第一次借阅的库之一,但现在看起来有点过时了. Marty.js is interesting, 它有很多功能, 但仍然涉及很多样板文件, 有些功能似乎是多余的. Reflux 看起来不错,有一些牵引力, 但是对初学者来说感觉有点难, 而且也缺乏适当的文件. Flummox and Alt 都非常相似,但Alt似乎没有多少样板,非常活跃的开发,最新的 documentation and a helpful Slack community.因此,我选择了Alt.

Alt Flux

Alt Flux Logo
Alt Flux

有了Alt, Flux的工作流程变得更加简单,而不会失去它的任何功能. Facebook’s Flux documentation 说了很多关于调度员的事情,但我们可以忽略它,因为 Alt, 按照约定,调度程序隐式地连接到操作, 通常不需要任何自定义代码. 这就给我们留下了 stores, actions, and components. 可以这样使用这三个层,它们可以很好地映射到 MVC thought model: Stores are Models, actions are Controllers,组件是 Views. 主要区别在于Flux模式的中心是单向数据流, which means, 控制器(动作)不能直接修改视图(组件), but, instead, 只能触发模型(存储)修改, 视图被动地与之绑定. 这已经成为一些人的最佳实践 enlightened 角的开发人员.

Flux vs. Alt Workflows

工作流程如下:

  1. 组件启动操作.
  2. 存储监听操作并更新数据.
  3. 组件绑定到存储,并在数据更新时呈现.

Actions

当使用Alt Flux库时,操作通常有两种方式:自动和手动. 类创建自动操作 generateActions 函数,它们直接转到调度程序. 手动方法被定义为操作类的方法, 他们可以带着额外的载荷去调度员那里. 自动操作最常见的用例是通知应用程序中的某些事件. 除其他外,手动操作是 preffered 处理服务器交互的方式.

因此REST API调用属于操作. 完整的工作流程如下:

  1. 组件触发操作.
  2. 操作创建者运行异步服务器请求, 结果作为有效负载传递给调度程序.
  3. 商店会监听动作, 相应的操作处理程序将结果作为参数接收, 然后商店相应地更新它的状态.

对于AJAX请求,我们可以使用 axios 库,它可以无缝地处理JSON数据和头文件. 我们可以使用ES7来代替承诺或回调 async/await pattern. If the POST 响应状态不是2XX, 抛出错误, 我们调度返回的数据, or received error.

让我们看一下登录页面,看看Alt工作流的一个简单示例. 注销操作不需要做任何特别的事情, 只通知商店, 所以我们可以自动生成它. 登录操作是手动的,并且需要登录数据作为操作创建者的参数. 在我们从服务器得到响应之后, 我们或者调度成功数据, or, 如果抛出错误, 我们发送接收到的错误.

类LoginActions {
  constructor() {
    //自动操作
    this.generateActions(“注销”);
  }

  // Manual action
  异步登录(data) {
    try {
      Const response = await axios.帖子(/身份验证/登录,数据);
      this.调度({ok: true,用户:response).data});
    } catch (err) {
      console.error(err);
      this.调度({ok: false, error: err.data});
    }
  }
}

module.exports = (alt.createaction (loginaction));

Stores

Flux存储有两个目的:它有动作处理程序,并携带状态. 让我们继续我们的登录页面示例,看看它是如何工作的.

Let’s create LoginStore,有两个状态属性: user,用于当前登录的用户,和 error,以获取当前与登录相关的错误. 本着减少样板的精神, Alt允许我们用一个函数绑定到一个类的所有操作 bindActions.

类LoginStore {
  constructor() {
    this.bindActions (loginaction);
    this.user = null;
    this.error = null;
  }
  ...

处理程序名称是按照约定定义的 on 到相应的操作名称. So the login 操作由 onLogin, and so forth. 注意,动作名称的第一个字母将以camelCase样式大写. In our LoginStore,我们有以下处理程序,由相应的操作调用:

  ...
  onLogin(data) {
    if (data.ok) {
      this.user = data.user;
      this.error = null;
      router.transitionTo('家');
    } else {
      this.user = null;
      this.error = data.error
    }
  }

  onLogout() {
    this.user = null;
    this.error = null;
  }
}

Components

将存储库绑定到组件的常用方法是使用某种React mixin. 但是,既然mixins正在进行 out of fashion肯定还有别的办法. 新方法之一是使用高阶分量. 我们将组件放入包装器组件中, 哪一个会负责听存储和调用重新渲染. 我们的组件将接收存储的状态 props. 这种方法也有助于将我们的代码组织成 smart and dumb 组件,最近很流行. 对于Alt,组件包装器由 AltContainer:

导出默认类Login扩展React.Component {
  render() {
    return (
      
        
      
  )}
}

Our LoginPage 组件还使用 changeHandler 前面介绍的装饰器. Data from LoginStore 用于在登录失败的情况下显示错误,重新呈现由 AltContainer. 单击登录按钮将执行 login 操作,完成Alt通量工作流:

@changeHandler
导出默认类LoginPage扩展React.Component {
  构造函数(道具){
    super(props);
    this.state = {
      loginForm: {}
    };
  }
  login() {
    LoginActions.login(this.state.loginForm)
  }
  render() {
    return (
      {{this.props.LoginStore.error}}
      
      
      
  )}
}

同构呈现

同构web应用程序是最近的一个热门话题,因为它们解决了传统单页面应用程序的一些最大的麻烦. 在这些应用程序中,标记是由浏览器中的JavaScript动态创建的. 其结果是,在关闭JavaScript的情况下,客户端无法使用该内容, most notably, 搜索引擎网络爬虫. 这意味着你的网页不会被索引,也不会出现在搜索结果中. There are ways to work around this,但它们远不是最理想的. 同构方法试图通过在服务器上预呈现单个页面应用程序的请求URL来解决此问题. With Node.如果你在服务器端有JavaScript,这意味着React也可以在服务器端运行. 这应该不会太难,对吧?

一个障碍是一些Flux库, 尤其是那些使用单例的, 在服务器端呈现时遇到困难. 当你有单一的Flux存储和多个并发请求到你的服务器时, 数据会被混淆. 一些库通过使用Flux实例来解决这个问题, 但这也有其他缺点, 特别是需要在代码中传递这些实例. Alt也提供Flux实例, but it has also solved the problem of 服务器端呈现 with singletons; it flushes stores after each request, 这样,每个并发请求都可以从零开始.

服务器端呈现功能的核心是由 React.renderToString. 整个React前端应用程序也在服务器端运行. This way, we don’t need to wait for the client-side JavaScript to create the markup; it’s pre-built on the server for the accessed URL, 并以HTML格式发送到浏览器. 当客户端JavaScript运行时,它会从服务器停止的地方开始. 为了支持这一点,我们可以使用 Iso 库,这意味着与Alt一起使用.

首先,我们在服务器上使用 alt.bootstrap. 可以用渲染数据预先填充Flux存储. 还需要决定为哪个URL呈现哪个组件, 哪些是客户端的功能 Router. 的单例版本 alt,所以在每次渲染后,我们需要 alt.flush() 商店要求把它们清理干净. Using the iso add-on, Flux的状态被序列化为HTML标记, 这样客户就知道在哪里接电话了:

//我们使用react-router来运行routes中提供的URL.jsx
var getHandler =函数(路由,url) {
  var deferred =承诺.defer();
  Router.run(routes, url, function (Handler) {
    deferred.resolve(Handler);
  });
  return deferred.promise;
};

app.使用(function *(next) {
  yield next;
  //我们用数据播种我们的存储
  alt.bootstrap(JSON.stringify(this.locals.data || {}));
  var iso = new iso ();
  const handler = yield getHandler(reactRoutes).request.url);
  const节点= React.renderToString(反应.createElement(处理器));
  iso.add(node, alt.flush());
  this.渲染('layout', {html: iso.render()});
});

在客户端,我们获取服务器状态并进行引导 alt with the data. Then we run Router and React.render 在目标容器上,它将根据需要更新服务器生成的标记.

Iso.Bootstrap(函数(状态,容器){
  //从服务器引导状态
  alt.bootstrap(state)
  Router.(路线,路由器运行.HistoryLocation, function (Handler, req) {
    let node = React.createElement(处理器)
    React.呈现(节点,容器)
  })
})

Lovely!

有用的前端库

如果不提到几个与React配合得特别好的前端库,React生态系统指南就不完整. 这些库解决了几乎每个web应用程序中最常见的任务:CSS布局和容器, 样式表单和按钮, validations, date picking, and so on. 这些问题已经解决了,再做无谓的重复工作是没有意义的.

React-Bootstrap

React-Bootstrap标志

Twitter’s Bootstrap 框架已经变得司空见惯,因为它对每个不想花大量时间在CSS上的web开发人员都有巨大的帮助. 特别是在原型阶段,Bootstrap是必不可少的. 要在React应用程序中利用bootstrap的强大功能,最好使用它 React-Bootstrap, 它带有很好的React语法,并使用原生React组件重新实现Bootstrap的jQuery插件. 生成的代码简洁易懂:


  

就我个人而言,我无法摆脱这样一种感觉:这就是HTML应该一直有的样子.

如果你想在Webpack中使用Bootstrap源代码,可以考虑使用 less-loader or bootstrap-sass-loader,这取决于您喜欢的预处理器. 它将允许您轻松地自定义要包含哪些Bootstrap组件, 也允许在CSS代码中使用LESS或SASS全局变量.

React Router

React Router Logo

React Router 已成为事实上的标准 routing in React. 它允许嵌套路由, 支持重定向, 可以很好地使用同构渲染, 并且有一个简单的基于js的语法:


  
    
    
      
    
    
  

React Router也提供了一个 Link 组件,你可以在你的应用中使用它来导航,只指定路由名:


甚至还有一个与React-Bootstrap集成的库, 所以,如果你正在使用Bootstrap的组件,并不想设置 active 类,你可以使用 react-router-bootstrap 然后像这样写代码:


不需要额外的设置. 主动链接会照顾好自己.

Formsy-React

处理表单可能很乏味,所以让我们从 formsy-react 库,它将帮助我们管理验证和数据模型. form - react库, strangely enough, 不包括实际的表单输入,因为鼓励用户自己编写(这很好). 但如果你满足于常用的,那就用吧 formsy-react-components. 包括引导类:

从Formsy -react中导入Formsy;
从'form -react-components'中导入{Input};
导出默认类FormsyForm扩展React.Component {
  enableButton() {
    this.设置状态({canSubmit:真});
  }
  disableButton() {
    this.设置状态({canSubmit:真});
  }
  submit(model) {
    FormActions.saveEmail(model.email);
  }
  render() {
    return (
      
        
        
      
  )}
}

日历和提前打印

日历和提前输入是每个UI工具包的锦上添花. Sadly, 这两个组件从Bootstrap 3中删除, 可能是因为它们对于通用的CSS框架来说太专业了. 幸运的是,我找到了有价值的替代者 react-pikaday and react-select. 我测试了10多个库,这两个是最好的. 它们也非常容易使用:

从'react-pikaday'导入Pikaday;
import Select from 'react-select';

导出默认类CalendarAndTypeahead扩展React.Component {
  构造函数(道具){
    super(props);
    this.options = [
      {value: 'one', label: 'one'},
      {value: 'two', label: 'two'}
    ];
  }
  dateChange(日期){
    this.设置状态({日期:日期});
  },
  selectChange(选择){
    this.设置状态({选择:选择});
  },
  render() {
    return (
      
      

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

世界级的文章,每周发一次.

订阅意味着同意我们的 privacy policy

Join the Toptal® community.