本节开始,我们将从最核心基础的文件系统进行设计实现,构建文件系统Store
一个基础的响应式Store类
从Vue3
开始,Vue响应式借助Proxy
重构后,整个响应式系统的应用变得非常的灵活,虽然目前业界依旧有 Pinia
等响应式管理库,但其本质依旧是在借助Vue的响应式API进行设计实现;所以很多时候其实并非需要第三方的响应式管理库,如果只是简单的响应式处理直接借助 Vue 的响应式API即可。
关于Vue3 的响应式实现细节,大家可以参考网上很多的Vue原理解析文章,或者大家有兴趣可以在下方留言,后面也可以出一期从0手写mini-vue3 源码的专栏
这里我们便借助 Vue 的响应式API封装一个具有响应式State管理的基类
import { reactive, UnwrapNestedRefs } from 'vue'
export abstract class Store<T extends Record<string, unknown>> {
state: UnwrapNestedRefs<T>;
constructor(state: T) {
this.state = reactive(state);
}
}
设计文件系统类接口
web端的编辑器虽然可以借助 File System Access API
对本地文件进行操作,但是因其兼容性问题,目前Web编辑器的文件还是存在内存中的,将文件以树型结构在内存中进行存储维护,所以首先我们先明确文件系统的主要作用是: 管理内存中的文件树
; 这里的管理主要包括以下部分:
- 创建文件/文件夹
- 删除文件/文件夹
- 查找文件/文件夹
- 移动文件/文件夹
- 文件/文件夹重命名
- 文件写入
首先,我们需要先设计出文件在内存中进行管理的数据结构定义
// 定义文件的类型
export const enum FileType {
File,
Directory,
}
// 文件的结构定义
export interface IFileSystemItem {
filename: string; /* 文件名 */
type: FileType.File; /* 文件类型 */
code: string; /* 文件的代码 */
ext: string; /* 文件的扩展名 */
fullPath: string; /* 文件的完整路径 */
status: number; /* 文件当前的状态: 用于记录当前对文件的操作 */
cacheBuffer: string | null /** 文件内容缓冲区 */;
language?: string; /* 代码语言 */
readonly?: boolean; /* 文件是否只读 */
visible?: boolean; /* 文件是否在文件树中显示 */
}
// 目录的结构定义
export interface IDirectoryItem extends Record<string, unknown> {
filename: string; /* 文件名 */
type: FileType.Directory; /* 文件类型 */
fullPath: string; /* 文件完整路径 */
status: number; /* 文件夹当前状态 */
readonly?: boolean; /* 文件夹是否只读 */
visible?: boolean; /* 文件夹是否在文件树中可见 */
children: (IFileSystemItem | IDirectoryItem)[]; /* 文件夹下的子文件 */
}
定义好文件系统在内存中的存储结构后,我们还需要定义文件系统的响应式 State
中的数据结构
export type FileSystemState = {
/* 文件树 - 单根文件夹 */
files: IDirectoryItem;
/* 文件Map映射,方便快速的根据文件路径查找文件 */
fileMap: Map<string, IFileSystemItem | IDirectoryItem>;
};
对于文件的操作,我们将通过文件的 status
字段进行记录,比如文件编辑、文件重命名等;那如何通过一个字段来记录多种状态呢? 这里我们采用二进制位Mask来处理,首先我们先定义几个基础的文件操作:
export const enum FileOperation {
Editing = 0b00000001,
Rename = 0b00000010,
Delete = 0b00000100,
Move = 0b00001000,
Create = 0b00010000,
}
关于如何通过二进制位Mask来记录不同的状态,我们将在下篇文章中具体实现文件系统进行介绍
完成以上步骤之后,现在我们可以开始定义文件系统的接口描述了,文件系统Store
是一个继承自响应式基类的文件管理子类,处理响应式文件State
数据之外,还有相关的文件操作逻辑:
export interface FileSystemProvider {
/* 响应式的文件数据 */
state: FileSystemState;
/**
* 创建文件
* @param path 文件路径 uri
* @param content 文件内容
* @param readonly 是否只读
* @param visible 是否可见
*/
createFile(
path: Uri,
content: string,
readonly?: boolean,
visible?: boolean
): IFileSystemItem;
/**
* 创建目录
* @param path 目录 Uri
* @param readonly 是否只读
* @param visible 是否可见
*/
createDirectory(
path: Uri,
readonly?: boolean,
visible?: boolean
): IDirectoryItem;
/**
* 读取文件内容
* @param path 文件路径 uri
*/
readFile(path: Uri | string): IFileSystemItem | IDirectoryItem | null;
/**
* 写入文件数据
* @param path 文件路径 uri
* @param content 文件内容
* @returns 文件变更状态数据
*/
writeFile(
path: Uri | string,
content: string,
isBuffer?: boolean
): ChangeFileState | null;
/**
* 删除文件或目录
* @param file 文件对象
* @returns 文件变更状态数据
*/
delete(file: IFileSystemItem | IDirectoryItem): ChangeFileState | null;
/**
* 文件重命名
* @param file 文件对象
* @param newName 文件名
* @returns 文件变更状态数据
*/
renameFile(file: IFileSystemItem, newName: string): ChangeFileState;
/**
* 文件夹重命名
* @param folder 文件对象
* @param newName 文件名
* @returns 文件变更状态数据
*/
renameFolder(folder: IDirectoryItem, newName: string): ChangeFileState[];
/**
* 移动文件
* @param sourcePath 源路径
* @param targetPath 目标路径
* @returns 文件变更状态数据数组
*/
moveFile: (
sourcePath: Uri | string,
targetPath: Uri | string
) => ChangeFileState[] | null;
/**
* 为文件对象添加操作
* @param file 文件对象
* @param operator 操作
*/
addOperator: (
file: IFileSystemItem | IDirectoryItem,
operator: FileOperation
) => void;
/**
* 为文件对象移除操作
* @param file 文件对象
* @param operator 操作
*/
removeOperator: (
file: IFileSystemItem | IDirectoryItem,
operator: FileOperation
) => void;
}
小结
这一章我们正式开始组件库的开发,组件的开发我认为最重要的不是组件如何渲染,而是数据如何维护和管理;
从这章开始我们介绍的是整个编辑器组件的核心:文件系统
; 我们将按照面向接口编程的方式,逐步设计和实现文件系统的管理和操作。这一章节我们通过TS类型定义设计实现了文件系统相关的接口定义,大家也可以根据目前文件系统的接口定义,尝试实现文件系统管理类。
如果大家在开发过程中有任何的问题欢迎下方留言评论,我将尽快为大家解答;加油!