在学习 Composition API 时,官方有个深入骨髓的小栗子,如果你没有看过也没关系,此文章对此精简和更具代码视觉的补充及说明如何合理使用。
例如有一个显示某用户的仓库列表视图。此外还有一个搜索列表功能。
Vue2 Options API
UserRepositories.vue
<template>
<div>
<RepositoriesSearch v-model="searchQuery" />
<RepositoriesList :list="repositoriesMatchingSearchQuery" />
</div>
</template>
<script>
import RepositoriesSearch from "./components/RepositoriesSearch.vue";
import RepositoriesList from "./components/RepositoriesList.vue";
import { fetchUserRepositories } from "./api/repositories";
export default {
components: { RepositoriesSearch, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
data() {
return {
repositories: [], // 1
searchQuery: "" // 2
};
},
computed: {
repositoriesMatchingSearchQuery() {
return this.repositories.filter(repository =>
repository.name.includes(this.searchQuery)
);
} // 2
},
mounted() {
this.getUserRepositories(); // 1
},
watch: {
user: "getUserRepositories" // 1
},
methods: {
async getUserRepositories() {
this.repositories = await fetchUserRepositories(this.user); // 使用 `this.user` 获取用户仓库
} // 1
}
};
</script>
Vue3 Composition API
第一步, 在setup中提取列表逻辑
UserRepositories.vue
<template>
<div>
<RepositoriesSearch v-model:value="searchQuery" />
<RepositoriesList :list="repositoriesMatchingSearchQuery" />
</div>
</template>
<script>
import RepositoriesSearch from "./components/RepositoriesSearch.vue";
import RepositoriesList from "./components/RepositoriesList.vue";
import { fetchUserRepositories } from "./api/repositories";
import { ref } from "vue";
export default {
components: { RepositoriesSearch, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const repositories = ref([]);
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(props.user);
};
return {
repositories,
getUserRepositories // 返回的函数与方法的行为相同
};
},
data() {
return {
searchQuery: "" // 2
};
},
computed: {
repositoriesMatchingSearchQuery() {
return this.repositories.filter(repository =>
repository.name.includes(this.searchQuery)
);
} // 2
},
mounted() {
this.getUserRepositories(); // 1
},
watch: {
user: "getUserRepositories" // 1
}
};
</script>
第二步,在setup中使用响应式porps、mounted、watch、compute
UserRepositories.vue
<template>
<div>
<RepositoriesSearch v-model:value="searchQuery" />
<RepositoriesList :list="repositoriesMatchingSearchQuery" />
</div>
</template>
<script>
import RepositoriesSearch from "./components/RepositoriesSearch.vue";
import RepositoriesList from "./components/RepositoriesList.vue";
import { fetchUserRepositories } from "./api/repositories";
import { ref, onMounted, toRefs, watch, computed } from "vue";
export default {
components: { RepositoriesSearch, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
// 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
const { user } = toRefs(props);
const repositories = ref([]);
const getUserRepositories = async () => {
// 更新 `prop.user` 到 `user.value` 访问引用值
repositories.value = await fetchUserRepositories(user);
};
onMounted(getUserRepositories); // 在 `mounted` 时调用 `getUserRepositories`
// 在 user 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
};
}
};
</script>
至此,已将原功能逻辑全部移植到setup中。但请不要怀疑它就是这样即 Vue一把梭。
一把梭是有它的优点的,它会使我们的逻辑更加集中,不至于像 OptionsAPI 那样逻辑间上下跳转才得以阅读。
如果不是第一个开发人员定会发懵。
如何更加合理的使用 Composition API(一把梭) 请往下看!
更合理使用 Composition API
如何合理,那我们就要把各个逻辑块独立封装提取,提取之后的方法体叫 组合式函数。
在项目中我们把它们放入约定的 composables 文件夹中
对上述两个逻辑单元进行封装提取如下:
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
};
}
composables/useRepositoryNameSearch.js
import { ref, computed } from "vue";
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref("");
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value);
});
});
return {
searchQuery,
repositoriesMatchingSearchQuery
};
}
之后的视图:
UserRepositories.vue
<template>
<div>
<RepositoriesSearch v-model:value="searchQuery" />
<RepositoriesList :list="repositories" />
</div>
</template>
<script>
import RepositoriesSearch from "./components/RepositoriesSearch.vue";
import RepositoriesList from "./components/RepositoriesList.vue";
import useUserRepositories from "./composables/useUserRepositories";
import useRepositoryNameSearch from "./composables/useRepositoryNameSearch";
import { toRefs } from "vue";
export default {
components: { RepositoriesSearch, RepositoriesList },
props: {
user: {
type: String,
required: true
}
},
setup(props) {
const { user } = toRefs(props);
const { repositories, getUserRepositories } = useUserRepositories(user);
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories);
return {
// 因为我们并不关心未经过滤的仓库
// 我们可以在 `repositories` 名称下暴露过滤后的结果
repositories: repositoriesMatchingSearchQuery,
getUserRepositories,
searchQuery
};
}
};
</script>
项目结构差异
v2项目结构
v3项目结构
总结
10年前, 一帮年轻人用着Sublime、HBiuder, 戴着有线耳机,梭着一手当下最流行的jQuery, 完全不顾中年人的感受! 10年后, 一帮中年人用着WebStrom、VSCode, 戴着无线耳机,梭着当下最流行的 组合式API,完全不顾年轻人的感受! 思来想去,其实这帮还是原班人马啊, 时光兜兜转转, 仿佛 jQuery 就在昨日!
扶我起来老夫还能继续梭!