# 自定义 StorageDriver

本文档提供了关于如何自定义 StorageDriver 的开发指南,以及相关的 API 信息和示例代码。

# 概述

StorageDriver 是一个用于存储和管理数据的接口。它只关心数据应如何被存储、删除以及管理分区等功能。通过自定义 StorageDriver,您可以实现对数据的读取、写入、删除等操作,并可以监听数据的变化。

# API 参考

# StorageContext 类

class StorageContext {
    public id: string;
    public feature: string;
}

StorageContext 类表示存储上下文,用于标识存储的数据和特性。

# PDFViewerStorageContext 类

class PDFViewerStorageContext extends StorageContext {
    public pdfViewer: PDFViewer;
}

PDFViewerStorageContext 类继承自 StorageContext 类,表示 PDFViewer 的存储上下文。它包含了一个 PDFViewer 实例,通过该 PDFViewer 实例, 我们可以获得当前文档、当前页等信息, 恰当地组合这些信息,可以对数据进行分区存储。

# PDFViewer 接口

interface PDFViewer {
    getInstanceId(): string;
}

9.1.0 版本以后, PDFViewer 接口增加了一个方法 getInstanceId(),用于获取 PDFViewer 的实例 ID, 当有多个 PDFViewer 实例时, 我们需要确保它们的ID不一致, 以便于对不同 PDFViewer 实例的数据进行分区存储。

# PDFViewerConstructor 接口

interface PDFViewerConstructor {
    new(options: {
        instanceId: string,
        customs: {
            storageDriver: StorageDriver;
        }
    }): PDFViewer;
}

PDFViewerConstructor 接口用于描述创建 PDFViewer 实例的构造函数信息。9.1.0 版本以后, 这个构造函数增加了一个包含 instanceId 和 customs 属性的 options 对象作为参数,其中 customs 属性包含了一个 StorageDriver 实例,用于存储和管理数据。应用层将自定义的 StorageDriver 实例通过 custom.storageDriver 传给 PDFViewer 就可以实现自定义存储功能。

# StorageDriver 接口

interface StorageDriver {
    public getAll(context: StorageContext): Promise<Record<string, any>>;
    public get<T>(context: StorageContext, key: string): Promise<T | null>;
    public set<T>(context: StorageContext, key: string, value: T): Promise<void>;
    public removeAll(context: StorageContext): Promise<void>;
    public remove(context: StorageContext, key: string): Promise<void>;
    public onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => void): Function;
    public onRemove(callback: (event: StorageDriverRemoveEvent) => void): Function;
}

StorageDriver 是一个接口,定义了一组用于存储和管理数据的方法。您可以通过实现 StorageDriver 这些方法来自定义自己的 StorageDriver。

  • getAll(context: StorageContext): Promise<Record<string, any>>:获取指定上下文中的所有数据。
  • get(context: StorageContext, key: string): Promise<T | null>:获取指定上下文中指定键的数据。
  • set(context: StorageContext, key: string, value: T): Promise:设置指定上下文中指定键的数据。
  • removeAll(context: StorageContext): Promise:移除指定上下文中的所有数据。
  • remove(context: StorageContext, key: string): Promise:移除指定上下文中指定键的数据。
  • onChange(callback: (event: StorageDriverChangeEvent) => void): Function:注册监听数据变化的回调函数。
  • onRemove(callback: (event: StorageDriverRemoveEvent) => void): Function:注册监听数据移除的回调函数。

# StorageDriverChangeEvent 接口

interface StorageDriverChangeEvent<T> {
    public context: StorageContext;
    public key: string;
    public oldValue: T;
    public newValue: T;
}

StorageDriverChangeEvent 接口表示数据变化的事件对象。它包含了上下文、键、旧值和新值等信息, 在数据变化发生后, 我们的回调函数可以接收到此对象。

# StorageDriverRemoveEvent 接口

interface StorageDriverRemoveEvent {
    public context: StorageContext;
    public key: string;
}

StorageDriverRemoveEvent 类表示数据移除的事件。它包含了上下文和键等信息, 在数据删除事件发生后, 我们的回调函数可以接收到此对象。

# 自定义 StorageDriver

要自定义 StorageDriver,您需要创建一个实现 StorageDriver 接口的子类,并实现其中的方法。

下面是一个基于 sessionStorage (opens new window) 实现的自定义 StorageDriver 的示例:

class MyStorageDriver extends StorageDriver {
    // 使用第三方库: https://www.npmjs.com/package/eventemitter3 实现事件分发
    private readonly eventEmitter = new EventEmitter();
    // 根据上下文信息生成一个存储空间名称,主要作用是对数据进行分区存储
    private getSpace(context: StorageContext): string {
        const instanceId = context.id;
        return [instanceId, context.feature].join('.');
    }
    async getAll(context: StorageContext): Promise<Record<string, any>> {
        const space = this.getSpace(context);
        const keys = this.getSpaceKeys(context);
        return keys.reduce((result, completeKey) => {
            const key = completeKey.slice(space.length + 1);
            const rawData = sessionStorage.getItem(completeKey);
            result[key] = rawData ? JSON.parse(rawData) : null;
            return result;
        }, {});
    }
    async get<T>(context: StorageContext, key: string): Promise<T | null> {
        const storageKey = this.generateUniqueKey(context, key);
        const valueStr = sessionStorage.getItem(storageKey);
        if(valueStr) {
            return JSON.parse(valueStr) as T;
        } else {
            return null as T;
        }
    }
    async set<T>(context: StorageContext, key: string, value: T): Promise<void> {
        const storageKey = this.generateUniqueKey(context, key);
        const oldValueJSON = sessionStorage.getItem(storageKey);
        const oldValue = oldValueJSON ? JSON.parse(oldValueJSON) : undefined;
        const newValue = JSON.stringify(value);
        if(oldValueJSON === newValue) {
            return;
        }
        sessionStorage.setItem(storageKey, newValue);
        this.emitChangeEvent({
            context,
            key,
            oldValue,
            newValue: value
        });
    }
    async removeAll(context: StorageContext): Promise<void> {
        const keys = this.getSpaceKeys(context);
        keys.forEach(key => {
            const oldValue = sessionStorage.getItem(key);
            sessionStorage.removeItem(key);
            const newValue = sessionStorage.getItem(key);
            if(newValue !== oldValue) {
                this.emitRemoveEvent({
                    context,
                    key
                } as StorageDriverRemoveEvent);
            }
        });
    }
    async remove(context: StorageContext, key: string): Promise<void> {
        const storageKey = this.generateUniqueKey(context, key);
        const oldValue = sessionStorage.getItem(storageKey);
        sessionStorage.removeItem(storageKey);
        const newValue = sessionStorage.getItem(storageKey);
        if(newValue !== oldValue) {
            this.emitRemoveEvent({
                context, key
            });
        }
    }
    onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => void): () => void {
        return this.addEventListener<T>('change', callback);
    }
    onRemove(callback: (event: StorageDriverRemoveEvent) => void): () => void {
        return this.addEventListener('remove', callback);
    }
    
    private addEventListener<T>(event: 'change' | 'remove', callback: (event: any) => void) {
        const listener = e => {
            callback(e);
        };
        this.eventEmitter.addListener(event, listener);
        return () => {
            this.eventEmitter.removeListener(event, listener);
        };
    }
    // 根据 namespace 和 key 生成一个唯一的索引值 
    private generateUniqueKey(context: StorageContext, key: string) {
        const space = this.getSpace(context);
        return [space, key].join('.');
    }
    // 获取命名空间下所有存储的键值对
    private getSpaceKeys(context: StorageContext): string[] {
        const space = this.getSpace(context);
        const prefix = space + '.';
        return Array(sessionStorage.length)
        .fill(0)
        .map((_, index) => sessionStorage.key(index))
        .filter(it => !!it)
        .filter(it => it!.indexOf(prefix) === 0) as string[];
    }
    // 数据变更时,触发数据变更事件
    private emitChangeEvent<T>(event: StorageDriverChangeEvent<T>) {
        this.eventEmitter.emit('change', event);
    }
    // 数据被删除时,触发数据删除事件
    private emitRemoveEvent<T>(event: StorageDriverRemoveEvent) {
        this.eventEmitter.emit('remove', event);
    }
}

您可以根据实际需求,实现自定义的 StorageDriver。在实现过程中,可以使用异步操作、数据库查询等技术来实现数据的存储和管理。

# 使用自定义 StorageDriver

要使用自定义的 StorageDriver,您需要创建一个 PDFViewer 实例,并将自定义的 StorageDriver 作为参数传递给构造函数。

下面是一个使用自定义 StorageDriver 的示例:

const storageDriver = new MyStorageDriver();

const pdfViewer = new PDFViewer({
    instanceId: 'pdf-viewer-1',
    customs: {
        storageDriver: storageDriver
    }
});

在创建 PDFViewer 实例时,将自定义的 StorageDriver 传递给 customs 属性。PDFViewer 将使用该 StorageDriver 实例来存储和管理数据。