本文将介绍如何在Vue 3中使用tsx
首先回答两个问题:
Why Vue 3
在Vue 2中,组件相关的逻辑都被写在一个文件中,经常会出现一个组件几千行代码,非常难以维护;而且一个功能的相关代码通常会分散写在data
methods
created
等各个选项中,想要复用一个功能也是比较困难的。而Vue 3的组合式API正是为了解决这个问题。
Why tsx
Vue中的组件是一个黑盒,你不知道他的内部结构(属性和事件),只能通过文档甚至阅读源码来了解,这样是很耗费时间的。而在tsx中,在编写代码时就可以获得代码提示,组件的内部结构可以一目了然,且具有代码约束力(当你编写了错误的代码时会获得一个报错),这样是可以极大的提高代码编写效率,减少低级错误出现。
组件的类型声明
Props
ts会从vue组件的props
声明中推断属性的类型,以下是一些基本的类型推断:
- String → string
- Number → number
- Boolean → boolean
- Array → unknown[]
- Object → Record<string, any>
- Date → string
- Function → Function
- Symbol → symbol
你可以通过PropType
来进行更加精确的类型声明,建议对所有的Array
Object
Function
都进行声明
import { defineComponent, PropType } from 'vue'
export default defineComponent({
props: {
title: String,
values: {
type: Array as PropType<number[]>,
required: true,
},
data: Object as PropType<{ id: number, name: string }>,
onClick: Function as PropType<(id: number) => void>
}
})
属性默认是可选的,在上面例子中的title
默认会得到一个string | undefined
的类型。如果你声明了required: true
,他的类型就会变成string
。
有一些第三方组件可能会缺少类型声明,这会导致typescript类型检查错误***属性并不存在
,这种情况建议使用属性解构,避开typescript类型检查
// Property 'onClick' does not exist on type ...
<ElButton onClick={onClickBtn}></Elbutton>
// 使用属性解构
<ElButton {...{onClick: onClickBtn} as any}></Elbutton>
Events
Events实际上是function props
的语法糖,@click="myCallback"
等价于onClick={myCallback}
。强烈建议避免使用Event
,而使用与之等效的方法属性,这样你会获得更好的类型提示。
defineComponent({
emits: {
// event runtime validation
'play': (value: string) => {
return true
},
'rest-time': () => true
},
setup(props, {emit}) {
onMounted(() => {
emit('play', 'game')
emit('rest-time')
})
}
})
<MyComponent onPlay={...} onRest-time={...} />
等价于
defineComponent({
props: {
onPlay: Function as PropType<(value: string) => void>,
'onRest-time': Function as PropType<() => void>,
},
setup(props) {
onMounted(() => {
props.onPlay('game')
props['onRest-time']()
})
}
})
<MyComponent onPlay={...} onRest-time={...} />
TSX语法
你可以继续使用template模板,不过我们更建议你使用tsx模板。
<template>
<div>contents</div>
</template>
<script lang="tsx">
defineComponent({
setup(props) {
return {
// ...
}
}
})
</script>
或者
<script lang="tsx">
defineComponent({
setup(props) {
return () => (
<div>contents</div>
)
}
})
</script>
Attributes / Props
// template
<input type="email" :placeholder="placeholderText" />
// tsx
<input type="email" placeholder={placeholderText} />
Directives
v-model
// template
<input v-model="val" />
<input v-model:name="val">
<input v-model.trim="val">
<input v-model:name.trim="val">
// tsx
<input v-model={val} />
<input v-model={[val, 'name']} />
<input v-model={[val, ['trim']]} />
<input v-model={[val, 'name', ['trim']]} />
// bind ref value
<input v-model={valueRef.value} />
v-models
// template
<A v-model="foo" v-model:bar="bar" />
// tsx
<A v-models={[[foo], [bar, "bar"]]} />
slot
const A = (props, { slots }) => (
<>
<h1>{ slots.default ? slots.default() : 'foo' }</h1>
<h2>{ slots.bar && slots.bar() }</h2>
</>
);
const App = {
setup() {
const slots = {
default: () => <div>A</div>,
bar: () => <span>B</span>,
};
return () => <A v-slots={slots} />;
},
};
Hooks
Hooks是从React借鉴过来的概念,Vue 3中的Hooks叫做组合式API,它包括Vue 3提供的响应性api(ref
, reactive
, computed
, onMounted
...),以及自己编写的hook函数。
Hooks可以对混乱的代码做逻辑拆分,相似的功能可以方便的进行复用,通过Hooks的灵活组合,可以轻松处理大型复杂组件。
以下是Vue官网的例子,组件的功能被拆分到不同的Hooks中。
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const { searchQuery, repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories)
const { filters, updateFilters, filteredRepositories } = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
在React中,使用Hooks时需要遵守一些规则。不过Vue 3的渲染机制不同于React,在Vue 3中使用Hooks规则稍有不同:
1. 只在最顶层使用 Hook, 不要在循环,条件或嵌套函数中调用 Hook
由于Vue 3使用Proxy来追踪数据改变,Hooks调用的顺序不会影响数据的依赖关系,所以这条规则不必遵守。
2. 只在 React(Setup 和 Hook) 函数中调用 Hook,不要在普通的 JavaScript 函数中调用 Hook
这条规则仍然适用于Vue 3。Hooks可以用来保存状态,而普通函数不可以。当数据依赖改变时,函数会被重新执行,如果刚好函数中声明了一些状态,例如ref
对象,那么这个状态就会被重置,从而导致意外的情况发生。所以Hook只能在Setup函数以及其他Hook中调用。
// ❎ 不要在普通函数中调用hook(ref)
function renderInput() {
// 当input重新渲染时,inputValue会被重置
const inputValue = ref('')
return <input v-model={inputValue.value} />
}
// ✅ 在setup中调用hook(ref)
setup() {
const inputValue = ref('')
return (
{renderInput(inputValue)}
)
}
// 通过参数传递inputValue
function renderInput(inputValue) {
return <input v-model={inputValue.value} />
}
Problems
目前,Vue 3对ts的支持度还没有达到100%,所以仍存在一些问题,例如:
tsx代码无法热更新,需手动刷新(截至2021/4/30)
<script lang="tsx"> defineComponent({ setup(props) { return () => ( <div>the text here won't change with HMR</div> ) } }) </script>
函数形式的属性默认值会干扰typescript类型推断,需改为箭头函数(截至2021/4/30)
// ❎ 会干扰typescript类型推断 export default defineComponent({ props: { objectProp: { type: Object, default() { return {} } }, arrayProp: { type: Array, default() { return [] } } } }) // ✅ 使用箭头函数 export default defineComponent({ props: { objectProp: { type: Object, default: () => ({}) }, arrayProp: { type: Array, default: () => [] } } })
References
- Vue 3 typescript支持 (https://v3.cn.vuejs.org/guide/typescript-support.html)
- jsx-next repo (https://github.com/vuejs/jsx-next)
- Vue 3 组合式api (https://v3.cn.vuejs.org/guide/composition-api-introduction.html)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。