脑子一片混乱,搞了大半天虽然把问题解决了,但总高兴不起来,工程化能力太弱,每次都遇到各种奇葩的问题,让我都快要从入门到跑路了,说不定是从入门到入土(前段时间看到有人这样说,很好玩,抄袭一下,哈哈)。
话不多说,先说结论,故事慢慢再讲。
解决办法,在babel-loader处理之前添加ts-loader,先让typescript来转码。好了,开始讲故事了:)
背景是要把现有的一个项目改造成create-react-app创建的项目时遇到的问题。原来项目用的是rollup打包的。使用npx create-react-app project-name --template typescript创建一个空的项目,然后把代码拷贝过来。做了简单的修改,项目就这样跑起来了,我以为这样就结束了,可是我却不知道噩梦刚刚开始。
其实大部分功能都是正常的,唯独一个场景。最后分析下来是修改字段时并没有触发set,其背后的重绘逻辑也没有得到执行。经过提炼后的源码大概是这样的
class Student {
@dirty
name: string = '';
dirty() {
console.log('学生属性变化了,需要重绘哈');
}
}
function dirty(target, key) {
const backedKey = `__${key}__`;
Object.defineProperty(target, key, {
get() {
return this[backedKey];
},
set(value) {
if (this[backedKey] !== value) {
this[backedKey] = createProxy(value, () => {
this.dirty();
});
this.dirty();
}
},
enumerable: true
});
}
经过对比前后两个项目打包后的结果我发现,老的打包结果如下
function Student() {
...
this.name = '';
...
}
而新的打包结果是
function Student() {
Object.defineProperty(this, 'name', {});
}
那么问题就找到了。前者在new时,会执行this.name = ‘’, 这样就会触发set函数,以后再修改实例的name属性时也会触发set。但是后者因为在构造里使用defineProperty给实例添加了name属性,那么以后再修改实例的name属性也不会执行原型链上的set,因为实例本身是有name属性的。下面就看怎么解决了。
刚开始我是一头雾水,因为到此时其实我不知道react-scripts的项目打包时并不会用typescript进行转码的,因为我一直认为我创建项目时用了typescript的模板,而且我写代码也是按照typescript的预发写的,我就错误地认为也会使用typescript进行转码。后面是因为看了react-rescripts的源码中的webpack.config.js才知道。其实我感觉我这里说的不严谨,虽然webpack.config.js对于ts文件只用了babel-loader,但是应该不能说它没有用typescript转码,否则那么多ts语法是怎么转换的呢?
我的解决方案是在webpack.config.js中添加ts-loader。众所周知,不能直接修改webpack.config.js文件,因为它在node_modules中。那么要怎么做呢?这一点难不倒聪明的开发者,已经有人提供了rescripts用来修改webpack的配置。前段时间我出于好奇浏览了一下rescripts的源码,对它的原理有所了解,感兴趣的小伙伴可以移步这里
// 这是.rscriptsrc.js文件的内容
module.exports = {
webpack: config => {
const { loader, options } = config.module.rules[1].oneOf[3];
config.module.rules[1].oneOf[3].use = [{ laoder, options}, 'ts-loader'];
delete config.module.rules[1].oneOf[3].loader;
delete config.module.rules[1].oneOf[3].options;
return config;
}
}
不要忘记安装rescripts和ts-loader哈。至此问题就解决了。也让我对前端又害怕了一分,工程化这种东西太浪费精力了,而且解决之后也没有太大的成就感,琐碎的东西还挺多,有些时候现成的工具只能去巴拉源码,苦呀,还是自己能力不够啊!
如果对你有帮助,请帮忙点赞哦,嘻嘻:)