Vue ⑧-Vue3 | 组合式API
Vue3 预体验
需求:点击按钮,让数字 + 1
vue2 的 选项式 API:
<script>
export default {data() {return {count: 0}},methods: {increment() {this.count++}}
}
</script>
vue3 的 组合式 API:
<script>
import { ref } from 'vue'
const count = ref(0)
const increment = () => count.value++
</script>
- 代码量变少了
- 分散式维护转为集中式维护,更易封装复用
create-vue
create-vue
是 Vue 官方新的脚手架工具,底层切换到了 vite(下一代构建工具),为开发提供极速响应。
-
前提环境条件:已安装 16.0 或更高版本的 Node.js
-
创建一个Vue应用:
npm init vue@latest
,这一指令将会安装并执行create-vue
关键文件:
vite.config.js
- 项目的配置文件 基于 vite 的配置package.json
- 项目包文件 核心依赖项变成了 Vue3.x 和 vitemain.js
- 入口文件 createApp 函数创建应用实例app.vue
- 根组件 SFC 单文件组件 script - template - style
变化一:脚本 script 和模板 template 顺序调整
变化二:模板 template 不再要求唯一根元素
变化三:脚本 script 添加 setup 标识支持组合式APIindex.html
- 单页入口 提供 id 为 app 的挂载点
组合式 API
setup 选项的写法和执行时机
<script>
export default {setup() {},beforeCreate() {},
}
</script>
setup 选项中写代码的特点
<script>
export default {setup() {// 数据const message = 'Hello Vue 3'// 函数const logMessage = () => {console.log(message)}return {message,logMessage}},beforeCreate() {console.log('beforeCreate 函数')}
}
</script><template><div>message: {{ message }}</div><button @click="logMessage">Log Message</button>
</template>
<script setup>
语法糖
在 <script
> 标签中,使用 <script setup>
语法糖,可以省略掉 export default
和 return
关键字,直接在 script
标签中书写代码。
<script setup>
// 数据
const message = 'Hello Vue 3'
// 函数
const logMessage = () => {console.log(message)
}
</script><template><div>message: {{ message }}</div><button @click="logMessage">Log Message</button>
</template>
reactive()
作用:接受对象类型数据的参数传入并返回一个响应式的对象
<script setup>
// 1. reactive:接受一个对象类型的数据,返回一个响应式的对象,当数据发生变化时,视图会自动更新
import { reactive } from 'vue'
const state = reactive({count: 0
})
const setCount = () => {state.count++
}
</script>
ref()
作用:接收简单类型或者对象类型的数据传入并返回一个响应式的对象
<script setup>
// 1. ref:接受简单类型或者复杂类型,返回一个响应式对象
// 本质:是在原有传入数据的基础上,外层包了一层对象
// 底层:包成复杂类型之后,再借助 reactive 实现的响应式
// 注意:
// 1. ref 不能直接访问数据,需要通过 .value
// 2. 在 template 中 .value 不需要加(帮我们扒了一层)// 推荐:以后声明数据,统一用 ref => 统一编码规范
import { ref } from 'vue'
const count = ref(0)const setCount = () => {count.value++
}
</script>
computed计算属性函数
计算属性基本思想和 Vue2 的完全一致,组合式 API 下的计算属性只是修改了写法
核心步骤:
- 导入
computed
函数 - 执行函数 在回调参数中
return
基于响应式数据做计算的值,用变量接收
<script setup>
// const 计算属性 => computed(() => {
// return 计算返回后的结果
//})import { ref, computed } from 'vue'
// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8])const computedList = computed(() => {return list.value.filter(item => item % 2 === 0)
})const addFn = () => {list.value.push(Math.ceil(Math.random() * 100))
}
</script><template><div><div>原始数据:{{ list }}</div><div>计算后的数据:{{ computedList }}</div><button @click="addFn">修改</button></div>
</template>
watch函数
作用: 侦听一个或者多个数据的变化,数据变化时执行回调函数
俩个额外参数:
immediate
(立即执行)deep
(深度侦听)
侦听单个数据
-
导入 watch 函数
-
执行 watch 函数传入要侦听的响应式数据 (ref对象) 和回调函数
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)const changeCount = () => {count.value++
}// 1. 监视单个数据的变化
watch(count, (newValue, oldValue) => {console.log('count', newValue, oldValue)
})</script><template><div>{{ count }}</div><button @click="changeCount">+1</button>
</template>
侦听多个数据
同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')const changeCount = () => {count.value++
}
const changeNickname = () => {nickname.value = '李四'
}// 2. 监视多个数据的变化
watch([count, nickname], (newArr, oldArr) => {console.log('watch', newArr, oldArr)
})</script><template><div>{{ count }}</div><button @click="changeCount">+1</button><div>{{ nickname }}</div><button @click="changeNickname">修改昵称</button>
</template>
immediate
在侦听器创建时立即触发回调,响应式数据变化之后继续执行回调
<script setup>
import { ref, watch } from 'vue'
const count = ref(0)
const nickname = ref('张三')const changeCount = () => {count.value++
}
const changeNickname = () => {nickname.value = '李四'
}// 3. immediate 立即执行
watch([count, nickname],(newArr, oldArr) => {console.log('watch', newArr, oldArr)},{immediate: true}
)</script><template><div>{{ count }}</div><button @click="changeCount">+1</button><div>{{ nickname }}</div><button @click="changeNickname">修改昵称</button>
</template>
deep
默认机制:通过 watch 监听的 ref 对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执行,需要开启 deep 选项
const state = ref({count: 0})
watch(state, () => console.log('数据变化了'))const changeStateByCount = () => {// 直接修改属性 => 不会触发回调state.value.count++
}
<script setup>
import { ref, watch } from 'vue'// 4. deep 深度监视,默认 watch 进行的是 浅层监视
// const ref1 = ref(简单类型) 可以直接监视
// const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
const userInfo = ref({name: 'zs',age: 18
})const setUserInfo = () => {// 此时修改了 userInfo.value 对象的地址,默认的 watch 才能监视到// userInfo.value = { name: 'ls', age: 19}userInfo.value.age++userInfo.value.name = 'ls'
}watch(userInfo,(newValue) => {console.log('userInfo', newValue)},{deep: true}
)</script><template><div>{{ userInfo }}</div><button @click="setUserInfo">修改userInfo</button>
</template>
精确侦听对象的某个属性
需求:在不开启deep的前提下,侦听 age 的变化,只有 age 变化时才执行回调
<script setup>
import { ref, watch } from 'vue'const userInfo = ref({name: 'zs',age: 18
})const setUserInfo = () => {userInfo.value.age++userInfo.value.name = 'ls'
}// 5. 对于对象中的属性,进行监视
watch(() => userInfo.value.age,(newValue, oldValue) => {console.log('userInfo.age', newValue, oldValue)}
)</script><template><div>{{ userInfo }}</div><button @click="setUserInfo">修改userInfo</button>
</template>
Vue3 的生命周期 API
选项式API | 组合式API |
---|---|
beforeCreate/created | setup |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
<script setup>
import { onMounted } from 'vue'// beforeCreate 和 created 的相关代码
// 一律放在 setup 中执行const getList = () => {setTimeout(() => {console.log('getList')}, 2000)
}
// 一进入页面的请求
getList()// 如果有些代码需要在 mounted 生命周期中执行
onMounted(() => {console.log('mounted生命周期函数')
})
// 写成函数的调用方式,可以调用多次,并不会冲突,而是按照顺序依此执行
onMounted(() => {console.log('mounted生命周期函数2')
})
</script>
组合式API - 父子通信
组合式API下的父传子
基本思想
- 父组件中给子组件绑定属性
- 子组件内部通过 props 选项接收
父组件
<script setup>
// 引入子组件
import sonComVue from './son-com.vue'<template>{/* 绑定属性 message */}<sonComVue message="this is app message">
</template>
子组件
<script setup>
// 通过 defineProps "编译器宏" 接受子组件传递的数据
const props = defineProps({message: String
})
</script><template>{{ message }}
</template>
组合式API下的子传父
基本思想
- 父组件中给子组件标签通过@绑定事件
- 子组件内部通过 emit 方法触发事件
父组件
<script setup>
// 引入子组件
import sonComVue from './son-com.vue'const getMessage = (message) => {console.log(message)
}
</script><template>{/* 绑定事件 changeMessage */}<sonComVue @get-message="getMessage">
</template>
子组件
<script setup>
// 通过 defineEmits 编译器宏生成 emit 方法
const emit = defineEmits(['get-message'])const sendMessage = () => {// 触发自定义事件,并传递参数emit('get-message', 'this is son message')
}
</script><template><button @click="sendMessage">发送消息</button>
</template>
模板引用
通过ref标识获取真实的dom对象或者组件实例对象
<script setup>
import { ref } from 'vue'// 模板引用(可以获取到 dom,也可以获取组件)
// 1. 调用 ref函数,生成一个 ref对象
// 2. 通过 ref标识,进行绑定
// 3. 通过 ref对象.value 即可访问到绑定的元素(必须等到元素渲染完才可以获取到)
const inp = ref(null)
</script><template><div><!-- 通过 ref标识,绑定元素 --><input ref="inp" type="text"></div>
</template>
defineExpose()
默认情况下在 <script setup>
语法糖下组件内部的属性和方法是不开放给父组件访问的。
可以通过 defineExpose
编译宏指定哪些属性和方法允许访问
<script setup>
const cnt = 99
const sayHi = () => {alert('Hi')
}defineExpose({sayHi,cnt
})
</script>
provide & inject
顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信
-
顶层组件通过 provide 函数提供数据
-
底层组件通过 inject 函数获取数据
跨层传递普通数据
顶层组件
provide('key', 'this is top message')
底层组件
const message = inject('key')
跨层传递响应式数据
顶层组件
const count = ref(100)
provide('count-key', count)
底层组件
const count = inject('count-key')
跨层传递方法
顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据
顶层组件
const sayHi = () => {alert('Hi')
}
provide('sayHi-Key', sayHi)
底层组件
const sayHi = inject('sayHi-Key')
sayHi()
defineOptions
背景说明:
- 有
<script setup>
之前,如果要定义 props, emits 可以轻而易举地添加一个与 setup 平级的属性。
setup() {},
props: {},
emits: {}
- 但是用了
<script setup>
后,就没法这么干了 setup 属性已经没有了,自然无法添加与其平级的属性。
为了解决这一问题,引入了 defineProps 与 defineEmits 这两个宏。但这只解决了 props 与 emits 这两个属性。
如果我们要定义组件的 name 或其他自定义的属性,还是得回到最原始的用法——再添加一个普通的 <script>
标签。
这样就会存在两个 <script>
标签。让人无法接受。
<script>
export default {name: "componentName"
}
</script><script setup>
// 这里是不能直接定义 name 的,所以需要两个 <script> 标签
<script>
使用 defineOptions 解决这个问题
<script setup>
// 用于定义组件的选项。它允许你在组件内部以更简洁的方式设置组件的配置项
defineOptions({name: 'componentName',props: {message: String},emits: ['custom-event']
})
</script><template><div>{{ message }}</div>
</template>
defineModel
在Vue3中,自定义组件上使用 v-model
,相当于传递一个 modelValue属性,同时触发 update:modelValue 事件
<Child v-model="isVisible">
<!-- 相当于 -->
<Child :modelValue="isVisible" @update:modelValue="isVisible = $event">
而在 Child
组件中,我们需要先定义 props,再定义 emits 。其中有许多重复的代码。如果需要修改此值,还需要手动调用 emit 函数。
父组件
<script setup>
import MyInput from './my-input.vue'
import { ref } from 'vue'
const txt = ref('123456')
</script><template>
<div><MyInput v-model="txt"></MyInput>{{ txt }}
</div>
</template>
子组件
<script setup>
import { defineProps, defineEmits } from 'vue'const props = defineProps({modelValue: String
})
const emits = defineEmits(['update:modelValue'])</script><template><inputtype="text":value="modelValue"@input="e => emit('update:modelValue', e.target.value)">
</template>
使用 defineModel 简化
<script setup>
import { defineModel } from 'vue'
const modelValue = defineModel()
</script><template>
<div><inputtype="text":value="modelValue"@input="e => modelValue = e.target.value">
</div>
</template>
plate>
子组件
<script setup>
import { defineProps, defineEmits } from 'vue'const props = defineProps({modelValue: String
})
const emits = defineEmits(['update:modelValue'])</script><template><inputtype="text":value="modelValue"@input="e => emit('update:modelValue', e.target.value)">
</template>
使用 defineModel 简化
<script setup>
import { defineModel } from 'vue'
const modelValue = defineModel()
</script><template>
<div><inputtype="text":value="modelValue"@input="e => modelValue = e.target.value">
</div>
</template>