Redux 最佳实践[译]

前端之家收集整理的这篇文章主要介绍了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/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,我们将不定期做技术分享活动。若有转载需要,请转发时注意自带作者信息一栏并本自媒体公号:力谱宿云,尊重原创作者及译者的劳动成果~ 谢谢配合~

猜你在找的React相关文章