前言
经过几个月后,Vue3 慢慢成长逐渐成熟,今天进入 RC
阶段,意味着核心 Api
稳定下来了。查看周边的生态也慢慢丰富了,所以抽出点时间查阅相关资料。当我阅读 V3 文档 的时候发现很多写法和 V2
差不多,过渡没有多大问题,当然在脚手架开发中不好说了。所以简单弄个脚手架项目。
最近整合 Vue3
项目可以参考:https://github.com/JaxsonWang/Vue3-Ant-Design-Admin-Pro
新建项目
新建 vue3
目前有俩种,分别是 vite
和 @vue/cli
,vite
下面有描述,这边依然采用 @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
项目,里面只包含最基础的 Babel
和 Vue3
的项目,如果你需要支持路由状态管理器和样式处理器需要自己额外安装。下面将踩坑体验下其中的过程。
Router、Vuex 和 SASS 支持
查看下项目发现和 vue2
改动不是很大,除了一些细节上的要点,剩下和v2
没什么区别了,下面就直接按照 Router
、Vuex
和 SASS
来支持:
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
写法需要暴露 props
在 render
使用,然后在父组件只需要 <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>