Vue3 学习
文档
文档说明
- 学习地址
- 快速上手
- vue 中文官网
- 代码测试
- Vue3 掘金
开始
创建
文档
使用 vue-cli 创建
1 2 3
| $ npm install - g @vue / cli # # 安装或者升级 $ vue--version # # 保证 vue cli 版本在 4.5 .0 以上 当前安装版本5 .0 .4 $ vue create my - project # # 创建项目
|
安装步骤
注意: 选项无法按箭头键选择时, 可以用数字键选择
- Use https://registry.npmmirror.com for faster installation? Yes 使用更快的 npm 镜像
- Target directory D:\桌面\github\vue-3.0 already exists. Pick an action: (Use
arrow keys) ** merge 文件已存在, 合并本地文件
- ? Please pick a preset: Default ([Vue 3] babel, eslint)**1 使用 vue3 配置项(一般选择自定义配置)
- ? Pick the package manager to use when installing dependencies: Yarn 安装
创建成功之后一般会自动安装依赖包
1 2
| $ cd vue - 3.0 $ yarn serve
|
使用 vite 创建
文档
1 2 3 4
| $ npm init vite - app < project - name > $ cd < project - name > $ npm install 或者 yarn $ npm run dev 或者 yarn dev
|
Composition API
setup
- 新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次
- 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
注意:
setup 细节问题
setup 是在 beforeCreate 生命周期回调之前就执行了, 而且就执行一次
- 由此可以推断出:setup 在执行的时候, 当前的组件还没有创建出来, 也就意味着: 组件实例对象 this 根本就不能用
2.this 是 undefined, 说明, 就不能通过 this 再去调用 data/computed/methods/props 中的相关内容了
- 其实所有的 composition API 相关回调函数中也都不可以
setup 中的返回值是一个对象, 内部的属性和方法是给 html 模版使用的
setup 中的对象内部的属性和 data 函数中的 return 对象的属性都可以在 html 模版中使用
setup 中的对象中的属性和 data 函数中的对象中的属性会合并为组件对象的属性
setup 中的对象中的方法和 methods 对象中的方法会合并为组件对象的方法
在 Vue3 中尽量不要混合的使用 data 和 setup 及 methods 和 setup
一般不要混合使用: methods 中可以访问 setup 提供的属性和方法, 但在 setup 方法中不能访问 data 和 methods
setup 不能是一个 async 函数: 因为返回值不再是 return 的对象, 而是 promise, 模板看不到 return 对象中的属性数据
setup 的参数
- setup(props, context) / setup(props, {attrs, slots, emit})
- props: 包含 props 配置声明且传入了的所有属性的对象
- attrs: 包含没有在 props 配置中声明的属性的对象, 相当于 this.$attrs
- slots: 包含所有传入的插槽内容的对象, 相当于 this.$slots
- emit: 用来分发自定义事件的函数, 相当于 this.$emit
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 57 58 59 60 61
| <template> <h2>Child子级组件</h2> <h3>msg:{{ msg }}</h3> <button @click="emitXxx">分发事件</button> </template> <script lang="ts"> import { defineComponent, ref } from "vue"; export default defineComponent({ name: "Child", props: ["msg"], emits: ["fn"], setup(props, { attrs, slots, emit }) { console.log("setup执行了", this);
const showMsg1 = () => { console.log("setup中的showMsg1方法"); }; function emitXxx() { emit("fn", "++"); } return { showMsg1, emitXxx }; } }); </script>
|
ref
- 作用: 定义一个数据的响应式
- 语法: const xxx = ref(initValue):
- 创建一个包含响应式数据的引用(reference)对象
- js 中操作数据: xxx.value
- 模板中操作数据: 不需要.value
- 一般用来定义一个基本类型的响应式数据
更新页面数量
1 2 3 4 5
| <template> <h2>setup和ref的基本使用</h2> <h3>{{ count }}</h3> <button @click="updateCount">更新数据</button> </template>
|
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
| < script lang = "ts" > import { defineComponent, ref } from "vue"; export default defineComponent({ name: "App", setup() { const count = ref(0); console.log(count); function updateCount() { console.log("====="); count.value++; } return { count, updateCount, }; }, }); < /script>
|
reactive
- 作用: 定义多个数据的响应式
- const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
- 响应式转换是“深层的”:会影响对象内部所有嵌套的属性
- 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
1 2 3 4 5 6 7 8 9
| <template> <h2>reactive的使用</h2> <h3>名字:{{ user.name }}</h3> <h3>年龄:{{ user.age }}</h3> <h3>性别:{{ user.gender }}</h3> <h3>媳妇:{{ user.wife }}</h3> <hr /> <button @click="updateUser">更新数据</button> </template>
|
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
| 定义多个数据的响应式 const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象 响应式转换是“ 深层的”: 会影响对象内部所有嵌套的属性 内部基于 ES6 的 Proxy 实现, 通过代理对象操作源对象内部数据都是响应式的 * / < script lang = "ts" > import { defineComponent, reactive } from "vue"; export default defineComponent({ name: "App", setup() { const obj = { name: "小明", age: 20, wife: { name: "小甜甜", age: 18, cars: ["奔驰", "宝马", "奥迪"], }, }; const user = reactive < any > (obj); console.log(user); const updateUser = () => { user.name = "小工"; user.wife.cars[3] = "奥拓"; }; return { updateUser, user, }; }, }); < /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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| <template> <h2>reactive的使用</h2> <h3>名字:{{ proxyUser.name }}</h3> <h3>年龄:{{ proxyUser.age }}</h3> <h3>性别:{{ proxyUser.gender }}</h3> <h3>媳妇:{{ proxyUser.wife }}</h3> <hr /> <button @click="updateUser">更新数据</button> </template> <script lang="ts"> import { defineComponent, reactive } from "vue"; export default defineComponent({ name: "App", setup() { interface User { name: string; age: number; wife: { name?: string; age: number; }; gender?: string; } const user: User = { name: "佐助", age: 20, wife: { name: "小樱", age: 19 } }; const proxyUser = new Proxy(user, { get(target, prop) { console.log("get方法调用了"); return Reflect.get(target, prop); }, set(target, prop, val) { console.log("set方法调用了"); return Reflect.set(target, prop, val); }, deleteProperty(target, prop) { console.log("delete方法调用了"); return Reflect.deleteProperty(target, prop); } }); const updateUser = () => { console.log(proxyUser.name); proxyUser.name = "鸣人"; console.log(proxyUser); proxyUser.gender = "男"; console.log(user); delete proxyUser.gender; proxyUser.wife.name = "雏田"; console.log(user); }; return { proxyUser, updateUser }; } }); </script>
|
reactive 与 ref-细节
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
| <template> <h2>reactive和ref的细节问题</h2> <h3>m1:{{ m1 }}</h3> <h3>m2:{{ m2 }}</h3> <h3>m3:{{ m3 }}</h3> <hr /> <button @click="update">更新数据</button> </template> <script lang="ts"> import { defineComponent, ref, reactive } from "vue"; export default defineComponent({ name: "App", setup() { const m1 = ref("abc"); const m2 = reactive({ name: "小明", wife: { name: "小红" } }); const m3 = ref({ name: "小明", wife: { name: "小红" } }); const update = () => { console.log(m3); m1.value += "==="; m2.wife.name += "==="; m3.value.wife.name += "==="; console.log(m3.value.wife); }; return { m1, m2, m3, update }; } }); </script>
|
watch 和 watchEffect
computed 函数:
- 与 computed 配置功能一致
- 只有 getter
- 有 getter 和 setter
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 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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
| <template> <h2>计算属性和监视</h2> <fieldset> <legend>姓名操作</legend> 姓氏:<input type="text" placeholder="请输入姓氏" v-model="user.firstName" /><br /> 名字:<input type="text" placeholder="请输入名字" v-model="user.lastName" /><br /> </fieldset> <fieldset> <legend>计算属性和监视的演示</legend> 姓名1:<input type="text" placeholder="显示姓名1" v-model="fullName1" /><br /> 姓名2:<input type="text" placeholder="显示姓名2" v-model="fullName2" /><br /> 姓名3:<input type="text" placeholder="显示姓名3" v-model="fullName3" /><br /> </fieldset> </template> <script lang="ts"> import { defineComponent, reactive, computed, watch, ref, watchEffect } from "vue"; export default defineComponent({ name: "App", setup() { const user = reactive({ firstName: "东方", lastName: "不败" });
const fullName1 = computed(() => { return user.firstName + "_" + user.lastName; }); const fullName2 = computed({ get() { return user.firstName + "_" + user.lastName; }, set(val: string) { const names = val.split("_"); user.firstName = names[0]; user.lastName = names[1]; } });
const fullName3 = ref(""); watch( user, ({ firstName, lastName }) => { fullName3.value = firstName + "_" + lastName; }, { immediate: true, deep: true } );
watchEffect(() => { const names = fullName3.value.split("_"); user.firstName = names[0]; user.lastName = names[1]; });
watch([() => user.firstName, () => user.lastName, fullName3], () => { console.log("===="); });
return { user, fullName1, fullName2, fullName3 }; } }); </script>
|
生命周期
生命周期 svg

