Vue3 体验

淮城一只猫 编程技术
阅读量 0 评论量 0

前言

经过几个月后,Vue3 慢慢成长逐渐成熟,今天进入 RC 阶段,意味着核心 Api 稳定下来了。查看周边的生态也慢慢丰富了,所以抽出点时间查阅相关资料。当我阅读 V3 文档 的时候发现很多写法和 V2 差不多,过渡没有多大问题,当然在脚手架开发中不好说了。所以简单弄个脚手架项目。

最近整合 Vue3 项目可以参考:https://github.com/JaxsonWang/Vue3-Ant-Design-Admin-Pro

新建项目

新建 vue3 目前有俩种,分别是 vite@vue/clivite 下面有描述,这边依然采用 @vue/cli 脚手架来新建项目。首先需要安装最新的 @vue/cli 来新建一个 vue3 项目:

npm install @vue/cli vue create vue3-demo # 输出如下 Vue CLI v4.5.6 ? Please pick a preset: (Use arrow keys) Default ([Vue 2] babel, eslint) > Default (Vue 3 Preview) ([Vue 3] babel, eslint) Manually select features 

选择第二个进行创建 Vue3 项目,里面只包含最基础的 BabelVue3 的项目,如果你需要支持路由状态管理器和样式处理器需要自己额外安装。下面将踩坑体验下其中的过程。

Router、Vuex 和 SASS 支持

查看下项目发现和 vue2 改动不是很大,除了一些细节上的要点,剩下和v2没什么区别了,下面就直接按照 RouterVuexSASS 来支持:

yarn add vue-router@next vuex@4.0.0-beta.4 yarn add sass sass-loader -D 

按照好了再进行相关配置就可以基本能正常开发一个 Vue3 项目了。

首先配置路由,在 src/router 文件夹下新建 index.js 写入:

