React 应用的架构模式 Flux
React 组件之间的数据通信在 初探 React 组件 有初步接触,父组件可以通过声明 props
来向子组件传递数据,但是子组件无法向父组件传递数据,兄弟组件之间也不能相互传递数据。React 的这种单向数据流的通信模式能确保数据的流动简单可控,非常严谨,React 也一直秉承着简单严谨的设计思想。
那如上提到的非父子组件的数据通信该怎么办呢?有几种方案:
回调函数 父组件注册回调函数,子组件执行回调函数,这种方案比较简单,适用范围也有限,适合简单的子组件向父组件的数据通信;
事件 事件通信是 JavaScript 最常用的通信方案,这种通信方案不用局限于组件之间的关系,比较灵活,但是当应用复杂庞大的时候,使用这种通信方式会导致管理混乱,不可控;
Flux Facebook 官方团队提出来专门应用于 React 的一套应用的架构模式,本文将要讲述的就是该方案。
React 专注在 UI 层上,从 MVC
分层的架构模式上来看,React 主要涉及到的是 V
,一个 React 的组件可以是一个 Views
或 Controller-views
的混合体,它完全没有 M
层。Facebook 官方团队并没有照搬比较流行的 MV*
,而是提出了更适合 React 的 Flux
架构模式。
Flux 主要包含四个部分:
Dispatcher 动作(
Action
)分发中心,用于分发动作给数据存储(Store
)中心;Actions 用户的交互,数据的交互都可以是一个动作;
Stores 数据存储中心,响应动作,对数据进行修改,数据有变化就对组件(
View
)发出通知;Views & Controller-Views 组件本身,发送
Action
,接收来自Store
的通知;
Store
中存储了组件的数据,要更新数据必须发起一个 Action
,不能直接去修改,要获取数据,也只能从 Store
中获取,因此 Flux 的数据流也是单向的。而 Dispatcher
只是一个分发器,将 Action
分发到 Store
中。
考虑到 Demo 代码体积较大,不能一一在文章中展现,我在 github 上传了 flux-demo,可以对照 demo 看看。
Demo 中有 3 个组件,FluxDemo 是入口组件,它包含了 2 个子组件 Dropdown 和 Content。Dropdown 组件在选中了某一项时会发起一个请求,请求的数据会在 Content 中展现出来。
接下来将着重讲解 Flux 的应用部分。
Dispatcher 分发中心
官方的 Flux 提供了一个简单的 Dispatcher
库,它有几个简单的 API,最常用的是 register
和 dispatch
。一个应用只需要一个 Dispatcher
中心,用于管理分发整个应用的 Action
。
register
用于在 Store
中注册回调,并且会返回一个 token
值,这个 token
可以用于卸载回调,还有一个很重要的功能就是用于 waitFor
。
dispatch
用于在 Action
中分发动作,但是这个动作的分发是同步的,如果想支持异步的动作分发可以对 Dispatcher
进行扩展,查看dispatcher/dispatcher.js。
// dispatcher/dispatcher.js class Dispatcher extends Flux.Dispatcher { constructor (...args) { super(...args); } dispatch (type, action = {}) { if (!type) { throw new Error('You forgot to specify type.'); } super.dispatch({type, ...action}); } dispatchAsync (url, types, action = {}) { const { request, success, failure } = types; // 使用fetch来获取数据 const promise = fetch(url).then((response) => response.json()); // 分发请求开始的动作 this.dispatch(request, action); promise.then( // 分发请求成功的动作 (response) => { this.dispatch(success, {...action, response}); }, // 分发请求错误的动作 (error) => { this.dispatch(failure, {...action, error}); } ); } }; export default new Dispatcher();
与服务器的数据交互都是异步的,当我们在分发一个异步动作的时候,这个动作可以拆分成 3 个动作:
请求开始
请求成功
请求错误
Actions 动作
一个组件可以对应一系列的 Actions
,建议以组件来划分 Actions
的粒度。在 Demo 中有一个向服务器请求数据的 Action
,查看action/flux-demo.js。
// action/flux-demo.js import dispatcher from '../dispatcher/dispatcher'; const fluxDemoActions = { fetchIntroduction (name, path) { const url = `http://127.0.0.1:3002${path}`; // 一个异步Action拆分成了3个不同状态的Action dispatcher.dispatchAsync(url, { request: 'FETCH_INTRODUCTION', success: 'FETCH_INTRODUCTION_SUCCESS', failure: 'FETCH_INTRODUCTION_ERROR' },{ name }); } }; export default fluxDemoActions;
每一个动作都要有一个唯一的动作类型,在上面的代码中,它定义了 3 个动作类型:
FETCH_INTRODUCTION
请求开始FETCH_INTRODUCTION_SUCCESS
请求数据成功FETCH_INTRODUCTION_ERROR
请求数据失败
这 3 个动作在 Stores
可以有相对应的动作处理函数。
Stores 数据存储中心
Stores
部分要复杂一些。Stores
的数据变更需要发送和接收通知,故需要用到事件系统,可以直接使用 EventEmmiter
模块,可以创建一个工具函数,用于创建 Store
。
const createStore = (methods) => { let name; class Store extends EventEmitter {}; for (name in methods) { Store.prototype[name] = methods[name]; } return new Store(); };
组件不能直接访问 Store
中存储的数据,如果能直接访问也意味着可以直接修改数据,为了确保数据流是单向的,这是不允许的,必须通过 Store
本身提供的数据访问接口来获取数据,最后,需要在 Store
中注册一些与 Action
中相对应的回调函数。
// 数据存储对象,外部不能直接访问 const data = { introduction: {} }; // 创建store const fluxDemoStore = createStore({ getIntroduction (name) { return data.introduction[name]; } }); // 注册回调函数的时候会返回一个token fluxDemoStore.dispatchToken = dispatcher.register((action) => { switch(action.type) { case 'FETCH_INTRODUCTION': // do something break; case 'FETCH_INTRODUCTION_SUCCESS': // 请求成功后,将数据存储到私有的对象中 data.introduction[action.name] = action.response.data; // 同时通过事件系统发送数据变更的通知 fluxDemoStore.emit('change'); break; case 'FETCH_INTRODUCTION_ERROR': // do something break; } }); export default fluxDemoStore;
这里需要注意的是,在注册回调函数时用到了 switch
,分发任何一个 Action
时,Store
中的回调函数就通过 action.type
来区分到底分发的是哪一个 Action
,查看store/flux-demo.js。
Views 组件
上面把 flux 部分都分析完后,还是回到组件层面。
Dropdown 组件在选择了某一项时,会触发一个请求数据的 Action
。如果想让一个组件尽量能复用,那么它最好不要和 flux 有关联,这样能确保组件的独立性,所以在选择的时候提供一个回调函数,那么 Action
就可以在回调函数中调用,查看component/flux-demo.js。
selectCallback = (name) => { const path = `/api/${name.toLowerCase()}`; this.setState({ selected: name }, () => { // 发起请求数据的Action fluxDemoActions.fetchIntroduction(name, path); }); }
那么该在哪个组件中获取数据更好呢?首先,消费数据的是 Content 组件,最简单的方案就是直接在 Content 组件中来监听数据的变化更新数据。当然也可以在父组件 FluxDemo 中进行,然后再通过声明 props
来传递过去。一个简单的划分原则就是看消费数据的组件是否需要复用,如果为了复用,要确保其通用性,肯定是不包含 flux 部分更好,而如果不会有复用,那么直接在组件内获取数据更好,因为数据少了一层传递,查看component/content.js
// content.js import fluxDemoStore from '../store/flux-demo'; class Content extends React.Component { constructor (props) { super(props); this.state = {}; } // 组件装载完毕则绑定数据更新的事件 componentDidMount () { fluxDemoStore.on('change', this.refreshContent); } // 组件卸载的时候同时卸载事件 componentWillUnmount () { fluxDemoStore.off('change', this.refreshContent); } refreshContent = () => { // 从store获取数据 const introduction = fluxDemoStore.getIntroduction(this.props.name); // 获取到数据通过setState来更新组件的状态 this.setState({ introduction }); } render () { return ( <div> <p>{this.state.introduction}</p> </div> ); } }; export default Content;
Flux 的架构模式初次接触的话可能不是那么容易消化,可以仔细看看 Demo,然后自己尝试去写一个应用,你就会真正理解 Flux 单向数据流的魅力。
建议继续学习:
- React入门:关于虚拟DOM(Virtual DOM) (阅读:2563)
- React初探 (阅读:2560)
- React 高效开发环境的搭建 (阅读:1975)
- 从零开始React服务器渲染 (阅读:1869)
- 用 Virtual DOM 加速开发 (阅读:1577)
- 一个“三端”开发者眼中的React Native (阅读:1488)
- 从工程化角度讨论如何快速构建可靠React组件 (阅读:1574)
- 前端路由实现与 react-router 源码分析 (阅读:1225)
- zepto/jQuery、AngularJS、React、Nuclear的演化 (阅读:1129)
- React一线问题十问十答 (阅读:1188)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:雨夜带刀 来源: 雨夜带刀
- 标签: Flux React
- 发布时间:2016-03-21 23:43:12
- [55] Oracle MTS模式下 进程地址与会话信
- [54] IOS安全–浅谈关于IOS加固的几种方法
- [53] 如何拿下简短的域名
- [52] Go Reflect 性能
- [51] 图书馆的世界纪录
- [51] android 开发入门
- [49] 读书笔记-壹百度:百度十年千倍的29条法则
- [47] 【社会化设计】自我(self)部分――欢迎区
- [39] 程序员技术练级攻略
- [32] 视觉调整-设计师 vs. 逻辑