firstore v1.0.5
firstore
firstore是一个全局状态管理工具,可用于小程序、Vue、React等。firstore的核心部分由state、actions、getters组成(参考了Pinia):state中的每一层都由Proxy代理;- 可以分别对
state、actions、getters进行监听; - 可以为
state创建还原点。
使用说明
安装
npm install firstore -SStore
调用
createStore函数可返回一个store实例。- 接收参数:
storeNamestore的名称字符串,用以区分不同store,如果传入已存在的storeName,则会报错。
configstore的初始状态对象;- 如果
config中存在state、actions、getters对象,则createStore会读取它们作为初始状态,否则它们会被视为{}。
- 接收参数:
通过
createStore创建一个store:// fooStore.js const { createStore } = require('firstore') const fooStore = createStore('foo',{ state:{ // state 中定义状态 name: 'zzc6332', age: 26, isAdmin: true }, actions:{ // actions 中定义一些方法,可以是异步操作 changeName(name){ this.name = name } }, getters:{ // getters 中定义一些计算属性 introduction: (state) => `我是${state.name}, 今年${state.age}岁。` } }) module.exports = fooStore
state
state中保存store的状态,在创建store时定义初始state,并可以通过多种方式修改。
读取 state
通过
store实例可直接读取state中的内容:const fooStore = require('./fooStore') console.log(fooStore.name) // 'zzc6332' console.log(fooStore.age) // 26通过
store实例的state属性可读取整个state:const fooStore = require('./fooStore') console.log(fooStore.state) // { name: 'zzc6332', age: 26, isAdmin: true }
修改 state
通过
store实例可直接修改state中的内容:const fooStore = require('./fooStore') fooStore.name = 'Joie' console.log(fooStore.name) // 'Joie' fooStore.age -= 8 console.log(fooStore.age) // 18通过
store实例的$patch方法可批量修改state中的内容:const fooStore = require('./fooStore') fooStore.$patch({ name: 'Joie', age: 18 }) console.log(fooStore.state) // { name: 'Joie', age: 18, isAdmin: true }
替换 state
通过
store实例的$set方法可替换整个state:const fooStore = require('./fooStore') fooStore.$set({ name: 'Joie', age: 18 }) console.log(fooStore.state) // { name: 'Joie', age: 18 }
重置 state
通过
store实例的$reset方法可重置整个state至初始状态:const fooStore = require('./fooStore') delete fooStore.name fooStore.name2 = 'Joie' console.log(fooStore.state) // { age: 26, isAdmin: true, name2: 'Joie' } fooStore.$reset() console.log(fooStore.state) // { name: 'zzc6332', age: 26, isAdmin: true }
actions
actions中定义与当前store相关的业务逻辑。
定义 actions
action必须定义为函数;- 如果将一个
action定义为非箭头函数,则其中的this指向其所属的store实例。
使用 actions
通过
store实例可直接调用actions中的函数:const fooStore = require('./fooStore') fooStore.changeName('Joie') console.log(fooStore.name) // 'Joie'
getters
getters中定义一些计算属性。
定义 getters
getter必须定义为函数;getter接收state作为第一个参数;getter需要将计算的结果作为返回值;- 如果将一个
getter定义为非箭头函数,则其中的this指向其所属的store实例。
读取 getters
当读取一个
getter时,会得到该getter函数执行的返回值;通过
store实例可直接读取getters中的计算属性:const fooStore = require('./fooStore') console.log(fooStore.introduction) // '我是zzc6332, 今年26岁。'注意:
如果通过解构赋值的方式将
getters中的计算属性取出,则取出的为当前的计算值,当依赖发生改变时不会再重新计算:const fooStore = require('./fooStore') const { introduction } = fooStore fooStore.age++ console.log(introduction) // '我是zzc6332, 今年26岁。' console.log(fooStore.introduction) // '我是zzc6332, 今年27岁。'
监听
监听 state
可通过
store实例的$onState方法监听state的变化。接收参数:
identifier- 需要监听的数据的标识符;
- 例如需监听
store.personList,则传入'personList'; - 例如需监听
store.personList[0].name,则传入'personList[0].name'; - 如需监听整个
state,则传入'*',此时每一次试图修改state的操作,若引起了数据变化,都会触发且每次仅触发一次callback(不论此次操作改变了多少项数据); - 如果需要批量监听,则传入需要批量监听的数据的标识符组成的数组:
- 例如需要批量监听
store.name和store.age,则传入[ 'name', 'age' ]; - 数组中也可包括
'*'。
- 例如需要批量监听
callback监听的数据发生变化时执行的回调函数;
定义参数:
mutation形参会接收一个对象,包含所监听的getter返回值变化的信息,包括:storeName- 该监听所属的
store的名称。
- 该监听所属的
chain- 监听的目标的标识符,即
$onState方法中传入的第一个参数identifier(如果是批量监听,则返回identifier数组中对应的标识符); - 如果监听的标识符是
'*',则mutation对象中不包含此项。
- 监听的目标的标识符,即
value- 监听的目标变化后的值;
- 如果监听的标识符是
'*',则mutation对象中不包含此项。
preValue- 监听的目标变化前的值;
- 如果监听的标识符是
'*',则mutation对象中不包含此项; - 注意:
deep模式下,如果监听的数据是一个函数对象,且它的对象属性被改变了,则preValue是它作为对象的快照;- 如果是监听的函数的引用地址改变了,则
preValue是之前的函数本身。
preState- 监听的目标变化前的
state对象的快照; - 注意:
- 如果
state中存在函数对象,则其在preState中将只保留对象部分的快照。
- 如果
- 监听的目标变化前的
type- 造成此次数据变化的方式,值为以下之一:
'direct'通过store实例直接操作数据;'$patch'通过$patch方法改变数据;'$set'通过$set方法改变数据;'$reset'通过$reset方法改变数据;'$load'通过$load方法还原数据。
- 造成此次数据变化的方式,值为以下之一:
byAction(实验性)参与本次数据变化的
action:- 如果此次数据变化不是由
action的调用造成,则该值为false; - 如果此次数据变化由
action的调用造成,则该值为一个包含了参与此次数据变化的action的描述对象的数组,描述对象的storeName属性表示该action所属的store的名称,actionName属性表示该action的名称;
- 如果此次数据变化不是由
异步
action的捕获:默认情况下,如果一个
action调用时启动了一个异步任务,且该异步任务造成了监听的数据的变化,该action不会被byAction捕获;如果监听的数据的变化存在于
action中异步调用的回调函数中,则可将该回调函数传入this.$cb()在action中同步调用,this.$cb(callback)将返回一个新的回调函数供异步调用,此时该action可以被byAction捕获:const { createStore } = require('firstore') const fooStore = createStore('foo', { state: { name: 'zzc6332', }, actions: { changeNameAsync(name, delay) { const callback = this.$cb(() => { this.name = name }) setTimeout(callback, delay) /* setTimeout(this.$cb(() => { this.name = name }), delay) // 这种方式不可行,this.$cb必须在 action 中被同步调用 */ } } }) fooStore.$onState('name', (mutation) => { console.log('name: ' + mutation.value + ', byAction: ' + JSON.stringify(mutation.byAction)) }) fooStore.changeNameAsync('Joie', 1000) // 1000ms后控制台输出: // - 'name: Joie, byAction: [{"storeName":"foo","actionName":"changeNameAsync"}]'如果需要在使用
async/await时被byAction捕获,可将需要在await之后修改数据的操作放入函数中传给this.$cb在action中同步调用,将返回的函数在await之后使用:const { createStore } = require('firstore') const fooStore = createStore('foo', { state: { name: 'zzc6332', }, actions: { async changeNameAsync(promise1, promise2) { const modify = this.$cb(res => { this.name = res }) const res1 = await promise1 console.log('获取 rest1 之后的操作') modify(res1) const rest2 = await promise2 console.log('获取 rest2 之后的操作') modify(rest2) } } }) fooStore.$onState('name', (mutation) => { console.log('name: ' + mutation.value + ', byAction: ' + JSON.stringify(mutation.byAction)) }) const promiseJoie = new Promise((resolve) => { setTimeout(() => { resolve('Joie') }, 1000) }) const promiseMocha = new Promise((resolve) => { setTimeout(() => { resolve('Mocha') }, 500) }) fooStore.changeNameAsync(promiseJoie, promiseMocha) // 1000ms后控制台输出: // - '获取 rest1 之后的操作' // - 'name: Joie, byAction: [{"storeName":"foo","actionName":"changeNameAsync"}]' // - '获取 rest2 之后的操作' // - 'name: Mocha, byAction: [{"storeName":"foo","actionName":"changeNameAsync"}]'
如果一个
action返回了一个promise,且该promise在其调用的then方法的回调中(不支持在被await时被捕获),或在action监听函数的after/onError函数的回调中修改了监听的数据,则该action会被byAction捕获:const { createStore } = require('firstore') const fooStore = createStore('foo', { state: { name: 'zzc6332', }, actions: { changeName(name) { this.name = name }, getNamePromise(name, delay) { return new Promise(resolve => { setTimeout(() => resolve(name), delay) }) } } }) fooStore.$onState('name', (mutation) => { console.log('name: ' + mutation.value + ', byAction: ' + JSON.stringify(mutation.byAction)) }) fooStore.$onAction('getNamePromise', (_, after) => { after((res, _this) => { _this.changeName(res) }) }) fooStore.getNamePromise('Joie', 1000) // 1000ms后控制台输出: // - 'name: Joie, byAction: [{"storeName":"foo","actionName":"getNamePromise"},{"storeName":"foo","actionName":"changeName"}]'
isImmediately- 是否在发起监听时立即调用一次
callback; - 默认为
false。
- 是否在发起监听时立即调用一次
deep- 是否深度监听;
- 默认为
true; - 如果开启了深度监听,则对于引用数据类型,只关注其结构和内容是否变化,不关注其引用地址是否改变;
- 如果关闭了深度监听,则对于引用数据类型,则只关注其引用地址是否改变,不关注其结构和内容是否变化,这种模式下如果调用了
store实例的$patch、$set、$reset、$load方法,任何监听的数据都会被认为发生了变化。
返回值:
$onState方法调用成功后会返回一个新的函数;调用该函数则可以关闭该次
$onState调用产生的所有监听,若关闭成功则该函数返回true;例:
const fooStore = require('./fooStore') const unSubscribe = fooStore.$onState(['name', 'age'], (mutation) => { const { chain, value, preValue } = mutation console.log(`${chain} 产生了变化,之前的值为 ${preValue},新的值为 ${value}`) }) fooStore.name = 'Joie' // 控制台输出:'name 产生了变化,之前的值为 zzc6332,新的值为 Joie' fooStore.age = 18 // 控制台输出:'age 产生了变化,之前的值为 26,新的值为 18' console.log(unSubscribe()) // 关闭监听成功,控制台输出:true console.log(unSubscribe()) // 监听已被关闭,控制台输出:false fooStore.name = 'Mocha' // 监听已被关闭,控制台不输出内容 fooStore.age = 1 // // 监听已被关闭,控制台不输出内容
监听 action
- 可通过
store实例的$onAction方法监听action的调用。 接收参数:
actionName- 需要监听的
action名的字符串; - 如果需要批量监听,则传入需要批量监听的
action名的字符串组成的数组; - 如果需要监听所有
action,则传入'*'。
- 需要监听的
callback- 监听的
action被调用时执行的回调函数; 定义参数:
info一个对象,包含所监听的
action调用的信息,包括:name- 监听的
action名的字符串。
- 监听的
storeName- 该监听所属的
store的名称。 args- 该次
action被调用时传入的参数。
- 该次
- 该监听所属的
preState- 该次
action执行前的state对象的快照; - 注意:
- 如果
state中存在函数对象,则其在preState中将只保留对象部分的快照。
- 如果
- 该次
after- 一个函数,调用时将一个回调函数作为参数传入;
- 如果
action的返回值是一个非Promise值:- 该值将作为第一个参数
result传入该回调函数; - 该次
action被调用并执行完毕后会执行这个回调函数;
- 该值将作为第一个参数
- 如果
action的返回值是一个Promise值:- 当
Promise的状态转变为fulfilled时,其结果(the fulfillment value)将作为第一个参数result传入该回调函数并执行; - 当
Promise的状态转变为rejected时,该回调函数不会执行; - 回调函数的第二个形参接收
store实例;
- 当
- 如果回调函数是非箭头函数,那么它的
this指向store实例。
onError
- 一个函数,调用时将一个回调函数作为参数传入;
- 如果
action执行时抛出错误,则会被onError捕获并作为 第一个参数error传入该回调函数并执行; - 如果
action执行完毕,返回值是一个Promise值:- 当
Promise的状态转变为rejected时,其结果(rejection reason)将作为第一个参数error传入该回调函数并执行; - 当
Promise的状态转变为fulfilled时,该回调函数不会执行;
- 当
- 回调函数的第二个形参接收
store实例;
- 如果
- 如果回调函数是非箭头函数,那么它的
this指向store实例。
- 监听的
返回值:
$onAction方法调用成功后会返回一个新的函数;- 调用该函数则可以关闭该次
$onAction调用产生的所有监听,若关闭成功则该函数返回true;
监听 getter
- 可通过
store实例的$onGetter方法监听getter返回值的变化。 - 接收参数
getterName- 需要监听的
getter名的字符串; - 如果需要批量监听,则传入需要批量监听的
getter名的字符串组成的数组。
- 需要监听的
callback- 监听的
getter返回值发生变化时执行的回调函数; - 定义参数:
mutation形参会接收一个对象,包含所监听的getter返回值变化的信息,包括:storeName- 该监听所属的
store的名称。
- 该监听所属的
name- 监听的
getter名的字符串。
- 监听的
value- 监听的
getter返回值变化后的值。
- 监听的
preValue- 监听的
getter返回值变化前的值。
- 监听的
preState- 监听的
getter返回值发生变化前的state对象的快照; - 注意:
- 如果
state中存在函数对象,则其在preState中将只保留对象部分的快照。
- 如果
- 监听的
type- 本次
getter返回值变化时,其依赖的数据变化的方式,同$onState。
- 本次
byAction(实验性)- 参与本次数据变化的
action,同$onState。
- 参与本次数据变化的
- 监听的
isImmediately- 是否在发起监听时立即调用一次
callBack; - 默认为
false。
- 是否在发起监听时立即调用一次
- 返回值
$onGetter方法调用成功后会返回一个新的函数;- 调用该函数则可以关闭该次
$onState调用产生的所有监听,若关闭成功则该函数返回true;
清空监听
- 可通过
store实例的$clearListeners方法清空监听。 $clearListeners方法接收一个参数,可选值为:'state'- 清空所有
$onState产生的监听;
- 清空所有
'actions'- 清空所有
$onAction产生的监听;
- 清空所有
'getters'- 清空所有
$onGetter产生的监听;
- 清空所有
'*'- 清空所有类型的监听。
还原点
state中的数据可以保存到还原点中,并可以随时通过还原点查看或恢复数据。通过
store实例中的一些方法可以实现还原点功能:$save$save方法执行后,会将当前state保存到一个还原点中,并返回该还原点的id。
$get- 将一个还原点
id传入$get方法执行后,将返回该对应原点中的state对象; - 如果传入的
id对应的还原点不存在,则返回undefined。
- 将一个还原点
$load- 将一个还原点
id传入$load方法执行后,会将对应还原点中的state加载到当前store实例中,并返回true; - 如果传入的
id对应的还原点不存在,则返回false。
- 将一个还原点
$delSave- 将一个还原点
id传入$delSave方法执行后,将删除对应的还原点,如果删除成功则返回true,如果找不到对应还原点则返回false; - 将
'*'传入$delSave方法执行后,将清空所有还原点。
- 将一个还原点
示例:
const fooStore = require('./fooStore') fooStore.$set({ name: 'Joie', age: 18 }) const rp1 = fooStore.$save() console.log(fooStore.state) // { name: 'Joie', age: 18 } fooStore.$patch({ name: 'Mocha', age: 1 }) console.log(fooStore.state) // { name: 'Mocha', age: 1 } console.log(fooStore.$get(rp1).name) // 'Joie' fooStore.$load(rp1) console.log(fooStore.state) // { name: 'Joie', age: 18 } fooStore.$delSave(rp1)