前言
随着vue3与vue2.7的发布,composition-api
已经得到越来越多的关注,其重要性不言而喻。composition-api
带来了首要好处就是更好的逻辑复用,同时相较于optioins-api
,composition-api
对于ts的支持度也得到了很大的提升。尽管如此,vue在很多地方对ts的支持还是不像react那样丝滑。本文就来探讨下如何使用vue2+tsx开发组件,来看看如何在vue中使用ts以及template语法对应的jsx应该怎么写。
依赖
首先来看看需要哪些关键的依赖,如下:
"devDependencies": {
"@babel/plugin-transform-typescript": "^7.18.4",
"@vue/babel-helper-vue-jsx-merge-props": "^1.2.1",
"@vue/babel-preset-jsx": "^1.2.4",
"@vue/composition-api": "^1.6.3",
"typescript": "^4.7.4",
"vite": "^2.9.12",
"vite-plugin-vue2": "^2.0.1",
"vue": "2.6.14",
"vue-template-compiler": "^2.6.14"
}
配置
接下来是vite及babel的配置
// vite.config.ts
import { defineConfig } from 'vite'
import { createVuePlugin } from "vite-plugin-vue2";
import path from 'path'
const cwd = process.cwd()
const entryDir = path.resolve(cwd, './src')
const outDir = path.resolve(cwd, './lib')
const rollupOptions = {
external: [
'vue',
'@vue/composition-api'
]
}
export default defineConfig({
build: {
rollupOptions,
lib: {
entry: path.resolve(entryDir, 'index.tsx'),
fileName: (format) => `index.${format}.js`,
formats: ['es']
},
outDir
},
plugins: [
createVuePlugin({
jsx: true
})
],
css: {
preprocessorOptions: {
less: {
javascriptEnabled: true
}
}
}
})
// babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset',
['@vue/babel-preset-jsx', { compositionAPI: true }]
],
plugins: [
['@babel/plugin-transform-typescript', { isTSX: true }]
]
}
如何在vue中使用ts
1. Props类型推断
我们都知道,在vue中声明props的类型都是用Constructor
的(如String/Number), 而要使得ts能够根据给定的构造器推导出props的类型就需要用到defineComponent()
:
import { defineComponent } from '@vue/composition-api'
defineComponent({
props: {
name: String,
msg: { type: String, required: true }
},
mounted() {
this.name // (property) name?: string | undefined
this.msg // (property) msg: string
},
setup(props) {
props.msg // (property) name?: string | undefined
},
render() {
return (
<div></div>
)
}
})
那么对于比较复杂的对象或者对象数组类型该如何处理呢?请看下面代码:
import { defineComponent } from '@vue/composition-api'
import type { PropType } from '@vue/composition-api'
interface Person {
name: string;
age: number;
}
defineComponent({
props: {
persons: Array as PropType<Person[]>
},
mounted() {
this.persons // (property) persons?: Person[] | undefined
},
setup(props) {
props.persons // (property) persons?: Person[] | undefined
},
render() {
return (
<div></div>
)
}
})
但遗憾的是这个类型推断并不能在render函数中起作用。说到这里可能会有人会说,直接在setup函数中返回函数不就行了,为什么要写render函数呢?这就涉及到vue2 jsx的babel插件问题了,请看下面代码:
import { defineComponent, h } from '@vue/composition-api'
defineComponent({
name: 'Test',
setup() {
return () => <div></div>
}
})
这代码似乎没什么毛病,但是看编译后的代码就知道问题出在哪里:
import { defineComponent } from "@vue/composition-api";
defineComponent({
name: "Test",
setup() {
const h = this.$createElement;
return () => h("div");
}
});
可以看到,编译后的h
函数并不会取@vue/composition-api
中的,而是取了this.$createElement
,我们都知道,setup作用域下this指向的是undefined,所以这个表达式并不成立,因此实际运行时代码会报错,那么我们只能退而求其次——使用render函数了。
虽然render函数没法拿到props的类型推断,但是可以通过手动指定this类型来解决。
import { defineComponent } from "@vue/composition-api";
import Vue from 'vue'
defineComponent({
name: "Test",
props: {
name: String
},
render(this: Vue & { name: string }) {
// ...
}
});
2. 自定义事件
在vue的开发中肯定是少不了使用自定义事件,我们来看下使用ts的情况下,使用自定义事件需要注意什么:
import { defineComponent } from '@vue/composition-api'
const Child = defineComponent({
name: 'Child',
setup(props, { emit }) {
const handleClick = () => {
emit('customEvent')
}
return {
handleClick
}
},
render() {
return (
<div onClick={this.handleClick}>child</div>
)
}
})
defineComponent({
name: 'Parent',
setup() {},
render() {
return (
// Type '{ onCustomEvent: () => void; }' is not assignable to type 'Readonly<Partial<{}> & Omit<{} & {}, never>>'.
<Child onCustomEvent={() => {}}>parent</Child>
)
}
})
这代码看起来似乎没什么问题,但是ts却会抛出异常令人不知所措,其实解决方法也很简单,只需要在Child
组件的ComponentOptions
中增加emits
选项就行了,如下:
const Child = defineComponent({
name: 'Child',
emits: ['customEvent'],
setup(props, { emit }) {
const handleClick = () => {
emit('customEvent')
}
return {
handleClick
}
},
render() {
return (
<div onClick={this.handleClick}>child</div>
)
}
})
如何在vue中使用jsx
相信大部分仅使用vue的小伙伴对jsx相对是比较陌生的,那么我们就来讨论下在vue中使用jsx需要注意哪些。
1. jsx的优势
我们先来说说jsx的优势是什么
- 灵活。我们都知道使用templete时其作用域是组件本身。它的优点就在于不需要通过
this.xxx
的形式访问组件上声明的变量,但同时它也失去了灵活性,比如一些需要在templete中使用一些常量也必须注册到data()
中,比如没法直接在templete中调用console.log
等等。而jsx则没有这些问题,任意的变量、数据都能在其中使用,也不必全都注册都组件实例上。 - 支持度高。jsx得到了babel、ts的原生支持,不必在为它写额外的plugin去支持。也许不就的将来jsx会成为类似于html一样的标准。
2. 内置指令
- v-model: 使用手动绑定
value
与change
事件代替。当然,也是用插件可以实现在jsx中使用modalValue
来代替手动绑定,但我个人觉得没必要。 - v-show:添加动态class来切换display的值
- v-if:
condition && <div></div>
condition ? <div></div> : <span></span>
- v-for:
list.map(item => <div key={item}>{item}</div>)
- v-bind:
<div type={someValue}></div>
- v-html:
<div domPropsInnerHTML='<span></span>'></div>
- v-text:
<div domPropsInnerText='123456'></div>
- v-on:
<div onClick={handleClick}></div>
- v-slot:
<div scopedSlots={{ default: () => {}, other: () => {} }}></div>
3. 自定义指令
自定义指令也是vue的一大特色,他可以封装一些原生dom操作,给日常开发带来了极大的便利。下面我们来看下如何在jsx中使用自定义指令
defineComponent({
name: 'Child',
render() {
// 相当于 v-custom:foo.bar="() => 1 + 1"
const directives = [
{
name: 'custom',
value: () => 1 + 1,
arg: 'foo',
modifiers: { bar: true }
}
]
return (
<div {...{directives}}>child</div>
)
}
})
结语
以上就是本文的所有内容了。本文主要介绍了vue中使用ts及使用jsx的一些注意点,目前我个人遇到的就是这些了。本人三年多的react,刚转vue,有哪些说的不好的还望多多指教。