• Vue3中组合式API(简单用法)


    推荐先熟悉一下 Vue组件基础知识Vue组件注册

    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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    该组件有这样几个功能:

    1. 从假定的外部 API 获取该用户的仓库,并在用户有任何更改时进行刷新
    2. 使用 searchQuery 字符串搜索仓库
    3. 使用 filters 对象筛选仓库

    当组件变大时,逻辑关注点(同一个功能下的所有逻辑,即一个完整功能的有关处)的列表也会增长,对于那个没有一开始就参与编写组件的人来说,后期的维护比较难搞。于是,组合式 API 的出现就是为了解决这个问题

    组合式 API 基础

    将同一个逻辑关注点代码收集在一起

    在Vue组件中,我们将此位置称为 setup

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    第一个逻辑关注点(在原始代码中 标记 1):

    相对应的功能点为 1. 从假定的外部 API 获取该用户的仓库,并在用户有任何更改时进行刷新

    我们从第一步开始:

    • 仓库列表
    • 更新仓库列表的函数
    • 返回列表和函数,以便其他组件选项可以对它们进行访问
    import { fetchUserRepositories } from './相应路径';
    
    // 在我们的组件内
    setup(props) {
        let repositories = [];
        const getUserRepositories = async () => {
            repositories = await fetchUserRepositories(props.user)
        }
        
        return {
            repositories,
            getUserRepositories  // 返回的函数,它的行为将其定义在 methods 选项中的行为相同
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    暂时上面的代码还不能生效,因为 repositories 变量是非响应式的,从用户角度看 视图上还是空的

    带 ref 的响应式变量

    在 Vue3.0 中,我们可以通过一个新的 ref 函数 使用任何响应式变量在任何地方起作用:

    import { ref } from 'vue';
    
    const counter = ref(0);
    
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    将值封装于一个对象中,是很有必要的,因为 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    这样,就算简单完成,当我们调用 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

    我们已经将第一个逻辑关注点中的“1”的几个部分移动到了 setup 方法中,彼此非常接近。没移进去的,就是在 mounted 钩子中调用 getUserRepositories ,并设置一个监听器,以便在 user prop 发生变化时执行此操作。

    在setup 内注册生命周期钩子

    为了使 组合式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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    我们需要监听 user ,随着 user prop 的变化而做出变化,为此,我们将使用独立的 watch 函数。

    watch 响应式更改

    我们可以在组件中使用 watch 选项并在 user property 上设置侦听器一样,从Vue导入的 watch 函数执行相同的操作,它接收 3 个参数:

    • 一个想要侦听的响应式引用或 getter 函数
    • 一个回调
    • 可选的配置选项

    代码的体现,快速了解它的工作模式:

    import { ref, watch } from 'vue';
    
    const counter = ref(0);
    watch(counter, (newVal, oldVal) => {
        console.log('The new counter value is: ' + counter.value);
    })
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    每当 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)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    当然,如果你想再了解以下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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    这里我们在 setup 顶部使用了 toRefs ,这是为了确保我们的侦听器能够根据 user prop 的变化做出相应的变化

    这时,我们第一个逻辑关注点就移到了一个地方。接下来,将第二个关注点执行相同的操作,基于 searchQuery 进行过滤,使用的是 计算属性

    独立的 computed 属性

    refwatch 类似,也可以使用从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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里给 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    以此类推,对于其他的 逻辑关注点 我们也可以这样做,其实,这就是将 代码移到 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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    搜索功能:

    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
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    现在我们有两个单独的功能模块,接下来就可以在组件中使用它们,主要是 如何在代码中体现:

    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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    过滤的逻辑,也按照之前的写法

    最终模板

    我们直接跳过过滤,最终模板为:

    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
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    学完之后,个人推荐先去看 Vue3 的 非兼容 更改,新特性相当于优化,可以在后期学习

  • 相关阅读:
    应用--WebApplication
    Effctive C++
    idea Springboot闲置物品交易平台VS开发mysql数据库web结构java编程计算机网页源码maven项目
    Spark 3.0 - 7.LR 多分类实现影评预测电影评分与指标评测
    Go微服务: redis分布式锁
    ARM64 linux并发与同步之经典自旋锁
    命令的历史管理
    MYSQL 基本操作 (2)
    Spring-AOP和事务管理
    【云原生】容器编排K8S
  • 原文地址:https://blog.csdn.net/weixin_45663702/article/details/126059556