Vue3 使用文档 Composition API 组合式 api【重点】
课程介绍
vue3.2没有新增什么东西:
vue2: 数据(变化比较大,用法,底层原理),指令(没变),mustache(没变),methods(语法有所变化), 生命周期(语法和种类都有发生变化),计算属性,watch(变化比较大), 组件的封装(通信的语法有所变化),路由(变化的router构建的方式,配置方式没有发生变化,路由守卫都没有变化), 状态管理(vuex、pinia)
vue3 特性
Vue.js 3.0 “One Piece” 2019年发布
新的响应式原理 ref reactive
diff 算法优化
- Vue 2 中的虚拟 Dom 是全量比较。Vue 3 新增静态标记(PatchFlag)。在与数据变化后,与上次虚拟 DOM 节点比较时,只比较带有 PatchFlag 标记的节点。并且可以从 flag 信息中得知具体需要比较的内容。
- 重写虚拟 DOM 的实现和 Tree-Shaking
组合式 api(composition api) setup 函数 【99%代码都在里面写】
新的计算属性 computed 新的侦听器 watch
hooks 语法 (当然是抄 react 的啦)
生命周期变化
vue2 v-for 优先级高于 v-if vue3 v-if 优先级高于 v-for
v-model的本质变化
- prop:value -> modelValue;
- event:input -> update:modelValue;
.sync修改符已移除, 由v-model代替
其他组件和 api 变化
main.js解读
vue2怎么项目,依赖于一个脚手架(@vue/cli),底层打包工具是webpack
vue3项目,使用vite创建(启动速度更快,配置更加的简单)
1 2 3 4 5 6 7 8 9 10 11 12 13
| import Vue from 'vue' import App from './App.vue' import router from '@/router' import store from '@/store'
Vue.config.productionTip = false
new Vue({ render: h => h(App), router, store }).$mount('#app')
|
vue2创建项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
|
vue3项目的main.ts
总结:
vue2和vue3编程的思维不一样,其配套的一些插件的编程思维也发生变化了。
vue2面向对象,我要一个什么东西, new一个。周边的生态(我要一个router,new一个router)
vue3函数式编程 我要一个东西,这个东西一定配套有一个函数,这个函数一执行就能返回这个东西。
vscode报红解决思路
准备工作
添加别名@
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| #node没有内置ts 需要下载node的ts配置 不然无法识别path模块
import {defineConfig} from 'vite' import vue from '@vitejs/plugin-vue' import {resolve} from 'path'
export default defineConfig({ plugins: [vue()], resolve: { alias: { '@': resolve(__dirname, 'src'), }, }, })
|
修改tsconfig.json 增加@的ts识别
1 2 3 4 5 6 7 8 9
| { "compilerOptions": { "paths": { "@/*": [ "./src/*" ], } },
|
vue3 router配置
创建路由文件
在src/router/index.ts。 该文件的功能如下:
- 配置路由
- 创建一个路由对象,然后将路由对象导出
- 在main.ts中将路由对象和vue实例联系起来
路由使用三步骤
- 建 【views 文件夹下面建立页面级别组件 如果是后台框架就在src文件夹下 建立layout文件夹】
- 配 【router/index.ts 里面配置页面级别组件和路由地址的一一对应关系】
- 测 【输入地址测试 要给出口】
router/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| #createRouter 创建路由工厂函数 #createWebHashHistory 选用历史模式 vue3 历史模式刷新不报错 #createWebHistory 或者选用hash模式 #RouteRecordRaw 路由配置的约束 import { createRouter, createWebHashHistory,RouteRecordRaw } from "vue-router";
#静态引入路由组件 import 静态组件 from '组件地址'
#配置路由地址和页面级别组件的一一对应关系 const routes:Array<RouteRecordRaw> = [ { path:'/路由地址', component:()=>import('组件地址') #懒加载路由组件 }, { path:'/路由地址', component:静态组件 }, # 404 修改了 /:pathMatch(.*)匹配所有路由 { path: '/:pathMatch(.*)', redirect: '/404' }, { path: '/404', component: () => import('@/views/not-found/index.vue'), hidden: true, } ]
#创建路由 const router = createRouter({ history: createWebHashHistory(), #hash模式 routes #导入路由配置 })
#导出配置好的路由 export default router
|
main.ts 导入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #导入 createApp 工厂函数 import { createApp } from 'vue' #导入App.vue import App from './App.vue'
import router from './router'
const app = createApp(App)
app.use(router)
app.mount('#app')
|
Composition API VS Option API
vue3这个版本,而且写法发生了巨大的变化。 肯定是vue2原有的写法有一些问题,退出vue3
vue2的写法
1 2 3 4 5 6 7 8 9 10 11 12
| export default { data: function() { return {} }, methods: { a: function() {} }, computed: {}, watch }
|
编码风格就做Option API(配置型api)
优点: 清晰,简单
缺点:this太多(this是动态), 因为this是动态的,所以只有在代码执行的时候才知道this指向哪一个,数据来源不太清晰,类型就不好推断,不适合和ts配置。 不太适合开发大型项目
配置项 Option API 数据,方法,计算属性等等 写在规定的配置项里面


- Composition API 组合式API
- 我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起



1 setup 函数 【重点】
- 新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次(理解为一个生命周期,比created更早,没有this)
- 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
1
| 在 `setup` 中你应该避免使用 `this`,因为它不会找到组件实例。`setup` 的调用发生在 `data` property、`computed` property 或 `methods` 被解析之前,所以它们无法在 `setup` 中被获取,这也是为了避免setup()和其他选项式API混淆。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| <template> {{msg}} <button @click='sayHi'>点击我</button> </template>
//基本用法 <script> export default { setup(){ #setup 函数里面执行的代码相当于 created生命周期
const msg = '小樱' const sayHi = () => { return 'hi' } #return的属性和方法 可以在模板里面直接用 return { msg, sayHi } } } </script>
#高级用法 推荐!!! <template> {{msg}} <button @click='sayHi'>点击我</button> </template>
#setup 表示script里面的代码全部都写在setup函数内部 而vue3代码全部可以在里面跑完 #定义在全局的 数据和方法 在模板中使用会自动解包 <script setup> #不需要return 定义好直接可以在模板里面使用 非常的nice! const msg = '小樱' const sayHi = () => { return 'hi' } </script>
|
2 ref shallowRef triggerRef 数据响应 【重点】
作用:
通常用来把一个基本类型变成响应式数据
- 把基本类型变成对象: { value: 值 }
- 重写了上一步对象下的value的getter/setter
- 改数据: 对象.value = 新值(触发setter,页面更新)
获取dom节点
获取组件实例
你也可以给ref传递一个对象也是可以的
1 2 3 4 5 6 7 8
| const user = ref({ name: 'jgmiu', age: 20 }) const changeUserName = () => { user.value.name = '张三' }
|
ref和ts配合使用:
定义一个ref变量(value是字符串)
1 2 3 4 5
| const name = ref('张三')
const name = ref<string>('张三')
|
定义一个ref变量(value是一个对象)
- 先定义一个接口或者type来描述对象
- 传给ref<定义好的接口或者type>
- 基本数据类型绑定
- 创建 RefImpl 响应式 Proxy 对象 ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
- 定义数据之后,模板里面直接使用值
- 修改数据 用.value 属性修改
- 响应式状态需要明确使用响应式 APIs 来创建。和从
setup() 函数中返回值一样,ref 值在模板中使用的时候会自动解包
- shallowRef 创建引用类型ref 不推荐直接使用ref(引用类型) 赋值得重新赋值
- triggerRef 如果通过shallowRef 定义引用类型数据要改变某个属性不会更新视图,必须调用triggerRef 收集数据依赖重新渲染视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <template> {{变量名}} <button @click='change'>点击我</button> </template>
<script setup lang='ts'> #引入ref 函数 Ref类型别名 import { ref,Ref,shallowRef } from 'vue'
#创建响应式数据 const 变量名:Ref<类型> = ref(初始值) //or const 变量名 = ref<类型>(值类型) #修改响应式数据 注意别修改原数据 const change = ()=> { 变量名.value = 修改之后的值 }
#如果是引用类型要定义ref 推荐shallowRef 只能做全赋值 不能去改深层次 const tableData = shallowRef([])
const changeTable = ()=>{ tableData.value = [1,2,3] }
#如果要单独修改深层次属性 需要配合triggerRef 让数据改变被监听修改视图 const formData = shallowRef({ name:'哈哈' })
const changeForm = ()=>{ formData.value.name = '嘿嘿' #triggerRef(shallowRef定义的值) triggerRef(formData) }
</script>
|
1 2 3 4 5 6 7 8 9 10 11 12
| <template> <标签 ref='变量名'></标签> </template>
<script setup> #引入ref 函数 import { ref } from 'vue'
#创建dom 绑定必须变量名相同 注意必须初始值为null 因为模板还未渲染 const 变量名 = ref<null | HTMLXXXElement>(null)
</script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| <template> <div> <h1>ref</h1> <p>{{ num1 }}</p> <p>{{ num2 }}</p> <button @click="num2++">num2++</button>
<hr /> <h3>ref绑定dom</h3> <input type="text" ref="iptRef" value="222" /> <hr /> <h3>shallowRef</h3> <p>{{ person }}</p> <button @click="changeName">修改name</button> </div> </template>
<script setup lang="ts"> import {ref, Ref, shallowRef, triggerRef} from 'vue' //方式1 引入Ref 进行约束 不推荐 const num1: Ref<number> = ref(10)
//方式2 函数泛型约束 推荐! const num2 = ref<number>(20)
//ref获取dom const iptRef = ref<null | HTMLInputElement>(null) setTimeout(() => { // ?. 是短路运算符缩写 iptRef.value && iptRef.value.value console.log(iptRef.value?.value) }, 1000)
interface Person { name: string age: number } //shallowRef const person = shallowRef<Person>() person.value = { name: '小橘猫', age: 11, } const changeName = () => { //重新赋值 // person.value = { // name: '大花猫', // age: 12, // }
//修改shallowRef某个属性并不会触发 响应式 person.value!.name = '大花猫' //triggerRef 重新触发响应式 triggerRef(person) console.log('person', person) } </script>
|
3 reactive shallowReactive readonly 数据响应 【重点】
作用: **引用类型的数据响应定义 **
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> {{state.属性1}} </template>
<script setup> #引入reactive 函数 import { reactive } from 'vue'
#创建响应式数据 const state = reactive({ 属性1:值1, })
#数组赋值 #方式1 push const tableData = reactive<Array<number>>([]) const getData1 = () => { //解构+push tableData.push(...[1, 2, 3, 4]) }
#方式2 把数组作为属性赋值 const state = reactive<{list: Array<number | undefined>}>({ list: [], }) const getData2 = () => { state.list = [2, 2, 3, 4] }
</script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <template> {{state.name}} {{state.k1.k2}} <button @click='change'>点击我</button> </template>
<script setup> #引入reactive 函数 import { reactive } from 'vue'
#创建响应式数据 const state = reactive<类型>({ name:'小樱', k1:{ k2:666 } })
#修改响应式数据 注意别修改原数据 const change = ()=>{ state.name = '小狼' state.k1.k2 = 999 } </script>
|
4 toRefs 【重点】
作用:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref 。
当从组合式函数返回响应式对象时, toRefs 非常有用,这样消费组件就可以在不丢失响应性的情况下对返回的对象进行解构/展开:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> #引入reactive toRefs 函数 import { reactive, toRefs } from 'vue'
#创建响应式数据
const state = reactive<类型>({ 属性1:值1, 属性2:值2 })
#数据解构出来,创建属性的ref,都通过value来获取值 const { 属性1, 属性2 } = toRefs(state) </script>
|
5 toRef unref toRaw
1 2 3 4 5 6 7 8 9 10 11 12 13
| #toRef const state = reactive({ 属性1:值1 })
const ref变量 = toRef(state, '属性1')
#unref 还原ref const msg = ref<string> = '你好啊' const unMsg = unref(msg)
#toRaw 还原reactive const stateRaw = toRaw(state)
|
6 计算属性
- computed函数:
- 与computed配置功能一致
- 只有getter
- 有getter和setter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| #简单用法 只有getter形式 const 计算属性变量 = computed<返回值类型>(() => { return 处理好的数据 });
#有getter 有 setter形式 const 计算属性变量 = computed<返回值类型>({ get() { return xx },
set(value) { }, });
|
7 侦听器 watch
watch函数
与watch配置功能一致
监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
通过配置deep为true, 来指定深度监视
watchEffect函数 【理解】
- 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
- 默认初始时就会执行第一次, 从而可以收集需要监视的数据
- 监视数据发生变化时回调
语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #简单用法 watch(ref 或者reactive 对象数据,(newV,oldV)=>{ },{ immediate:true, deep:true flush?: 'pre' | 'post' | 'sync' }) #观察多个数据变化 watch([数据1,数据2],()=>{ })
#注意:如果观察是reactive内部某个属性变化 需要箭头函数返回观察的属性 watch(()=>reactive对象.属性,()=>{ }) #watchEffect 会立即执行里面代码 监视所有回调中使用的数据 const stop = watchEffect((onCleanup)=>{ #形参onCleanup函数 观察变化之前执行回调函数 用于清除副作用 onCleanup(()=>{ }) #观察数据变化 执行里面副作用代码 })
#stop 停止观察的回调
|
8 生命周期
因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。
下表包含如何在 setup () 内部调用生命周期钩子:
| 选项式 API |
Hook inside setup |
beforeCreate |
Not needed* 不需要 |
created |
Not needed* 不需要 |
beforeMount |
onBeforeMount 挂载之前 |
mounted |
onMounted 页面加载完成时执行 ### |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted 页面销毁时执行 ### |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
activated |
onActivated |
deactivated |
onDeactivated |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <script setup > import { onMounted, onActivated, onUnmounted, onUpdated, onDeactivated } from 'vue';
onMounted(() => { console.log("组件挂载") })
onUnmounted(() => { console.log("组件卸载") })
onUpdated(() => { console.log("组件更新") }) onActivated(() => { console.log("keepAlive 组件 激活") })
onDeactivated(() => { console.log("keepAlive 组件 非激活") }) </script>
|
9 defineProps 和 defineEmits 父子传参
注意:defineProps 和 defineEmits 都是只在 <script setup> 中才能使用的编译器宏
1 2 3 4 5 6
| 为了声明 `props` 和 `emits` 选项且具备完整的类型推断,可以使用 `defineProps` 和 `defineEmits` API,它们在 `<script setup>` 中都是自动可用的:
- **`defineProps` 和 `defineEmits` 都是只在 `<script setup>` 中才能使用的****编译器宏**。他们不需要导入,且会在处理 `<script setup>` 的时候被编译处理掉。 - `defineProps` 接收与 `props` 选项相同的值,`defineEmits` 也接收 `emits` 选项相同的值。 - `defineProps` 和 `defineEmits` 在选项传入后,会提供恰当的类型推断。 - 传入到 `defineProps` 和 `defineEmits` 的选项会从 setup 中提升到模块的范围。因此,传入的选项不能引用在 setup 范围中声明的局部变量。这样做会引起编译错误。但是,它*可以*引用导入的绑定,因为它们也在模块范围内。
|
父传子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| #父组件 <template> <子组件 自定义属性1='静态' :自定义属性2='动态值'></子组件> </template>
#子组件 <template> {{自定义属性1}} {{自定义属性1}} </template> <script setup lang='ts'> #定义类型 or 使用接口 type Props = { 自定义属性1: 类型1 自定义属性2: 类型2 } #接受子组件传递的参数 const props = defineProps<Props>()
#or 定义默认值 const props = withDefaults(defineProps<Props>(), { 自定义属性1: '默认字符串', 自定义属性2: 10, 自定义属性3: () => [2, 2, 2], #数组 对象必须用箭头函数返回 自定义属性4: () => ({对象}), }) </script>
|
子传父
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| #子组件 <script setup> #定义子组件的 自定义事件 //定义事件 const $emits = defineEmits<{ (e: '自定义事件1',参数: 类型): void (e: '自定义事件2'): void }>()
#触发子传父事件 $emits('自定义事件1', 数据) </script>
#父组件 <template> <子组件 @自定义事件1="处理函数"></子组件> </template>
<script setup>
#子组件 触发自定义事件1 父组件的处理函数 收到子传父的数据 const 处理函数 = (data) => { } </script>
|
10 defineExpose 子组件暴露数据方法
使用 <script setup> 的组件是默认关闭的,也即通过模板 ref 或者 $parent 链获取到的组件的公开实例,不会暴露任何在 <script setup> 中声明的绑定。
为了在 <script setup> 组件中明确要暴露出去的属性,使用 defineExpose 编译器宏:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #子组件 <script setup> type MyExpose = { 属性1:类型1 方法1():void } #子组件暴露 defineExpose<MyExpose>({ 属性1:值1, 方法1() {} })
</script>
#父组件 <template> <子组件 ref='子组件ref'></子组件> </template>
<script setup> import 子组件 from './地址' import { onMounted, ref } from 'vue';
const 子组件ref = ref<InstanceType<typeof 子组件> | null>(null) #注意 在生命周期onMounted后 才能接受子组件暴露的数据和方法 onMounted(() => { 子组件ref.value.方法1(); 子组件ref.value.属性1 })
</script>
|
11 自定义hooks函数
- 使用Vue3的组合API封装的可复用的功能函数
- 用来取代vue2 mixin 目的也是抽离公共js逻辑
- 命名 userXxx开头的函数 一般会把一个功能封装在一个js中
例如 封装一个table宽度动态变化功能的hooks函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import { onMounted, onUnmounted, ref } from 'vue'
const useTableWidth = () => { const tableRef = ref(null)
const w = ref('')
const calcTableWidth = () => { w.value = document.body.clientWidth - 140 + 'px' }
onMounted(() => { calcTableWidth() window.addEventListener('resize', calcTableWidth) })
onUnmounted(() => { window.removeEventListener('resize', calcTableWidth) })
return { tableRef, w, } }
export default useTableWidth
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| <template> <!-- 表格 --> <el-table ref="tableRef" :style="{ width: w }" ></el-table> </template>
<script setup> import useTableWidth from '@/hooks/useTableWidth'; //计算table宽度
const { tableRef, w } = useTableWidth() </script>
|
12 其他新组件
Fragment(片断)
- 在Vue2中: 组件必须有一个根标签
- 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
1 2 3 4 5
| #其实 两个div被包在Fragment 虚拟元素中 感觉没有根元素 <template> <div></div> <div></div> </template>
|
Teleport(传送门)
- Teleport 提供了一种干净的方法, 让组件的html在父组件界面外的特定标签
- 例如:可以把一个组件里面的弹窗popUp 直接插进body标签里面
1 2 3 4 5 6
| #相当于 body.appendChild(弹窗) <teleport to="body"> <div class="pop"> 弹窗 </div> </teleport>
|
作业:
语法跟着文档,过两边
渲染商品列表(vue3.2 + ts)