Skip to content

同步

AutoStore内置同步机制,可以方便的实现两个AutoStore之间的数据同步。

使用方法

完全同步

store1state同步到store2state中。

ts
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中。

ts
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' });

指南

同步配置

sync方法可以接受一个配置对象,配置对象可以有以下属性:

属性 类型 说明
from string 当前storestate的路径
to string 目标storestate的路径
direction 'both' | 'forward' | 'backward' 同步方向,both=双向,forward=前向同步,backward=后向同步
filter (operate:StateOperate)=>boolean 过滤函数,返回true表示同步,返回false表示不同步
immediate boolean 是否立即同步,默认为true,即初始化时马上执行一次同步

同步方向

默认情况下,同步是双向的。

ts
const store1 = new AutoStore({...})
const store2 = new AutoStore({...})

// 默认双向同步
store1.sync(store2)

// 复制store1的变更到store2
store1.sync(store2,{direction:'forward'})

// 复制store2的变更到store1
store1.sync(store2,{direction:'backward'})

局部同步

通过formto属性同步状态的局部,而不是全同步。

ts
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函数过滤。

ts
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类型,包含了更新信息,类型如下:
ts
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是同步的。

ts

// 完全克隆,但不同步
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方法后,默认不会同步。

打开/关闭同步

同步可以通过onoff方法来打开和关闭同步。

ts
const store1 = new AutoStore({...})
const store2 = new AutoStore({...})

const syncer = store1.sync(store2)

// 关闭同步
syncer.off()

// 打开同步
syncer.on()

路径映射

允许通过pathMap来控制两个AutoStore之间的路径映射关系。

ts
const fromStore = new AutoStore({
    order: {
        a: 1,
        b: 2,
        c: 3,
    },
});
const toStore = new AutoStore({
    myorder: {},
});
fromStore.sync(toStore, {
    to: 'myorder',
    immediate: false,
    pathMap: {
        to: (path: string[]) => {
            return [path.join('.')];
        },
        from: (path: string[]) => {
            return path.reduce<string[]>((result, cur) => {
                result.push(...cur.split('.'));
                return result;
            }, []);
        },
    },
});

以上代码:

  • to参数表示将fromStore中的order.a映射到toStore中的myorder[order.a]
  • from参数表示将toStore中的myorder[order.a]映射到fromStore中的order.a

所以同步后生效后:

ts
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也会同步更新。

ts
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,则代表需要在初始化进行一次全同步。

以上的代码需要进行修改:

ts
fromStore.sync(toStore, {
    to: 'myorder',
    pathMap: {
        to: (path: string[], value: any) => {
            if (typeof value !== 'object') {
                return [path.join('.')];
            }
        },
        from: (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中的所有属性(包括后代对象),然后逐条进行更新。

如果路径映射代码是:

ts
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

因此代码需要修改为:

ts
fromStore.sync(toStore, {
    to: 'myorder',
    pathMap: {
        to: (path: string[], value: any) => {
            if (typeof value !== 'object') {
                return [path.join('.')];
            }
        },
        from: (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进行同步。比如将浏览器的AutoStoreWebWorkerAutoStore进行同步。

远程同步需要通过安装@autostore/syncer来实现。

使用方法

以下是@autostore/syncer的简单使用示例,实现workerbrowser之间的同步。

  • 第 1 步:开发一个Transport用来进行传输更新操作,实现IAutoStoreSyncTransport接口。
ts
// 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接口,接口定义如下:

ts
export interface IAutoStoreSyncTransport {
    // 如果为false,表示当前不可用,更新操作会被暂时写入缓存
    ready: boolean;
    // 发送更新操作
    send(operate: StateRemoteOperate): void;
    // 接收更新操作
    receive(callback: (operate: StateRemoteOperate) => void): void;
}
  • 第 2 步:配置本地AutoStore

这里本地AutoStore需要配置syncer,并指定transport

ts
// 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
ts
// 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
})

控制同步操作

开始和停止同步。

ts
const syncer = new AutoStoreSyncer(workerStore, {
    transport: new WorkerTransport(self),
    autostart: true,
});

// 开始同步
syncer.start();

// 停止同步
syncer.stop();

同步缓存

默认情况下,当transport不可用时,即transport.ready=false,更新操作会被暂时写入缓存。

transport可用时,应该调用syncer.flush(),将缓存中的更新操作发送到远程AutoStore

ts
const syncer = new AutoStoreSyncer(workerStore, {
    transport: new WorkerTransport(self),
});

syncer.flush();
  • 注意transport是否可用,是由Transport自己决定。当可用状态切换时,应当调用syncer.flush()
  • transport不可用时,更新操作会被暂时写入缓存,缓存大小由maxCacheSize控制,默认为100

同步映射

支持通过entryremoteEntry来控制两个AutoStore之间的同步映射关系。

ts
new AutoStoreSyncer(localStore, {
    transport: localTransport,
    entry: ['order'],
    remoteEntry: ['remoteOrder'],
});

以上代表要将localStore中的order映射到remoteStore中的remoteOrder

同步参数

参数 类型 默认值 说明
autostart boolean true 是否自动开始同步
maxCacheSize number 100 缓存大小
entry string[] [] 同步映射关系
remoteEntry string[] [] 同步映射关系
immediate boolean false 是否立即同步
onSend (operate:StateRemoteOperate) => boolean 发送更新操作回调,返回false表示不发送
onReceive (operate:StateRemoteOperate) => boolean 接收更新操作回调,返回false表示接收
ts
type StateRemoteOperate {
    type      : StateOperateType,
    path      : string[],
    value     : Value,
    indexs    : number[],
    flags     : number
}

手动全同步

调用syncer.update()方法可以向远程store要求一次全同步。

ts
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.update(); 
expect(localStore.state.y).toEqual(remoteStore.state.x);
  • 配置options.immediatetrue会在启动时自动调用syncer.update()

停止同步

可以通过.stop方法停止同步。

ts
const syncer = new AutoStoreSyncer(localStore, {
    transport: localTransport,
});
syncer.stop();

当调用stop()后,syncer会停止同步,然后:

  • 调用transport.onStop方法,因此可以在停止同步时进行断开连接等工作。
  • 同时会向对方发送一个stop操作指令,对方的syncer会收到这个指令,也会停止同步和调用transport.onStop方法。

示例

以下是同步时进行路径映射的示例: