本篇基于 Vue 3.0.7, vite 2.1.2 编写,由于 Vue 与 vite 改动较大,以最新版本为准,本文仅供参考。
写本篇文章主要是为了记录在正式使用 Vue 3 + vite 2 投入开发中遇到的一些问题,不适合没有任何 Vue 开发经验的同学阅读。本文中将会运用到 Vue 3 的 Composition api,vue-router@next。
首先,我的项目是基于 vite 2 架基的,同时使用了 PostCSS + Tailwind 2 CSS。UI Framework 使用了国外的 PrimeVue。初始化的过程不再讲述了。
首先是,Vue Router 的使用。和 Vue 2 的 Router 并没有什么比较大的区别。
不同的是,Vue Router 3 使用 VueRouter 的默认导出来创建一个实例,而 Vue Router 4 使用 createRouter
来创建实例。与 Vue 一致,Vue Router 也摒弃了 class 的写法,全面转向函数式编程(Functional Programming)。(注:Vue 2 使用 Vue Router 3, Vue 3 使用 Vue Router 4)
tsx
1// Vue router 3
2import VueRouter from 'vue-router'
3const router = new VueRouter({
4 routes,
5})
6// Vue router 4
7import {
8 createRouter,
9 createWebHashHistory,
10} from 'vue-router'
11const router = createRouter({
12 history: createWebHashHistory(),
13 routes,
14})
COPY
Vue Router 支持给每个路由(Route)添加一个 Meta 对象,存储在 Meta 对象中的值会在当前 Router 实例中取得。同时,Vue 3 原生支持了 JSX(大概只是比上代好一点点???),为此,我们也可以像 React 那样操作。
比如,我使用 Routes 来构建一个侧边菜单,当然为了简单,侧边菜单至多只有两层。
tsx
1interface MenuModel {
2 title: string
3 path: string
4 icon: any
5 subItems?: Array<MenuModel>
6 hasParent: boolean
7 fullPath: string
8 query?: any
9}
COPY
以上是我的菜单需要的结构,而如何将 icon 存储下来,在 render 函数直接使用呢。如果是 React,我们可以这样写。
1icon: JSX.Element
COPY
然后直接使用 {menu.icon}
就行了。在 Vue3 中,如果使用 JSX,同样可以这样操作。在 Routes 中稍加修改。在 Meta 中,直接传入一个 JSX.Element
。
tsx
1import dashboardFilled from '@iconify-icons/ant-design/dashboard-filled'
2import { InlineIcon as Icon } from '@iconify/vue'
3const routeForMenu: Array<RouteRecordNormalized> = [
4 {
5 path: '/dashboard',
6 component: DashBoardView,
7 name: 'dashboard',
8 meta: { title: '仪表盘', icon: <Icon icon={dashboardFilled} /> },
9 },
10 // ...
11]
COPY
同样的在 Sidebar.tsx 中。
tsx
1export const Sidebar = defineComponent({
2 setup() {
3 return () => <div>
4 // ....
5 {menu.icon}
6 </div>
7 }
8})
COPY
当然这种用法只限于 JSX/TSX。使用 Vue 的模板的话,就会渲染一个 VNode 对象了。
Vue Template
因为 JSX.Element 只是一个 Object,在 Vue 模板中只会判断 components 注册了没有,而不会关心这个 Object 是不是 VNode。而 JSX 中则会去判断是 VNode 则 render。
如果想在 Vue 模板中使用外部的 JSX,那么就需要去 components 注册一下就行了。
tsx
1// icon.tsx
2export const Icon = <FontAwesomeIcon icon={faAlignJustify} />
COPY
vue
1<template>
2 <Icon />
3</template>
4
5<script lang="ts">
6import { Icon } from './icon'
7import { defineComponent } from 'vue'
8
9export default defineComponent({
10 components: { Icon },
11
12})
13</script>
COPY
上面的是没注册的,下面的是注册的
在 Vue 2 中,data 中的属性以 _
和 $
打头的会被忽略,从而无法使用响应式流。在 Vue 3 中,data 还是和 Vue 2 一样无法使用,在 setup 函数中亦如此。但是官网文档没写不让用。
tsx
1<template>
2 <div class="w-10 m-auto">
3
4 <p>
5 <p>{{ $$a ?? 0}}</p>
6 <Button @click="$$a++">$$a++</Button>
7 </p>
8
9 <p>
10 <p>{{ a }}</p>
11 <Button @click="a++">a++</Button>
12 </p>
13 </div>
14</template>
15
16<script lang="ts">
17import { defineComponent, ref } from 'vue'
18import Button from 'primevue/button'
19export default defineComponent({
20 components: { Button },
21 setup() {
22 const $$a = ref(0)
23 const $a = ref(0)
24 const a = ref(0)
25 return {
26 $$a,
27 a,
28 }
29 },
30})
31</script>
COPY
但在 TSX 中,你完全可以这样写。
tsx
1export const PlaceHolderView = defineComponent({
2 setup() {
3 const router = useRouter()
4
5 const $$a = ref(0)
6 return () => (
7 <p>
8 {$$a.value}
9 <p>
10 <Button onClick={() => $$a.value++}>$$a++</Button>
11 </p>
12 </p>
13 )
14 },
15})
COPY
在 Vue 中有个 v-slot 的东西,在 React 中则有个 Children,当然 Vue 的 slots 做的比 React 多得多。而在 React 中除了传递 Children,还可以通过 props 传递 React.reactElement。React 更加灵活。
在 Vue 3 TSX 写法中,v-slots.default
等于 React 的 children。
tsx
1const Children = defineComponent({
2 setup() {
3 return () => <p>Children</p>
4 },
5})
6
7const Parent = defineComponent({
8 setup({}, { slots }) {
9 return () => (
10 <div class="">
11 <p>Parent</p>
12 {slots.default?.()}
13 </div>
14 )
15 },
16})
17
18const RootView = defineComponent({
19 setup() {
20 return () => (
21 <p>
22 <Parent>
23 <Children></Children>
24 </Parent>
25 </p>
26 )
27 },
28})
COPY
slots 位于 setup 的第二个参数中,获取当前组件所有的 slots,并且是一个函数,需要 call 一下。
tsx
1<Parent v-slots={{default: () => <Children />}}></Parent>
COPY
也可以用上面的方式传入。
v-slots 对 TSX 的方式不太友好,建议还是使用 React 的方式编写。通过传递 Props 来渲染子组件。
在 Vue 模板中,我们会用 @
去监听一个事件。在 React 的 JSX 中用 on 前缀来监听一个事件,如果是自定义事件,一般会定义一个新的函数。而在 Vue 中使用 emit
函数去发起一个事件。但是在 TSX 如何去监听呢。答案也是 on,你甚至可以不用手写一个函数。
tsx
1const Parent = defineComponent({
2 setup({}, { slots, emit }) {
3 onMounted(() => {
4 emit('mount', 1)
5 })
6 return () => (
7 null
8 )
9 },
10})
11
12const RootView = defineComponent({
13 setup({}, ctx) {
14 return () => (
15 <Parent
16 onMount={(val) => {
17 console.log('mount', val)
18 }}
19 >
20 </Parent>
21 )
22 },
23})
COPY
显然,onMount 这个 Props 是不存在的,我们也没有定义,但是在 Parent 中 emit 的事件为 mount
。就得到了这个 Props。这个过程是发生在编译阶段的,所以在不同的架手架行为可能不同。甚至说随时可能 breaking change,对 ts 的支持也很不友好,充满了红线。所以不建议使用。
以上就是近几天在开发过程中遇到的全部问题了,但是肯定远远不止这些。那么就先告一段落了。
Reference: