Vue组件,可以将我们重复使用的部分连同其功能,一起提取为可重用的代码段。但如果项目非常大时,几百个组件,就过于不适合了。因此在处理这样的大型应用时,共享、重用代码变得尤为重要。
以下是一段 仓库列表的视图组件代码,同时还拥有搜索和筛选功能:
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data() {
return {
repositories: [], // 1
filters: {}, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories() { ... }, // 3
repositoriesMatchingSearchQuery() { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories() {
// 使用 this.user 获取用户
}, // 1
updateFilters() { ... }, // 3
},
mounted() {
this.getUserRepositories() // 1
}
}
该组件有这样几个功能:
searchQuery 字符串搜索仓库filters 对象筛选仓库当组件变大时,逻辑关注点(同一个功能下的所有逻辑,即一个完整功能的有关处)的列表也会增长,对于那个没有一开始就参与编写组件的人来说,后期的维护比较难搞。于是,组合式 API 的出现就是为了解决这个问题
将同一个逻辑关注点代码收集在一起
在Vue组件中,我们将此位置称为 setup。
**注意:**在setup中应该避免使用 this ,因为它不会找到组件实例。setup 的调用发生在 data property、computed property 或 methods 被解析之前,所以它们无法在 setup 中被获取
setup 选项是一个接收 props 和 context 的函数,将 setup 返回的所有内容都暴露给组件的其余部分(计算属性、方法、生命周期钩子等等)以及组件的模板。
将 setup 选项 添加到 组件中 :
export default {
components: { RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
console.log(props); // { user: '' }
return {}; // 这里返回的任何内容都可以用于组件的其余部分
},
// 组件其余部分
}
第一个逻辑关注点(在原始代码中 标记 1):
相对应的功能点为 1. 从假定的外部 API 获取该用户的仓库,并在用户有任何更改时进行刷新
我们从第一步开始:
import { fetchUserRepositories } from './相应路径';
// 在我们的组件内
setup(props) {
let repositories = [];
const getUserRepositories = async () => {
repositories = await fetchUserRepositories(props.user)
}
return {
repositories,
getUserRepositories // 返回的函数,它的行为将其定义在 methods 选项中的行为相同
}
}
暂时上面的代码还不能生效,因为 repositories 变量是非响应式的,从用户角度看 视图上还是空的
在 Vue3.0 中,我们可以通过一个新的 ref 函数 使用任何响应式变量在任何地方起作用:
import { ref } from 'vue';
const counter = ref(0);
ref 接收参数并将其包裹在一个带有 value property 的对象中返回,然后可以使用该 property 访问或更改响应式变量的值:
import { ref } from 'vue';
const counter = ref(0); // 0-初始值
console.log(counter); // { value: 0 }
console.log(counter.value); // 0
counter.value++;
console.log(counter.value); // 1
将值封装于一个对象中,是很有必要的,因为 Number 或 String 等基本类型是通过值而非引用类型 传递的,这样我们就可以在整个应用中安全地传递它
相当于,ref 为我们的值创建一个响应式引用,在整个组合式API中会经常使用 引用 的概念
有了 ref 这个强大的响应式引用,我们就可以创建一个 repositories 变量:
import { fetchUserRepositories } from './相应路径'
import { ref } from 'vue';
// 组件内
setup(props) {
const repositories = ref([]);
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user);
}
return {
repositories,
getUserRepositories
}
}
这样,就算简单完成,当我们调用 getUserRepositories 时,repositories都将发生变化,视图也会跟着更新以反映变化,这时我们原代码的组件就为:
import { fetchUserRepositories } from '@/相应的路径'
import { ref } from 'vue';
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const repositories = ref([]);
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user);
}
return {
repositories,
getUserRepositoeies
}
},
data() {
return {
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories() { ... }, // 3
repositoriesMatchSearchQuery() { ... } // 2
},
watch() {
user: 'getUserRepositories' // 1
},
mounted() {
this.getUserRepositories(); // 1
}
}
我们已经将第一个逻辑关注点中的“1”的几个部分移动到了 setup 方法中,彼此非常接近。没移进去的,就是在 mounted 钩子中调用 getUserRepositories ,并设置一个监听器,以便在 user prop 发生变化时执行此操作。
为了使 组合式API 的功能和 选项式API 一样完整,我们还需要一种在setup 中注册生命周期钩子的方法。
这归功于Vue导出的几个新函数。组合式API上的生命周期钩子与选项式API的名称相同,但前缀 on: 即mounted 看起来像 onMounted。
在 setup 代码块中体现:
import { fetchUserRepositories } from '@/相应的路径';
import { ref, onMounted } from 'vue';
// 组件内部
setup(props) {
const repositories = ref([]);
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user);
}
onMounted(getUserRepositories); // 在 mounted 时调用 getUserRepositories
return {
repositories,
getUserRepositories
}
}
我们需要监听 user ,随着 user prop 的变化而做出变化,为此,我们将使用独立的 watch 函数。
我们可以在组件中使用 watch 选项并在 user property 上设置侦听器一样,从Vue导入的 watch 函数执行相同的操作,它接收 3 个参数:
代码的体现,快速了解它的工作模式:
import { ref, watch } from 'vue';
const counter = ref(0);
watch(counter, (newVal, oldVal) => {
console.log('The new counter value is: ' + counter.value);
})
每当 counter 被修改时,例如 counter.value=5,侦听将触发并执行回调(第二个参数),在本例中,它将把 The new counter is: 5 记录到控制台中。
以下是等效的选项式 API:
export default {
data() {
return {
counter: 0
}
},
watch: {
counter(newVal, oldVal) {
console.log('The new counter value is: ' + this.counter)
}
}
}
当然,如果你想再了解以下watch的用法,watch的详细用法
代码中体现:
import { fetchUserRepositories } from '@/相应路径';
import { ref, onMounted, watch, toRefs } from 'vue';
// 组件内
setup(props) {
// 使用 toRefs 创建对 props 中的 user property 的响应式引用
const { user } = toRefs(props);
const repositories = ref([]);
const getUserRepositories = async () => {
// 更新 prop.user 到 user.value 访问引用值
repositories.value = await fetchUserRepositories(user.value);
}
onMounted(getUserRepositories);
// 在 user prop 的响应式引用上设置一个侦听器
watch(user, getUserRepositories);
return {
repositories,
getUserRepositories
}
}
这里我们在 setup 顶部使用了 toRefs ,这是为了确保我们的侦听器能够根据 user prop 的变化做出相应的变化
这时,我们第一个逻辑关注点就移到了一个地方。接下来,将第二个关注点执行相同的操作,基于 searchQuery 进行过滤,使用的是 计算属性
与 ref 和 watch 类似,也可以使用从Vue导入的 computed 函数在Vue组件外部创建计算属性,回到 counter 的例子:
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
// 这里先执行 ++, 后执行 computed
这里给 computed 函数传递了一个参数,类似于 getter 的回调函数,输出的是一个只读的响应式引用。为了访问新创建的计算变量的 value ,我们需要像 ref 一样使用 .value property
让我们将搜索功能移到 setup 中:
import { fetchUserRepositories } from '@/相应路径';
import { ref, computed, watch, onMounted, toRefs } from 'vue';
// 组件内
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);
// 在 user porp 的响应式引用上设置一个侦听器
watch(user, getUserRepositories);
const searchQuery = ref('');
const repositoriesMatchSearchQuery = computed(() => {
return repositories.value.filter(
repositories => repository.name.includes(searchQuery.value)
)
});
return {
repositories,
getUserRepositories,
searchQuery,
repositoriesMatchingSearchQuery
}
}
以此类推,对于其他的 逻辑关注点 我们也可以这样做,其实,这就是将 代码移到 setup 选项中,将之前分散的同一个逻辑下的代码 提取到 独立的 组合式函数中,现在进行创建模块,然后引用:
先从创建 useUserRepositories 函数开始:
import { fetchUserRepositories } from '@/相应路径';
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
}
}
搜索功能:
import { ref, computed } from 'vue';
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('');
const repositoriesMathingSearchQuery = computed(() => {
return repositories.value.fliter(repositories => {
return repositories.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
现在我们有两个单独的功能模块,接下来就可以在组件中使用它们,主要是 如何在代码中体现:
import useUserRepositories from '@/相应路径';
import useRepositoryNameSearch from '@/相应路径';
import { toRefs } from 'vue';
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props);
const { repositories, getUserRepositories } = useUserRepositories(user);
const { searchQuery, repositoryNameSearchQuery } = useRepositoryNameSearch(repositories);
}
return {
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery
}
},
data() {
return {
filters: { ... } //3
}
},
computed: {
filteredRepositories() { ... } // 3
},
methods: {
updateFilters() { ... } // 3
}
过滤的逻辑,也按照之前的写法
我们直接跳过过滤,最终模板为:
import { toRefs } from 'vue';
import useUserRepositories from '@/相应路径';
import useRepositoryNameSearch from '@/相应路径';
import useRepositoryFilters from '@/相应路径';
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props);
const { repositories, getUserRepositories } = useUserRepositories(user);
const { searchQuery, repositoryNameSearchQuery } = useRepositoryNameSearch(repositories);
const { filters, updateFilters, filteredRepositories } = useRepositoryFilters(repositoriesMatchingSearchQuery);
}
return {
// 可以在 repositories 名称下暴露过滤后的结果
repositories: filteredRepositories,
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
学完之后,个人推荐先去看 Vue3 的 非兼容 更改,新特性相当于优化,可以在后期学习