前言
上一篇文章我们分析了Quill的剪切板模块源码
今天我们来看看它的另一个模块,主题,看看它们内部是怎么实现的。
正文
编辑器都会有对应的主题,Quill也有。
它内部有2种主题,对应的是snow和bubble。
snow主题的工具栏是常驻的,在编辑器顶部,而bubble主题的工具栏要选中文本才会出现。我们公司是使用的snow主题。
工具栏的功能比如标题,加粗,斜体,居中,颜色,背景色等等。
snow主题:
bubble主题:
以下分析都是基于quilljs的1.3.7版本
初始化
我们看看Quill是怎么初始化主题:
class Quill {
constructor(container, options = {}) {
this.options = expandConfig(container, options);
// ...省略部分代码...
this.theme = new this.options.theme(this, this.options);
this.keyboard = this.theme.addModule('keyboard');
this.clipboard = this.theme.addModule('clipboard');
this.history = this.theme.addModule('history');
this.theme.init();
}
}
expandConfig方法会扩展传入的options,比如如果你没有传任何模块,默认会给你带上剪切板模块(clipboard),键盘模块(keyboard),历史模块(前进,撤销,history)
function expandConfig (container, userConfig) {
userConfig = extend(true, {
container: container,
modules: {
clipboard: true,
keyboard: true,
history: true
}
}, userConfig);
...省略代码
}
同时还会引入(Quill.import)你传入的theme的class类(snow或bubblue),赋值给userConfig.theme。最后expandConfig方法会返回userConfig。
if (!userConfig.theme || userConfig.theme === Quill.DEFAULTS.theme) {
userConfig.theme = Theme;
} else {
userConfig.theme = Quill.import(`themes/${userConfig.theme}`);
if (userConfig.theme == null) {
throw new Error(`Invalid theme ${userConfig.theme}. Did you register it?`);
}
}
expandConfig方法扩展配置后,就开始初始化,我们用的是主题是snow,对应的是SnowTheme,也就是this.options.theme,赋值给this.theme。
如果你的参数没有传工具栏(toolbar)的相关配置,初始化的会使用默认配置,也就是TOOLBAR_CONFIG。比如标题支持1,2,3,默认,还有加粗,下划线等等。
const TOOLBAR_CONFIG = [
[{ header: ['1', '2', '3', false] }],
['bold', 'italic', 'underline', 'link'],
[{ list: 'ordered' }, { list: 'bullet' }],
['clean']
];
class SnowTheme extends BaseTheme {
constructor (quill, options) {
if (options.modules.toolbar != null && options.modules.toolbar.container == null) {
options.modules.toolbar.container = TOOLBAR_CONFIG;
}
super(quill, options);
this.quill.container.classList.add('ql-snow');
}
// ...省略部分代码
}
可以看到SnowTheme是继承BaseTheme,然后BaseThememe也是继承Theme,也会调用super方法,执行父类的constructor函数,
class BaseTheme extends Theme {
constructor (quill, options) {
super(quill, options);
...省略
}
addModule (name) {
let module = super.addModule(name);
if (name === 'toolbar') {
this.extendToolbar(module);
}
return module;
}
}
class Theme {
constructor (quill, options) {
this.quill = quill;
this.options = options;
this.modules = {};
}
init () {
Object.keys(this.options.modules).forEach((name) => {
if (this.modules[name] == null) {
this.addModule(name);
}
});
}
addModule (name) {
let moduleClass = this.quill.constructor.import(`modules/${name}`);
this.modules[name] = new moduleClass(this.quill, this.options.modules[name] || {});
return this.modules[name];
}
}
我们接着来看看最上面的初始化部分,实例化theme后,会调用addModule方法,依次添加keyboard,keyboard,history。
这个addModule方法不存在SnowTheme上,而是在BaseTheme上,调用的时候,接着会调用super.addModule,也就是Theme的addModule方法,addModule方法内部会把引入的模块实例化,并赋值给this.modules[name]。
接着可以看到初始化后三个模块后,又调用theme的init方法。
这个init方法是处于Theme模块下,里面的this是指向SnowTheme的实例。
因为之前keyboard,keyboard,history都执行了addModule,已经添加到this.modules(this指向SnowTheme的实例),所以遍历this.options.modules(keyboard,keyboard,history,toolbar),只有toolbar为null,然后它又调用BaseTheme的addModule方法,又会调用super的addModule方法,这时候才会实例化toolbar。
接着继续执行,会调用extendToolbar方法,extendToolbar方法是处于SnowBase类下面的,会扩展工具栏的相关逻辑。
直到现在,主题的实例才算完成。
这个过程有点绕,看了好久才稍微有点理解
有一点需要注意一下:对象的继承,继承的对象的this指向实例的对象,比如下面的代码是类A实例化,指向的就是类A.
class B {
b() {
console.log(this) // 打印 A {}
}
}
class A extends B {
a() {
}
}
new A().b()
总结
quill经过前面这么多年的发展,代码逻辑已经很完善了,通过多重继承类,类里面的调用逻辑就能看出来,但是确实不怎么好理解。
虽然不怎么好理解,但是读完又发现写的很妙。
这个过程就像是在跟作者交流,感受他们的思想,确实有益处。