下面 先从setup Fuction 开始编写Compositon API.
1⃣️ setup
新的 setup
组件选项 在 创建组件之前执行,一旦 props
被解析,并充当合成 API 的入口点。
我们从 setup
返回的所有内容 都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板。
⚠️ setup执行时间:The setup
executes before any of the following options are evaluated:( Components、 Props 、Data、Methods、Computed Properties、Lifecycle methods)
⚠️ 由于在执行 setup
时,尚未创建组件实例,因此在 setup
选项中没有 this
。这意味着,除了 props
之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法。与其他Component选项不同,setup该方法无权访问“ this”。为了获得对属性的访问权限(之前用this获取的),setup
具有两个可选参数。
a、参数
第一个是props
,例如:
// src/components/UserRepositories.vue export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String } }, setup(props) { console.log(props) // { user: '' } return {} // 这里返回的任何内容都可以用于组件的其余部分 } // 组件的“其余部分” }
⚠️ setup
函数中的 props
是响应式的,当传入新的 prop 时,它将被更新。
但是,因为 props
是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。
如果需要解构 prop,可以通过使用 setup
函数中的 toRefs
来安全地完成此操作。
// MyBook.vue import { toRefs } from 'vue' setup(props) { const { title } = toRefs(props) console.log(title.value) }
第二个参数是context
,它可以访问一堆有用的数据:
// MyBook.vue export default { setup(props, context) { // Attribute (非响应式对象) console.log(context.attrs) // 插槽 (非响应式对象) console.log(context.slots) // 触发事件 (方法) console.log(context.emit)
context.parent;
context.root;
}
}
context
是一个普通的 JavaScript 对象,也就是说,它不是响应式的,这意味着可以安全地对 context
使用 ES6 解构。
// MyBook.vue export default { setup(props, { attrs, slots, emit }) { //????attrs
和slots
是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以attrs.x
或slots.x
的方式引用 property。请注意,与props
不同,attrs
和slots
是非响应式的。如果你打算根据attrs
或slots
更改应用副作用,那么应该在onUpdated
生命周期钩子中执行此操作 ... } }
b、访问组件的property
执行 setup
时,组件实例尚未被创建。因此,你只能访问以下 property:props
、attrs、
slots、
emit
换句话说,你将无法访问以下组件选项:data 、
computed、
methods
c、结合模板使用
如果 setup
返回一个对象,则可以在组件的模板中 像传递给 setup
的 props
property 一样 访问该对象的 property:
⚠️ 注意,从 setup
返回的 refs 在模板中访问时是被自动解开的,因此不应在模板中使用 .value
。
<template> <div>{{ readersNumber }} {{ book.title }}</div> </template> <script> import { ref, reactive } from 'vue' export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) // expose to template return { readersNumber, book } } } </script>
d、使用渲染函数
setup
还可以返回一个渲染函数,该函数可以直接使用 在同一作用域中声明的响应式状态:
import { h, ref, reactive } from 'vue' export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) // Please note that we need to explicitly expose ref value here return () => h('div', [readersNumber.value, book.title]) } }
e、使用this
在 setup()
内部,this
不会是该活跃实例的引用
因为 setup()
是在解析其它组件选项之前被调用的,所以 setup()
内部的 this
的行为与其它选项中的 this
完全不同。
这在和其它选项式 API 一起使用 setup()
时可能会导致混淆。
2⃣️、ref函数 & 模版引用
a、ref函数
<template> <div>Capacity: {{ capacity }}</div> </template> <script> import { ref } from "vue"; export default { setup() { const capacity = ref(3); // 基本上,它将原始整数(3)包装在一个对象中,这将使我们能够跟踪更改。以前data()选项 已经将我们的原始(容量)包装在一个对象内。 return { capacity }; //最后一步,我们需要显式返回一个对象,该对象的属性需要模板正确呈现。这个返回的对象是我们如何在中公开需要访问哪些数据的方法renderContext。像这样明确表示有点冗长,但这也是故意的。它有助于长期维护,因为我们可以控制暴露给模板的内容,并跟踪定义模板属性的位置。 } }; </script>
b、模版引用
作为模板使用的 ref 的行为与任何其他 ref 一样:它们是 响应式的,可以传递到 (或从中返回) 复合函数中。
为了获得 对模板内元素或组件实例的引用,我们可以像往常一样声明 ref 并从 setup() 返回。
<template> <div ref="root">This is a root element</div> </template> <script> import { ref, onMounted } from 'vue' export default { setup() { const root = ref(null) onMounted(() => { // DOM元素将在初始渲染后分配给ref console.log(root.value) // <div>这是根元素</div> }) return { root } } } </script>
v-for
中的用法具体地址:https://vue3js.cn/docs/zh/guide/composition-api-template-refs.html
3⃣️、provide/inject
通常父传递给子组件,可用 props。 但是当有深层结构的传递,props就满足不了要求,可以让父组件provide, 子(孙子)组件inject.
可以看成“ long range props”。
位置:两者都只能在当前活动实例的 setup()
期间调用。
vue2中👇
app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide() {
//情况1 return { todoLength: this.todos.length //⚠️ 要访问 组件实例 property,我们需要将provide
转换为 返回对象的函数。 }
//情况2
return {
todoLength: Vue.computed(() => this.todos.length) //如果我们想 对祖先组件中的更改 做出反应。
}
},
template: `
...
`
})
vue3中👇
<!-- src/components/MyMap.vue --> <template> <MyMarker /> </template> <script> import { provide, reactive, readonly, ref } from 'vue' import MyMarker from './MyMarker.vue export default { components: { MyMarker }, setup() { const location = ref('North Pole') //为了使 依赖值和注入值 产生 响应性,在提供值时要使用 reactive,ref 函数. const geolocation = reactive({ longitude: 90, latitude: 135 }) const updateLocation = () => { location.value = 'South Pole' //有时我们需要在 注入数据的组件内部更新注入的数据。在这种情况下,我们建议 提供一个方法来负责改变响应式 property } provide('location', readonly(location)) // provide俩参数,第一个是name(必填),第二个是默认值(可选)。 provide('geolocation', readonly(geolocation)) // 确保通过provide 传递的数据不会被 注入的组件更改, provide('updateLocation', updateLocation) } } </script>
//使用注入 <script> import { inject } from 'vue' export default { setup() { const userLocation = inject('location', 'The Universe') //显式导入,inject函数俩参数:要注入的property的名称(必填);一个默认的值(可选) const userGeolocation = inject('geolocation')const updateUserLocation = inject('updateLocation')
return {
userLocation, userGeolocation,updateUserLocation
}
}
}
</script>
文档地址:https://vue3js.cn/docs/zh/guide/component-provide-inject.html
3⃣️、setup中的钩子
组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on
:即 mounted
看起来像 onMounted
。
4⃣️、watch
响应式更改
它接受 3 个参数:
- 一个响应式引用 或 我们想要侦听的 getter 函数
- 一个回调
- 可选的配置选项
import { ref, watch } from 'vue'
const counter = ref(0)
watch(counter, (newValue, oldValue) => {
console.log('The new counter value is: ' + counter.value)
})
//每当 counter 被修改时 counter.value=5,watch 将触发并执行回调 (第二个参数),在本例中,它将把 'The new counter value is:5' 记录到我们的控制台中
5⃣️、toRefs
toRefs
是 为了确保 侦听器能够对 user
prop 所做的更改 做出反应。
toRefs
是 为了确保 侦听器能够对 user
prop 所做的更改 做出反应。
6⃣️、独立的 computed
属性
与 ref
和 watch
类似,也可以使用 从 Vue 导入的 computed
函数,在 Vue 组件外部创建计算属性。
computed
函数返回一个作为 computed
的第一个参数传递的 getter 类回调的输出的一个只读的响应式引用。
为了访问新创建的计算变量的 value,我们需要像使用 ref
一样使用 .value
property。
import { ref, computed } from 'vue'
const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)
counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2
7⃣️、组合式函数
(现在只有两个功能,获取用户数据➕搜索功能。对于其他的逻辑关注点我们也可以这样做,setup随着功能的增多,会越来越大👇)
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'
// in our component
setup (props) {
// 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
const { user } = toRefs(props)
const repositories = ref([])
const getUserRepositories = async () => {
// 更新 `props.user ` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
// 在用户 prop 的响应式引用上设置一个侦听器
watch(user, getUserRepositories)
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(
repository => repository.name.includes(searchQuery.value)
)
})
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
所以要先将一个功能逻辑的代码提取到一个独立的组合式函数(单独的文件),然后再调用👇。
// 例 :🌰 创建一个独立的组合式函数-> useUserRepositories
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch } from 'vue'
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
onMounted(getUserRepositories)
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
如何使用该函数 👇
// src/components/UserRepositories.vue import { toRefs } from 'vue' import useUserRepositories from '@/composables/useUserRepositories' import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch' import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: { user: { type: String } },
setup(props) {
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const { searchQuery,repositoriesMatchingSearchQuery } = useRepositoryNameSearch(repositories)
const { filters, updateFilters, filteredRepositories } = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
总结: