展望2016,REACT.JS 最佳实践 | TW洞见

今日洞见

译者:ThoughtWorks-吕靖,译自 Péter Márton:React.js Best Practices for 2016。

本文所有内容,包括文字、图片和音视频资料,版权均属ThoughtWorks公司所有,任何媒体、网站或个人未经本网协议授权不得转载、链接、转贴或以其他方式复制发布/发表。已经本网协议授权的媒体、网站,在使用时必须注明"内容来源:ThoughtWorks洞见",并指定原文链接,违者本网将依法追究责任。

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

过去的2015年,React 在全世界范围都是一派欣欣向荣的景象,开发者会议无一不热衷于这个话题。在过去一年中发生了很多重要的里程碑事件。

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

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

如果你才刚刚开始学习 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 Abramov 的Getting 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

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

不可变对象可以让我们免于痛楚,并通过引用级别的比对检查来改善渲染性能 。比如说在 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

前面有提到过,我们可以在 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 工具,请留言让我们知道!

原文发布于微信公众号 - 思特沃克(ThoughtWorks)

原文发表时间:2016-03-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏tkokof 的技术,小趣及杂念

Dev-C++,Time to say goodbye ?(更新2012-02-04)

  记得在那段很久以前的学生时代,自己曾经很反感Visual Studio的过度臃肿,再加上当时学校的实验PC陈腐老旧:经常假死的Windows 2000,外加...

13020
来自专栏静晴轩

Html<a>标签href的困惑记载

近日,在工作中遇到一个小问题(给手游平台做些网页活动,其中牵涉到一个按钮链接,就习以为常的用了<a>标签,Click响应之后走一段js代码逻辑-弹出一个分享微信...

49150
来自专栏郭霖

Android通知栏微技巧,那些你所没关注过的小细节

本篇文章首发于我的微信公众号,其实通常情况下我都不会将微信文章再在博客上发表的,因为我认为两者区别比较大。微信文章偏向于短小精炼,毕竟要在手机上阅读,博客文章则...

35880
来自专栏娱乐心理测试

vue开发类似淘宝商品评价页面(星级,上传多张图片)

最近在写一个关于vue的商城项目,然后集成在移动端中,开发需求中有一界面,类似淘宝商城评价界面!实现效果图如下所示:

20020
来自专栏数据小魔方

学会自定义主题,让你的仪表盘瞬间高逼格~

今天这一篇跟大家介绍如何在PowerBI和Tableau中自定义主题来更换默认主题,让你的仪表盘随心所欲的变换主题。 关于Excel的主题配色相关内容已经推送过...

56070
来自专栏张戈的专栏

关于网站图标favicon.ico那点事儿,你造吗?

众所周知,联盟成员导航是中国博客联盟的特色之一。而网站图标则是导航的装饰之一,起到锦上添花的作用,让页面更精美耐看。 但是随着成员数量的增长,图标便成了页面的拖...

88660
来自专栏Material Design组件

Human Interface Guidelines — Modality

16930
来自专栏腾讯防水墙

验证码前端性能分析及优化实践

在越来越注重用户体验的趋势下,验证码作为一种自打诞生以来就被贴上“多余”标签的产品,更应该给用户提供良好的体验。本文以滑动验证码作为切入点,分析了验证码可能存在...

1.4K90
来自专栏前端儿

【转】不同内核浏览器的差异以及浏览器渲染简介

浏览器最重要或者说核心的部分是“Rendering Engine”,可大概译为“解释引擎”,不过我们一般习惯将之称为“浏览器内核”。负责对网页语法的解释(...

22110
来自专栏iOS技术

iOS图片浏览器(功能强大/性能优越)

支持 cocopods,功能完善,性能不错,代码质量尚可,喜欢的朋友可以给个小星星?。

61970

扫码关注云+社区

领取腾讯云代金券