Awesome
refer
redux-like library for handling global state on functional style
refer 介绍
refer 是一个状态管理库,受到了 redux 的启发和影响,源码结构以及 API 也高度相似。所不同的是,refer 体积更小,概念更少,灵活性更强。
通常来说,用户只需要用到 createStore
一个 api。去掉了 redux 的 combineReducers、bindActionCreators 以及 applyMiddleware 等动作。
在深入了解 refer 之后,你将发现,那些概念是不必要的。每个动作都是一个 handler,所谓的 middleware、actionCreator 和 reducer 等, 只是一串相继执行的 handlers 的不同阶段而已。
引入 refer
npm install refer
refer 遵循 UMD 模块规范。在没有模块化环境时,将输出到全局变量 Refer
。
script
标签引入
<script src="refer.min.js"></script>
<script>console.log(Refer)</script>
AMD 模块加载
define(['refer'], function(Refer) {
console.log(Refer)
})
commonjs
var Refer = require('refer')
console.log(Refer)
ES2015
import Refer from 'refer'
console.log(Refer)
理解 refer
refer 跟 redux 一样,崇尚(pure function)纯函数。
什么是 refer handler?
refer handler
是接受一个参数并返回一个值的普通函数,或者这类函数所组成的数组,甚至可以任意嵌套。
// 返回一个数的后继
let succ = n => n + 1
let double = x => x * 2
let succ_double = [succ, double]
let handler1 = [succ, [double, succ], double]
什么是 refer handlers?
refer handlers
是一个 key-value 对象。value 为 refer handler
, key 则是其字符串代号。
let handlers = {
succ: n => n + 1,
double: x => x * 2,
succ_double: ['succ', 'double'],
key1: 'succ',
key2: ['succ', [x => x - 1, 'key1']]
}
refer handlers
的 key 可以作为 value,或者 value 数组里的一项,增加复用能力。
你已经掌握了 refer 的一半。
refer
的 createStore
函数,接受两个参数: handlers
和 initialState
,返回一个对象。
handlers
就是你刚才所掌握的,单参数函数的排列组合与命名。initialState
则默认为空对象{}
createStore
返回的对象拥有几个属性。
getState 函数
返回当前 state 数据,默认是 initialState
,除非 state 被改变。
subscribe(callback) 函数
订阅 state 变化的消息,callback 被调用时不接受任何参数。该订阅函数将返回一个解除订阅的函数。
replaceState(nextState, silent) 函数
用 nextState 替换当前的 state,如果 silent 为 true,则不触发消息发布
dispatch(key, value) 函数
接受两个参数 key 与 value,key 匹配出 refer handlers
对象里的 handler
,然后以 pipe
的形式依次执行里面的函数。
第一个函数接收的参数为 value,第二个函数接收的参数为第一个函数的返回值,以此类推。
注意:最后一个函数必须返回一个更新 state 的函数。dispatch 方法在同步 handler 时返回更新后的 state
let handlers = {
add: [n => n * 2, n => n / 2, n => state => state + n],
reduce: n => state => state - n
}
// initialState 不要求必须是对象,可以是任意值
let store = createStore(handlers, 0)
store.dispatch('add', 10) // 返回最新的 state: 10
store.dispatch('reduce', 100) // 返回最新的 state: 99
handler 里的任意函数都可以返回一个 promise,它后面的函数将会在 promise.then 之后继续进行。不需要像redux-promise
这类中间件的支持,refer 天生支持异步。
如果有函数返回 promise,dispatch 方法返回的也是 promise。
注意:在两种情况下,dispatch 返回的是 promise 对象
- handler 里的某个函数返回 promise
- handler 里任意函数抛出错误,这个错误将以 Promise.reject(error) 形式返回。
let handlers = {
add: [n => n * 2, n => n / 2, n => state => state + n],
reduce: n => state => state - n,
asyncAdd: n => state => Promise.resolve(state + n),
errorAdd: n => state => { throw new Error('I am error')}
}
let store = createStore(handlers, 0)
store.dispatch('add', 10) // 返回最新的 state: 10
// 异步返回最新的 state
store.dispatch('asyncAdd', 10)
.then(state => console.log(state)) // 20
store.dispatch('errorAdd', 100)
.catch(error => console.log(error.message)) // I am error
由于 dispatch 函数在同步和异步两种情况下都可能返回 promise。可以用下面列举的方式,兼顾成功与失败两种情形。
-
确定 handler 中所有函数都是同步执行的情况
- 判断 dispatch 的返回值是不是 thenable 对象,如果是,则说明存在错误
-
handler 里的函数有可能同步也有可能异步的情况
- 用 Promise.resolve(dispatch(key, value)).then(success).catch(fail) 处理
-
handler 里存在异步情况
- dispatch(key, value).then(success).catch(fail)
actions 对象
actions
对象的 key 跟 refer handlers
完全一致,它的值则是一个函数: value => dispatch(key, value)。
因此,你再需要使用 bindActionCreators
函数手动构造一个 actions
。refer 已经提供好了。
let handlers = {
add: [n => n * 2, n => n / 2, n => state => state + n],
reduce: n => state => state - n,
asyncAdd: n => state => Promise.resolve(state + n),
errorAdd: n => state => { throw new Error('I am error')}
}
let store = createStore(handlers, 0)
let { add, asyncAdd, errorAdd } = store.actions
add(10) // 返回最新的 state: 10
// 异步返回最新的 state
asyncAdd(10)
.then(state => console.log(state)) // 20
errorAdd(100)
.catch(error => console.log(error.message)) // I am error
你已经掌握了 refer 的八成
refer 的理念非常简单,就是依次执行一组函数,根据 key 来调节各组函数的执行时机,根据各个函数是否返回 promise 来决定下一个函数是同步还是异步执行。
refer 的高级用法
如何写中间件?
redux 的中间件是一个函数,而 refer 的中间件则是一个可复用的 refer handlers
createStore(handlers, initialState)
的 handlers 参数,支持数组类型,也就是说,你可以把多个 handlers 打包成一个数组传入。
createStore([handlers1, handlers2, handlers3])
各个 handlers 的同名 handler,会按照 handlers 在数组里的索引顺序拼接起来。
// 多个 handlers 将拼接成一个更大的 handlers 对象
{
[key]: [handler1.key, handler2.key, handler3.key]
}
也就是说,handler2 的某个 key 所对应的 handler 的第一个函数,其接收的参数将收到 handlers1 的影响,如果它没有同名的 handler,则接收 value,如果它有,则接受 handers1[key] 所返回的值。
值得注意的是,每个 handlers 的 key ,其代号意义,只在该 handlers 对象中有效。不能在 handlersA 中使用它不存在,但 handlersB 中存在的 handler 代号。
了解 refer 的生命周期钩子
handlers 的 key 没有限制。但 refer 选取了几个特殊的 key 值,它们不参与更新 state。
handlers[lifeCycleHook]
将在 dispatch 函数执行的各个特定阶段依次被调用,它们会被传入相应一个对象参数 data
data.key
: dispatch 的 key 参数data.value
: dispatch 的 value 参数data.currentState
: 当前的 statedata.nextState
: 下一个 statedata.error
: dispatch 出错时的错误信息
@SHOULD_DISPATCH
在 dispatch 执行时,最先被调用的生命周期函数,如果它返回 false,将终止此次 dispatch。该函数的 data 参数只有 key 、value、 currentState 三个属性。
@DISPATCH
紧接着 @SHOULD_DISPATCH 之后,该生命周期函数被调用,data 参数的属性与 @SHOULD_DISPATCH 相同。
@SHOULD_UPDATE
在 nextState 存在时被调用,data 参数的属性有 key、 value、 currentState 与 nextState 四个,如果它返回 false,将终止更新 state。
@WILL_UPDATE
在 currentState 即将被 nextState 所替换前调用,data 参数与 @SHOULD_UPDATE 相同。
@DID_UPDATE
在 currentState 被 nextState 所替换后调用,data 参数与 @SHOULD_UPDATE 相同。注意:此时的 data.nextState
才是跟 store.getState() 返回值相等。
@SYNC
在 currentState 以同步方式被更新后调用,参数与 @SHOULD_UPDATE 相同。
@ASYNC_START
在 currentState 以异步方式更新开始阶段被调用,data 参数为 key、value、currentState 与 nextState,其中 nextState 为 promise 对象。
@ASYNC_END
在 currentState 以异步方式更新结束时调用,data 参数为 key、value、currentState 与 (nextState || error )。当异步更新成功时,nextState 属性存在,error 不存在;当异步更新存在错误时,nextState 不存在,error 存在。
@THROW_ERROR
当 dispatch 中存在错误时调用,该生命周期函数将接收一个 error 参数对象。该函数的返回值将被 Promise.reject 包裹,作为 dispatch 的返回值。
也就是说,用户可以通过 @THROW_ERROR 钩子,控制 dispatch 出错时返回的 promise 对象携带的错误信息是什么。用户可以 dispatch(key, value).catch(fail)
进行处理。
注意:dispatch 时的所有错误信息都会进入这个生命周期钩子函数,包括 handler 里的函数故意抛出的错误,或者返回 Promise.reject。
了解 refer 的生态
refer 还比较新,目前并没有社区生态,希望你能加入。
refer-logger 是根据 refer 生命周期钩子写的日志中间件。
import createLogger from 'refer-logger'
let logger = createLogger({
scope: 'scopeName',
debug: true
})
createLogger
接受一个对象参数,scope 属性默认为 Root
, debug 为 true 时则在 @THROW_ERROR 钩子中抛出错误(因为部分浏览器不显示 promise 里包含的错误信息)。
当 action 被调用时,控制台将输出 key、value、nextState、currentState 以及此 action 的发生时间,消耗时长等信息。
refer-dom 是 refer 与 virtual-dom 结合的产物,它完整模拟了 react
的 component api,可以无缝跑起 react component 代码,只需要将 react 依赖指向 refer-dom 即可。
refer-dom 的 component api 是 react component 的超集,你可以在组件的 getHandlers 方法返回自定义的 handlers,然后用组件的 actions 属性里的函数来更新组件的 state。setState 方法在 refer-dom 里,只是更新 state 的其中一个 action 而已。
基于 refer 是如此简单易用,相信你能发挥出更大的威力。一起来构建 refer 生态吧。