D32.Vue
F21.创建vue3项目(K124-K129)
该笔记是从vue2过渡到vue3的,所以不会特别详细的介绍某些vue2学过的,主要介绍vue3新增的
1.Vue3快速上手
A. Vue3简介
1)2020年9月18日,Vue.js发布3.0版本,代号:One Piece(海贼王)
2)耗时2年多、2600+次提交、30+个RFC、600+次PR、99位贡献者
B. Vue3带来了什么
1)性能的提升
a. 打包大小减少41%
b. 初次渲染快55%, 更新渲染快133%
c.内存减少54%
d. …
2)源码的升级
a. 使用Proxy代替defineProperty实现响应式
b. 重写虚拟DOM的实现和Tree-Shaking
c. …
3)拥抱TypeScript
a. Vue3可以更好的支持TypeScript
4)新的特性
a. Composition API(组合API)
- setup配置
- ref与reactive
- watch与watchEffect
- provide与inject
- ...
b. 新的内置组件
- Fragment
- Teleport
- Suspense
- ...
c. 其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除keyCode支持作为 v-on 的修饰符
- ...
2.创建Vue3.0工程
A.使用 vue-cli 创建
官方文档:https://cli.vuejs.org/zh/guide/creating-a-project.html#vue-create### 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上 vue --version ### 安装或者升级你的@vue/cli npm install -g @vue/cli ### 创建 vue create vue_test ### 启动 cd vue_test npm run serve
B.使用 vite 创建
1)官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite
a. 什么是vite?—— 新一代前端构建工具
b. 优势如下: - 开发环境中,无需打包操作,可快速的冷启动
- 轻量快速的热重载(HMR)
- 真正的按需编译,不再等待整个应用编译完成
### 创建工程 ## npm 6.x $ npm init vite@latest <project-name> --template vue ### 如: npm init vite@latest vue3-study --template vue ## npm 7+,需要加上额外的双短横线 $ npm init vite@latest <project-name> -- --template vue ### 使用 PNPM: pnpm create vite <project-name> -- --template vue ## pnpm create vite vite-app -- --template vue ### 进入工程目录 cd <project-name> ### 安装依赖 pnpm install ### 运行 pnpm run dev
vite里面导入vue模块要加后缀名,不然会报错
import Demo from ‘./components/Demo.vue’
C.命令行界面
1)在安装了 Vite 的项目中,可以在 npm scripts 中使用 vite 可执行文件,或者直接使用 npx vite 运行它。下面是通过脚手架创建的 Vite 项目中默认的 npm scripts:{ "scripts": { "dev": "vite", // 启动开发服务器,别名:`vite dev`,`vite serve` "build": "vite build", // 为生产环境构建产物 "preview": "vite preview" // 本地预览生产构建产物 } }
可以指定额外的命令行选项,如 --port 或 --https。运行 npx vite --help 获得完整的命令行选项列表
D.常用 Composition API
1)官方文档: https://v3.cn.vuejs.org/guide/composition-api-introduction.html
E.Composition API 的优势
1)Options API 存在的问题
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改
2)Composition API 的优势
可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起
F.全局API的转移
a. Vue 2.x 有许多全局 API 和配置
例如:注册全局组件、注册全局指令等//注册全局组件 Vue.component('MyButton', { data: () => ({ count: 0 }), template: '<button @click="count++">Clicked {{ count }} times.</button>' }) //注册全局指令 Vue.directive('focus', { inserted: el => el.focus() }
b. Vue3.0中对这些API做出了调整:
将全局的API,即:Vue.xxx调整到应用实例(app)上
G.其它改变
1)data选项应始终被声明为一个函数
2)过度类名的更改:
a. Vue2.x写法.v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
b. Vue3.x写法
.v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
3)移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes
4)移除v-on.native修饰符
a. 父组件中绑定事件<my-component v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" />
b. 子组件中声明自定义事件
<script> export default { emits: ['close'] } </script>
5)移除过滤器(filter)
过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器
6)…
H.路径别名
1)vite.config.tsimport { resolve } from 'path'; export default defineConfig({ resolve: { // 配置别名 alias: { '@': path.resolve(__dirname, './src'), '@c': path.resolve(__dirname, './src/components'), '@v': path.resolve(__dirname, './src/views'), '@h': path.resolve(__dirname, './src/hooks'), '@s': path.resolve(__dirname, './src/store'), }, }, });
1.path模块需要 pnpm i @types/node -D 2.tsconfig.node.json文件配置 "compilerOptions": { "allowSyntheticDefaultImports": true }, 3.tsconfig.json文件配置 "compilerOptions": { "baseUrl": "./", "paths": { "@/*": ["src/*"], "@h/*": ["src/hooks/*"], "@s/*": ["src/store/*"] }, }, 解决文件路径红线
I.导入ts文件
1)当在main.ts文件导入别名的时候报错导入路径不能以“.ts”扩展名结束。考虑改为导入“./utils/Bus.js”
2)解决方案
既然不允许用扩展名,那么我省略扩展名好了
vite.config.tsexport default defineConfig({ resolve: { alias: { '@': path.resolve(__dirname, 'src') }, extensions: ['.ts', '.js', '.mjs', '.jsx', '.tsx', '.json'] } })
这样,扩展名就不需要写了
J.使用Scss
1)首先用pnpm i sass -D
2)在style标签中使用lang='scss’就可以了
3)注意
如果像这样,定义了很多scss文件
直接引入是找不到这些文件里面定义的内容的
会有这样的报错
解决方法
在src目录中定义sytle.scss文件
style.scss
@import '@/styles/reset.scss'; @import '@/styles/variables.scss'; @import '@/styles/mixins.scss';
然后在vite.config.ts中配置
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ // 使用scss css: { preprocessorOptions: { scss: { additionalData: `@import "./src/style.scss";` }, }, }, })
F22.Vuex(K130-K136)
1.理解 Vuex
A.Vuex 是什么
1)概念:专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对Vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
B.什么时候使用 Vuex
1)多个组件依赖于同一状态
2)来自不同组件的行为需要变更同一状态
C.原理图
2.搭建Vuex环境
1)下载安装 pnpm add vuex@3
2)创建文件:src/store/index.jsimport Vue from 'vue' import Vuex from 'vuex' // 引入Vuex Vue.use(Vuex) // 应用Vuex插件 const actions = {} // 准备actions——用于响应组件中的动作 const mutations = {} // 准备mutations——用于操作数据(state) const state = {} // 准备state——用于存储数据 // 创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
3)在main.js中创建vm时传入store配置项
import Vue from 'vue' import App from './App.vue' import store from './store' // 引入store Vue.config.productionTip = false new Vue({ el: '#app', render: h => h(App), store, // 配置项添加store beforeCreate() { Vue.prototype.$bus = this } })
3.使用Vuex编写
1)初始化数据、配置actions、配置mutations,操作文件store.jsimport Vue from 'vue' import Vuex from 'vuex' // 引入Vuex Vue.use(Vuex) // 应用Vuex插件 // 准备actions——用于响应组件中的动作 const actions = { /* jia(context,value){ console.log('actions中的jia被调用了') context.commit('JIA',value) }, jian(context,value){ console.log('actions中的jian被调用了') context.commit('JIAN',value) }, */ jiaOdd(context,value){ // context 相当于精简版的 $store console.log('actions中的jiaOdd被调用了') if(context.state.sum % 2){ context.commit('JIA',value) } } } // 准备mutations——用于操作数据(state) const mutations = { JIA(state,value){ console.log('mutations中的JIA被调用了') state.sum += value }, JIAN(state,value){ console.log('mutations中的JIAN被调用了') state.sum -= value } } // 准备state——用于存储数据 const state = { sum:0 //当前的和 } // 创建并暴露store export default new Vuex.Store({ actions, mutations, state, })
2)组件中读取vuex中的数据:$store.state.sum
3)组件中修改vuex中的数据:$store.dispatch('action中的方法名',数据)
或$store.commit('mutations中的方法名',数据)
备注:若没有网络请求或其他业务逻辑,组件中也可以越过actions,即不写dispatch,直接编写commit
src/components/Count.vue<template> <div> <h1>当前求和为:{{ $store.state.sum }}</h1> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment">+</button> <button @click="decrement">-</button> <button @click="incrementOdd">当前求和为奇数再加</button> </div> </template> <script> export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, methods: { increment(){ // commit 是操作 mutations this.$store.commit('JIA',this.n) }, decrement(){ // commit 是操作 mutations this.$store.commit('JIAN',this.n) }, incrementOdd(){ // dispatch 是操作 actions this.$store.dispatch('jiaOdd',this.n) }, } } </script> <style lang="css">button{margin-left: 5px;}</style>
4.getters 配置项
1)概念:当state中的数据需要经过加工后再使用时,可以使用getters加工
2)在store.js中追加getters配置...... const getters = { bigSum(state){ return state.sum * 10 } } // 创建并暴露store export default new Vuex.Store({ ...... getters })
3)组件中读取数据:$store.getters.bigSum
5.四个map方法的使用
1)mapState方法:用于帮助我们映射state中的数据为计算属性computed: { //借助mapState生成计算属性:sum、school、subject(对象写法) ...mapState({sum:'sum',school:'school',subject:'subject'}), //借助mapState生成计算属性:sum、school、subject(数组写法) ...mapState(['sum','school','subject']), },
2)mapGetters方法:用于帮助我们映射getters中的数据为计算属性
computed: { //借助mapGetters生成计算属性:bigSum(对象写法) ...mapGetters({bigSum:'bigSum'}), //借助mapGetters生成计算属性:bigSum(数组写法) ...mapGetters(['bigSum']) },
3)mapActions方法:用于帮助我们生成与actions对话的方法,即:包含$store.dispatch(xxx)的函数
methods:{ //靠mapActions生成:incrementOdd、incrementWait(对象形式) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //靠mapActions生成:incrementOdd、incrementWait(数组形式) ...mapActions(['jiaOdd','jiaWait']) }
4)mapMutations方法:用于帮助我们生成与mutations对话的方法,即:包含$store.commit(xxx)的函数
methods:{ //靠mapActions生成:increment、decrement(对象形式) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //靠mapMutations生成:JIA、JIAN(对象形式) ...mapMutations(['JIA','JIAN']), }
备注:mapActions与mapMutations使用时,若需要传递参数需要:在模板中绑定事件时传递好参数,否则参数是事件对象
<template> <div> <h1>当前求和为:{{ sum }}</h1> <h3>当前求和的10倍为:{{ bigSum }}</h3> <h3>我是{{ name }},我在{{ school }}学习</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="addOdd(n)">当前求和为奇数再加</button> <button @click="addWait(n)">等一等再加</button> </div> </template> <script> import {mapState, mapGetters, mapMutations, mapActions} from 'vuex' export default { name: 'Count', data() { return { n:1, //用户选择的数字 } }, computed: { ...mapState(['sum','school','name']), ...mapGetters(['bigSum']) }, methods: { ...mapMutations({increment:'ADD', decrement:'SUBTRACT'}), ...mapActions(['addOdd', 'addWait']) }, } </script> <style> button{ margin-left: 5px; } </style>
6.模块化+命名空间
1)目的:让代码更好维护,让多种数据分类更加明确
2)修改store.js 为了解决不同模块命名冲突的问题,将不同模块的namespaced: true,之后在不同页面中引入getter、actions、mutations时,需要加上所属的模块名const countAbout = { namespaced:true,//开启命名空间 state:{x:1}, mutations: { ... }, actions: { ... }, getters: { bigSum(state){ return state.sum * 10 } } } const personAbout = { namespaced:true,//开启命名空间 state:{ ... }, mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { countAbout, personAbout } })
3)开启命名空间后,组件中读取state数据:
//方式一:自己直接读取 this.$store.state.personAbout.list //方式二:借助mapState读取: ...mapState('countAbout',['sum','school','subject']),
4)开启命名空间后,组件中读取getters数据:
//方式一:自己直接读取 this.$store.getters['personAbout/firstPersonName'] //方式二:借助mapGetters读取: ...mapGetters('countAbout',['bigSum'])
5)开启命名空间后,组件中调用dispatch
//方式一:自己直接dispatch this.$store.dispatch('personAbout/addPersonWang',person) //方式二:借助mapActions: ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
6)开启命名空间后,组件中调用commit
//方式一:自己直接commit this.$store.commit('personAbout/ADD_PERSON',person) //方式二:借助mapMutations: ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
F22.vue3自动导入配置(K137-K143)
1.自动导入组件库组件
1)unplugin-vue-components
2)安装
pnpm install unplugin-vue-components -D
然后将下面的代码添加到 Vite 的配置文件
3)Vite配置// vite.config.js import { defineConfig } from 'vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver, AntDesignVueResolver, VantResolver, HeadlessUiResolver, ElementUiResolver } from 'unplugin-vue-components/resolvers' export default defineConfig({ plugins: [ Components({ // ui库解析器,也可以自定义 resolvers: [ ElementPlusResolver(), AntDesignVueResolver(), VantResolver(), HeadlessUiResolver(), ElementUiResolver() ] }) ] })
4)插件会生成一个ui库组件以及指令路径components.d.ts文件,详情看这个vue3的
issue types(defineComponent): support for expose component types
// components.d.ts // generated by unplugin-vue-components // We suggest you to commit this file into source control // Read more: https://github.com/vuejs/vue-next/pull/3399 declare module 'vue' { export interface GlobalComponents { ElAside: typeof import('element-plus/es')['ElAside'] ElButton: typeof import('element-plus/es')['ElButton'] ElContainer: typeof import('element-plus/es')['ElContainer'] ElDropdown: typeof import('element-plus/es')['ElDropdown'] ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'] ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'] ElHeader: typeof import('element-plus/es')['ElHeader'] ElIcon: typeof import('element-plus/es')['ElIcon'] ElMain: typeof import('element-plus/es')['ElMain'] ElMenu: typeof import('element-plus/es')['ElMenu'] ElMenuItem: typeof import('element-plus/es')['ElMenuItem'] ElResult: typeof import('element-plus/es')['ElResult'] } } export { }
想了解其他的打包工具(Rollup, Vue CLI),请参考 unplugin-vue-components
5)自动导入自己的组件
直接写组件名即可,插件会帮你引入进来 注意别重名// vite.config.js import { defineConfig } from 'vite' import Components from 'unplugin-vue-components/vite' export default defineConfig({ plugins: [ Components({ // 指定组件位置,默认是src/components dirs: ['src/components', 'src/views'], // ui库解析器 // resolvers: [ElementPlusResolver()], extensions: ['vue'], // 配置文件生成位置 dts: 'src/components.d.ts' }) ] })
插件会生成一个自己组件路径的components.d.ts文件,详情再看这个vue3的
issue types(defineComponent): support for expose component types
// components.d.ts // generated by unplugin-vue-components // We suggest you to commit this file into source control // Read more: https://github.com/vuejs/vue-next/pull/3399 declare module 'vue' { export interface GlobalComponents { BaseFilter: typeof import('./components/Common/BaseFilter.vue')['default'] BaseHeader: typeof import('./components/Common/BaseHeader.vue')['default'] BasePagination: typeof import('./components/Common/BasePagination.vue')['default'] BaseSidebar: typeof import('./components/Common/BaseSidebar.vue')['default'] BaseTags: typeof import('./components/Common/BaseTags.vue')['default'] BaseTitle: typeof import('./components/Common/BaseTitle.vue')['default'] } } export { }
2.自动导入Vue 函数和自己的公用函数
A.自动导入Vue 函数
1)自动导入vue3的hooks,借助unplugin-auto-import/vite这个插件
支持vue, vue-router, vue-i18n, @vueuse/head, @vueuse/core等自动引入
2)安装pnpm i -D unplugin-auto-import
3)Vite配置
// vite.config.js import { defineConfig } from 'vite' import AutoImport from 'unplugin-auto-import/vite' export default defineConfig({ plugins: [ AutoImport({ // Auto import functions from Vue, e.g. ref, reactive, toRef... // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 imports: ['vue', 'vue-router', ,'vue-i18n', '@vueuse/head', '@vueuse/core',], // 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts' dts: 'src/auto-import.d.ts' }) ] })
4)原理: 安装的时候会自动生成auto-imports.d文件(默认是在根目录)
// Generated by 'unplugin-auto-import' // We suggest you to commit this file into source control declare global { const ref: typeof import('vue')['ref'] const reactive: typeof import('vue')['reactive'] const computed: typeof import('vue')['computed'] const createApp: typeof import('vue')['createApp'] const watch: typeof import('vue')['watch'] const customRef: typeof import('vue')['customRef'] const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] . . . } export {}
5)可以选择auto-import.d.ts生成的位置,使用ts建议设置为src/auto-import.d.ts
可以选择auto-import.d.ts生成的位置,使用ts建议设置为src/auto-import.d.ts
B.自动导入自己的公用函数
1)默认导入axios,分别导入pinia的storeToRefsAutoImport({ // Auto import functions from Vue, e.g. ref, reactive, toRef... // 自动导入 Vue 相关函数,如:ref, reactive, toRef 等 imports: [ 'vue', 'vue-router', { axios: [ // default imports ['default', 'axios'], // import { default as axios } from 'axios', ], pinia: ['storeToRefs'], // import { storeToRefs } from 'pinia' }, '@vueuse/core', ], }),
2)自动导入自己定义的函数
AutoImport({ // Auto import for module exports under directories // by default it only scan one level of modules under the directory dirs: ['./src/utils'], // 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts' dts: 'src/auto-import.d.ts', // 在vue模板中使用 vueTemplate: true, }),
观察auto-import.d.ts文件
export {} declare global { const axios: typeof import('axios')['default'] const bus: typeof import('./utils/bus')['bus'] const diffDays: typeof import('./utils/date')['diffDays'] const formatDate: typeof import('./utils/date')['formatDate'] const http: typeof import('./utils/service')['http'] const isDark: typeof import('./utils/dark')['isDark'] }
3.自动导入组件库样式
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'; plugins: [vue(), //按需导入element-plus的css样式 createStyleImportPlugin({ resolves: [ElementPlusResolve()], }), ], //查看 插件 API 获取 Vite 插件的更多细节 https://www.vitejs.net/guide/api-plugin.html
4.自动导入icon
1)安装依赖# @iconify-json/ep 是 Element Plus 的图标库 pnpm i -D unocss @iconify-json/ant-design
2)修改 vite.config.ts 配置
// vite.config.ts import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import Unocss from 'unocss/vite' import { presetAttributify, presetIcons, presetUno, transformerDirectives, transformerVariantGroup, } from 'unocss'; export default defineConfig({ plugins: [ vue(), // 添加以下配置 Unocss({ presets: [ presetUno(), presetAttributify(), presetIcons({ scale: 1.2, warn: true, }), ], transformers: [transformerDirectives(), transformerVariantGroup()], }) ] })
3)修改 main.ts
// 在 main.ts 里添加以下代码 import 'uno.css'
4)使用
// svg图片 <i class="i-ant-design-picture-filled w-330px h-240px" /> // 图标字体 <i i="ant-design-picture-filled" />