文中涉及的React demo代码使用了16.8的新增特性Hooks:
它可以让你在不编写class的情况下使用state以及其他的React特性。
前言
刚立项时,可能只有一个根组件Root
:管你啥业务,一把梭。
项目慢慢有起色,好事者就拆出了一些子组件,必然,它们间将有一些数据流动,问题不大,可以让他们“父子相连”。
父子相连现在项目爆了,业务N
倍增长,不得不拆出更多的子孙组件出来,实现更多复杂业务。
但愿逻辑比较简单,数据流动是一层层往下:
组件树现实总是残酷的,往往结构都是这样的,父子孙组件间关系混乱:
逻辑混乱
怎么办???
方案
只要思想不滑坡,办法总比困难多:
- 方案1,梳理项目逻辑,重新设计组件(手动Fxxk!!!)
- 方案2,辞职,换个公司重开(手动狗头)
项目迭代过程中,不可避免出现组件间状态共享,而导致逻辑交错,难以控制。
我们会想:能不能有一种实践规范,将所有可能公用的状态、数据及能力提取到组件外,数据流自上往下,哪里需要哪里自己获取,而不是prop drilling
,大概如下:
于是这样一种数据结构冒了出来:
const store = {
state: {
text: 'Goodbye World!'
},
setAction (text) {
this.text = text
},
clearAction () {
this.text = ''
}
}
外部变量store
,其中state
来存储数据,store
里面有一堆功能各异的action
来控制state
的改变。
我们规定:只能通过调用action
来改变state
,于是我们就可以通过action
清晰地掌握着state
的动向,日志、监控、回滚等能力随着而来。
于是我们大概的看到了Flux
的雏形。
Flux
2013年,Facebook亮出React的时候,也跟着带出的Flux。Facebook认为两者相辅相成,结合在一起才能构建大型的JavaScript应用。
做一个容易理解的对比,React
是用来替换jQuery
的,那么Flux
就是以替换Backbone.js
、Ember.js
等MVC
一族框架为目的。
如上图,数据总是“单向流动”,相邻部分不存在互相流动数据的现象,这也是
Flux
一大特点。
-
View
发起用户的Action
-
Dispatcher
作为调度中心,接收Action
,要求Store
进行相应更新 -
Store
处理主要逻辑,并提供监听能力,当数据更新后触发监听事件 -
View
监听到Store
的更新事件后触发UI
更新
感兴趣可以看看每个模块的具体含义:
Action
一个普通的
Javascript
对象,一般使用type
与payload
描述了该action的具体含义。
在Flux
中一般定义actions
:一组包含派发action
对象的函数。
// actions.js
import AddDispatcher from '@/dispatcher'
export const counterActions = {
increment (number) {
const action = {
type: 'INCREMENT',
payload: number
}
AddDispatcher.dispatch(action)
}
}
以上代码,使用counterActions.increment
,将INCREMENT
派发到Store
。
Dispatcher
将Action派发到Store,通过flux提供的Dispatcher注册唯一实例。
Dispatcher.register
方法用来登记各种Action
的回调函数
import { CounterStore } from '@/store'
import AddDispatcher from '@/dispatcher'
AppDispatcher.register(function (action) {
switch (action.type) {
case INCREMENT:
CounterStore.addHandler();
CounterStore.emitChange();
break;
default:
// no op
}
});
以上代码,AppDispatcher
收到INCREMENT
动作,就会执行回调函数,对CounterStore
进行操作。
Dispatcher
只用来派发Action
,不应该有其他逻辑。
Store
应用状态的处理中心。
Store
中复杂处理业务逻辑,而由于数据变更后View
需要更新,所以它也负责提供通知视图更新的能力。
因为其随用随注册,一个应用可以注册多个Store的能力,更新data flow为:
mul-store细心的朋友可以发现在上一小节CounterStore
中调用了emitChange
的方法,对,它就是用来通知变更的。
import { EventEmitter } from "events"
export const CounterStore = Object.assign({}, EventEmitter.prototype, {
counter: 0,
getCounter: function () {
return this.counter
},
addHandler: function () {
this.counter++
},
emitChange: function () {
this.emit("change")
},
addChangeListener: function (callback) {
this.on("change", callback)
},
removeChangeListener: function (callback) {
this.removeListener("change", callback)
}
});
以上代码,CounterStore
通过继承EventEmitter.prototype
获得触发emit
与监听on
事件能力。
View
Store
中的数据的视图展示
View
需要监听视图中数据的变动来保证视图实时更新,即
- 在组件中需要添加
addChangeListerner
- 在组件销毁时移除监听
removeChangeListener
我们看个简单的Couter例子,感受下Flux
的代码。
(手动分割线)
认真体验的朋友可能会注意到:
- 点击
reset
后,store
中的couter
被更新(没有emitChange
所以没实时更新视图); - 业务逻辑与数据处理逻辑交错,代码组织混乱;
好,打住,再看个新的数据流。
网友评论