为什么 ReactJS 不适合复杂的前端项目?

《More than React》系列的文章会一共分为五篇。本文是第一篇,介绍用ReactJS开发时遇到的种种问题。后面四篇文章的每一篇将会分别详细讨论其中一个问题,以及Binding.scala如何解决这个问题。

More than React系列文章:

More than React(一)为什么ReactJS不适合复杂的前端项目?

More than React(二)React.Component损害了复用性?

More than React(三)虚拟DOM已死?

More than React(四)HTML也可以静态编译?


背景介绍

去年 4 月,我第一次在某个客户的项目中接触到ReactJS 。

我发现ReactJS要比我以前用过的AngularJS简单很多,它提供了响应式的数据绑定功能,把数据映射到网页上,使我可以轻松实现交互简单的网站。

然而,随着我越来越深入的使用ReactJS,我发现用ReactJS编写交互复杂的网页很困难。 我希望有一种方式,能够像ReactJS一样简单解决简单问题。此外,还要能简单解决复杂问题。

于是我把ReactJS用Scala重新写了一个。代码量从近三万行降到了一千多行。

用这个框架实现的TodoMVC应用,只用了154行代码。而用ReactJS实现相同功能的TodoMVC,需要488行代码

下图是用Binding.scala实现的TodoMVC应用。

这个框架就是Binding.scala

问题一:ReactJS组件难以在复杂交互页面中复用

ReactJS中的最小复用单位是组件。ReactJS的组件比AngularJS的Controller和View 要轻量些。 每个组件只需要前端开发者提供一个 render 函数,把 propsstate 映射成网页元素。

这样的轻量级组件在渲染简单静态页面时很好用, 但是如果页面有交互,就必须在组件间传递回调函数来处理事件。

我将在《More than React(二)React.Component损害了复用性?》中用原生DHTML API、ReactJS和Binding.scala实现同一个需要复用的页面,介绍Binding.scala如何简单实现、简单复用复杂的交互逻辑。

问题二:ReactJS的虚拟DOM 算法又慢又不准

ReactJS的页面渲染算法是虚拟DOM差量算法。

开发者需要提供 render 函数,根据 propsstate 生成虚拟 DOM。 然后 ReactJS 框架根据 render 返回的虚拟 DOM 创建相同结构的真实 DOM.

每当 state 更改时,ReacJS 框架重新调用 render 函数,获取新的虚拟 DOM 。 然后,框架会比较上次生成的虚拟 DOM 和新的虚拟 DOM 有哪些差异,然后把差异应用到真实DOM上。

这样做有两大缺点:

  1. 每次 state 更改,render 函数都要生成完整的虚拟 DOM. 哪怕 state 改动很小,render函数也会完整计算一遍。如果 render 函数很复杂,这个过程就白白浪费了很多计算资源。
  2. ReactJS框架比较虚拟DOM差异的过程,既慢又容易出错。比如,假如你想要在某个 <ul> 列表的顶部插入一项 <li> ,那么ReactJS框架会误以为你修改了 <ul> 的每一项 <li>,然后在尾部插入了一个 <li>

这是因为 ReactJS收到的新旧两个虚拟DOM之间相互独立,ReactJS并不知道数据源发生了什么操作,只能根据新旧两个虚拟DOM来猜测需要执行的操作。 自动的猜测算法既不准又慢,必须要前端开发者手动提供 key 属性、shouldComponentUpdate 方法、componentDidUpdate 方法或者 componentWillUpdate 等方法才能帮助 ReactJS 框架猜对。

我将在《More than React(三)虚拟DOM已死?》中比较ReactJS、AngularJS和Binding.scala渲染机制,介绍简单性能高的Binding.scala精确数据绑定机制。

问题三:ReactJS的HTML模板功能既不完备、也不健壮

ReactJS支持用JSX编写HTML模板。

理论上,前端工程师只要把静态HTML原型复制到JSX源文件中, 增加一些变量替换代码, 就能改造成动态页面。 理论上这种做法要比Cycle.js、Widok、ScalaTags等框架更适合复用设计师提供的HTML原型。

不幸的是,ReactJS对HTML的支持残缺不全。开发者必须手动把classfor属性替换成classNamehtmlFor,还要把内联的style样式从CSS语法改成JSON语法,代码才能运行。 这种开发方式下,前端工程师虽然可以把HTML原型复制粘贴到代码中,但还需要大量改造才能实际运行。 比Cycle.js、Widok、或者、ScalaTags省不了太多事。

除此之外,ReactJS还提供了propTypes机制校验虚拟DOM的合法性。 然而,这一机制也漏洞百出。 即使指定了propTypes,ReactJS也不能在编译前提前发现错误。只有测试覆盖率很高的项目时才能在每个组件使用其他组件时进行校验。 即使测试覆盖率很高,propTypes仍旧不能检测出拼错的属性名,如果你把onClick写成了onclick, ReactJS就不会报错,往往导致开发者额外花费大量时间排查一个很简单的bug。

我将在《More than React(四)HTML也可以编译?》中比较ReactJS和Binding.scala的HTML模板,介绍Binding.scala如何在完整支持XHTML语法的同时静态检查语法错误和语义错误。

问题四:ReactJS与服务器通信时需要复杂的异步编程

ReactJS从服务器加载数据时的架构可以看成MVVM(Model–View–ViewModel)模式。 前端工程师需要编写一个数据库访问层作为Model,把ReactJS的state当做ViewModel,而render当做View。 Model负责访问数据库并把数据设置到state(即View Model)上,可以用Promise和fetch API实现。 然后,render,即View,负责把View Model渲染到页面上。

在这整套流程中,前端程序员需要编写大量闭包组成的异步流程, 设置、访问状态的代码五零四散, 一不小心就会bug丛生,就算小心翼翼的处理各种异步事件,也会导致程序变得复杂,既难调试,又难维护。

我将在《More than React(五)为什么别用异步编程?》中比较ReactJS和Binding.scala的数据同步模型,介绍Binding.scala如何自动同步服务器数据,避免手动异步编程。

结论

尽管Binding.scala初看上去很像ReactJS, 但隐藏在Binding.scala背后的机制更简单、更通用,与ReactJS和Widok截然不同。

所以,通过简化概念,Binding.scala灵活性更强,能用通用的方式解决ReactJS解决不了的复杂问题。

比如,除了上述四个方面以外,ReactJS的状态管理也是老大难问题,如果引入Redux或者react-router这样的第三方库来处理状态,会导致架构变复杂,分层变多,代码绕来绕去。而Binding.scala可以用和页面渲染一样的数据绑定机制描述复杂的状态,不需要任何第三方库,就能提供服务器通信、状态管理和网址分发的功能。

以下表格中列出了上述Binding.scala和ReactJS的功能差异:

Binding.scala ReactJS
复用性 最小复用单位 方法 组件
复用难度 不论交互内容还是静态内容都容易复用 容易复用静态内容组件,但难以复用交互组件
页面渲染算法 算法 精确的数据绑定 虚拟 DOM
性能
正确性 自动保证正确性 需要开发者手动设置 key 属性,不然复杂的页面会错乱。
HTML 模板 语法 Scala XML 字面量 JSX
是否支持 HTML 或 XHTML 语法 完整支持 XHTML 残缺支持。正常的 XHTML 无法编译。开发者必须手动把 classfor 属性替换成 classNamehtmlFor,还要把内联的 style 样式从 CSS 语法改成 JSON 语法。
如何校验模板语法 自动编译时校验 运行时通过 propTypes 校验但无法检测简单的拼写错误。
服务器通讯 机制 自动远程数据绑定 MVVM + 异步编程
实现难度 简单 复杂
其他 如何分派网址或者锚点链接 支持把网址当成普通的绑定变量来用,无需第三方库。 不支持,需要第三方库 react-router
功能完备性 完整的前端开发解决方案 本身只包含视图部分功能。需要额外掌握 react-router 、 Redux 等第三方库才能实现完整的前端项目。
学习曲线 API 简单,对没用过 Scala 的人来说也很好懂 上手快。但功能太弱导致后期学习第三方库时曲线陡峭。
Binding.scala ReactJS

两个多月前,我在Scala.js的论坛发布Binding.scala时,当时Scala.js社区最流行的响应式前端编程框架是Widok。Tim Nieradzik是Widok的作者。他在看到我发布的框架后,称赞这个框架是Scala.js社区最有前途的 HTML 5渲染框架。

他是对的,两个月后,现在Binding.scala已经成为Scala.js社区最流行的响应式前端编程框架。

Awesome Scala网站对比了Scala的响应式前端编程框架,Binding.scala的活跃程度和流行度都比Udash、Widok等其他框架要高。

我在最近的几个项目中,也逐渐放弃JavaScript和ReactJS,改用Scala.js和Binding.scala搭建新时代的前端技术栈。

相关链接

Share

秒懂ReactJS

这篇文章是为ReactJs小白准备的,希望他们快速抓住ReactJs的要点并能在实践中随机应变。

两句话版本

  • ReactJs把视图更新简化为一个render函数
  • render函数接收两个参数,分别是配置项和状态

长版本

ReactJs是一个专注于View的Web前端框架。Web前端的View就是浏览器中的Dom元素,改变View的唯一途径就是修改浏览器中的Dom元素,因此ReactJs的核心任务就是如何修改Dom元素,作为一个成功的框架,ReactJs使修改Dom元素变得高效而又简单。

ReactJs把修改Dom的操作简化成一个函数renderInto(parentDom, props, states)=>htmlString,这个函数的意图就是根据props,states计算出视图对应的html字符串并添加为parentDom的子节点。props和states就是普通的javascript对象,这个函数的核心逻辑就是计算html元素的机构及元素属性然后拼接成字符串返回。作为框架,ReactJs用JSX形式的DSL解决了拼接html的任务并接管了更新到parentDom的职责。看一个例子,理解这个函数并理解ReactJs怎么使用这个函数你就可以一个人开始ReactJs之旅了。

var props = {name: 'myname'}; 
var states = {amount: 1000}; 


function render(props, states) { 
 var title = ’Hello, ' + props.name; 
 var description = 'You have ' + states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 </div> 
 ); 
}

函数第一行根据props计算title,第二行根据states计算description,最后以JSX形式返回拼接好的html字符串。

如果你用过AngularJs,EmberJs等类似的前端框架,你可能会觉得没什么了不起,不就是把模板和逻辑放到一起吗?是的,没错,但这不仅仅是组织形式上的改变,而是编程隐喻的转变—从复杂的MVC或MVVM模式到简单的render函数。还有一点不同是JSX最终编译成调用react-dom的javascript语句,而不是直接生成字符串。

render函数还只是ReactJs这座冰山的一角,”React”会在render函数的输入变化时再次调用这个函数。再看一个例子。

var props = {name: 'myname'}; 
var states = {amount: 1000}; 


function handleClickAdd() { 
 states = {amount: states.amount + 1}; 
} 


function render(props, states) { 
 var title = ’Hello, ' + props.name; 
 var description = 'You have ' + states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 <button onClick={handleClickAdd}>+1</button> 
 </div> 
 ); 
}

这个例子增加了一个”+1”按钮,当用户点击按钮时会修改states,ReactJs在states变化时的”React”就是再次调用render函数,然后用新输出更新浏览器的dom。

可能你会问,props和states不就是Model吗?是的,可以理解成Model,但此Model非彼Model,props和states都是为View服务的而非和View平起平坐。

可能你还会问,为啥不把props和states合并成一个对象?要回答这个问题,就涉及到复杂视图的场景。想想看,当视图内的元素不断增加时,代码上如何处理,还要在一个render函数里折腾吗?肯定不会。我猜你已经想到了,要把大问题拆小。ReactJs给出的解决方法就是把大视图拆成若干个小视图,每个视图都有自己的render函数,在JSX中可以直接使用视图标签。看一个例子。

var Score = React.createClass({ 
 initialState: function() { 
 return {amount: 1000}; 
 }, 


 function handleClickAdd() { 
 this.setState({amount: this.states.amount + 1}); 
 } 


 render: function() { 
 var title = ’Hello, ' + this.props.name; 
 var description = 'You have ' + this.states.amount + ' score now.'; 


 return ( 
 <div className="score-board"> 
 <h1>{title}</h1> 
 <p>{description}</p> 
 <button onClick={handleClickAdd}>+1</button> 
 </div> 
 ); 
 } 
}); 


var ScoreList = React.createClass({ 
 render() { 
 return ( 
 <ul className="score-list"> 
 <li><Score name="Tom" /></li> 
 <li><Score name="Jerry" /></li> 
 </ul> 
 ); 
 } 
}); 


ReactDOM.render( 
 <ScoreList />, 
 document.getElementById('content') 
);

这个例子中有两类View,分别是Score和ScoreList。ScoreList的render函数中使用Score标签并给出配置项name的值。详细看一下Score,ReactJs提供createClass方法定义视图,在render函数中通过this.props访问外部传入的配置项,通过this.states访问视图内部的状态。从意图上看,props外部传入视图的配置项,拥有者是父视图,视图内部只能读取配置项,states的拥有者是视图自身。

区分props和states的结果就是,子视图没办法直接改变父视图,视图改变一定是自触发改变的视图开始向子视图传播。对上面的例子,当Tom的Score改变时,ScoreList其他部分一定不会改变,所以视图更新从Tom的Score视图开始就可以,这就保证了能更高效地计算视图变化,再加上VirtualDom的使用,使ReactJs的效率大大超过其他框架。

当子视图需要改变父视图时,也一定是从父视图开始向下更新。假如上面的例子中ScoreList还有平均分的视图,当Tom的分数改变时,需要更新ScoreList中的平均分。这就需要Score视图在处理”+1”输入时把变化通知到ScoreList,做法时给Score增加配置项,ScoreList中定义更新平均分的函数并把函数作为配置项传给Score。当ScoreList更新时,因为Jerry的props和states都没变化,所以Jerry的Score视图不需要更新。

这就是ReactJs的全部秘密了(不过Web前端本身是一个复杂系统,你还需要了解更多其他内容)。

Share

展望2016,React.js 最佳实践

React.js 作为前端框架的后起之秀,却在2015年携着虚拟 DOM,组件化,单向数据流等利器,给前端 UI 构建掀起了一波声势浩大的函数式新潮流。新鲜出炉的一篇 React.js 最佳实践,基本涵盖了所有的 React.js 生态周边,可用于实践参考。文章不仅表明了 Flux 经常被滥用的观点,也推荐开发者使用 Redux 作为 JavaScript 的可预测状态容器,并且提出保持状态扁平化和使用 Immutable.js 等数据处理解决方案。与此同时,也从高阶组件,组件测试以及组件级别热重载等方面提供了建议,当然也涉及了 Webpack,HTTP 2,使用 ES2015 乃至 Linters 等代码层面的建议。

译者:吕靖,译自 Péter Márton:React.js Best Practices for 2016。原文地址:https://blog.risingstack.com/react-js-best-practices-for-2016/

过去的2015年,React 在全世界范围都是一派欣欣向荣的景象,开发者会议无一不热衷于这个话题。在过去一年中发生了很多重要的里程碑事件,更多详情可以查看我们关于 React in 2015 的总结。

在新的2016年里,最有趣的问题来了:我们该如何开发一个应用,有什么推荐使用的库?

作为一名长时间使用 React.js 的开发者来说,我对这个问题有自己的答案以及最佳实践,但也有可能你不会完全认同。我也非常乐于倾听你的想法和观点:请留言以便讨论。


01

如果你才刚刚开始学习 React.js,可以查看我们的 React.js 教程,或者 Pete Hunt 所写的 React howto

数据处理

在 React.js 应用中处理数据轻而易举,与此同时亦充满挑战。

这是因为你可以通过各种方式将属性数据传递给 React 组件,并从中构建渲染树;然而这种方式也并非那么显而易见,到底该如何更新视图。

2015之初诞生了很多不同 Flux 库,并不断产出了更加实用的响应式方案。

让我们看看现在的情况:

Flux

根据我们的经验,Flux 经常被滥用,(这意味着大家总是在不需要的时候就用上它)

Flux 提供了一种非常清晰的方式来存储和更新应用状态,并且只会在必要的时候才触发页面渲染。

Flux 致力于应用的全局状态管理,比如:管理已登录用户状态,路由状态,或者是活跃账户状态,但若是用来管理临时数据或者本地数据,瞬间就变成了痛苦。

我们不推荐使用 Flux 来管理路由相关的数据,比如 /items/:itemId。而只是获取路由数据并存储在组件的 state 之中。在这种情况下,它会在组件消失之后一起被销毁。

如果你想了解更多关于 Flux 的信息,The Evolution of Flux Frameworks 非常值得一读。

使用 Redux

Redux 是一个 JavaScript 应用的可预测状态容器。

如果你觉得需要 Flux 或者一种类似的解决方案,你应该了解一下 redux,以及学习 Dan AbramovGetting started with redux 课程,这能够迅速提高你的开发技能。

Redux 延续并改进了 Flux 的思想,并从 Elm 架构中取经,规避了 Flux 的复杂度。(译者注:Elm 是一门面向 Web 的函数式编程语言,致力于改善客户端 Web 编程体验。)

保持状态扁平化

API 经常会返回嵌套资源。这在 Flux 或基于 Redux 的架构中处理起来会非常困难。我们推荐使用 normalizr 之类的库将数据进行扁平化处理,保持状态尽可能地扁平化

示意:

const data = normalize(response, arrayOf(schema.user))

state = _.merge(state, data.entities)

(我们使用 isomorphic-fetch 来与 APIs 进行交互)

使用 immutable 状态

共享的可变性状态乃万恶之源。 —— Pete Hunt, React.js Conf 2015

 


02

不可变对象是一种在创建之后就不可修改的对象。

不可变对象可以让我们免于痛楚,并通过引用级别的比对检查来改善渲染性能 。比如说在 shouldComponentUpdate 中:

shouldComponentUpdate(nexProps) {
 // instead of object deep comparsion
 return this.props.immutableFoo !== nexProps.immutableFoo
}

如何在 JavaScript 中实现不可变呢?

最痛苦的方式就是小心为之,示例代码如下,你需要在单元测试中通过 deep-freeze-node 来反复验证。(在修改之前冻结,并在结束后验证结果。)

return {
  ...state,
  foo
}

return arr1.concat(arr2)

相信我,这是最平淡无奇的例子了。

更简单也更自然的方式就是使用 Immutable.js

import { fromJS } from 'immutable'

const state = fromJS({ bar: 'biz' })
const newState = foo.set('bar', 'baz')

Immutable.js 非常之快,背后理念也异常漂亮。哪怕你并不想使用它,我也推荐阅读这个由 Lee Byron 所制作的视频 Immutable Data and React。视频对于 Immutable.js 的工作原理有着非常深刻的讲解。

观察式与响应式方案

如果你不喜欢 Flux/Redux 或者只是想要更加 reactive,不要失望!这儿有很多其他数据处理的解决方案。这就有一个相关库的简要列表供你参考:

  • cycle.js (“A functional and reactive JavaScript framework for cleaner code”)
  • rx-flux (“The Flux architecture with RxJS”)
  • redux-rx (“RxJS utilities for Redux.”)
  • mobservable (“Observable data. Reactive functions. Simple code.”)

路由

几乎所有的客户端应用都或多或少需要使用路由。如果你在浏览器中使用 React.js,你就会在挑选库的时候碰到这个分歧点。

我们的选择是出自优秀的 rackt 社区的 react-router。Racket 给 React.js 的拥簇者带来了很多高质量资源。

你可以查看他们的文档以便于集成 react-router,但是更重要的是:如果你使用 Flux/Redux,我们建议你将路由状态和你的 store 或全局状态保持同步

同步的路由状态可以帮助你对 Flux/Redux 的 Actions 所提供的路由行为有所控制,并且能够在组件中读取路由状态和参数。

Redux 用户可以通过 redux-simple-router 这个库轻松实现它。

代码分割,惰性加载

只有一小部分 webpack 用户知道应用代码是可以分割的,将 bundler 的输出拆分成多个 JavaScript 块:

require.ensure([], () => {
  const Profile = require('./Profile.js')
  this.setState({
    currentComponent: Profile
  })
})

这在大型应用中会非常有用,因为在每次部署之后,用户浏览器就没有必要下载那些很少用到的代码,比如 profile 页面。

更多代码块将导致更多 HTTP 请求 —— 但是使用 HTTP/2 multiplexed 的话就不成问题。

结合 chunk hashing,你也可以在代码改变之后优化缓存命中率。(译者注:终端用户访问加速节点时,如果该节点有缓存住了要被访问的数据时就叫做命中,如果没有的话需要回原服务器获取,就是没有命中。)

react-router 的下个版本就将在代码分割这方面提供更多帮助。

想要了解 react-router 的未来走向,可以查看 Ryan Florence 所写的这篇博文: Welcome to Future of Web Application Delivery

组件

大部分人都对 JSX 存有怨言。首先,你需要知道的是这在 React 中并不是必须的。

在最后,JSX 都会通过 Babel 被编译成 JavaScript。你可以直接编写 JavaScript 来替代 JSX,但是在处理 HTML 的时候使用 JSX 会感觉更加自然。

特别是对于不懂技术的人来说,他们依然可以理解和修改必要的部分。

JSX 是一种与 XML 类似的 JavaScript 语法扩展。你可以通过一个简单的 JSX 语法转换器来编译 React。 —— JSX in depth

如果你想要了解更多关于 JSX 的信息,可以查看 JSX Looks Like An Abomination – But it’s Good for You 这篇文章。

使用 Class

React 和 ES2015 的 Class 语法搭配完美。

class HelloMessage extends React.Component {
  render() {
    return <div>Hello {this.props.name}</div>
  }
}

我们喜欢高阶组件更胜于 mixins,所以对于我们来说,保留 createClass 就更像一个语法问题而不是技术问题。我们相信使用 createClass 而不是 React.Component 绝对无可厚非,反之亦然。

属性类型

如果你在2016年依然没有检查 properties,那么你应该从现在开始做起,这将为你节省大量时间,相信我。

MyComponent.propTypes = {
  isLoading: PropTypes.bool.isRequired,
  items: ImmutablePropTypes.listOf(
    ImmutablePropTypes.contains({
      name: PropTypes.string.isRequired,
    })
  ).isRequired
}

当然,验证 Immutable.js 所编写的 properties 也是可能的,可以使用react-immutable-proptypes

高阶组件

目前来说,mixins 已死,而且在 ES6 Class 组件中已经不再被支持,我们应当寻找不同的替代方案。

那什么是高阶组件呢?

PassData({ foo: 'bar' })(MyComponent)

本质上来说,你可以由原始组件创造一个新的组件并且扩展它的行为。你可以在多种情况下使用它,比如授权:requireAuth({ role: 'admin' })(MyComponent) (检查上层组件中的用户,若是未登录则需要重定向),或者是连接你的组件和 Flux/Redux 仓库。

在RisingStack,我们也将数据获取和类似 Controller 的逻辑分割成高阶组件,并保持视图层尽可能简单。

测试

在开发周期中,维持测试的高覆盖率是非常重要的一部分。幸运的是, React.js 社区诞生了很多优秀的库可以帮助我们达到这一点。

组件测试

我们最喜爱的库之一是由 AirBnb 所开发的 enzyme,可用于组件测试。非常神奇的是,它的浅渲染特性可以对组件的逻辑及其渲染输出进行测试。尽管它还不能替代你的 selenium 测试,但是将前端测试提升到了一个新的水平。

it('simulates click events', () => {
  const onButtonClick = sinon.spy()
  const wrapper = shallow(
    <Foo onButtonClick={onButtonClick} />
  )
  wrapper.find('button').simulate('click')
  expect(onButtonClick.calledOnce).to.be.true
})

看起来就非常简洁,不是么?

你在使用 chai 作为测试断言库嘛?相信你会喜欢 chai-enyzime 的!

Redux 测试

测试一个 reducer 非常简单,它响应新到来的 actions,并且将原来的状态进行更新:

it('should set token', () => {
  const nextState = reducer(undefined, {
    type: USER_SET_TOKEN,
    token: 'my-token'
  })

  // immutable.js state output
  expect(nextState.toJS()).to.be.eql({
    token: 'my-token'
  })
})

