组合式API
composition API vs options API
-
vue2 采用的就是
optionsAPI
(1) 优点:
易于学习和使用
, 每个代码有着明确的位置 (例如: 数据放 data 中, 方法放 methods中)(2) 缺点: 相似的逻辑, 不容易复用, 在大项目中尤为明显
(3) 虽然 optionsAPI 可以通过mixins 提取相同的逻辑, 但是也并不是特别好维护
-
vue3 新增的就是
compositionAPI
(1) compositionAPI 是基于 逻辑功能 组织代码的, 一个功能 api 相关放到一起
(2) 即使项目大了, 功能多了, 也能快速定位功能相关的 api
(3) 大大的提升了
代码可读性
和可维护性
-
vue3 推荐使用 composition API, 也保留了options API
即就算不用composition API, 用 vue2 的写法也完全兼容!!
问题小结:optionsAPI
的优缺点是什么? vue3 新增的 compositionAPI
有什么特征? 有什么优势?
体验 composition API
需求: 鼠标移动显示鼠标坐标 x, y
options API 版本
<template>
<!-- 可以有多个根元素 -->
<div>当前鼠标位置</div>
<div>x: {{ mouse.x }}</div>
<div>y: {{ mouse.y }}</div>
<div>当前点击次数:{{ count }}</div>
<button @click="add">点击</button>
</template>
<script>
export default {
// vue2 中采用的是 options API
// 常见的配置项: data created methods watch computed components
data() {
return {
mouse: {
x: 100,
y: 100,
},
count: 0
}
},
mounted() {
document.addEventListener("mousemove", this.mouseMove)
},
// vue2 叫 destroyed
destroyed() {
document.removeEventListener("mousemove", this.mouseMove)
},
methods: {
add() {
this.count++
},
mouseMove(e) {
this.mouse.x = e.clientX
this.mouse.y = e.clientY
}
}
}
</script>
composition API 版本
<template>
<!-- 可以有多个根元素 -->
<div>当前鼠标位置</div>
<div>x: {{ mouse.x }}</div>
<div>y: {{ mouse.y }}</div>
<div>当前点击次数:{{ count }}</div>
<button @click="add">点击</button>
</template>
<script>
import { onMounted, onUnmounted, reactive, ref } from 'vue'
const useCount = () => {
// ---------业务A------------
const count = ref(0)
const add = () => {
count.value++
}
onMounted(() => {
// 多个生命周期,不会被合并
console.log('业务A生命周期');
})
// ---------业务A 结束------------
return { count, add }
}
const useMove = () => {
// -------------业务B-------------
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)
})
// -------------业务B结束-------------
return { mouse }
}
/*
封装
1.先把业务代码完成
2。封装成函数 useMove ,把需要用到的属性/方法等,return { mouse }
3.页面需要使用,就调用封装好的方法 useMove(),拿到返回的数据
4. 把返回的数据,return 给 template 使用
*/
export default {
setup() {
const { count, add } = useCount()
const { mouse } = useMove()
// 业务需要的代码,需要 return 出去, template才能使用
return {
count,
add,
mouse,
}
},
}
</script>
抽离逻辑
function 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
}
function useCount() {
const count = ref(0)
const add = () => {
count.value++
}
return {
count,
add,
}
}
问题小结:optionsAPI
的优缺点是什么? vue3 新增的 compositionAPI
有什么特征? 有什么优势?
optionsAPI:
- 优点:
易于学习和使用
, 每个代码有着明确的位置 - 缺点: 相似的逻辑, 不容易复用
compositionAPI:
- 基于 逻辑功能 组织代码
- 可维护性好!
setup 函数
composition api的使用, 需要配置一个setup 函数
- setup 函数是一个新的组件选项, 作为组件中 compositionAPI 的起点
- 从生命周期角度来看, setup 会在 beforeCreate 钩子函数之前执行
- setup 中不能使用 this, this 指向 undefined
- 在模版中需要使用的数据和函数,需要在
setup
返回。
<template>
<div class="container">
<h1 @click="say()">{{msg}}</h1>
</div>
</template>
<script>
export default {
setup () {
console.log('setup执行了')
console.log(this)
// 定义数据和函数
const msg = 'hi vue3'
const say = () => {
console.log(msg)
}
return { msg , say}
},
beforeCreate() {
console.log('beforeCreate执行了')
console.log(this)
}
}
</script>
reactive 函数
前置说明:
- setup 需要有返回值, 只有返回的值才能在模板中使用
- 默认普通的数据, 不是响应式的
作用: 传入一个复杂数据类型,转换成响应式数据 (返回该对象的响应式代理)
<template>
<h1>hello vue3</h1>
<div>{{ obj }}</div>
<button @click="add">点我+1</button>
</template>
<script>
// 1. 必须要导入,才能使用
import { reactive } from 'vue';
export default {
setup() {
// 2. reactive 包裹起来才是响应式对象
const obj = reactive({
name: 'zs',
age: 20
})
const add = () => {
obj.age++
console.log('obj', obj);
}
return { obj, add }
},
}
</script>
总结: 通常是用来定义响应式 对象数据
问题小结:
- 默认 setup 函数中返回的 普通对象 是响应式的么 ?
- reactive 函数的作用是什么 ?
ref 函数
reactive 处理的数据, 必须是复杂类型, 如果是简单类型无法处理成响应式,此时需要使用 ref 函数!
作用: 对传入的数据(一般简单数据类型),包裹一层对象, 转换成响应式。
- ref 函数接收一个的值, 返回一个ref 响应式对象, 有唯一的属性 value
- 在 setup 函数中, 通过 ref 对象的 value 属性, 可以访问到值
- 在模板中, ref 属性会自动解套, 不需要额外的 .value
- ref函数也支持传入复杂类型,传入复杂类型,也会做响应式处理
<template>
<h1>hello vue3</h1>
<!-- 4. template自动解套,不需要.value -->
<div>{{ money }}</div>
<button @click="add">点我+1000</button>
<button @click="money += 1000">点我+1000</button>
</template>
<script>
/*
1. 先引进 ref
2. 调用 ref ,变成响应式数据
3. ref调用的话,在 script 里面需要 .value
4. template自动解套,不需要.value
*/
// 1. 先引进 ref
import { ref } from 'vue'
export default {
setup() {
// 2. 调用 ref ,变成响应式数据
let money = ref(10000)
console.log('money', money);
const add = () => {
// 3. ref调用的话,在 script 里面需要 .value
money.value += 1000
}
return { money, add }
},
}
</script>
ref 和 reactive 的最佳使用方式:
- 明确的对象,明确的属性,用reactive,其他用 ref
- 建议尽量使用 ref
问题小结:
- 默认 setup 函数中返回的 普通数据 是响应式的吗 ?
- 如果不是,那可以用什么函数包裹起来 ?
- ref 函数会返回 ref对象,在script中使用需要添加 .value吗? 在模板中需要 .value 吗?
script setup语法(★)
script setup是在单文件组件 (SFC) 中使用组合式 API 的编译时语法糖。相比于普通的 script 语法更加简洁
要使用这个语法,需要将 setup
attribute 添加到 <script>
代码块上:
<script setup>
console.log('hello script setup')
</script>
顶层的绑定会自动暴露给模板,所以定义的变量,函数和import导入的内容都可以直接在模板中直接使用
<template>
<h1>{{ money }}</h1>
<button @click="add">点我+1000</button>
</template>
<!-- <script>
import { ref } from 'vue';
export default {
setup() {
let money = ref(10000)
console.log('money', money);
const add = () => {
money.value += 1000
}
return { money, add }
},
}
</script> -->
<!--
需要在 script 加上 setup 的属性,开启语法糖
1. export default 不用自己写
2. setup() 不需要自己写
3. return 的数据,不需要自己写
-->
<script setup>
import { ref } from 'vue';
let money = ref(10000)
console.log('money', money);
const add = () => {
money.value += 1000
}
</script>
案例:显示鼠标案例
使用setup语法完成鼠标案例
<template>
<div>当前鼠标位置</div>
<div>x: {{ mouse.x }}</div>
<div>y: {{ mouse.y }}</div>
<div>当前点击次数:{{ count }}</div>
<button @click="add">点击</button>
</template>
<script setup>
import { onMounted, onUnmounted, reactive, ref } from 'vue';
// -------------业务A------------
const mouse = reactive({
x: 100,
y: 100
})
const mouseMove = (e) => {
mouse.x = e.clientX
mouse.y = e.clientY
}
onMounted(() => {
document.addEventListener('mousemove', mouseMove)
})
onUnmounted(() => {
document.removeEventListener('mousemove', mouseMove)
})
// -------------业务B------------
const count = ref(0)
const add = () => {
count.value++
}
</script>
计算属性computed函数
computed函数调用时, 要接收一个处理函数, 处理函数中, 需要返回计算属性的值
<template>
<div>我今年的年纪 <input type="text" v-model.number="age" /></div>
<div>我明年的年龄 {{ nextAge }}</div>
<div>我后年的年龄 <input type="text" v-model="nextAge2" /></div>
</template>
<script setup>
import { computed, ref } from 'vue';
const age = ref(20)
// 写法1,直接返回数据
const nextAge = computed(() => {
return age.value + 1
})
// 写法2,使用set/get钩子函数,一般应用在 既可以输入,也可以依赖其他数据
const nextAge2 = computed({
get() { return age.value + 2 },
set(val) { age.value = val - 2 },
})
</script>
侦听器watch函数
watch监视, 接收三个参数
1. 参数1: 监视的数据源
2. 参数2: 回调函数
3. 参数3: 额外的配置
<script setup>
import { ref, watch } from 'vue';
const money = ref(10000)
const count = ref(0)
const obj = ref({
name: 'zs',
age: 20
})
// watch监视, 接收三个参数
// 1. 参数1: 监视的数据源
// 2. 参数2: 回调函数
// 3. 参数3: 额外的配置
// 1. 监听单个属性(常见)
// watch(money, (val, oldVal) => {
// console.log('现在的工资', val);
// console.log('原来的工资', oldVal);
// })
// 2. 监听多个数据,使用不多(了解即可)
// watch([money, count], (val, oldVal) => {
// console.log('现在的数据', val);
// console.log('原来的数据', oldVal);
// })
// 3. 监听整个对象的变化(常见)
// watch(obj, (val, oldVal) => {
// console.log('现在的数据', val);
// console.log('原来的数据', oldVal);
// }, { deep: true, immediate: true })
// 4.监听对象的某个属性(了解即可)
// 写法注意: 直接写 obj.value.age,等于只是取值,watch不能建立依赖
// 我们需要的是告诉watch,帮我监听 obj.value.age 的属性 ----> () => obj.value.age
watch(
() => obj.value.age,
(val, oldVal) => {
console.log('现在的数据', val);
console.log('原来的数据', oldVal);
}
)
</script>
<template>
<h1>我的工资 {{ money }}</h1>
<button @click="money += 1000">点我加工资 1000</button>
<h1>点击次数 {{ count }}</h1>
<button @click="count++">点我次数 + 1</button>
<h1>{{ obj.age }}</h1>
<button @click="obj.age++">点我年龄 + 1</button>
</template>
<style scoped>
</style>
生命周期钩子的使用
生命周期函数 vue3 中的生命周期函数, 需要在 setup 中调用
<script setup>
import { onMounted, onUnmounted, onUpdated, ref } from 'vue';
const count = ref(0)
console.log('setup生命周期', document.querySelector('button'))
onMounted(() => {
console.log('onMounted生命周期', document.querySelector('button'))
})
onMounted(() => {
console.log('多个相同生命周期,会依次执行')
})
onUpdated(() => {
console.log('onUpdated生命周期')
})
onUnmounted(() => {
console.log('onUnmounted生命周期')
})
</script>
<template>
<h1>{{ count }}</h1>
<button @click="count++">点我+1</button>
</template>
<style scoped>
</style>
组件通讯-父传子
目标:能够实现组件通讯中的父传子组件通讯
步骤:
- 父组件提供数据
- 父组件将数据传递给子组件
- 子组件通过defineProps进行接收
- 子组件渲染父组件传递的数据
核心代码:
父组件
<script setup>
import { ref } from "vue";
import Son from "./components/Son.vue";
const money = ref(100000)
const car = ref('宝马')
</script>
<template>
<h1>父组件</h1>
<Son :money="money" :car="car"></Son>
<Son :money="money"></Son>
</template>
<style scoped>
</style>
子组件
<script setup>
// vue2的写法
// props:['money','car']
import { computed } from 'vue';
// vue3 父子组件,传递方式是一样的,接受方式需要使用 defineProps
// 1.普通写法
// defineProps(['money', 'car'])
// 2.带上类型和默认值
const props = defineProps({
money: Number,
car: {
type: String,
default: '五菱宏光'
}
})
// 要在 script 使用 props ,需要储存在变量中
console.log('props', props);
const doubleMoney = computed(() => {
return props.money * 2
})
</script>
<template>
<h1>子组件</h1>
<div>{{ money }}</div>
<div>双倍的金钱{{ doubleMoney }}</div>
<div>{{ car }}</div>
</template>
<style scoped>
</style>
注意:如果使用defineProps接收数据,这个数据只能在模板中渲染,如果想要在script中也操作props属性,应该接收返回值。
组件通讯-子传父
目标:能够实现组件通讯中的子传父
步骤:
- 子组件通过defineEmit获取emit对象(因为没有this)
- 子组件通过emit触发事件,并且传递数据
- 父组件提供方法
- 父组件通过自定义事件的方式给子组件注册事件
核心代码
子组件
<script setup>
+ // 子组件接受父组件方法 defineEmits
+ // vue2的方法 this.$emits('changeMoney')
+ const emits = defineEmits(['changeMoney'])
</script>
<template>
+ <button @click="emits('changeMoney', 1000)">点我改变父组件的money</button>
</template>
父组件
<script setup>
import { ref } from "vue";
import Son from "./components/Son.vue";
const money = ref(100000)
const car = ref('宝马')
+ const changeMoney = (val) => {
+ money.value += val
+ }
</script>
<template>
<h1>父组件</h1>
+ <Son :money="money" :car="car" @changeMoney="changeMoney"></Son>
</template>
<style scoped>
</style>
依赖注入 - provide 和 inject
依赖注入, 可以非常方便的实现 跨层级的 组件通信
父组件利用 provide 提供数据
<script setup>
import { provide, ref } from 'vue';
import Son from './components/Son.vue';
const money = ref(10000)
const add = (value) => {
money.value += value
}
// 语法几乎和 localStorage 一样
// 数据 和 方法 传输方式一样
provide('money', money)
provide('add', add)
</script>
<template>
<h1>父组件</h1>
<Son></Son>
</template>
<style scoped>
</style>
子组件可以使用
<script setup>
import { inject } from 'vue';
import GrandSon from './GrandSon.vue';
// 使用 inject 接收数据
const money = inject('money')
console.log('money', money);
// 方法也可以传递数据
const add = inject('add')
</script>
<template>
<h1>我是子组件</h1>
<div>子组件 - {{ money }}</div>
<button @click="add(1000)">点我加你工资1000</button>
<GrandSon></GrandSon>
</template>
<style scoped>
</style>
孙子组件也可以直接使用
<script setup>
import { inject } from 'vue';
// 不用中间商,孙组件也可以直接使用
const money = inject('money')
const add = inject('add')
</script>
<template>
<h1>我是孙组件</h1>
<div>我也想要钱 {{ money }}</div>
<button @click="add(2000)">点我加你工资2000</button>
</template>
<style scoped>
</style>
模板中 ref 的使用
联想之前的 ref 和 $refs, 获取模板的元素(dom元素,组件)
1 创建 ref => const hRef = ref(null)
2 模板中建立关联 => <h1 ref="hRef">钩子函数-----123</h1>
3 使用 => hRef.value
<script setup>
/*
获取模板元素
1. 创建 空 h1Ref
2. h1Ref 和 dom 元素绑定,等 dom 渲染完成(onMounted),就会存放到h1Ref
3. 可以通过 h1Ref 获取到 dom 并修改内容
*/
import { onMounted, ref } from 'vue';
const h1Ref = ref(null)
// 渲染完成后才可以读取到
console.log('setup', h1Ref.value);
onMounted(() => {
console.log('onMounted', h1Ref.value);
})
const change = () => {
h1Ref.value.innerHTML = '我变成了胡哥'
}
</script>
<template>
<h1 ref="h1Ref">Hello vue3</h1>
<button @click="change">点我改变 h1 的 innerHTML</button>
</template>
<style scoped>
</style>
ref操作组件
<script setup>
import { onMounted, ref } from "vue";
import Son from "./components/Son.vue";
// vue2 获取子组件的方法 this.$refs.form.validate()
const son = ref(null)
onMounted(() => {
son.value.sayHi()
})
const sayHi = () => {
son.value.sayHi()
}
</script>
<template>
<h1>Hello vue3</h1>
<Son ref="son"></Son>
<button @click="sayHi">sayHi</button>
</template>
<style scoped>
</style>
需要配合defineExpose
<script setup>
const sayHi = () => {
alert('大家跟我喊,传智播客 yyds')
}
// 父组件想使用子组件的方法,需要子组件主动暴露 defineExpose
// 要先定义完,再暴露,不能写在 sayHi 之前
defineExpose({
sayHi
})
</script>
<template>
<h1>我是子组件</h1>
</template>
<style scoped>
</style>
vue3中废弃了过滤器
vue3.0中不能使用过滤器,直接使用函数进行替代
<script setup>
import dayjs from 'dayjs'
import moment from 'moment'
const now = new Date()
const other = new Date('2020-11-12 12:00:00')
const formatTime = (value) => {
// dayjs 和 moment 语法基本一样
// return dayjs(value).format('YYYY-MM-DD hh:mm:ss')
return moment(value).format('YYYY-MM-DD hh:mm:ss')
}
</script>
<template>
<h3>{{ formatTime(now) }}</h3>
<h3>{{ formatTime(other) }}</h3>
<hr />
</template>
<style scoped>
</style>
补充-toRefs 函数
使用场景: 如果对一个响应数据, 进行解构 或者 展开, 会丢失他的响应式特性!
原因: vue3 底层是对 对象 进行监听劫持
作用: 对一个响应式对象的所有内部属性, 都做响应式处理
- reactive/ref的响应式功能是赋值给对象的, 如果给对象解构或者展开, 会让数据丢失响应式的能力
- 使用 toRefs 可以保证该对象展开的每个属性都是响应式的
<template>
<div>{{ money }}</div>
<div>{{ car }}</div>
<div>{{ name }}</div>
<button @click="money++">改值</button>
</template>
<script setup>
import { reactive, ref, toRefs } from 'vue'
const user = ref({
name: 'zs',
age: 18,
})
const { name, age } = toRefs(user.value)
</script>
问题小结: toRefs 函数的作用是什么 ?
作用: 对一个 响应式对象 的所有内部属性, 都做响应式处理, 保证展开或者解构出的数据也是响应式的
\