Redux 最佳实践[译]

摘要

Redux 是 其余 flux 框架 推荐使用的 React 框架。当我开始写这篇文章时,它仍是 1.0.0 版本,当这篇文章发布时,它已是 3.0.0 了。javascript

它的做者,Dan Abramov 已经发布一些很棒的 文档,可是他依然没有彻底指明如何在大规模项目中使用 Redux,因此人们开始问了 “有哪些大型项目使用了 Redux”. 好吧,但愿这篇文章能够解决这些疑惑。html

咱们将会讨论:java

  • Redux 的全部技术栈react

  • Redux 的各个模块都作了什么ios

  • 如何划分 Redux 项目结构git

  • 如何处理 WebSocket 的异步数据github

正文

我应该有哪些知识储备?

阅读 Redux 官方文档。npm

阅读了 Dan 的文章 Smart & Dumb Componentsredux

开发 Redux 项目须要使用哪些工具?

Redux 不只仅是 Redux, 它是一堆相关东西的集合,其中有些已经发布到 v1.0.0,有些还在酝酿中。axios

你的工具包可能包含下面的绝大多数:

  • Webpack

使用它打包你的文件,而不是使用 Browserify、Require 或者任何你挣扎使用的工具。为何?由于一部分 Redux 初始化示例 展现了它的热加载能力,并且这些示例是使用 Webpack 构建的。

点击保存就能够直接看到样式的更新是如此方便,以致于我都不想再使用保存->刷新页面->跳转到指定页面这样繁琐重复的方式了。

  • Babel

一部分缘由是它让你可使用 ES6/7 的语法糖, 另外一部份缘由是热加载如今是做为一个 Babel plugin 实现的。

  • React

虽然在这篇文章发布时 0.13 版是稳定版本,但咱们依然期待 0.14 版, 由于它能修复一些上下文相关的问题。

由于 ES6 提供了 classes 机制,因此 React 也 弃用 Mixins了。如今应该使用 高阶组件 来代替--把你的 React 组件包裹在一个提供 上下文 的父元素中。Redux 充分利用了这一点。

  • Redux

这个没什么好说的。

  • React-Redux

严格来讲,它与 Redux 无关,它是为 React 编写的,提供了把 React 组件和 Redux Store 链接在一块儿的高阶组件。

  • Middleware

你有两个选择:thunks 或者别的 promise 库。不管哪一个选择,它都能让你在 action creatores 中运行异步代码成为可能。

  • Request Library

这正是上述异步代码的缘由。我使用 Axios,它基于 promise,所以能够和 promise 中间件很好的兼容。

  • React-Router(-redux)

表面上看,使用路由就是更新导航栏并显示对应的应用页面。然而更底层的缘由是它提供了一逻辑机制去拆分你的代码。

路由带来的问题是,它给了你更多的 state,而这些 state 却不属于你的 store。Redux-Router 能够确保你的 state 被 Redux 管理。

如何使用 Redux 的不一样部分?

咱们都知道 Flux 是一个单向数据流框架,但即便这样,咱们如何使用它?

在应用中你须要:

  • 获取应用的初始状态

  • 根据状态绘制内容

  • 处理 UI 交互

  • 处理 request 而且保持 state 与 store的同步

  • 更新和重绘内容

在一个不太规范的框架中,你能够随意放置内容,可能在一个活着两个不一样的地方作了上述全部事情。

我按照如下的标准组织个人代码:

使用路由来确保你的组件拥有正确的数据

这是一个很好的方式,由于它划分了数据集合。使用 Route 中的 onEnter 方法 来指定须要渲染的东西。你没必要让这个方法等到数据集合加载完毕,由于。。。

使用智能组件来确保你的木偶组件能够渲染

你的智能组件应该是配置在 Route 中的组件,你的智能组件的 render 方法控制子组件的渲染数据:

