前几天发了一篇《DevUI中VUE的TSX函数式组件实践》,主要讲如何使用tsx
,在Vue3中实现函数式组件的写法。
这里再追述一些内容。
使用TSX的原因
代码质量约束
使用ts
,可以提高开发侧代码的质量……
当然,我清楚这是通过强类型倒逼开发者
的方式实现,逼死的情况也可能存在,但也请看到正面的积极的作用:强化抽象思维,强化整体观,代码静态纠错,等等。
如果开发者能越过这道门槛,相信能跃迁到另一个层级。
编译期间自动生成d.ts
文件
假设你项目里装了typescript
包,运行下面的脚本:
npx tsc src/index.ts --outDir public -d
你会得到一个和src
结构一致的目录,如下图:
(无视截图的css文件哈)
这里,tsc
后面
- 第一个参数是入口文件
--outDir
是输出目录-d
表示生成声明文件
请注意:
tsc
是一个独立的、功能完整的ts
编译工具,它会根据入口文件和引用依赖,编译整个项目,且支持tsx
。 至于打包、压缩,是另一个事。
如果带着d.ts
文件发布为npm
包,那么在引用这个包的时候,VSCode会根据入口文件对应的d.ts
,递归出所有的输出类型。
很多早期发布的包,没有带
d.ts
,或者不是基于ts
开发的,都是通过@types/xxx
额外的npm
包来弥补,比如@types/node
。
其实最爽的,是在生成d.ts
文件时,不仅会提取输出模块的类型,而且会将JSDoc
格式的注释也提取出来。
同时,JSDoc
支持的markdown
写法,也就是说,注释提示可以带格式。
type TProps = {
/**
* ### 图片地址URL
*/
src?: string
/**
* ### 指定宽度
*/
width?: number
/**
* ### 指定高度
*/
height?: number
/**
* ### 填充模式
* - `stretch` 拉伸充满
* - `cover` 裁切充满,保持图片比例
* - `contain` 全部显示,留白,保持图片比例
*/
mode?: 'stretch' | 'cover' | 'contain'
}
就很专业了。
Vue文件的代码提示较弱
根据在DevUI
项目中对vue
使用经历,感觉.vue
文件,以及Vue本身的类型推断,有些局限,而且IDE支持也没有那么好。
比如上面的Image
组件的mode
属性,用vue
的话props
这么写:
export default defineComponent({
props: {
mode: { type: String }
}
})
因为vue
背后是做了类型推断的,如果说在js
范畴,这种props
写法也能够提示出属性的名称、划线属性类型错误,其实也非常nb了。但到了ts
范畴,就相对弱了很多。
用tsx
这么写
export default (props: { mode: 'stretch' | 'cover' | 'contain' }) => <img ...>
在vue的写法中,只能约束到string
这种大的类型,但用了ts
可以更准确到枚举值验证,而且IDE还能把枚举值列出来。
你说气不气人。
为什么是函数式组件
借鉴React成熟做法
在写React
的时候,如果写一个类组件,大概是这个样子
import React from 'react'
export class extends React.Component<{ mode?: string }> {
//...
}
能看到组件对react
包是有实际依赖的。
但函数式组件就不同了
import React from 'react'
export default (props: { mode?: string }) => <img mode={props.mode} />
这里对react
的引用,实际是一个历史遗留问题,本身函数组件对react并没有任何依赖。
但也正是这个遗留问题,造成babel-loader
在解析jsx
的时候,如果发现没有react
引用会报错。
上一篇提到的@vue/babel-plugin-jsx
插件就是解决这个问题的,这样,这个组件就可以很纯粹地变成:
export default (props: { mode?: string }) => <img mode={props.mode} />
IDE对函数式组件的良好支持
我们把这种函数式组件,可以抽象成一种类型
type TVueFuncComponent<TProps> = (props: TProps) => JSX.Element
重点来了:VSCode,对这种函数式组件类型,支持非常友好。
在现有的vue写法中,都是基于props
来做类型推测,也未见官方发布函数式组件的正规写法。
Vue函数式组件的实现方法
在上一篇文章中,已经列举了了两种,这里汇总成3种组件:
无状态组件
export default (props: { mode?: string }) => <img mode={props.mode} />
这个就不多说了,熟悉jsx
语法就好了。
有状态组件
高阶函数
import { reactive } from 'vue'
const factory = () => {
const state = reactive({ count: 0 })
return (props: TProps) => {
return <button onClick={() => state.count += 1}>
Click me {state.count} times
</button>
}
}
export default factory()
生命周期控制
高阶组件
import { onMounted, defineComponent } from 'vue'
const Comp = defineComponent({
props: {
mode: { type: String }
},
setup(props) {
return () => <img mode={props.mode} />
}
})
export default (props: TProps) => <Comp { ...props } />
经实际编译验证,由于这三种类型都可以输出成TVueFuncComponent<TProps>
类型,IDE按照函数式JSX组件解析d.ts
,可以完美实现代码提示。
jsx组件只能在jsx中引用时,才能有提示,在
.vue
文件中的template
里面无法提示,template不是jsx。
以上。