测试 actions 也很简单,但是异步 actions 就不太一样了。对于测试异步的 Redux actions 来说,我们推荐使用 redux-mock-store,非常有帮助。

it('should dispatch action', (done) => {
  const getState = {}
  const action = { type: 'ADD_TODO' }
  const expectedActions = [action]

  const store = mockStore(getState, expectedActions, done)
  store.dispatch(action)
})

更深度地了解 redux 测试,可以查看官方文档。

使用 npm

虽然 React.js 并不依赖代码打包工具就可以很好地工作,但我们还是推荐使用 Webpack 或者 Browserify 来发挥 npm 的能力。Npm 上满是高质量的 React.js 包,还可以帮你非常优雅地管理依赖。

(请不要忘记复用你自己的组件,这是一种绝佳的代码优化方式。)

Bundle 大小

这本身不是一个 React 相关的问题,但是大多数人都在打包他们的 React 应用,所以我认为提到这点很重要。

当你打包源代码的时候,时刻警惕打包后的文件大小。为了保持体积最小化,你应该考虑如何 require/import 依赖。

对比以下代码片段,这两种不同的方式对输出的影响区别巨大:

import { concat, sortBy, map, sample } from 'lodash'

// vs.
import concat from 'lodash/concat';
import sortBy from 'lodash/sortBy';
import map from 'lodash/map';
import sample from 'lodash/sample';

可以查看这篇文章 Reduce Your bundle.js File Size By Doing This One Thing 获取更多详情。

我们也喜欢将代码分离出至少 vendors.jsapp.js 两个文件,因为 vendors 相对于我们的代码库来说更新不是那么频繁。

将输出文件名称进行哈希化处理 (Webpack 中的 chunk hash),并使用长缓存,我们可以大大减少用户需要下载的代码大小。结合惰性加载,优化效果可想而知。

如果你还不太熟悉 Webpack,可以查看这本优秀的 React webpack 手册

组件级别热重载

如果你曾经使用过热加载来编写单页面应用,当你在处理某些与状态相关的事情时,可能你就会明白当你在编辑器中点击保存,整个页面就重新加载了是多么令人讨厌。这样子就不得不重新点击一遍应用,重复如此会令人抓狂的。

通过 React,在重载组件的同时保持组件状态已经成为可能 —— 耶,从此不再痛苦!(没有蛀牙!

关于如何搭建热重载,可以参考 react-transform-boilerplate

使用ES2015

 

03

前面有提到过,我们可以在 React.js 组件中使用 JSX,然后使用Babel.js进行编译。

其实 Babel 的能力远不止如此,它也可以让我们现在就可以给浏览器编写 ES6/ES2015 代码。在 RisingStack,我们在服务器端和客户端都使用了 ES2015 特性,这都已经在最新的 LTS Node.js 版本中被实现了。

Linters

或许你已经给你的 JavaScript 代码制定了代码规范,但是你知道也有用于 React 的代码规范了吗?我们强烈推荐挑选一个并开始遵循它。

在 RisingStack,我们也将 linters 强制运行在 CI 系统上,git push 亦然。可以试试 pre-push 或者 pre-commit

我们使用标准的 JavaScript 代码风格,并使用了 eslint-plugin-react对 React.js 代码进行规范 。

(就是,我们不再使用分号。)

GraphQL 和 Relay

GraphQL 和 Relay 相对而言属于新技术,在 RisingStack,目前我们还没有在产品环境中使用它们,暂时保持关注。

我们曾经写过一个 Relay 的 MongoDB ORM库,叫做 graffiti,可以使用已有的 mongoose 模型直接创建一个 GraphQL 服务器。

如果你想要学习这些新技术,我们建议你可以找来玩一玩。

尽情享用这些 React.js 最佳实践

有些突出的技术和库其实跟 React.js 并不相关 —— 但是保持视野开阔,关注社区的其他人都在做些什么。React 社区在2015年里就受到了 Elm 架构 的很多启发。

如果你知道其它在2016年必不可少的 React.js 工具,请留言让我们知道!


译者:吕靖。

原作者: Péter Márton

CTO at RisingStack, brewing beer with Node.js

https://twitter.com/slashdotpeter

Share