import { createRouter, createWebHashHistory } from 'vue-router' const routes = [ { path: '/', name: 'Home', component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue') }, { path: '/about-vue', name: 'About-Vue', component: () => import(/* webpackChunkName: "about-vue" */ '../views/About.vue') } ] const router = createRouter({ history: createWebHashHistory(), routes: routes }) export default router 

这边新建路由实例和以前版本不一样,其他没啥区别了,细节上的差别可以查阅下文档。

至于 Vuex 和老版本完全一样,没有不一样的地方,这边就不详细解说了:

import { createStore } from 'vuex' import getters from './getters' import app from './modules/app' import user from './modules/user' const store = createStore({ getters, modules: { app, user } }) export default store 

学习源码:vue3-router-vuex

JSX 的支持

对于复杂的 UI 界面使用 template 去编写不合适了,例如类似递归渲染,比如菜单,使用 JSX 更合适,在这个项目里可以一部分用 template 编写也可以一部分使用 JSX 编写,可是相当舒服,Vue3 在社区采纳 JSX 很多方案,最终确定由 ant-design-vue 团队定制的 JSX 方案,大致看了下还不错,安装也相当简单:

yarn install @vue/babel-plugin-jsx -D 

babel.config.js 添加规则:

plugins: [ '@vue/babel-plugin-jsx' ] 

然后新建个 jsx 文件可以进行编写了,我这边编写一个 jsx 渲染的页面,定义一个路由:

const routes = [ { path: '/', name: 'Home', component: () => import(/* webpackChunkName: "home" */ '../views/Home.vue') }, { path: '/about-vue', name: 'About-Vue', component: () => import(/* webpackChunkName: "about-vue" */ '../views/About.vue') }, { path: '/about-jsx', name: 'About-JSX', component: () => import(/* webpackChunkName: "about-jsx" */ '../views/About.jsx') } ] 

About.jsx 文件如下:

import { mapGetters } from 'vuex' const styles = { about: { border: '1px solid #ffaaee' } } const About = { name: 'About', computed: { ...mapGetters([ 'systemName' ]) }, methods: { onInputChange(event) { this.$store.dispatch('app/setSystemName', event.target.value) } }, render() { return <> <div> <h3>这是 JSX 页面</h3> <label> <input type="text" placeholder="系统名称" style={styles.about} value={this.systemName} onInput={this.onInputChange}/> </label> </div> </> } } export default About 

后面被人请教 jsx 的组件事件调用,使用方法也相当简单,先编写个组件传递事件:

import { defineComponent } from 'vue' const HelloVue = defineComponent({ props: { msg: { require: false, type: String, default: '' } }, methods: { emitClick(event) { this.$emit('hello-vue', event) } }, render() { return <> <h3 onClick={this.emitClick}>{ this.msg }</h3> </> } }) export default HelloVue 

在父类接受事件回调:

import { mapGetters } from 'vuex' import HelloVue from '@/components/HelloVue.jsx' const styles = { about: { border: '1px solid #ffaaee' } } const About = { name: 'About', components: { HelloVue }, computed: { ...mapGetters([ 'systemName' ]) }, methods: { onInputChange(event) { this.$store.dispatch('app/setSystemName', event.target.value) }, helloVueAction(event) { alert('我被点击了!') console.log(event) } }, render() { return <> <div> <h3>这是 JSX 页面</h3> <hello-vue msg="这是一个被调用的 JSX 的组件,你可以点击试试看!" onHello-vue={this.helloVueAction}/> <label> <input type="text" placeholder="系统名称" style={styles.about} value={this.systemName} onInput={this.onInputChange}/> </label> </div> </> } } export default About 

大致就像这样一个效果,更多用法请参考文档:Babel Plugin JSX for Vue 3.0

学习源码:vue3-router-vuex-jsx

TypeScript支持

既然 JSX 都用上了,那么对 TypeScript 支持不能少,虽然所在的公司几乎用不上,不过多掌握一门技术对自己没有坏处。

vue create vue3-demo 选项选择最后一个然后添加 typescript 支持就行了,我是后面才发觉,不然上面那些安装方法直接省略...ts 的写法无法就是加上数据类型支持,不过我接触的比较少,所以用起来不是太熟练,typescript 愣生生被我用成 anyscript 了,当然一套下来感觉还是没 React 爽快。

学习源码:Vue3 + Vue Router + Vuex + TSX

参考文档


以下皆为历史记录

Vite

Vue-Cli 使用中,发现热更新和编译页面非常慢,所以作者放弃基于 Webpack 开发的脚手架,全新开发新的脚手架:Vite ,诸多新特征查阅相关文档,这边不做详述,但对于老版本的脚手架来比,上手几乎没有任何难点,参考的 Api 和老版本一致,新建新的 Vue3 项目很简单:

npm init vite-app hello-vue3 

就会生成比较简单的基础 Vue3 项目,运行 npm run dev 即可看到项目内容。

安装 TypeScript

下面把项目转换成 TSX 的项目,首先安装相关依赖:

yarn add typescript @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint eslint-plugin-vue -D 

配置 tsconfig.json 文件:

{ "include": [ "./**/*.ts" ], "compilerOptions": { "jsx": "react", "target": "es2020", "module": "commonjs", "sourceMap": true, "strict": true, "noUnusedLocals": true, "noImplicitReturns": true, "moduleResolution": "node", "esModuleInterop": true } } 

配置语法校验规则 .eslintrc.js 文件:

module.exports = { parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', ecmaVersion: 2020, sourceType: 'module', ecmaFeatures: { tsx: true, jsx: true, }, }, extends: [ 'plugin:vue/vue3-recommended', 'plugin:@typescript-eslint/recommended' ], rules: { } } 

src/ 目录下新建 typescript 类型推断优化配置:

source.d.ts 静态资源优化:

declare const React: string declare module '*.json' declare module '*.png' declare module '*.jpg' 

shim.d.ts

declare module '*.vue' { import Vue from 'vue'; export default Vue; } 

修改 index.html 文件入口:

<script type="module" src="/src/main.ts"></script> 

转换 TSX 项目

修改项目入口文件 main.ts :

import { createApp } from 'vue' import App from './App' import './index.css' createApp(App).mount('#app') 

然后这边报错,是因为 App 类型推断错误导致,所以继续修改 App.vue 文件,将文件转换成 App.tsx

import { defineComponent } from 'vue' export default defineComponent({ name: 'App', setup() { return () => <> <div class="container"> Hello World </div> </> } }) 

然后重新启动服务即可看到全新的 TSX 项目。

编写组件

本来在 components 文件夹下新建组件,无奈引入的时候一直报错,搜查资料也没有搜到相关资料,所以这边就在当前目录下随意折腾。编写 tsx 组件没有想象中那么复杂,比如新建个 Title.tsx 组件:

import { defineComponent } from 'vue' export default defineComponent({ name: 'Title', setup() { return () => <> <h1 class="title"> This is title. </h1> </> } }) 

然后在 App.tsx 引入调用即可:

import { defineComponent } from 'vue' import Title from './Title' export default defineComponent({ name: 'App', setup() { return () => <> <div class="container"> <Title/> Hello World </div> </> } }) 

如果组件开放 props 属性给父组件传入参数也很简单,只需要把 Title.tsx 修改成:

import { defineComponent } from 'vue'; export default defineComponent({ name: 'Title', props: { title: { type: String, require: false, default: 'This is title.' } }, setup(props) { return () => <> <h1 class="title"> { props.title } </h1> </> } }) 

组件的 props 声明和之前没有什么区别,在 tsx 写法需要暴露 propsrender 使用,然后在父组件只需要 <Title title="Hey!This my title!" /> 使用即可。

子组件事件传递

如果需要将子组件的事件传递到父组件也简单,只需要用到 setup 的第二个参数对象中的 emit 使用方式和 vue2 一样:

import { defineComponent } from 'vue'; export default defineComponent({ name: 'Title', props: { title: { type: String, require: false, default: 'This is title.' } }, setup(props, context) { return () => <> <h1 class="title" onClick={() => context.emit('data')}> { props.title } </h1> </> } }) 

但目前不知道在父组件如何调用...这边暂时就到这里吧。


在插件的支持下,传递事件如下:

import { defineComponent, PropType } from 'vue' export default defineComponent({ name: 'Title', props: { title: { type: String, require: false, default: 'This is title.' }, onTitleClick: Function as PropType<(event: MouseEvent) => void> }, setup (props, context) { return () => <> <h1 class="title" onClick={(event: MouseEvent) => context.emit('title-click', event)}> { props.title } </h1> </> } }) 

注意 props 要声明事件, 然后外部调用:

import { defineComponent } from 'vue' import Title from './components/HelloWorld' export default defineComponent({ name: 'App', setup () { const onTitleClick = (event: any) => { console.log(event) } return () => <> <div class="container"> <Title title="这是一个例子" onTitleClick={onTitleClick} /> Hello World </div> </> } }) 

下面是历史文章,可以无视~

前言

从去年开始就有 Vue3 各种消息,一直比较期待 V3 的版本,因为 V2 针对 TypeScript 不是太完善,支持不是太好,一直没用上。其次针对 React 来比, V2 又显然太死板,并且在大项项目构架上来看,复用性很差。所以在之前的 Vue3 PPT 讲到诸多特性,知道前几天的 Beta 版本出现,虽然 Alpha 版本尝鲜过,无奈问题太多。所以趁此机会折腾一波,前置学习下,趁机弯道超过各位大佬(#滑稽脸)。

建立

最近的 Beta 版本不需要建立 Webpack 项目,所以直接基于 Vue-CLI 脚手架生成的项目,直接注入相关插件就支持 Vue3 项目了。

使用 Vue Cli 建立项目:

vue create vue3-demo Vue CLI v4.3.1 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, CSS Pre-processors, Lin ter ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfi lls, transpiling JSX)? No ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) ? Pick a linter / formatter config: Standard ? Pick additional lint features: Lint on save, Lint and fix on commit ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated confi g files ? Save this as a preset for future projects? No vue add vue-next # 安装插件 

安装上去发现 package.json 添加几个依赖以及 App.vue 发生改变,但还需要进行进一步配置:

  • 删除 shims-tsx.d.ts 文件,控制台一直显示错误,新版本存在兼容性问题,等官方说明,删掉不影响使用;
  • 修改 App.vue 内容:
<template> <div class="home"> <img src="./assets/logo.png" alt="logo"> <h1>Hello Vue 3!</h1> <button @click="inc">Clicked {{ count }} times.</button> <div class="todo-area"> <todo-list/> </div> </div> </template>a <script lang="ts"> import { ref } from 'vue' import TodoList from '@/components/TodoList.vue' export default { setup () { const count = ref(0) const inc = () => { count.value++ } return { count, inc } }, components: { TodoList } } </script> <style lang="scss" scoped> .home { img { width: 200px; } h1 { color: #41b983; font-family: Arial, Helvetica, sans-serif; } } </style> 

@/components/TodoList.vue 随便填充点内容,启动服务 yarn dev 就可以看到新版本的页面了。

Router / Vuex 整合

未完待续

实战

打算利用新的 Api 做个简单的 Todo List 例子:

<template> <div class="todo-list"> <h3>Todo List</h3> <div class="add-todo-area"> <label> <input v-model="addTodoName"/> </label> <label> <button @click="addTodoAction">新增清单</button> </label> </div> <div class="todo-area"> <h3>任务清单</h3> <ul class="todo-list"> <li v-for="item in undoneTodoList" :key="item.id" class="todo-item" > {{ item.name }} <button @click="doneTodo(item)">已完成</button> <button @click="delTodoAction(item, true)">删除</button> </li> </ul> </div> <div class="done-todo-area"> <h3>已完成的任务清单</h3> <ul class="todo-list"> <li v-for="item in completedTodoList" :key="item.id" class="todo-item" > {{ item.name }} <button @click="delTodoAction(item, false)">删除</button> </li> </ul> </div> </div> </template> <script lang="ts"> import { ref, reactive } from 'vue' import { TodoList } from './types/TodoList' export default { setup () { const addTodoName = ref<string>('') const undoneTodoList: TodoList[] = reactive([]) // 清单列表 const completedTodoList: TodoList[] = reactive([]) // 已完成的清单列表 const addTodoAction = () => { const obj = { id: new Date().valueOf(), name: addTodoName } undoneTodoList.push(JSON.parse(JSON.stringify(obj))) addTodoName.value = '' } const delTodoAction = (item: TodoList, todo: boolean) => { if (todo) { undoneTodoList.splice(undoneTodoList.findIndex(i => i.id === item.id), 1) } else { completedTodoList.splice(completedTodoList.findIndex(i => i.id === item.id), 1) } } const doneTodo = (item: TodoList) => { undoneTodoList.splice(undoneTodoList.findIndex(i => i.id === item.id), 1) completedTodoList.push(item) } return { addTodoName, addTodoAction, delTodoAction, doneTodo, undoneTodoList, completedTodoList } } } </script> <style lang="scss" scoped> </style> 

参考

Vue Composition API RFC

Github vue-cli-plugin-vue-next

Github vue-next

Github vue-next-webpack-preview

喵~