render () {
  if (this.hasData()) {
    return this.renderComponents();
  } else {
    return this.renderLoadingScreen();
  }
}

智能组件尽量的作数据预处理,以使你的木偶组件足够 “木偶”

好比说,当你传递一个处理句柄给木偶组件时,带上它须要的 id,这样
木偶组件就不须要本身获取 id 了:

renderComponents () {
  return <DumbComponent 
    onSelect={this.itemSelected.bind(this, this.props.item.id)}
  >;
}

使用木偶组件去渲染全部东西

不要放哪怕一个 <div> 到你的智能组件中,任什么时候候,智能组件都应该仅仅是木偶组件的组合。拆分你的关注点,不要在这里写一点东西。

使用智能组件调用 actions creators

当一个木偶组件和用户有交互时,它本身不该该处理任何逻辑--它应该仅仅调用从智能组件中传过来的处理函数,而后由这个函数去处理。

而后智能组件采集必要的数据传递给 action creator。

在 ActionCreators 中转换应用数据结构到 API 数据结构

你的 ActionCreators 负责在应用数据结构和 API 数据结构间转换。这个操做是双向的--发起请求,处理返回值。

由于 action 的输出会被 reducer 处理,而 reducer 并不知道本身是被怎样调用的,你可能发现有时候你不能仅仅返回 API 的调用结果--你须要补充它的附加字段,好比:若是你的 action 是 PROJECT_UPDATE,你须要返回新的项目名和 id,而 API 仅仅返回 {savedAt: "<some date>"},你就须要这样传递参数:

function updateProject(projectId, projectName) {
  request.put(`/project/${projectId}`, {projectName}).then(
    response => Object.assign(
      {projectId, projectName}, 
      response.data
    )
  );
}

使用 reducers 同步你的 state

有趣的是,一个 reducer 能够处理任何的 action。一个数据清理的场景是,当用户注销时,清理 store 中的全部数据:

switch (action.type) {
   ...
   case USER_LOGOUT: 
     return {}
}

文件结构

如何组织文件结构是件复杂的事,由于它比处理一成不变的东西多了不少艺术性和我的风格。

我找到了 Redux 应用中的两个分离点,而后我围绕这两个分离点组织文件结构。

一个分离点是 数据。你的 actions 能够在任何地方被调用(虽然一般都是被智能组件调用)。你的 reducers 和 actions 是绑定的。actions 能够组合在一块儿,根据模块构建你的应用:可能一部分是处理用户登陆和权限,另外一部分是用户管理的项目。全部这些都有建立、查询、更新和删除,而这些都应该放在一块儿。

另外一个分离点是 视图。根据视图你就能够布局你的应用--不一样页面的路由,聚合数据和交互的智能组件,渲染数据的木偶组件。

多个视图能够调用同一个 action。好比,项目列表页面可让你简单的编辑项目名,而项目详情页面能够提供一个编辑项目名的表单。而这二者都有不一样分离的路由,不一样的智能组件,不一样的木偶组件和不一样的数据集。

因此,我这样组织个人项目文件:

public/
  index.html
  client/
    index.js
    modules/
      reducers.js
      users/
        constants.js
        actions/
          user_fetch.js
          user_login.js
          permissions_fetch.js
        reducers/
          index.js
          user.js
          permission.js
      projects/
    routes/
      login/
        index.js
        containers/
          login.js
        components/
          login.js
      logged_in/
      project_list/
      project_view/

modules 目录负责处理和数据相关的文件,不一样模块的数据处理经过子目录的方式划分。这使得您将来能够把这些模块单独打包到你的 npm 仓库,它们之间没有依赖。

每一个 action 和 reducer 都有本身单独的文件。有的项目 试图把一个模块中的全部内容都放倒一个文件中。我我的反对在中大型项目中采用这种作法,当项目愈来愈大时,应该把东西拆成尽量小的块。

