16-setup()与组合式API-computed-watch
一、setup()
1、基本使用:
setup()
钩子是在组件中使用组合式 API 的入口,通常只在以下情况下使用:
-
需要在非单文件组件中使用组合式 API 时。
-
需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
// 返回值会暴露给模板和其他的选项式 API 钩子
return {
count
}
},
mounted() {
console.log(this.count) // 0
}
}
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
在模板中访问从 setup
返回的 ref 时,它会自动浅层解包,因此你无须再在模板中为它写 .value
。当通过 this
访问时也会同样如此解包。(ref返回的是一个包装对象,但在其他模板中访问setup返回的值时直接访问对象就是具体的值【也就是自动解包】)
setup()
自身并不含对组件实例的访问权,即在 setup()
中访问 this
会是 undefined
。你可以在选项式 API 中访问组合式 API 暴露的值,但反过来则不行。(在setup内部不可以使用this,当你像访问ref或其他响应值需要具体属性名字+.value访问)
2、访问 Props
setup
函数的第一个参数是组件的 props
。和标准的组件一致,一个 setup
函数的 props
是响应式的,并且会在传入新的 props 时同步更新。
export default {
props: {
title: String
},
setup(props) {
console.log(props.title)
}
}
请注意如果你解构了 props
对象,解构出的变量将会丢失响应性。因此我们推荐通过 props.xxx
的形式来使用其中的 props。
如果你确实需要解构 props
对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,那么你可以使用 toRefs() 和 toRef() 这两个工具函数:
import { toRefs, toRef } from 'vue'
export default {
setup(props) {
// 将 `props` 转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// `title` 是一个追踪着 `props.title` 的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
3、Setup 上下文
传入 setup
函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup
中可能会用到的值:
export default {
setup(props, context) {
// 透传 Attributes(非响应式的对象,等价于 $attrs)
console.log(context.attrs)
// 插槽(非响应式的对象,等价于 $slots)
console.log(context.slots)
// 触发事件(函数,等价于 $emit)
console.log(context.emit)
// 暴露公共属性(函数)
console.log(context.expose)
}
}
该上下文对象是非响应式的,可以安全地解构:
export default {
setup(props, { attrs, slots, emit, expose }) {
...
}
}
4、暴露公共属性
expose
函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用(ref)访问该组件的实例时,将仅能访问 expose
函数暴露出的内容:
export default {
setup(props, { expose }) {
// 让组件实例处于 “关闭状态”
// 即不向父组件暴露任何东西
expose()
const publicCount = ref(0)
const privateCount = ref(0)
// 有选择地暴露局部状态
expose({ count: publicCount })
}
}
下面我们来解释一些如何配合ref模板引用来使用
<!-- 子组件代码 -->
<template>
<button @click="increment">Increment</button>
</template>
<script>
import { ref } from 'vue';
export default {
setup(_, { expose }) {
const count = ref(0);
const increment = () => {
count.value++;
};
expose({
count,
increment
});
return {
increment
};
}
};
</script>
<!-- 父组件代码 -->
<template>
<ChildComponent ref="childComp" />
<button @click="incrementChildCount">Increment Child Count from Parent</button>
<p>Child Count from Parent: {{ childComp?.count }}</p>
</template>
<script>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
setup() {
// 定义对象接受子组件expose对象
const childComp = ref(null);
//父组件使用子组件的暴露的方法重新定义新函数
const incrementChildCount = () => {
if (childComp.value) {
childComp.value.increment();
}
};
return {
childComp,
incrementChildCount
};
}
};
</script>
上述值得我们注意的就是子组件ref值,父组件setup定义接受子组件expose对象的对象名,setup返回ref值要一致,也就是上述代码的这三个部分
1、<ChildComponent ref="childComp" />
<script>
2、const childComp = ref(null);
return {
3、 childComp,
incrementChildCount
};
</script>
5、与渲染函数一起使用
setup
也可以返回一个渲染函数,此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态:
import { h, ref } from 'vue'
export default {
setup() {
const count = ref(0)
return () => h('div', count.value)
}
}
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说,这样没有问题,但如果我们想通过模板引用将这个组件的方法暴露给父组件,那就有问题了
我们可以通过调用 expose()
解决这个问题:
import { h, ref } from 'vue'
export default {
setup(props, { expose }) {
const count = ref(0)
const increment = () => ++count.value
expose({
increment
})
return () => h('div', count.value)
}
}
这样既可以使用h渲染函数,也能将想暴露给父组件的属性可以正常返回
二、computed
接受一个 getter 函数,返回一个只读的响应式 ref 对象。该 ref 通过 .value
暴露 getter 函数的返回值。它也可以接受一个带有 get
和 set
函数的对象来创建一个可写的 ref 对象。
1、创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
2、创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
三、watch
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
watch()
默认是懒侦听的,即仅在侦听源发生变化时才执行回调函数。
第一个参数是侦听器的源。这个来源可以是以下几种:
- 一个函数,返回一个值
- 一个 ref
- 一个响应式对象
- …或是由以上类型的值组成的数组
第二个参数是在发生变化时要调用的回调函数。这个回调函数接受三个参数:新值、旧值,以及一个用于注册副作用清理的回调函数。该回调函数会在副作用下一次重新执行前调用,可以用来清除无效的副作用,例如等待中的异步请求。
immediate
:在侦听器创建时立即触发回调。第一次调用时旧值是undefined
。deep
:如果源是对象,强制深度遍历,以便在深层级变更时触发回调。参考深层侦听器。flush
:调整回调函数的刷新时机。参考回调的刷新时机及watchEffect()
。onTrack / onTrigger
:调试侦听器的依赖。参考调试侦听器。once
:回调函数只会运行一次。侦听器将在回调函数首次运行后自动停止。
示例:
1、侦听一个 getter 函数:
const state = reactive({ count: 0 })
watch(
() => state.count,
(count, prevCount) => {
/* ... */
}
)
2、侦听一个 ref:
const count = ref(0)
watch(count, (count, prevCount) => {
/* ... */
})
// 上述第一个参数一定不要是count.value 参数是count监听的是一个响应式对象,参数是count.value监听的是一个静态对象,也就是侦听器不回触发
3、当侦听多个来源时,回调函数接受两个数组,分别对应来源数组中的新值和旧值:
watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
/* ... */
})
4、当使用 getter 函数作为源时,进行深层监听(即监听复杂数据类型)
const state = reactive({ count: 0 })
watch(
() => state,
(newValue, oldValue) => {
// newValue === oldValue
},
{ deep: true }
)