转载

Redux 最佳实践[译]

摘要

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

它的作者, Dan Abramov 已经发布一些很棒的 文档 ,但是他依然没有完全指明如何在大规模项目中使用 Redux,所以人们开始问了 “有哪些大型项目使用了 Redux”. 好吧,希望这篇文章可以解决这些疑惑。

我们将会讨论:

  • Redux 的所有技术栈

  • Redux 的各个模块都做了什么

  • 如何划分 Redux 项目结构

  • 如何处理 WebSocket 的异步数据

正文

我应该有哪些知识储备?

阅读 Redux 官方文档。

阅读了 Dan 的文章 Smart & Dumb Components 。

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

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

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

  • 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,我们将不定期做技术分享活动。

若有转载需要,请转发时注意自带作者信息一栏并本自媒体公号:力谱宿云,尊重原创作者及译者的劳动成果~ 谢谢配合~

原文  https://segmentfault.com/a/1190000005757189
正文到此结束
Loading...