为了使不一样的模块的 reducers 保持类似的结构,增长了 index.js 文件,它导出了该目录中的全部 reducer,而后顶层的 reducers.js 引入全部模块的 reducers。这些单独的 reducers 文件都会用于生成 Redux store。

routes 目录负责管理全部视图相关的文件,按不一样的路由划分子目录。每一个 route 目录包涵三个部分:

  • 在 containers 目录中的智能组件

  • 在 components 目录中的木偶组件

  • 包含 Route 的 index.js 文件

一样的,随着路径层级变深,会分解成更多的小组件。我推荐这种方式,由于它容许你仅仅在须要的时候实例化这些路由。并且意味着你的路由仅仅包含子其子目录中的文件,这样感受很好而且解偶了。

经过使用 onEnter 和 onLeave 方法,你的路由文件一样能够做为数据的关卡。在这里你能够触发 fetch action 来获取组件须要的数据。这在你使用深层路由嵌套的时候颇有用,好比,给定路由 /app/project/10/permission,你能够:

  • /app 中获取当前用户的登陆信息

  • /project 中获取该用户可见的项目

  • /10 中获取项目 10 的详细信息

  • /permission 中获取该用户的权限列表

当切换到另一个路由 /app/project/11,你仅仅须要获取更改的数据(/11 对应的数据),这时你就只须要一次对项目 11 的请求了:

import Projects from "./containers/projects";
import ProjectDetailRoute from "routes/project_detail";
export default class ProjectList {
  constructor () {
    this.path = "project";
    this.projectDetailRoute = new ProjectDetailRoute();
  }
  getChildRoutes (state, cb) {
    cb(null, [this.projectDetailRoute]);
  }
  getComponents (cb) {
    cb(null, ProjectTasks);
  }
  onEnter () {
    this.fetchProjects();
  }
  fetchProjects () {
    ...
  }
}

如何命名

Actions: <名词>-<动词>,好比 Project-Create,User-Login。依据是按照对象类型而不是动做类型分组。

Reducers: <名词>。

如何处理第三方异步数据

很明显的这里有条正确的流程(Action->Reducer->SmartContainer->DumbComponent)。但如何让你的更改符合这个流程?

第三方异步数据一般来自于 WebSocket。你可能仅仅想在应用的某些部分监听它,好比登陆时,或者某些页面。并且,从 UI 到 actions 的处理流程是,用户触发了一个事件,木偶组件把事件传播到智能组件,而后触发一个 action。

但在这种状况下,没有木偶组件渲染内容,而由路由决定你什么时候接收数据,action 把数据注入到 redux。这个智能组件不须要任何木偶组件,也应该独立于其余智能组件。

React-Route 很好的处理了这个问题:
Route 能够有多个组件:

getComponents () {
  cb(null, {view: ViewContainer, data: DataContainer};
}

该智能组件能够这样渲染:

render () {
  return <div>{this.props.view}{this.props.data}</div>
}

DataContainer 能够经过 componentDidUpdate 对 props 的更改做出响应,或者根据 componentWillUnmount 关闭链接。

总结

我已经连续两周在写这篇文章了,由于我总以为还有些东西须要加进去。故事没有结束,但我把它发布出来以使 Redux 新手能够看到我对 Reactiflux 的探索。请评论和注释这篇文章,我将在接下来的几周内持续关注它。

做者信息

原文做者: Will Becker
原文连接: https://medium.com/lexical-labs-engineering/redux-best-practices-64d59775802e#.1b8hgoju1
翻译自MaxLeap团队_UX成员:Henry Bai
力谱宿云团队首发:https://blog.maxleap.cn/archives/930

欢迎关注微信订阅号:从移动到云端欢迎加入咱们的MaxLeap活动QQ群:555973817,咱们将不按期作技术分享活动。如有转载须要,请转发时注意自带做者信息一栏并本自媒体公号:力谱宿云,尊重原创做者及译者的劳动成果~ 谢谢配合~

相关文章
相关标签/搜索