生命周期
与 2.x 版本生命周期相对应的组合式 API
beforeCreate
-> 使用 setup()
created
-> 使用 setup()
beforeMount
-> onBeforeMount
mounted
-> onMounted
beforeUpdate
-> onBeforeUpdate
updated
-> onUpdated
beforeDestroy
-> onBeforeUnmount
destroyed
-> onUnmounted
errorCaptured
-> onErrorCaptured
新增的钩子函数
组合式 API 还提供了以下调试钩子函数:
- onRenderTracked
- onRenderTriggered
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| <template> <h2>Child子级组件</h2> <h4>msg:{{ msg }}</h4> <button @click="update">更新数据</button> </template> <script lang="ts"> import { defineComponent, ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from "vue"; export default defineComponent({ name: "Child", beforeCreate() { console.log("2.x中的beforeCreate..."); }, created() { console.log("2.x中的created..."); }, beforeMount() { console.log("2.x中的beforeMount..."); }, mounted() { console.log("2.x中的mounted..."); }, beforeUpdate() { console.log("2.x中的beforeUpdate..."); }, updated() { console.log("2.x中的updated..."); }, beforeUnmount() { console.log("2.x中的beforeUnmount..."); }, unmounted() { console.log("2.x中的unmounted..."); },
setup() { console.log("3.0中的setup"); const msg = ref("abc"); const update = () => { msg.value += "==="; }; onBeforeMount(() => { console.log("3.0中的onBeforeMount"); }); onMounted(() => { console.log("3.0中的onMounted"); }); onBeforeUpdate(() => { console.log("3.0中的onBeforeUpdate"); }); onUpdated(() => { console.log("3.0中的onUpdated"); }); onBeforeUnmount(() => { console.log("3.0中的onBeforeUnmount"); }); onUnmounted(() => { console.log("3.0中的onUnmounted"); }); return { msg, update }; } }); </script>
|
hooks
自定义 hook 函数
使用 Vue3 的组合 API 封装的可复用的功能函数
自定义 hook 的作用类似于 vue2 中的 mixin 技术
自定义 Hook 的优势: 很清楚复用功能代码的来源, 更清楚易懂
需求 1: 收集用户鼠标点击的页面坐标
收集用户鼠标点击的页面坐标
useMousePosition.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { onBeforeUnmount, onMounted, ref } from "vue"; export default function () { const x = ref(-1); const y = ref(-1);
const clickHandler = (event: MouseEvent) => { x.value = event.pageX; y.value = event.pageY; }; onMounted(() => { window.addEventListener("click", clickHandler); }); onBeforeUnmount(() => { window.removeEventListener("click", clickHandler); }); return { x, y }; }
|
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 57 58 59 60 61 62 63 64 65 66
| <template> <div> <h1>鼠标点击获取坐标</h1> <h2>x:{{ x }},y:{{ y }}</h2> <h2>请求接口数据</h2> <h3 v-if="loading">正在加载中....</h3> <h3 v-else-if="errorMsg">错误信息:{{ errorMsg }}</h3> <ul v-else> <li>id:{{ addressData.id }}</li> <li>address:{{ addressData.address }}</li> <li>distance:{{ addressData.distance }}</li> </ul> <hr /> <ul v-for="item in data" :key="item.id"> <li>id:{{ item.id }}</li> <li>title:{{ item.title }}</li> <li>price:{{ item.price }}</li> </ul> </div> </template> <script lang="ts"> import { defineComponent, watch } from "vue"; import useMousePosition from "./useMousePosition"; import useRequest from "./useRequest"; interface AddressData { id: number; address: string; distance: string; } interface ProductsData { id: string; title: string; price: number; } export default defineComponent({ setup() { const { x, y } = useMousePosition(); const { data: addressData } = useRequest<AddressData>("/data/address.json"); const { loading, data, errorMsg } = useRequest<ProductsData[]>( "/data/products.json" ); watch(data, () => { if (data.value) { console.log(data.value.length); } }); return { x, y, loading, data, addressData, errorMsg }; } }); </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
| import { ref } from "vue";
import axios from "axios";
export default function <T>(url: string) { const loading = ref(true); const data = (ref < T) | (null > null); const errorMsg = ref(""); axios .get(url) .then((response) => { loading.value = false; data.value = response.data; }) .catch((error) => { loading.value = false; errorMsg.value = error.message || "未知错误"; }); return { loading, data, errorMsg }; }
|
toRefs
- 把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref
- 应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用
- 问题: reactive 对象取出的所有属性值都是非响应式的
- 解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性
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> <h2>toRefs的使用</h2>
<h3>name:{{ name }}</h3> <h3>age:{{ age }}</h3>
<h3>name2:{{ name2 }}</h3> <h3>age2:{{ age2 }}</h3> </template> <script lang="ts"> import { defineComponent, reactive, toRefs } from "vue";
function useFeatureX() { const state = reactive({ name2: "自来也", age2: 47 }); return { ...toRefs(state) }; } export default defineComponent({ name: "App", setup() { const state = reactive({ name: "自来也", age: 47 }); const { name, age } = toRefs(state); setInterval(() => { name.value += "==="; console.log("======"); }, 1000);
const { name2, age2 } = useFeatureX(); return { name, age, name2, age2 }; } }); </script>
|
ref 获取元素
利用 ref 函数获取组件中的标签元素
功能需求: 让输入框自动获取焦点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <h2>ref的另一个作用:可以获取页面中的元素</h2> <input type="text" ref="inputRef" /> </template> <script lang="ts"> import { defineComponent, onMounted, ref } from "vue"; export default defineComponent({ name: "App", setup() { const inputRef = ref<HTMLElement | null>(null); onMounted(() => { inputRef.value && inputRef.value.focus(); }); return { inputRef }; } }); </script>
|
shallowReactive 与 shallowRef
shallowReactive 与 shallowRef
shallowReactive: 只处理了对象内最外层属性的响应式(也就是浅响应式)
shallowRef: 只处理了 value 的响应式, 不进行对象的 reactive 处理
总结:
reactive 与 ref 实现的是深度响应式, 而 shallowReactive 与 shallowRef 是浅响应式
什么时候用浅响应式呢?
一般情况下使用 ref 和 reactive 即可,
如果有一个对象数据, 结构比较深, 但变化时只是外层属性变化 ===> shallowReactive
如果有一个对象数据, 后面会产生新的对象来替换 ===> shallowRef
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 57
| <template> <h2>App</h2>
<h3>m1: {{m1}}</h3> <h3>m2: {{m2}}</h3> <h3>m3: {{m3}}</h3> <h3>m4: {{m4}}</h3>
<button @click="update">更新</button> </template>
<script lang="ts"> import { reactive, ref, shallowReactive, shallowRef } from "vue"; export default { setup() { const m1 = reactive({ a: 1, b: { c: 2 } }); const m2 = shallowReactive({ a: 1, b: { c: 2 } }); const m3 = ref({ a: 1, b: { c: 2 } }); const m4 = shallowRef({ a: 1, b: { c: 2 } }); const update = () => {
m4.value.a += 1; };
return { m1, m2, m3, m4, update }; } }; </script>
|
readonly 与 shallowReadonly
- readonly:
- 深度只读数据
- 获取一个对象 (响应式或纯对象) 或 ref 并返回原始代理的只读代理。
- 只读代理是深层的:访问的任何嵌套 property 也是只读的。
- shallowReadonly
- 浅只读数据
- 创建一个代理,使其自身的 property 为只读,但不执行嵌套对象的深度只读转换
- 应用场景:
- 在某些特定情况下, 我们可能不希望对数据进行更新的操作, 那就可以包装生成一个只读代理对象来读取数据, 而不能修改或删除
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
| <template> <h2>readonly和shallowReadonly</h2> <h3>state:{{ state2 }}</h3> <hr /> <button @click="update">更新数据</button> </template> <script lang="ts"> import { defineComponent, reactive, readonly, shallowReadonly } from "vue"; export default defineComponent({ name: "App", setup() { const state = reactive({ name: "佐助", age: 20, car: { name: "奔驰", color: "yellow" } }); const state2 = shallowReadonly(state); const update = () => { state2.car.name += "==="; }; return { state2, update }; } }); </script>
|
toRaw 与 markRaw
- toRaw
- 返回由
reactive
或 readonly
方法转换成响应式代理的普通对象。
- 这是一个还原方法,可用于临时读取,访问不会被代理/跟踪,写入时也不会触发界面更新。
- markRaw
- 标记一个对象,使其永远不会转换为代理。返回对象本身
- 应用场景:
- 有些值不应被设置为响应式的,例如复杂的第三方类实例或 Vue 组件对象。
- 当渲染具有不可变数据源的大列表时,跳过代理转换可以提高性能。
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
| <template> <h2>toRaw和markRaw</h2> <h3>state:{{ state }}</h3> <hr /> <button @click="testToRaw">测试toRaw</button> <button @click="testMarkRaw">测试markRaw</button> </template> <script lang="ts"> import { defineComponent, markRaw, reactive, toRaw } from "vue"; interface UserInfo { name: string; age: number; likes?: string[]; } export default defineComponent({ name: "App", setup() { const state = reactive<UserInfo>({ name: "小明", age: 20 });
const testToRaw = () => { const user = toRaw(state); user.name += "=="; console.log("哈哈,我好帅哦"); }; const testMarkRaw = () => { const likes = ["吃", "喝"]; state.likes = markRaw(likes); setInterval(() => { if (state.likes) { state.likes[0] += "="; console.log("定时器走起来"); } }, 1000); }; return { state, testToRaw, testMarkRaw }; } }); </script>
|
customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
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
| <template> <h2>CustomRef的使用</h2> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script lang="ts"> import { customRef, defineComponent, ref } from "vue"; function useDebouncedRef<T>(value: T, delay = 200) { let timeOutId: number | any; return customRef((track, trigger) => { return { get() { track(); return value; }, set(newValue: T) { clearTimeout(timeOutId); timeOutId = setTimeout(() => { value = newValue; trigger(); }, delay); } }; }); } export default defineComponent({ name: "App", setup() { const keyword = useDebouncedRef("abc", 500); return { keyword }; } }); </script>
|
toRef(子父组件通讯)
- 为源响应式对象上的某个属性创建一个 ref 对象, 二者内部操作的是同一个数据值, 更新时二者是同步的
- 区别 ref: 拷贝了一份新的数据值单独操作, 更新时相互不影响
- 应用: 当要将 某个 prop 的 ref 传递给复合函数时,toRef 很有用
Child 子级组件
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
| <template> <h2>Child子级组件</h2> <h3>age:{{ age }}</h3> <h3>length:{{ length }}</h3> </template> <script lang="ts"> import { defineComponent, computed, Ref, toRef } from "vue";
function useGetLength(age: Ref) { return computed(() => { return age.value.toString().length; }); } export default defineComponent({ name: "Child", props: { age: { type: Number, required: true } }, setup(props) { const length = useGetLength(toRef(props, "age")); return { length }; } }); </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
| <template> <h2>toRef的使用及特点:</h2> <h3>state:{{ state }}</h3> <h3>age:{{ age }}</h3> <h3>money:{{ money }}</h3> <hr /> <button @click="update">更新数据</button> <hr /> <Child :age="age" /> </template> <script lang="ts"> import { defineComponent, reactive, toRef, ref } from "vue"; import Child from "./child.vue"; export default defineComponent({ name: "App", components: { Child }, setup() { const state = reactive({ age: 5, money: 100 }); const age = toRef(state, "age"); const money = ref(state.money); const update = () => { state.age += 2; }; return { state, age, money, update }; } }); </script>
|
customRef
- 创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
- 需求: 使用 customRef 实现 debounce 的示例
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
| <template> <h2>CustomRef的使用</h2> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script lang="ts"> import { customRef, defineComponent, ref } from "vue"; function useDebouncedRef<T>(value: T, delay = 200) { let timeOutId: number | any; return customRef((track, trigger) => { return { get() { track(); return value; }, set(newValue: T) { clearTimeout(timeOutId); timeOutId = setTimeout(() => { value = newValue; trigger(); }, delay); } }; }); } export default defineComponent({ name: "App", setup() { const keyword = useDebouncedRef("abc", 500); return { keyword }; } }); </script>
|
provide 与 inject
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
| import { reactive, provide } from "vue"; export default { name: "parent", components: { childTest, }, setup() { let person = reactive({ name: "张三", age: 18, sex: "男", }); provide("person", person);
return { person, }; }, };
import { inject } from "vue"; export default { setup() { let person = inject("person"); return { person, }; }, };
|
响应式数据的判断
- isRef: 检查一个值是否为一个 ref 对象
- isReactive: 检查一个对象是否是由
reactive
创建的响应式代理
- isReadonly: 检查一个对象是否是由
readonly
创建的只读代理
- isProxy: 检查一个对象是否是由
reactive
或者 readonly
方法创建的代理
组件
Fragment(片断)
- 在 Vue2 中: 组件必须有一个根标签
- 在 Vue3 中: 组件可以没有根标签, 内部会将多个标签包含在一个 Fragment 虚拟元素中
- 好处: 减少标签层级, 减小内存占用
1 2 3 4
| <template> <h2>aaaa</h2> <h2>aaaa</h2> </template>
|
Teleport(瞬移)
- Teleport 提供了一种干净的方法, 让组件的 html 在父组件界面外的特定标签(很可能是 body)下插入显示
ModalButton.vue
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
| <template> <button @click="modalOpen = true"> Open full screen modal! (With teleport!) </button>
<teleport to="body"> <div v-if="modalOpen" class="modal"> <div> I'm a teleported modal! (My parent is "body") <button @click="modalOpen = false">Close</button> </div> </div> </teleport> </template>
<script> import { ref } from "vue"; export default { name: "modal-button", setup() { const modalOpen = ref(false); return { modalOpen }; } }; </script>
<style> .modal { position: absolute; top: 0; right: 0; bottom: 0; left: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; flex-direction: column; align-items: center; justify-content: center; }
.modal div { display: flex; flex-direction: column; align-items: center; justify-content: center; background-color: white; width: 300px; height: 300px; padding: 5px; } </style>
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <h2>App</h2> <modal-button></modal-button> </template>
<script lang="ts"> import ModalButton from "./ModalButton.vue";
export default { setup() { return {}; },
components: { ModalButton } }; </script>
|
Suspense(不确定的)
- 它们允许我们的应用程序在等待异步组件时渲染一些后备内容,可以让我们创建一个平滑的用户体验
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
| <template> <Suspense> <template v-slot:default> <AsyncComp /> </template>
<template v-slot:fallback> <h1>LOADING...</h1> </template> </Suspense> </template>
<script lang="ts">
import AsyncAddress from "./AsyncAddress.vue"; import { defineAsyncComponent } from "vue"; const AsyncComp = defineAsyncComponent(() => import("./AsyncComp.vue")); export default { setup() { return {}; },
components: { AsyncComp, AsyncAddress } }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <h2>AsyncComp22</h2> <p>{{ msg }}</p> </template>
<script lang="ts"> export default { name: "AsyncComp", setup() { return { msg: "abc" }; } }; </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <h2>{{ data }}</h2> </template>
<script lang="ts"> import axios from "axios"; export default { async setup() { const result = await axios.get("/data/address.json"); return { data: result.data }; } }; </script>
|
createVNode
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
| <template> <div ref="here"></div> </template>
<script lang="ts" setup> import { render, ref, createVNode, onMounted } from "vue"; const here = ref();
const div = createVNode("div", { class: ["className"], style: { width: "100px", height: "100px", border: "1px solid black" }, onClick: () => ({}) });
onMounted(() => { render(div, here.value); }); </script>
|
插槽
1 2 3 4 5 6 7 8 9 10
| <div v-slot:'插槽名'>我是插槽内容</div>
<template #插槽名> <slot name="插槽名"></slot> </template>
<slot name='插槽名'></slot>
|
$attrs
1 2
| <子组件 v-bind="$attrs"></子组件>
|
1 2 3 4
| export default { name: "父级组件名", inheritAttrs: true };
|
路由
步骤
- 安装
npm install vue-router@next --save
- 新建组件
在 components 文件下新建 vue 页面文件
- 在 src 目录下新建 router.ts 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { createRouter, createWebHashHistory } from "vue-router";
import Reactive from "./components/reactive.vue";
const router = createRouter({ history: createWebHashHistory(), routes: [ { path: "/reactive", component: Reactive } ] });
export default router;
|
注意
找不到模块“xxx.vue”或其相应的类型声明问题解决
在项目根目录或 src 文件夹下创建一个后缀为 .d.ts 的文件,并写入以下内容:
在 src 新建 vue.d.ts 文件
1 2 3 4 5 6 7 8 9 10
| declare module '*.vue' { import { App, defineComponent } from 'vue' const component: ReturnType < typeof defineComponent > & { install(app: App): void } export default component }
|
- 在入口文件 main.ts 中挂载使用 router
1 2 3 4 5 6 7 8
| import { createApp } from "vue"; import App from "./App.vue"; import "./index.css"; import router from "./router";
const app = createApp(App); app.use(router); app.mount("#app");
|
1
| <router-view></router-view>
|
1
| <router-link to="/reative">reative</router-link>
|
其他
导入 axios 时,报错
Uncaught SyntaxError: The requested module ‘/@modules/axios/index.js’ does not provide an export named ‘default’
方法一
处理: 下载 axios 的位置必须在”dependencies”中而不能是 “devDependencies”
方法二
- 将 vite 升级到了 vite2.x 版本
- 对应插件@vitejs/plugin-vue
package.json
1 2 3 4 5
| "devDependencies": { "@vue/compiler-sfc": "^3.0.4", "@vitejs/plugin-vue": "^2.3.3", "vite": "^1.0.0-rc.13" }
|
插件配置
根目录新建 vite.config.js
1 2 3 4
| const vue = require("@vitejs/plugin-vue"); module.exports = { plugins: [vue()] };
|