今日目标
✔ 了解 Vue3 的变化。
✔ 掌握 Composition API 的用法。
Vue3 基本概述
目标
了解 Vue3 现状,以及它的优点,展望它的未来。
内容
优点
Composition API ,能够更好的组织、封装、复用代码,RFCs。
性能:打包大小减少 41%、初次渲染快 55%、更新渲染快 133%、内存减少 54%,主要原因在于 Proxy,VNode,Tree Shaking support。
Better TS support,源码。
新特性:Fragment、Teleport、Suspense。
趋势:未来肯定会有越来越多的企业使用 Vue3.0 + TS 进行大型项目的开发。
对于个人来说:适应市场需求,学习流行的技术提升竞争力,加薪!
小结
Vue3 有哪些优点?
Vite 基本使用
目标
内容
1 2 3 4
| npm init vite-app <project-name> cd <project-name> npm install npm run dev
|
小结
Vite 是什么?
创建 Vue 应用
目标
掌握如何创建 Vue3 应用实例。
步骤
在 main.js
中按需导入 createApp
函数。
定义 App.vue
根组件,导入到 main.js
。
使用 createApp
函数基于 App.vue
根组件创建应用实例。
挂载至 index.html
的 #app
容器。
main.js
1 2 3 4 5 6 7 8
|
import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.mount('#app')
|
App.vue
1 2 3 4 5 6 7 8
| <template> <div class="container">我是根组件</div> </template> <script> export default { name: 'App', } </script>
|
提示
如果出现上面的错误提示,尝试把 VSCode 中的 Check JS 前面的勾取消掉试下。
小结
安装开发工具
选项/组合 API
目标
理解什么是 Options API 写法,什么是 Composition API 写法。
需求
Vue2
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
| <template> <div class="container"> <p>X 轴:{{ x }} Y 轴:{{ y }}</p> <hr /> <div> <p>{{ count }}</p> <button @click="add()">自增</button> </div> </div> </template> <script> export default { name: 'App', data() { return { x: 0, y: 0, count: 0, } }, mounted() { document.addEventListener('mousemove', this.move) }, methods: { move(e) { this.x = e.pageX this.y = e.pageY }, add() { this.count++ }, }, destroyed() { document.removeEventListener('mousemove', this.move) }, } </script>
|
Vue3
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
| <template> <div class="container"> <p>X 轴:{{ x }} Y 轴:{{ y }}</p> <hr /> <div> <p>{{ count }}</p> <button @click="add()">自增</button> </div> </div> </template> <script> import { onMounted, onUnmounted, reactive, ref, toRefs } from 'vue' export default { name: 'App', setup() { const mouse = reactive({ x: 0, y: 0, }) const move = (e) => { mouse.x = e.pageX mouse.y = e.pageY } onMounted(() => { document.addEventListener('mousemove', move) }) onUnmounted(() => { document.removeEventListener('mousemove', move) })
const count = ref(0) const add = () => { count.value++ }
return { ...toRefs(mouse), count, add, } }, } </script>
|
小结
Vue3 Composition API 可以把 __ 和 __ 组合到一起?
setup 入口函数
目标
掌握 setup 函数的基本使用。
内容
是什么:setup
是 Vue3 中新增的组件配置项,作为组合 API 的入口函数。
执行时机:实例创建前调用,甚至早于 Vue2 中的 beforeCreate。
注意点:由于执行 setup 的时候实例还没有 created,所以在 setup 中是不能直接使用 data 和 methods 中的数据的,所以 Vue3 干脆把 setup 中的 this 绑定了 undefined,防止乱用!
虽然 Vue2 中的 data 和 methods 配置项虽然在 Vue3 中也能使用,但不建议了,建议数据和方法都写在 setup 函数中,并通过 return 进行返回可在模版中直接使用(一般情况下 setup 不能为异步函数)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <h1 @click="say()">{{ msg }}</h1> </template> <script> export default { setup() { const msg = 'Hello Vue3' const say = () => { console.log(msg) } return { msg, say } }, } </script>
|
- 了解:setup 也可以返回一个渲染函数(面试问,setup 中的 return 一定只能返回一个对象吗)。
1 2 3 4 5 6 7 8 9
| <script> import { h } from 'vue' export default { name: 'App', setup() { return () => h('h2', 'Hello Vue3') }, } </script>
|
小结
reactive 包装数组
目标
掌握使用 reactive 函数包装数组为响应式数据。
内容
reactive 是一个函数,用来将普通对象/数组包装成响应式式数据使用(基于 Proxy),无法直接处理基本数据类型!
需求
📝 点击删除当前行信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <ul> <li v-for="(item, index) in arr" :key="item" @click="removeItem(index)">{{ item }}</li> </ul> </template>
<script> export default { name: 'App', setup() { const arr = ['a', 'b', 'c'] const removeItem = (index) => { arr.splice(index, 1) } return { arr, removeItem, } }, } </script>
|
问题
数据确实是删了,但视图没有更新(不是响应式的)!
解决
使用 reactive 包装数组使变成响应式数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <ul> <li v-for="(item, index) in arr" :key="item" @click="removeItem(index)">{{ item }}</li> </ul> </template>
<script> import { reactive } from 'vue' export default { name: 'App', setup() { const arr = reactive(['a', 'b', 'c']) const removeItem = (index) => { arr.splice(index, 1) } return { arr, removeItem, } }, } </script>
|
小结
reactive 的作用是什么?
reactive 包装对象
目标
掌握使用 reactive 函数包装对象为响应式数据。
需求
📝 列表渲染、删除功能、添加功能。
列表删除
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
| <template> <ul> <li v-for="(item, index) in state.arr" :key="item.id" @click="removeItem(index)">{{ item.name }}</li> </ul> </template>
<script> import { reactive } from 'vue' export default { name: 'App', setup() { const state = reactive({ arr: [ { id: 0, name: 'ifer', }, { id: 1, name: 'elser', }, { id: 2, name: 'xxx', }, ], }) const removeItem = (index) => { state.arr.splice(index, 1) } return { state, removeItem, } }, } </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
| <template> <ul> <li v-for="(item, index) in state.arr" :key="item.id" @click="removeItem(index)">{{ item.name }}</li> </ul> </template> <script> import { reactive } from 'vue'
function useRemoveItem() { const state = reactive({ arr: [ { id: 0, name: 'ifer', }, { id: 1, name: 'elser', }, { id: 2, name: 'xxx', }, ], }) const removeItem = (index) => { state.arr.splice(index, 1) } return { state, removeItem } }
export default { name: 'App', setup() { const { state, removeItem } = useRemoveItem() return { state, removeItem, } }, } </script>
|
添加功能
- 错误写法一:user 对象没有用 reactive 进行包裹。
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
| <template> <form @submit.prevent="handleSubmit"> <input type="text" v-model="user.id" /> <input type="text" v-model="user.name" /> <input type="submit" /> </form> <ul> <li v-for="(item, index) in state.arr" :key="item.id" @click="removeItem(index)">{{ item.name }}</li> </ul> </template>
<script> import { reactive } from 'vue'
function useRemoveItem() { const state = reactive({ arr: [ { id: 0, name: 'ifer', }, { id: 1, name: 'elser', }, { id: 2, name: 'xxx', }, ], }) const removeItem = (index) => { state.arr.splice(index, 1) } return { state, removeItem } } function useAddItem(state) { const user = { id: '', name: '', } const handleSubmit = () => { state.arr.push({ id: user.id, name: user.name, }) user.id = '' user.name = '' } return { user, handleSubmit, } }
export default { name: 'App', setup() { const { state, removeItem } = useRemoveItem() const { user, handleSubmit } = useAddItem(state) return { state, removeItem, user, handleSubmit, } }, } </script>
|
- 错误写法二:直接 push 了原对象,导致会相互影响。
1 2 3 4 5 6
| const handleSubmit = () => { state.arr.push(user) user.id = '' user.name = '' }
|
- 解决方法如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const handleSubmit = () => {
const userCopy = Object.assign({}, user) state.arr.push(userCopy) user.id = '' user.name = '' }
|
拆分文件
remove.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import { reactive } from 'vue' export default function userRemoveItem() { const state = reactive({ arr: [ { id: 0, name: 'ifer', }, { id: 1, name: 'elser', }, { id: 2, name: 'xxx', }, ], }) const removeItem = (index) => { state.arr.splice(index, 1) } return { state, removeItem } }
|
add.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { reactive } from 'vue' export default function useAddItem(state) { const user = reactive({ id: '', name: '', }) const handleSubmit = () => { const userCopy = Object.assign({}, user) state.arr.push(userCopy) user.id = '' user.name = '' } return { user, handleSubmit, } }
|
App.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
| <template> <form @submit.prevent="handleSubmit"> <input type="text" v-model="user.id" /> <input type="text" v-model="user.name" /> <input type="submit" /> </form> <ul> <li v-for="(item, index) in state.arr" :key="item.id" @click="removeItem(index)">{{ item.name }}</li> </ul> </template>
<script> import userRemoveItem from './hooks/remove' import useAddItem from './hooks/add' export default { name: 'App', setup() { const { state, removeItem } = userRemoveItem() const { user, handleSubmit } = useAddItem(state) return { state, removeItem, user, handleSubmit, } }, } </script>
|
为什么换成 Proxy
Vue3 生命周期
目标
掌握组合 API 中生命周期钩子函数的写法。
内容
组合 API生命周期写法,其实 选项 API 的写法在 Vue3 中也是支持。
Vue3(组合 API)常用的生命周期钩子有 7 个,可以多次使用同一个钩子,执行顺序和书写顺序相同。
setup、onBeforeMount、onMounted、onBeforeUpdate、onUpdated、onBeforeUnmount、onUnmounted。
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <hello-world v-if="state.bBar" /> <button @click="state.bBar = !state.bBar">destroy cmp</button> </template>
<script> import HelloWorld from './components/HelloWorld.vue' import { reactive } from 'vue' export default { name: 'App', components: { HelloWorld, }, setup() { const state = reactive({ bBar: true, }) return { state, } }, } </script>
|
HelloWorld.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
| <template> <p>{{ state.msg }}</p> <button @click="state.msg = 'xxx'">update msg</button> </template>
<script> import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, reactive } from 'vue' export default { name: 'HelloWorld', setup() { const state = reactive({ msg: 'Hello World', })
onBeforeMount(() => { console.log('onBeforeMount') }) onMounted(() => { console.log('onMounted') }) onBeforeUpdate(() => { console.log('onBeforeUpdate') }) onUpdated(() => { console.log('onUpdated') }) onBeforeUnmount(() => { console.log('onBeforeUnmount') }) onUnmounted(() => { console.log('onUnmounted') }) return { state, } }, } </script>
|
小结
Vue3 把 Vue2 中的哪两个钩子换成了 setup?
记录鼠标位置
定义一个响应式数据对象,包含 x 和 y 属性。
在组件渲染完毕后,监听 document 的鼠标移动事件。
指定 move 函数为事件对应回调,在函数中修改坐标。
组件销毁时,解绑事件。
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 28 29 30 31 32 33 34 35
| <template> <div>x: {{ mouse.x }} y: {{ mouse.y }}</div> </template>
<script> import { onMounted, onUnmounted, reactive } from 'vue' const useMouse = () => { const mouse = reactive({ x: 0, y: 0, }) const move = (e) => { mouse.x = e.pageX mouse.y = e.pageY } onMounted(() => { document.addEventListener('mousemove', move) }) onUnmounted(() => { document.removeEventListener('mousemove', move) }) return mouse } export default { name: 'App', setup() { const mouse = useMouse() return { mouse, } }, } </script>
|
toRef
目标
掌握 toRef 函数的使用。
内容
toRef 函数的作用:转换响应式对象中某个属性为单独响应式数据,并且转换后的值和之前是关联的(ref 函数也可以转换,但值非关联,后面详讲 ref 函数)。
需求
📝 需求:在模板中渲染 name 和 age。
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> <div class="container"> <h2>name: {{ obj.name }} age: {{obj.age}}</h2> <button @click="updateName">修改数据</button> </div> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { const obj = reactive({ name: 'ifer', age: 10, address: '河南', sex: '男', }) const updateName = () => { obj.name = 'xxx' } return { obj, updateName } }, } </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
| <template> <div class="container"> <h2>{{ name }}</h2> <button @click="updateName">修改数据</button> </div> </template> <script> import { reactive } from 'vue' export default { name: 'App', setup() { const obj = reactive({ name: 'ifer', age: 10, address: '河南', sex: '男', }) let { name } = obj const updateName = () => { name = 'xxx' } return { name, updateName } }, } </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
| <template> <div class="container"> <h2>{{ name }}</h2> <button @click="updateName">修改数据</button> </div> </template> <script> import { reactive, toRef } from 'vue' export default { name: 'App', setup() { const obj = reactive({ name: 'ifer', age: 10, }) const name = toRef(obj, 'name') const updateName = () => { name.value = 'xxx' } return { name, updateName } }, } </script>
|
toRefs
目标
掌握 toRefs 函数的使用。
内容
toRefs 函数的作用:转换响应式对象中所有属性为单独响应式数据,并且转换后的值和之前是关联的。
需求
📝 模板中需要写 obj.name、obj.age …很麻烦,期望能够直接能使用 name、age 属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div class="container"> <h2>{{ name }} {{ age }}</h2> <button @click="updateName">修改数据</button> </div> </template> <script> import { reactive, toRefs } from 'vue' export default { name: 'App', setup() { const obj = reactive({ name: 'ifer', age: 10, }) const updateName = () => { obj.name = 'xxx' obj.age = 18 } return { ...toRefs(obj), updateName } }, } </script>
|
ref 函数
目标
掌握 ref 函数的使用。
基本使用
ref 函数,常用于把简单数据类型包裹为响应式数据,注意 JS 中操作值的时候,需要加 .value
属性,模板中正常使用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div class="container"> <div>{{ name }}</div> <button @click="updateName">修改数据</button> </div> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { const name = ref('ifer') const updateName = () => { name.value = 'xxx' } return { name, updateName } }, } </script>
|
点击计数
定义一个简单数据类型的响应式数据。
定义一个修改数字的方法。
在 setup 返回数据和函数,供模板中使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <h3>{{ count }}</h3> <button @click="add">累加1</button> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { const count = ref(0) const add = () => { count.value++ } return { count, add } }, } </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 23 24 25 26
| <template> <div class="container"> <div>{{ data?.name }}</div> <button @click="updateName">修改数据</button> </div> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { const data = ref(null) setTimeout(() => { data.value = { name: 'ifer', } }, 1000) const updateName = () => { data.value.name = 'xxx' } return { data, updateName } }, } </script>
|
小结
当你明确知道需要包裹的是一个对象,那么推荐使用 reactive,其他情况使用 ref 即可。
ref 属性
目标
能够通过 ref 属性获取 DOM 或组件。
内容
获取单个 DOM 或者组件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div ref="dom">我是box</div> </template> <script> import { onMounted, ref } from 'vue' export default { name: 'App', setup() { const dom = ref(null) onMounted(() => { console.log(dom.value) }) return { dom } }, } </script>
|
配合 v-for 循环可以获取一组 DOM 或者组件。
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
| <template> <ul> <li v-for="i in 4" :key="i" :ref="setDom">第 {{ i }} li</li> </ul> </template> <script> import { onMounted } from 'vue' export default { name: 'App', setup() { const domList = [] const setDom = (el) => { domList.push(el) } onMounted(() => { console.log(domList) }) return { setDom } }, } </script>
|
问题:有数据更新的时候,domList 会越来越多。
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> <ul> <li v-for="i in 4" :key="i" :ref="setDom">第 {{ i }} li</li> </ul> <div> <h3>{{ num }}</h3> <button @click="handleClick">+1</button> </div> </template> <script> import { onMounted, ref } from 'vue' export default { name: 'App', setup() { const domList = [] const setDom = (el) => { domList.push(el) } onMounted(() => { console.log(domList) })
const num = ref(1) const handleClick = () => { num.value++ console.log(domList) } return { setDom, num, handleClick } }, } </script>
|
解决:onBeforeUpdate 的时候清空 domList 即可。
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> <ul> <li v-for="i in 4" :key="i" :ref="setDom">第 {{ i }} li</li> </ul> <hr /> <h3>{{ num }}</h3> <button @click="handleClick">+1</button> </template> <script> import { onMounted, ref, onBeforeUpdate } from 'vue' export default { name: 'App', setup() { let domList = [] const setDom = (el) => { domList.push(el) } onMounted(() => { console.log(domList) })
onBeforeUpdate(() => (domList = [])) const num = ref(1) const handleClick = () => { num.value++ console.log(domList) } return { setDom, num, handleClick } }, } </script>
|
🧐 ref 处理基本数据类型用的是 Object.defineProperty 进行数据劫持,处理复杂数据类型用的是 Proxy(内部借助了 reactive 函数)。
小结
根据 ref 获取 v-for 循环的元素时,得到的结果是所有元素还是某一个?
🧐 customRef
作用:创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显示控制,文档。
- 使用 ref 完成双向数据绑定的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script> import { ref } from 'vue' export default { name: 'App', setup() { let keyword = ref('vue') return { keyword, } }, } </script>
|
- customRef 的基本语法。
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> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script> import { customRef } from 'vue' function myRef(value) { return customRef(() => { return { get() { console.log(1) return value }, set(newValue) { console.log(newValue) }, } }) } export default { name: 'App', setup() { let keyword = myRef('vue') return { keyword, } }, } </script>
|
- get/set 的使用。
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
| <template> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script> import { customRef } from 'vue' function myRef(value) { return customRef(() => { return { get() { console.log(1) return value }, set(newValue) { value = newValue }, } }) } export default { name: 'App', setup() { let keyword = myRef('vue') return { keyword, } }, } </script>
|
- track 追踪数据的变化和 trigger 触发视图更新。
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
| <template> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script> import { customRef } from 'vue' function myRef(value) { return customRef((track, trigger) => { return { get() { console.log(1) track() return value }, set(newValue) { value = newValue trigger() }, } }) } export default { name: 'App', setup() { let keyword = myRef('vue') return { keyword, } }, } </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
| <template> <input type="text" v-model="keyword" /> <p>{{ keyword }}</p> </template> <script> import { customRef } from 'vue' function myRef(value, delay) { let timer = null return customRef((track, trigger) => { return { get() { console.log(1) track() return value }, set(newValue) { clearTimeout(timer) timer = setTimeout(() => { value = newValue trigger() }, delay) }, } }) } export default { name: 'App', setup() { let keyword = myRef('vue', 300) return { keyword, } }, } </script>
|
🧐 shallowReactive 和 shallowRef
通过 reactive 和 ref 创建出来的数据都是递归劫持的,如果只想劫持第一层的变化可以使用 shallowReactive 或 shallowRef。
shallowReactive
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
| <template> <p>{{ state.age }}</p> <p>{{ state.a.b.c.d }}</p> <button @click="handleChange">change</button> </template>
<script> import { shallowReactive } from 'vue' export default { name: 'App', setup() { const state = shallowReactive({ age: 18, a: { b: { c: { d: 'Hello World', }, }, }, }) const handleChange = () => { state.age = 19 state.a.b.c.d = 'xxx' } return { state, handleChange } }, } </script>
|
shallowRef: 如果传入的是基本类型和 ref 没区别,传入的是对象则不是响应式的(不会再借助 reactive 函数了)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <template> <p>{{ state.age }}</p> <button @click="handleChange">change</button> </template>
<script> import { shallowRef } from 'vue' export default { name: 'App', setup() { const state = shallowRef({ age: 18, }) const handleChange = () => { state.value = { age: 19 } } return { state, handleChange } }, } </script>
|
🧐 readonly 和 shallowReadonly
readonly
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> <p>{{ state.name }}</p> <button @click="handleClick">click</button> </template>
<script> import { readonly } from 'vue'
export default { name: 'App', setup() { const origin = { name: 'ifer', } const state = readonly(origin) const handleClick = () => { state.name = 'xxx' console.log(state.name) } return { state, handleClick } }, } </script>
|
🧐 toRaw 和 markRaw
toRaw
返回 reactive 或 readonly 代理的原始对象,对这个原始对象的修改不会引起页面更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template>Hello World</template> <script> import { reactive, readonly, toRaw } from 'vue' export default { name: 'App', setup() { const origin = { name: 'ifer', } const state1 = reactive(origin) const state2 = readonly(origin)
console.log(toRaw(state1) === toRaw(state2)) console.log(toRaw(state1) === origin) }, } </script>
|
markRaw
readonly 是改都没改,这 markRaw 是改了没有响应式。
a,作用:标记一个对象,使其永远不会再成为响应式对象。
b,场景:有些值不应被设置为响应式的,例如复杂的第三方类库等;当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
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
| <template> <p>{{ state }}</p> <button @click="handleClick">click</button> </template>
<script> import { reactive, markRaw } from 'vue' export default { name: 'App', setup() { let obj = { name: 'ifer', age: 18, } markRaw(obj) const state = reactive(obj) const handleClick = () => { state.name = 'xxx' } return { state, handleClick, } }, } </script>
|
computed
目标
掌握 computed 函数的使用。
基本
作用:computed 函数用来定义计算属性。
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> <p>firstName: {{ person.firstName }}</p> <p>lastName: {{ person.lastName }}</p> <p>fullName: {{ person.fullName }}</p> </template> <script> import { computed, reactive } from 'vue' export default { name: 'App', setup() { const person = reactive({ firstName: '朱', lastName: '逸之', }) person.fullName = computed(() => { return person.firstName + ' ' + person.lastName })
return { person, } }, } </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
| <template> <p>firstName: {{ person.firstName }}</p> <p>lastName: {{ person.lastName }}</p> <input type="text" v-model="person.fullName" /> </template> <script> import { computed, reactive } from 'vue' export default { name: 'App', setup() { const person = reactive({ firstName: '朱', lastName: '逸之', }) person.fullName = computed({ get() { return person.firstName + ' ' + person.lastName }, set(value) { const newArr = value.split(' ') person.firstName = newArr[0] person.lastName = newArr[1] }, }) return { person, } }, } </script>
|
小结
watch
目标
掌握 watch 函数的使用。
监听一个 ref 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <p>{{ age }}</p> <button @click="age++">click</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const age = ref(18) watch(age, (newValue, oldValue) => { console.log(newValue, oldValue) })
return { age } }, } </script>
|
监听多个 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
| <template> <p>age: {{ age }} num: {{ num }}</p> <button @click="handleClick">click</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const age = ref(18) const num = ref(0)
const handleClick = () => { age.value++ num.value++ } watch([age, num], (newValue, oldValue) => { console.log(newValue, oldValue) })
return { age, num, handleClick } }, } </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
| <template> <p>{{ age }}</p> <button @click="handleClick">click</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const age = ref(18)
const handleClick = () => { age.value++ }
watch( age, (newValue, oldValue) => { console.log(newValue, oldValue) }, { immediate: true, } )
return { age, handleClick } }, } </script>
|
开启深度监听
问题:修改 ref 对象里面的数据并不会触发监听,说明 ref 并不是默认开启 deep 的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <p>{{ obj.hobby.eat }}</p> <button @click="obj.hobby.eat = '面条'">修改 obj.hobby.eat</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const obj = ref({ hobby: { eat: '西瓜', }, }) watch(obj, (newValue, oldValue) => { console.log(newValue === oldValue) })
return { obj } }, } </script>
|
- 解决:当然直接修改整个对象的话肯定是会被监听到的(注意模板中对 obj 的修改,相当于修改的是 obj.value)。
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> <p>{{ obj.hobby.eat }}</p> <button @click="obj = { hobby: { eat: '面条' } }">修改 obj</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const obj = ref({ hobby: { eat: '西瓜', }, }) watch(obj, (newValue, oldValue) => { console.log(newValue, oldValue) console.log(newValue === oldValue) })
return { obj } }, } </script>
|
- 解决:开启深度监听 ref 数据。
1 2 3 4 5 6 7 8 9 10
| watch( obj, (newValue, oldValue) => { console.log(newValue, oldValue) console.log(newValue === oldValue) }, { deep: true, } )
|
监听 reactive 数据
基本操作
注意:监听 reactive 数据时,强制开启了深度监听,配置无效;监听对象的时候 newValue 和 oldValue 是全等的。
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
| <template> <p>{{ obj.hobby.eat }}</p> <button @click="obj.hobby.eat = '面条'">click</button> </template>
<script> import { watch, reactive } from 'vue' export default { name: 'App', setup() { const obj = reactive({ name: 'ifer', hobby: { eat: '西瓜', }, }) watch(obj, (newValue, oldValue) => { console.log(newValue === oldValue) })
return { obj } }, } </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> <p>{{ obj.hobby.eat }}</p> <button @click="obj.hobby.eat = '面条'">修改 obj</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const obj = ref({ hobby: { eat: '西瓜', }, }) watch(obj.value, (newValue, oldValue) => { console.log(newValue, oldValue) console.log(newValue === oldValue) })
return { obj } }, } </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
| <template> <p>{{ obj.hobby.eat }}</p> <button @click="obj.hobby.eat = '面条'">修改 obj</button> </template>
<script> import { watch, reactive } from 'vue' export default { name: 'App', setup() { const obj = reactive({ hobby: { eat: '西瓜', }, })
watch( () => obj.hobby.eat, (newValue, oldValue) => { console.log(newValue, oldValue) console.log(newValue === oldValue) } )
return { obj } }, } </script>
|
- 监听 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
| <template> <p>{{ age }}</p> <button @click="age++">click</button> </template>
<script> import { watch, ref } from 'vue' export default { name: 'App', setup() { const age = ref(18)
watch( () => age.value, (newValue, oldValue) => { console.log(newValue, oldValue) } )
return { age } }, } </script>
|
小结
掌握 watch 的各种用法。
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
| <template> <p>{{ obj.hobby.eat }}</p> <button @click="obj.hobby.eat = '面条'">修改 obj</button> </template>
<script> import { reactive, watchEffect } from 'vue' export default { name: 'App', setup() { const obj = reactive({ hobby: { eat: '西瓜', }, })
watchEffect(() => { console.log(obj.hobby.eat) })
return { obj } }, } </script>
|
provide/inject
目标
掌握使用 provide 函数和 inject 函数完成跨层级组件通讯。
内容
📝 把 App.vue 中的数据传递给孙组件,Child.vue。
App.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
| <template> <div class="container"> <h2>App {{ money }}</h2> <button @click="money = 1000">发钱</button> <hr /> <Parent /> </div> </template> <script> import { provide, ref } from 'vue' import Parent from './Parent.vue' export default { name: 'App', components: { Parent, }, setup() { const money = ref(100) provide('money', money) const changeMoney = (m) => (money.value -= m) provide('changeMoney', changeMoney) return { money } }, } </script>
|
Parent.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div> Parent <hr /> <Child /> </div> </template>
<script> import Child from './Child.vue' export default { components: { Child, }, } </script>
|
Child.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <template> <div> Child <p>{{ money }}</p> <button @click="changeMoney(1)">花 1 块钱</button> </div> </template>
<script> import { inject } from 'vue' export default { setup() { const money = inject('money') const changeMoney = inject('changeMoney') return { money, changeMoney } }, } </script>
|
小结
响应式数据的判断
isRef: 检查一个值是否为 ref 对象。
isReactive: 检查一个对象是否是由 reactive 创建的响应式代理。
isReadonly: 检查一个对象是否是由 readonly 创建的只读代理。
isProxy: 检查一个对象是否是由 reactive 或者 readonly 方法创建的代理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| <template> <div>name: {{ name }}</div> <div>age: {{ age }}</div> </template>
<script> import { reactive, readonly, ref, toRefs, isRef, isReactive, isReadonly, isProxy } from 'vue' export default { setup() { const person = reactive({ name: 'xxx', age: 18 }) const num = ref(0) const readonlyPerson = readonly(person) console.log(isRef(num)) console.log(isReactive(person)) console.log(isReadonly(readonlyPerson)) console.log(isProxy(person)) console.log(isProxy(readonlyPerson)) return { ...toRefs(person), } }, } </script>
|
setup 函数参数
目标
掌握 setup 中参数的使用。
需求
父传子
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <template> <h1>父组件</h1> <p>{{ money }}</p> <hr /> <Son :money="money" /> </template> <script> import { ref } from 'vue' import Son from './Son.vue' export default { name: 'App', components: { Son, }, setup() { const money = ref(100) return { money } }, } </script>
|
Son.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <h1>子组件</h1> <p>{{ money }}</p> </template> <script> export default { name: 'Son', props: { money: { type: Number, default: 0, }, }, setup(props) { console.log(props.money) }, } </script>
|
子传父
App.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
| <template> <h1>父组件</h1> <p>{{ money }}</p> <hr /> <Son :money="money" @change-money="updateMoney" /> </template> <script> import { ref } from 'vue' import Son from './Son.vue' export default { name: 'App', components: { Son, }, setup() { const money = ref(100) const updateMoney = (newMoney) => { money.value -= newMoney } return { money, updateMoney } }, } </script>
|
Son.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
| <template> <h1>子组件</h1> <p>{{ money }}</p> <button @click="changeMoney(1)">花 1 元</button> </template> <script> export default { name: 'Son', props: { money: { type: Number, default: 0, }, }, emits: ['change-money'], setup(props, { emit }) { const changeMoney = (m) => { emit('change-money', m) } return { changeMoney } }, } </script>
|
小结
setup 第一个参数的是什么?
第二个参数 context 中包含什么信息?
v-model
目标
掌握 Vue3 中 v-model 的用法。
基本操作
在 Vue2 中 v-mode 语法糖简写的代码。
1
| <Son :value="msg" @input="msg=$event" />
|
在 Vue3 中 v-model 语法糖有所调整。
1
| <Son :modelValue="msg" @update:modelValue="msg=$event" />
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <h2>count: {{ count }}</h2> <hr /> <Son :modelValue="count" @update:modelValue="count = $event" /> </template> <script> import { ref } from 'vue' import Son from './Son.vue' export default { name: 'App', components: { Son, }, setup() { const count = ref(10) return { count } }, } </script>
|
Son.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <h2>子组件 {{ modelValue }}</h2> <button @click="$emit('update:modelValue', 100)">改变 count</button> </template> <script> export default { name: 'Son', props: { modelValue: { type: Number, default: 0, }, }, } </script>
|
传递多个
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <h2>count: {{ count }} age: {{ age }}</h2> <hr /> <Son v-model="count" v-model:age="age" /> </template> <script> import { ref } from 'vue' import Son from './Son.vue' export default { name: 'App', components: { Son, }, setup() { const count = ref(10) const age = ref(18) return { count, age } }, } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <h2>子组件 {{ modelValue }} {{ age }}</h2> <button @click="$emit('update:modelValue', 100)">改变 count</button> <button @click="$emit('update:age', 19)">改变 age</button> </template> <script> export default { name: 'Son', props: { modelValue: { type: Number, default: 0, }, age: { type: Number, default: 18, }, }, } </script>
|
小结
Fragment
Teleport
作用
传送,能将特定的 HTML 结构(一般是嵌套很深的)移动到指定的位置,解决 HTML 结构嵌套过深造成的样式影响或不好控制的问题。
需求
在 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
| <template> <div class="child"> <dialog v-if="bBar" /> <button @click="handleDialog">显示弹框</button> </div> </template>
<script> import { ref } from 'vue' import Dialog from './Dialog.vue' export default { name: 'Child', components: { Dialog, }, setup() { const bBar = ref(false) const handleDialog = () => { bBar.value = !bBar.value } return { bBar, handleDialog, } }, } </script>
|
解决
1 2 3 4 5 6 7 8
| <template> <div class="child"> <teleport to="body"> <dialog v-if="bBar" /> </teleport> <button @click="handleDialog">显示弹框</button> </div> </template>
|
Suspense
异步组件加载期间,可以使用此组件渲染一些额外的内容,增强用户体验。
异步组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <template> <div class="app"> App <Test /> </div> </template>
<script> import { defineAsyncComponent } from 'vue' const Test = defineAsyncComponent(() => import('./Test.vue')) export default { name: 'App', components: { Test, }, } </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
| <template> <div class="app"> App <Suspense> <template v-slot:default> <Test /> </template> <template v-slot:fallback> <div>loading...</div> </template> </Suspense> </div> </template>
<script> import { defineAsyncComponent } from 'vue' const Test = defineAsyncComponent(() => import('./Test.vue')) export default { name: 'App', components: { Test, }, } </script>
|
一个细节
setup 也可以返回一个 Promise 实例,但要异步引入此组件并配合 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
| <template> <div class="test">Test</div> </template>
<script> import { ref } from 'vue' export default { name: 'Test',
async setup() { const count = ref(0) return await new Promise((resolve, reject) => { setTimeout(() => { resolve({ count }) }, 3000) }) }, } </script>
|
script setup
文档
data
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { reactive, toRefs } from 'vue' const state = reactive({ name: 'ifer', age: 18, }) const { name, age } = toRefs(state) </script> <template> <h1>name: {{ name }} age: {{ age }}</h1> </template>
|
method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script setup> import { reactive, toRefs } from 'vue' const state = reactive({ name: 'ifer', age: 18, }) const { name, age } = toRefs(state)
const changeName = () => { name.value = 'xxx' } </script> <template> <h1>name: {{ name }} age: {{ age }}</h1> <button @click="changeName">修改名字</button> </template>
|
computed
1 2 3 4 5 6 7 8 9 10 11 12
| <script setup> import { reactive, computed, isRef } from 'vue' const state = reactive({ firstName: '热', lastName: '巴', }) const fullName = computed(() => state.firstName + state.lastName) console.log(isRef(fullName)) </script> <template> <h1>fullName: {{ fullName }}</h1> </template>
|
watch
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { ref, watch } from 'vue' const count = ref(0) watch(count, (newValue, oldValue) => { console.log(newValue, oldValue) }) </script> <template> <h1>count: {{ count }}</h1> <button @click="count++">+1</button> </template>
|
props
父组件
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { reactive } from 'vue' import Hello from './Hello.vue' const person = reactive({ name: 'ifer', age: 18, }) </script> <template> <Hello v-bind="person" /> </template>
|
子组件
1 2 3 4 5 6 7 8 9 10
| <script setup> const props = defineProps({ name: String, age: Number, }) </script> <template> <div>name: {{ props.name }} age: {{ age }}</div> </template>
|
emit
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <script setup> import { reactive } from 'vue' import Hello from './Hello.vue' const person = reactive({ name: 'ifer', age: 18, }) const updateAge = () => { person.age++ } </script> <template> <Hello v-bind="person" @updateAge="updateAge" /> </template>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> const props = defineProps({ name: String, age: Number, }) const emit = defineEmits(['updateAge'])
const updateAge = () => { emit('updateAge') } </script> <template> <div>name: {{ props.name }} age: {{ age }}</div> <button @click="emit('updateAge')">update name</button> <button @click="$emit('updateAge')">update name</button> <button @click="updateAge">update name</button> </template>
|
v-model
父组件
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { reactive } from 'vue' import Hello from './Hello.vue' const person = reactive({ name: 'ifer', age: 18, }) </script> <template> <Hello v-model="person.name" v-model:age="person.age" /> </template>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <script setup> const props = defineProps({ modelValue: String, age: Number, }) const emit = defineEmits(['update:modelValue', 'update:age'])
const updateName = () => { emit('update:modelValue', 'xxx') } const updateAge = () => { emit('update:age', 20) } </script> <template> <div>name: {{ props.modelValue }} age: {{ age }}</div> <button @click="updateName">update name</button> <button @click="updateAge">update age</button> </template>
|
defineExpose
标准组件写法中,父组件通过 ref 拿到子组件实例,并可以直接访问子组件中的 data 和 method。
script-setup 模式下,data 和 method 默认只能给当前组件的 template 使用,外界通过 ref 无法访问到。
解决:需要手动的通过 defineExpose 进行暴露。
父组件
1 2 3 4 5 6 7 8 9 10 11
| <script setup> import { ref, nextTick } from 'vue' import Hello from './Hello.vue' const childRef = ref(null) nextTick(() => { childRef.value.updatePerson('xxx', 20) }) </script> <template> <Hello ref="childRef" /> </template>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <script setup> import { reactive } from 'vue' const person = reactive({ name: 'ifer', age: 18, }) const updatePerson = (name, age) => { person.name = name person.age = age } defineExpose({ updatePerson, }) </script> <template> <h2>name: {{ person.name }} age: {{ person.age }}</h2> </template>
|
slot
父组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <script setup> import Hello from './Hello.vue' </script> <template> <Hello> <h2>默认插槽</h2> <template #title> <h2>具名插槽</h2> </template> <template #footer="{ person }"> <h2>通过作用域插槽获取到的数据:{{ person.name }}</h2> </template> </Hello> </template>
|
子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> import { reactive, useSlots } from 'vue' const slots = useSlots() const person = reactive({ name: 'ifer', age: 18, }) console.log(slots) </script> <template> <slot /> <slot name="title" /> <slot name="footer" :person="person" /> </template>
|
CSS 变量注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <script setup> import { reactive } from 'vue' const state = reactive({ color: 'pink', }) </script> <template> <h2>Hello Vue3</h2> </template> <style scoped> h2 { color: v-bind('state.color'); } </style>
|
原型绑定与组件使用
main.js
1 2 3 4 5
| import { createApp } from 'vue' import App from './App.vue' const app = createApp(App) app.config.globalProperties.year = '再见 2021,你好 2022~~' app.mount('#app')
|
App.vue
1 2 3 4 5 6 7
| <script setup> import { getCurrentInstance } from 'vue' const { proxy } = getCurrentInstance() </script> <template> <h1>{{ proxy.year }}</h1> </template>
|
对 await 支持
1 2 3 4 5 6 7 8
| <script setup> const r = await fetch('https://autumnfish.cn/api/joke') const d = await r.text() console.log(d) </script> <template> <h1>{{ proxy.year }}</h1> </template>
|
定义组件的 name
1 2 3 4 5 6 7 8 9 10
| <template> <div>Hello</div> </template> <script setup></script>
<script> export default { name: 'HelloCmp', } </script>
|
mixins
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能,一个混入对象可以包含任意组件选项,当组件使用混入对象时,所有混入对象的选项将被“混合”进该组件本身。
follow.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| export const follow = { data() { return { loading: false, } }, methods: { followFn() { this.loading = true setTimeout(() => { this.loading = false }, 2000) }, }, }
|
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <a href="javascript:;" @click="followFn">{{ loading ? '请求中...' : '关注' }}</a> <Son /> </template> <script> import Son from './Son.vue' import { follow } from './follow' export default { name: 'App', components: { Son, }, mixins: [follow], } </script>
|
Son.vue
1 2 3 4 5 6 7 8 9 10
| <template> <a href="javascript:;" @click="followFn">{{ loading ? '请求中...' : '关注' }}</a> </template> <script> import { follow } from './follow' export default { name: 'Son', mixins: [follow], } </script>
|
小结
其他变更
参考 Vue3 迁移指南
全局 API 的变更,链接。
data 只能是函数,链接。
自定义指令 API 和组件保持一致,链接。
keyCode 作为 v-on 修饰符被移除、移除 v-on.native 修饰符、filters 被移除,链接。
$on、$off、$once 被移除,链接。
过渡类名的更改,链接。
…