Redux 最佳实践[译]
摘要Redux 是 其他 flux 框架 推荐使用的 React 框架。当我开始写这篇文章时,它还是 1.0.0 版本,当这篇文章发布时,它已经是 3.0.0 了。 它的作者,Dan Abramov 已经发布一些很棒的 文档,但是他依然没有完全指明如何在大规模项目中使用 Redux,所以人们开始问了 “有哪些大型项目使用了 Redux”. 好吧,希望这篇文章可以解决这些疑惑。 我们将会讨论:
正文我应该有哪些知识储备?阅读 Redux 官方文档。 阅读了 Dan 的文章 Smart & Dumb Components。 开发 Redux 项目需要使用哪些工具?Redux 不仅仅是 Redux, 它是一堆相关东西的集合,其中有些已经发布到 v1.0.0,有些还在酝酿中。 你的工具包可能包含下面的绝大多数:
使用它打包你的文件,而不是使用 Browserify、Require 或者任何你挣扎使用的工具。为什么?因为一部分 Redux 初始化示例 展示了它的热加载能力,而且这些示例是使用 Webpack 构建的。 点击保存就可以直接看到样式的更新是如此方便,以至于我都不想再使用保存->刷新页面->跳转到指定页面这样繁琐重复的方式了。
一部分原因是它让你可以使用 ES6/7 的语法糖, 另一部份原因是热加载现在是作为一个 Babel plugin 实现的。
虽然在这篇文章发布时 0.13 版是稳定版本,但我们依然期待 0.14 版, 因为它能修复一些上下文相关的问题。 因为 ES6 提供了 classes 机制,所以 React 也 弃用 Mixins了。现在应该使用 高阶组件 来代替--把你的 React 组件包裹在一个提供 上下文 的父元素中。Redux 充分利用了这一点。
这个没什么好说的。
严格来说,它与 Redux 无关,它是为 React 编写的,提供了把 React 组件和 Redux Store 连接在一起的高阶组件。
你有两个选择:thunks 或者别的 promise 库。无论哪个选择,它都能让你在 action creatores 中运行异步代码成为可能。
这正是上述异步代码的原因。我使用 Axios,它基于 promise,因此可以和 promise 中间件很好的兼容。
表面上看,使用路由就是更新导航栏并显示对应的应用页面。然而更底层的原因是它提供了一逻辑机制去拆分你的代码。 路由带来的问题是,它给了你更多的 state,而这些 state 却不属于你的 store。Redux-Router 可以确保你的 state 被 Redux 管理。 如何使用 Redux 的不同部分?我们都知道 Flux 是一个单向数据流框架,但即使这样,我们如何使用它? 在应用中你需要:
在一个不太规范的框架中,你可以随意放置内容,可能在一个活着两个不同的地方做了上述所有事情。 我按照以下的标准组织我的代码: 使用路由来确保你的组件拥有正确的数据 这是一个很好的方式,因为它划分了数据集合。使用 Route 中的 onEnter 方法 来指定需要渲染的东西。你不必让这个方法等到数据集合加载完毕,因为。。。 使用智能组件来确保你的木偶组件可以渲染 你的智能组件应该是配置在 Route 中的组件,你的智能组件的 render 方法控制子组件的渲染数据: render () { if (this.hasData()) { return this.renderComponents(); } else { return this.renderLoadingScreen(); } } 智能组件尽可能的做数据预处理,以使你的木偶组件足够 “木偶” 比如说,当你传递一个处理句柄给木偶组件时,带上它需要的 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 目录包涵三个部分:
同样的,随着路径层级变深,会分解成更多的小组件。我推荐这种方式,因为它允许你仅仅在需要的时候实例化这些路由。而且意味着你的路由仅仅包含子其子目录中的文件,这样感觉很好并且解偶了。 通过使用 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 很好的处理了这个问题: 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 欢迎关注微信订阅号:从移动到云端欢迎加入我们的MaxLeap活动QQ群:555973817,我们将不定期做技术分享活动。若有转载需要,请转发时注意自带作者信息一栏并本自媒体公号:力谱宿云,尊重原创作者及译者的劳动成果~ 谢谢配合~ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |