# # 自定义 StorageDriver

本节将介绍如何自定义 StorageDriver，以及相关的 API 信息和示例代码。

## # 概述

StorageDriver 是一个用于存储和管理数据的接口，主要关注数据的存储、删除和管理分区方式。通过自定义 StorageDriver，您可以实现数据的读取、写入、删除等操作，并监控数据的变化。

## # API Reference

### # `StorageContext` 类

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

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

### # `PDFViewerStorageContext` 类

    class PDFViewerStorageContext extends StorageContext {
        public pdfViewer: PDFViewer;
    }

`PDFViewerStorageContext` 类继承自 `StorageContext` 类，表示 `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` 实例，用于存储和管理数据。为了实现自定义存储功能，应用层可以通过 `custom.storageDriver` 将自定义的 `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`。

- `getAll(context: StorageContext): Promise<Record<string, any>>`: 获取指定上下文中的所有数据。
- `get<T>(context: StorageContext, key: string): Promise<T | null>`: 获取指定上下文中指定键的数据。
- `set<T>(context: StorageContext, key: string, value: T): Promise<void>`: 设置指定上下文中指定键的数据。
- `removeAll(context: StorageContext): Promise<void>`: 移除指定上下文中的所有数据。
- `remove(context: StorageContext, key: string): Promise<void>`: 移除指定上下文中指定键的数据。
- `onChange<T>(callback: (event: StorageDriverChangeEvent<T>) => 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` 实例来存储和管理数据。