babel转换class时使用defineProperty导致的装饰器问题

脑子一片混乱,搞了大半天虽然把问题解决了,但总高兴不起来,工程化能力太弱,每次都遇到各种奇葩的问题,让我都快要从入门到跑路了,说不定是从入门到入土(前段时间看到有人这样说,很好玩,抄袭一下,哈哈)。

话不多说,先说结论,故事慢慢再讲。

  1. react-scripts中的webpack配置在打包时,使用的是@babel/plugin-transform-class,并不会用typescript进行编译
  2. @babel/plugin-transform-class对类中字段的转码使用的是defineProperty,大部分情况下没有问题,但是在跟类属性的装饰器一起使用时导致装饰器失效,并不会报错:(

解决办法,在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哈。至此问题就解决了。也让我对前端又害怕了一分,工程化这种东西太浪费精力了,而且解决之后也没有太大的成就感,琐碎的东西还挺多,有些时候现成的工具只能去巴拉源码,苦呀,还是自己能力不够啊!

如果对你有帮助,请帮忙点赞哦,嘻嘻:)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值