同步
AutoStore提供了@autostorejs/syncer可以方便的实现两个AutoStore之间的数据同步。
使用方法
提示
同步功能由@autostorejs/syncer提供,只需要导入即可。
完全同步
将store1的state同步到store2的state中。
import '@autostorejs/syncer';
const store1 = new AutoStore({
order: {
name: 'fisher',
price: 2,
count: 3,
total: computed((order) => order.price * order.count),
},
});
const store2 = new AutoStore<typeof store1.state.order>();
store1.sync(store2);部分同步
将store1.state.order同步到store2.state.myorder中。
import '@autostorejs/syncer';
const store1 = new AutoStore({
order: {
name: 'fisher',
price: 2,
count: 3,
total: computed((order) => order.price * order.count),
},
});
const store2 = new AutoStore<typeof store1.state.order>({
myorder: {},
});
store1.sync(store2, { from: 'order', to: 'myorder' });指南
同步方向
默认情况下,同步是双向的。
const store1 = new AutoStore({...})
const store2 = new AutoStore({...})
// 默认双向同步
store1.sync(store2)
store1.sync(store2,{direction:'both'})
// 复制store1的变更到store2
store1.sync(store2,{direction:'forward'})
// 复制store2的变更到store1
store1.sync(store2,{direction:'backward'})局部同步
通过form和to属性同步状态的局部,而不是全同步。
const store1 = new AutoStore({...})
const store2 = new AutoStore({...})
// 完全同步
store1.sync(store2)
// 将store1.state.order同步到store2.state
store1.sync(store2,{from:"order"})
// 将store1.state.order同步到store2.state.myorder
store1.sync(store2,{from:"order",to:"myorder"})
// 将store1.state同步到store2.state.myorder
store1.sync(store2,{to:"myorder"})过滤同步
默认情况下,会对from路径下的所有数据进行同步,可以额外通过filter函数过滤。
const store1 = new AutoStore({
order:{
a:1,
b:2,
c:3,
d:4
}
})
const store2 = new AutoStore({...})
// 完全同步,
store1.sync(store2,{
filter:(operate)=>operate.path[0]==='order'
})filter可以返回true表示同步,返回false表示不同步。operate对象是StateOperate类型,包含了更新信息,类型如下:
export type StateOperate<Value = any, Parent = any> = {
type: StateOperateType;
path: string[];
value: Value;
indexs?: number[];
oldValue?: Value;
parentPath?: string[];
parent?: Parent;
reply?: boolean;
flags?: number | symbol;
};克隆同步
AutoStore提供一个Clone方法,可以克隆一个store,克隆的store与原store是相互独立的,但是state是同步的。
// 完全克隆,但不同步
const store1 = new AutoStore({...})
const store2 = store1.clone({...<AutoStore Options>...})
// 完全克隆,但不同步
const store2 = store1.clone()
const store2 = store1.clone({sync:'none'})
// 完全克隆,但双向同步
const store2 = store1.clone({sync:'both'})
// 完全克隆,前向同步
const store2 = store1.clone({sync:'farward'})
// 完全克隆,前向同步
const store2 = store1.clone({sync:'backward'})
// 克隆store1.state.<path>,前向同步
const store2 = store1.clone({
entry:"<path>",
sync:'backward'
})- 执行
clone方法后,默认不会同步。
打开/关闭同步
同步可以通过start和stop方法来打开和关闭同步。
const store1 = new AutoStore({...})
const store2 = new AutoStore({...})
const syncer = store1.sync(store2)
// 关闭同步
syncer.stop()
// 打开同步,默认是自动开始的
syncer.start()路径映射
允许通过pathMap来控制两个AutoStore之间的路径映射关系。
const fromStore = new AutoStore({
order: {
a: 1,
b: 2,
c: 3,
},
});
const toStore = new AutoStore({
myorder: {},
});
fromStore.sync(toStore, {
to: 'myorder',
immediate: false,
pathMap: {
<<<<<<< .mine
toRemote: (path: string[], value: any) => {
return [path.join('.')];
=======
toLocal: (path: string[]) => {
if (typeof path[0] !== 'object') {
>>>>>>> .theirs
<<<<<<< .mine
=======
}
>>>>>>> .theirs
},
toLocal: (path: string[]) => {
return path.reduce<string[]>((result, cur) => {
result.push(...cur.split('.'));
return result;
}, []);
},
},
});以上代码:
toRemote参数表示将fromStore中的order.a映射到toStore中的myorder[order.a]。toLocal参数表示将toStore中的myorder[order.a]映射到fromStore中的order.a。
所以同步后生效后:
fromStore.state.order.a = 11;
fromStore.state.order.b = 12;
fromStore.state.order.c = 13;
expect(toStore.state).toEqual({
myorder: {
'order.a': 11,
'order.b': 12,
'order.c': 13,
},
});反之,如果修改toStore中的myorder[order.a],fromStore中的order.a也会同步更新。
toStore.state.myorder['order.a'] = 21;
toStore.state.myorder['order.b'] = 22;
toStore.state.myorder['order.c'] = 23;
expect(fromStore.state).toEqual({
order: {
a: 21,
b: 22,
c: 23,
},
});注意
以上配置了immediate=false,所以不会立即进行全同步,而是进行增量同步,仅当fromStore变化时才会同步变更项到toStore中。
如果我们指定immediate=true,则代表需要在初始化进行一次全同步。
以上的代码需要进行修改:
fromStore.sync(toStore, {
to: 'myorder',
pathMap: {
toRemote: (path: string[], value: any) => {
return [path.join('.')];
},
toLocal: (path: string[], value: any) => {
if (typeof value !== 'object') {
return path.reduce<string[]>((result, cur) => {
result.push(...cur.split('.'));
return result;
}, []);
}
},
},
});重点:
当immediate=true时,我们无法在进行第一次同步时使用Object.assign来快速同步,而只能使用for...in来深度遍历fromStore中的所有属性(包括后代对象),然后逐条进行更新。
如果路径映射代码是:
pathMap: {
to: (path: string[], value: any) => {
return [path.join('.')];
};
}则会在toStore中写入:
{
myorder:{
'order':{a:1,b:2,c:3}
'order.a':1,
'order.b':2,
'order.c':3
}
}多了一个order属性,为了避免这种情况,要点在于,当value是基本类型时,才进行路径转换,如果值类型是object,则需要返回undefined。
因此代码需要修改为:
fromStore.sync(toStore, {
to: 'myorder',
pathMap: {
toRemote: (path: string[], value: any) => {
if (typeof value !== 'object') {
return [path.join('.')];
}
},
toLocal: (path: string[], value: any) => {
if (typeof value !== 'object') {
return path.reduce<string[]>((result, cur) => {
result.push(...cur.split('.'));
return result;
}, []);
}
},
},
});- 当遍历
fromStore时,如果遇到object类型时,则返回undefined,这样toStore中就不会写入多余的order属性。
以下完整示例:
远程同步
远程同步可以实现将本地的AutoStore与远程的AutoStore进行同步。比如将浏览器的AutoStore与WebWorker的AutoStore进行同步。
远程同步需要通过安装@autostore/syncer来实现。
使用方法
以下是@autostore/syncer的简单使用示例,实现worker与browser之间的同步。
- 第 1 步:开发一个
Transport用来进行传输更新操作,实现IAutoStoreSyncTransport接口。
// transport.ts
export class WorkerTransport implements IAutoStoreSyncTransport {
private _callback: (operate: StateRemoteOperate) => void;
ready = true;
constructor(public worker) {
this.worker.addEventListener('message', this.onReceive.bind(this));
}
send(operate: StateRemoteOperate) {
this.worker.postMessage(operate);
}
receive(callback: (operate: StateRemoteOperate) => void): void {
this._callback = callback;
}
onReceive(data: StateRemoteOperate) {
this._callback(data);
}
}需要实现IAutoStoreSyncTransport接口,接口定义如下:
export interface IAutoStoreSyncTransport {
// 如果为false,表示当前不可用,更新操作会被暂时写入缓存
ready: boolean;
// 发送更新操作
send(operate: StateRemoteOperate): void;
// 接收更新操作
receive(callback: (operate: StateRemoteOperate) => void): void;
}- 第 2 步:配置本地
AutoStore
这里本地AutoStore需要配置syncer,并指定transport。
// browser.ts
import {AutoStore} from '@autostore/core'
import { AutoStoreSyncer } from '@autostore/syncer'
const worker = new Worker('./worker.ts')
const browserStore = new AutoStore({...})
const syncer = new AutoStoreSyncer(browserStore,{
transport: new WorkerTransport(worker),
autostart: true
})- 第 3 步:远程
AutoStore
// worker.ts
import { AutoStore } from '@autostore/core'
import { AutoStoreSyncer } from '@autostore/syncer'
import { WorkerTransport } from './transport'
const workerStore = new AutoStore({...})
const syncer = new AutoStoreSyncer(workerStore,{
transport: new WorkerTransport(self),
autostart: true
})控制同步操作
开始和停止同步。
const syncer = new AutoStoreSyncer(workerStore, {
transport: new WorkerTransport(self),
autostart: true,
});
// 开始同步
syncer.start();
// 停止同步
syncer.stop();同步缓存
默认情况下,当transport不可用时,即transport.ready=false,更新操作会被暂时写入缓存。
当transport可用时,应该调用syncer.flush(),将缓存中的更新操作发送到远程AutoStore。
const syncer = new AutoStoreSyncer(workerStore, {
transport: new WorkerTransport(self),
});
syncer.flush();- 注意,
transport是否可用,是由Transport自己决定。当可用状态切换时,应当调用syncer.flush()。 - 当
transport不可用时,更新操作会被暂时写入缓存,缓存大小由maxCacheSize控制,默认为100。
同步映射
支持通过entry和remoteEntry来控制两个AutoStore之间的同步映射关系。
new AutoStoreSyncer(localStore, {
transport: localTransport,
entry: ['order'],
remoteEntry: ['remoteOrder'],
});以上代表要将localStore中的order映射到remoteStore中的remoteOrder。
手动全同步
调用syncer.push()方法将本地的AutoStore推送给远程AutoStore。
也可以调用syncer.pull()方法从远程拉取数据到本地的AutoStore。
const remoteStore = new AutoStore({
x: {
order: {
price: 100,
count: 2,
total: computed((order) => order.price * order.count),
},
},
});
const localStore = new AutoStore({
y: {},
});
const remoteSyncer = new AutoStoreSyncer(remoteStore, { transport: remoteTransport });
const localSyncer = new AutoStoreSyncer(localStore, { transport: localTransport, entry: ['y'], remoteEntry: ['x'] });
expect(localStore.state.y).toEqual({});
localSyncer.push();
expect(localStore.state.y).toEqual(remoteStore.state.x);- 配置
options.immediate为true会在启动时自动调用syncer.push()。
停止同步
可以通过.stop方法停止同步。
const syncer = new AutoStoreSyncer(localStore, {
transport: localTransport,
});
syncer.stop();当调用stop()后,syncer会停止同步,然后:
- 调用
transport.onStop方法,因此可以在停止同步时进行断开连接等工作。 - 同时会向对方发送一个
stop操作指令,对方的syncer会收到这个指令,也会停止同步和调用transport.onStop方法。
同步配置
sync方法可以接受一个配置对象,配置对象可以有以下属性:
| 属性 | 类型 | 说明 |
|---|---|---|
autostart | boolean | 自动开始同步 |
from | string | 当前store的state的路径 |
to | string | 目标store的state的路径 |
direction | 'both' | 'forward' | 'backward' | 同步方向,both=双向,forward=前向同步,backward=后向同步 |
filter | (operate:StateOperate)=>boolean | 过滤函数,返回true表示同步,返回false表示不同步 |
immediate | boolean | 是否立即同步,默认为true,即初始化时马上执行一次同步 |
maxCacheSize | number | 缓存大小,默认为100 |
onSend | (operate) => boolean | void | 发送到远程之前触发,可以在此修改 operate,叠加自己的数据到了 operate, 返回 false 可以阻止发送 |
onReceive | (operate) => boolean | void | 从远程接收到数据时触发,可以在此修改 operate,叠加自己的数据到了 operate, 返回 false 可以阻止接收 |
pathMap | {toLocal,toRemote} | 路径映射,用于将当前store的state的路径映射到目标store的state的路径,用于双向同步 |
示例
以下是同步时进行路径映射的示例: