
当一个组价越来越大的时候,data、methods、computed、directives、mixin 等混杂在一起。里面的功能是混杂的,在一堆混杂的逻辑里面查找如 name 相关的逻辑的话,就会有一定的困难。这样的话会造成可维护性降低。
Composition API 就是解决这个问题的有效方案。
1. setup 函数的使用setup 函数是在 created 时候(实例被初始化前)执行的。里边返回数据或者方法,插值函数可以检测到。
const app = Vue.createApp({
template: `
hello world
name: {{ name }}, age: {{ age }}
`,
setup(props, context) {
return {
name: "sjh",
age: 28,
handleClick: () => {
alert(123)
}
}
}
})
app.mount("#root")
因为是实例初始化前执行,因此 setup 里不能使用 this 访问到实例。
setup 函数里有两个参数,第一个是 props,指组件从父组件那儿接收到的参数,第二个是 context,暴露了 attrs、slot、emit 等实用方法。
上面的数据
2. ref,reactive 响应式引用的用法和原理上面看着很合理,但是 name 和 age 都是非响应式的,即数据改变并不会触发渲染。
如何解决问题呢?现在需要响应式的引用:通过 proxy 对数据进行封装。当数据变化时,触发模板等内容的更新。
后面的例子都是通过一秒后修改 name 的值来查看是否有响应式。如果页面数据没有改变,说明没有响应式。
2.1 ref:响应式封装基础类型数据使用 ref 函数,如 ref("sjh"),对基础类型的数据进行封装,变成 proxy({value: "sjh"}) 这样的一个响应式引用。
const { ref } = Vue;
const app = Vue.createApp({
template: `
name: {{ name }}, age: {{ age }}
`,
setup(props, context) {
const name = ref("sjh")
const age = ref(28)
// 一秒后,name 的值改变
setTimeout(() => {
name.value = "hhhhh"
}, 1000)
return { name, age }
}
})
app.mount("#root")
改成响应式引用后,数据变化 ,页面上的内容也会相应改变。
2.2 reactive:响应式封装非基础类型数据使用 reactive 函数,将非基础类型数据转化为响应式的。一般就是对象或者数组。
原理: reactive({ name: "sjh" }) 转化成 proxy({ name: "sjh" })
const { ref, reactive } = Vue;
const app = Vue.createApp({
template: `
name: {{ obj.name }}, age: {{ obj.age }}
`,
setup(props, context) {
const obj = reactive({ name: "sjh", age: 20 })
setTimeout(() => {
obj.name = "hhhhh"
}, 1000)
return { obj }
}
})
app.mount("#root")
2.3 readonly:数据变成只读形式
用 readonly 包裹响应式引用,该响应式数据变成只读,即不可修改该数据。
2.4 toRefs:解构时不丢失响应使用过对象解构的同学会觉得插值函数里的写法好麻烦啊,所以会用对象解构。但是这里需要注意的一点,如果直接使用结构后的值,这些值不是响应式的。
解构会将对象或数组解构成普通的数据,丢失了响应。因此解构之前需要用 toRefs 包裹一下,使解构后所有值变成响应式的。
原理:toRefs({ name: "sjh", age: 20 }) 转化成 { name: proxy({ value: "sjh" }), age: proxy({ value: 20 }) },将内部的所有数据变成响应式的。现在使用解构后就不会丢失响应式了。
const { ref, reactive, toRefs } = Vue;
const app = Vue.createApp({
template: `
name: {{ name }}, age: {{ age }}
`,
setup(props, context) {
const obj = reactive({ name: "sjh", age: 20 })
setTimeout(() => {
obj.name = "hhhhh"
}, 1000)
const { name, age } = toRefs(obj)
return { name, age }
}
})
app.mount("#root")
3. toRef 以及 context 参数
3.1 toRef:从响应式对象中获取单个响应式数据
有一种使用场景,当解构的时候并非解构响应式对象里所拥有的 key,则解构出来的数据没有响应式,值为 undefined。
如下代码:data 里边只有 name,但是想解构 age 出来,此时前端页面会因为 age 是 undefined 而报错。
const { ref, reactive, toRefs } = Vue;
const app = Vue.createApp({
template: `
{{ age }}
`,
setup(props, context) {
const data = reactive({ name: "sjh" })
const { age } = toRefs(data)
setTimeout(() => {
age.value = 20
}, 1000)
return { age }
}
})
app.mount("#root")
toRef 就可以有效地解决问题。
语法:toRef(data, 'age'),意思是,尝试从 data 里取 age 数据,如果没找到,则返回一个值为 null 的响应式数据。
const { ref, reactive, toRefs, toRef } = Vue;
const app = Vue.createApp({
template: `
{{ age }}
`,
setup(props, context) {
const data = reactive({ name: "sjh" })
const age = toRef(data, "age")
setTimeout(() => {
age.value = 20
}, 1000)
return { age }
}
})
app.mount("#root")
3.2 context 参数的意义
在上文已经有稍微讲一下 context 了,在这里深入讲一下。
之前说过,context 里边有三个值,分别为 attrs,slots,emit。
attrs:为父组件传过来的 non-props,即没有通过 props 接收的属性。
slots:父组件传递过来的插槽内容。例如,通过 slots.default() 可以获取默认插槽的虚拟 DOM 形式。
例如,想玩些花的,用 h 函数来渲染通过父组件传过来的默认插槽:
const app = Vue.createApp({
template: `
parent
`,
})
app.component("child", {
setup(props, context) {
const data = reactive({ name: "sjh" })
const { name } = toRefs(data)
// return { name }
const { attrs, slots, emit } = context
return () => Vue.h('div', {}, slots.default())
}
})
app.mount("#root")
如果不使用 Composition API,就需要用 this.$slots.default() 来获取默认插槽
emit:触发自定义事件的方法。
例如,子组件点击的时候,触发父组件的自定义事件:
const app = Vue.createApp({
template: `
parent
`,
setup(props) {
const handleChange = () => alert("change")
return { handleChange }
}
})
app.component("child", {
template: `
child
`,
setup(props, context) {
const { attrs, slots, emit } = context
const handleClick = () => emit("change")
return { handleClick }
}
})
app.mount("#root")
3. 示例:使用 Composition API 开发 todolist
现在写一个 todolist 来感受一下 Composition API 的优势:
const { ref, reactive, toRefs, toRef } = Vue;
// 关于 list *** 作的内容进行了封装
const listRelativeEffect = () => {
const list = reactive([])
const addItemToList = (item) => {
list.push(item)
}
return { list, addItemToList }
}
// 关于输入框 *** 作的内容进行了封装
const inputRelativeEffect = () => {
const inputValue = ref("")
const handleInputValueChange = (e) => {
inputValue.value = e.target.value
}
return { inputValue, handleInputValueChange }
}
const app = Vue.createApp({
template: `
{{ item }}
`,
setup(props) {
const { list, addItemToList } = listRelativeEffect()
const { inputValue, handleInputValueChange } = inputRelativeEffect()
return {
list,
addItemToList,
inputValue,
handleInputValueChange,
}
}
})
app.mount("#root")
使用了 Composition API 后,相关的逻辑可以分离出来,使得代码可维护性和可读性都有了很大的提升。
4. computed 方法生成计算属性之前在 vue 基础中已经学了传统的 computed 语法了,现在讲一下 Composition API 里的计算属性。
4.1 基本使用实现一个简单的计数器,并且有一行计算属性是计数的结果乘以 2 的结果:
const { ref, computed } = Vue;
const app = Vue.createApp({
template: `
{{ count }} --
{{ countAddFive }}
`,
setup(props) {
const count = ref(0)
const handleClick = () => {
count.value += 1
}
const countAddFive = computed(() => {
return 2 * count.value
})
return { count, countAddFive, handleClick }
}
})
app.mount("#root")
可以看到,在 Composition API 中,需要先引入 computed 函数,然后正常使用即可。
4.2 进阶使用其实更详细的,computed 方法接收一个对象,里面接收 get 和 set 两个属性。
get 和 set 的值均为方法。get 方法返回提供的数据的值,set 方法用来处理属性设置的时候要执行的相应逻辑。
直接上代码,看的更清晰:
const app = Vue.createApp({
template: `
{{ count }} --
{{ countAddFive }}
`,
setup(props) {
const count = ref(0)
const handleClick = () => {
count.value += 1
}
const countAddFive = computed({
// 获取到的值时存储的 count 的值加上 5
get: () => count.value + 5,
// 设置 count 的值的时候把传进来的值减了 5
set: (param) => count.value = param - 5,
})
return { count, countAddFive, handleClick }
}
})
5. watch 和 watchEffect 的使用和差异性
5.1 watch 基本用法
Composition API 里提供了 watch 方法,和传统的 watch 方法类似,写在 setup 函数里。
Composition API 中使用 watch 的方式:
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
可以看到 watch 方法接收两个参数,第一个参数指的是要监听的数据,第二个参数是监听到数据变化后的回调。回调函数有两个参数,第一个参数是数据改变后的值,第二个参数是数据改变前的值。
可以看出,watch 有两个特性:
具备一定的惰性,只有在数据变化的时候才会执行参数可以拿到改变前和改变后的值 5.2 watch 监听非响应式数据注意:监听的数据必须具有响应式,或者是个有返回值的函数
如 reactive 返回的响应式对象,里边的属性值时没有响应式的,如果要监听里面的单个属性而非整个响应式对象,就需要写成函数,或者将属性变成响应式。
解法一:写成函数
const { ref, watch, reactive } = Vue;
const app = Vue.createApp({
template: `
Name:
Name is {{ nameObj.name }}
`,
setup(props) {
const nameObj = reactive({ name: "sjh" })
watch(() => nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
return {
nameObj
}
}
})
app.mount("#root")
解法二:将属性变成响应式
const { ref, watch, reactive, toRefs } = Vue;
const app = Vue.createApp({
template: `
Name:
Name is {{ name }}
`,
setup(props) {
const nameObj = reactive({name: "sjh"})
const { name } = toRefs(nameObj)
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
})
return {
name
}
}
})
app.mount("#root")
5.3 watch 监听多个数据
监听多个数据的时候,没必要重复写,写成数组复用即可。
const { name, age } = toRefs(data)
watch([name, age], ([curName, curAge], [prevName, prevAge]) => {
console.log([curName, curAge], [prevName, prevAge])
})
5.4 watchEffect 基本用法
watchEffect 侦听器相比于 watch 侦听器,更偏向于 effect。
watchEffect 和 watch 的区别 只要写了 watchEffect,回调就立即执行,没有惰性watchEffect 会自己感知内部的代码数据是否因为相关依赖发生了改变,如果发生了改变就会执行,因此不需要传递很多参数。watch 需要通过参数指定监听哪个数据。watchEffect 无法获取之前的数据和当前的数据。watch 可以获取之前和当前的数据。watchEffect(() => {
// 当 nameObj.name 发生改变,会触发 watchEffect 的回调函数
console.log(nameObj.name)
})
暂停 watchEffect
例如,五秒后结束监听
const stop = watchEffect(() => {
console.log(nameObj.name)
setTimeout(() => {
stop()
}, 5000)
})
watch 暂停监听的语法和 watchEffect 一模一样
watch 立即执行设置参数 immediate: true 即可
const stop = watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue)
}, {
immediate: true
})
6. 生命周期钩子
现在用 Composition API 来写生命周期钩子,直接摘官网的:
| 选项式 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 |
选项式 API 就是 vue2 的那种 API,因为 vue 组件是通过对象内的配置新建出来的。
因为 setup 函数执行的时间就在 beforeCreate 和 created 之间,因此 Composition API 没有提供响应的钩子。
export default {
setup() {
// mounted
onMounted(() => {
console.log('Component is mounted!')
})
}
}
7. Provide, inject, 模板 Ref 的用法
provide 和 inject
provide 提供了可跨层级的数据的提供和获取。在 Composition API 里是这样使用的:
const app = Vue.createApp({
setup(props) {
const { provide } = Vue
provide("name", ref("sjh"))
},
template: `
`
})
app.component("child", {
setup(props) {
const { inject } = Vue
// 第二个参数是未接收到 name 时的默认值
const name = inject("name", "defaultName")
return {
name
}
},
template: `{{ name }}`
})
用 provide 和 inject 进行子组件通知父组件
因为单向数据流的原则,子组件不能直接修改父组件的数据,因此是通过子组件通知父组件,然后父组件本身来修改自己的数据。原理和以前的 emit 类似,见下面的代码:
const app = Vue.createApp({
setup(props) {
const { provide, ref, readonly } = Vue
const name = ref("name before change")
provide("name", readonly(name))
provide("changeName", (value) => {
name.value = value
})
},
template: `
`
})
app.component("child", {
setup(props) {
const { inject } = Vue
const name = inject("name")
// 获取父组件方法
const changeName = inject("changeName")
// 当点击的时候,通知父组件进行修改
const handleClick = () => {
changeName("changed name")
}
return {
name,
handleClick
}
},
template: `{{ name }}`
})
模板 ref 用法
这里的 ref 是获取 dom 节点,和之前的响应式 ref 冲突了。这里有个固定的写法:const nodeName = ref(null),此时会根据声明的变量名去 template 上寻找名称匹配的 ref 属性。
const app = Vue.createApp({
setup(props) {
const { ref, onMounted } = Vue
// 获取 ref 为 hello 节点
const hello = ref(null)
onMounted(() => {
console.log(hello.value)
})
return {
hello
}
},
template: `
hello world
`
})
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)