在项目中需要用vant组件库的ImagePreview组件遇到报错,导致组件无法正常使用。

按照vant的文档使用ImagePreview时,浏览器抛出了下面的异常错误:
错误提示

通过浏览器断点调试,发现是ImagePreview组件中的ImagePreviewItem组件在渲染Image组件时报错了,如下图:
错误提示

从报错中看到this.onLoad方法是undefined,从图中还可以看到已经定义过onLoad方法了,那为什么onLoad方法消失了呢?

探索原因

uniapp官方文档中写着页面的生命周期中包含onLoad方法,怀疑是与此方法冲突导致,但是为什么会影响到组件methods中方法还未知原因。

然后我再一个普通的页面的methods中也写了一个onLoad方法,发现他竟然直接被当做生命周期执行了,绑定在DOM上的onLoad也变成了undefiend。现在有理由证明是uniapp对页面或组件的vue实例进行操作。

经过翻阅uniapp源码发现,他为了支持自己框架的一些life cycle hooks,对页面或组件的vue实例进行了mixin操作,可以看到,当页面或组件的methods包含了与框架生命周期同名的函数,将会被直接绑定实例外层,然后从methods中删除。
错误提示
经过debug,也完全证实了上面的结论。

解决问题

既然已经发现了问题的原因,那要如何解决问题呢?uniappvant都是第三方库,去给他们提issue让两边的团队去协商解决吧,这显然是不可能的。或者放弃使用uniapp或者vant?这个主意不错,但是并不能解决根本问题,当你发现其他vant组件或其他者第三方组件也有冲突时,要怎么办呢。

我想到代码都是要通过babel转义的,那我是不是能通过babel-plugin 来解决呢?后来发现,项目中的babel-plugin只能对本地代码的AST语法树进行操作,并不能解析到第三方库的代码,因为他们都是通过import导入进来的,babel-plugin只能解析到importAST

仔细一想,解析第三方库的话,用babel-loader就好了。于是我便写了一个替换方法名的loader,并在vue.config.js中添加了对vant库的loader

// fixConflictLoader.js
const mixedNameReg = /\b(onLoad|onError)\b/g;
module.exports = function (source) {
let _source = source;
if (mixedNameReg.test(_source)) {
_source = _source.replace(mixedNameReg, function ($0) {
return $0 + "InVant";
});
return _source;
} else {
return source;
}
};

// vue.config.js
const path = require("path");
const fixConflictLoader = path.join(__dirname, "./fixConflictLoader");
module.exports = {
chainWebpack: config => {
config.module
.rule("compile")
.test(/\.js$/)
.include.add(resolve("node_modules/vant/es")) // vant 的按需引入使用的是此项目包
.end()
.use(fixConflictLoader)
.loader(fixConflictLoader);
}
}

这个方法非常的暴力,直接解析vant的源码,然后批量替换其中与uniapp生命周期冲突的方法名,实际也解决了我所遇到的问题。

如果你有更好的方法,欢迎与我探讨交流。