前言
经过几个月后,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>