学习笔记-组合式api基础

414 阅读5分钟

问题: 什么叫选项式api和组合式api ? ? ?

组合式api: 我们可以将界面上重复的部分连同其功能一起提取为可重用的代码段. 实现代码的共享和重用

 

作用: 将同一个逻辑关注点相关代码收集在一起。而这正是组合式 API 使我们能够做到的。

白话文: 就是将有关系的data, computed, method, watch通过setUp组织到一起, 便于阅读和理解

setUp组件选项

特点: 新的setup在组件创建之前执行

警告: 尽量避免在setup中使用this , 因为setup的调用在data, method, computed之前, 无法获取到他们

setup方法:

参数: props, context

返回值: 返回的任何内容都可以用于组件的其他部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板

export default {
  props: {
    type: String,
    require: true
  }
  setup(props){
    console.log(props)
    return props  // 这里返回的任何内容都可以用于组件的其余部分
  }
}

实际应用:

一个组件需要一个仓库列表, 并且在用户有任何更改就进行刷新

// 引入一个定义好的接口api (获取用户列表api)
import { fetchUserRepositories } from '@/api/repositories'
// 在组件内部
setup(props){
  // 定义一个空的仓库数组
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }
  return {
    repositories, // 外部需要的仓库里列表
    getUserRepositories // 外部需要的列表更新函数
  }
}

注意: 此刻repositories 变量是非响应式的

 

带ref的响应式变量

在vue3中我们可以使用新的ref函数使任何任何响应式变量在任何地方起作用

import { ref } from 'vue'
const counter = ref(0)

ref接收参数, 并且将其包裹在一个带有value的对象返回

console.log(counter) // {value: 0}
console.log(counter.value) // 0
counter.value++
console.log(conter.value) // 1

将值封装在一个对象中,看似没有必要,但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的。这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的:

pass-by-reference-vs-pass-by-value-animation.gif

 

在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性。

换句话说,ref 为我们的值创建了一个响应式引用。在整个组合式 API 中会经常使用引用的概念。

 

使用ref改进, 创建响应式的repositories 变量

// 引入一个定义好的接口api (获取用户列表api)
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from "vue"
// 在组件内部
setup(props){
  // 定义一个空的仓库数组
  let repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }
  return {
    repositories, // 外部需要的仓库里列表
    getUserRepositories // 外部需要的列表更新函数
  }
}

官方示例:

// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
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,
      getUserRepositories
    }
  },
  data () {
    return {
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

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

 

在setup中注册生命函数钩子

这要归功于 Vue 导出的几个新函数。组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on:即 mounted 看起来会像 onMounted

 

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
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
  }
}

 

watch响应式更改

跟组件调用的watch一样, 我们也可以使用vue导入的watch函数

三个参数:

  • 一个是想要监听的响应式引用或者getter函数
  • 一个回调
  • 可选的配置选项
import { ref, watch} from 'vue'
const counter = ref(0)
watch(counter, (newVal, oldVal) => {
  console.log( counter.value )
})

等效于:

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log(this.counter)
    }
  }
}

官网示例:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// 在我们组件中
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.value)
  }
  onMounted(getUserRepositories)
  // 在 user prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)
  return {
    repositories,
    getUserRepositories
  }
}

你可能已经注意到在我们的 setup 的顶部使用了 toRefs。这是为了确保我们的侦听器能够根据 user prop 的变化做出反应。

 

独立的computed属性

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } 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 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)


  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    // filter 返回数组, 包含了符合条件的所有元素
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })


  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery

总: 对逻辑关注点提取

上述代码使得setup选项变得非常大

这就需要我们在开始其他任务前, 首先将上述代码提取到一个独立的组合函数

 

首先从获取仓库列表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
  }
}

 

然后是搜索功能useRepositoryNameSearch()

// src/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
  }
}

这两个抽成单独的 .js 功能模块

 

接下来就是在组件中如何使用它们了

// src/components/UserRepositories.vue
// 导入获取仓库列表的功能模块的js
import useUserRepositories from '@/composables/useUserRepositories'
// 导入搜索功能模块的js
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: {
      type: String,
      required: true
    }
  },
  setup (props) {
    const { user } = toRefs(props)
    // 直接使用获取仓库列表的方法, 传入user参数
    // 返回仓库列表, 和列表查询方法
    const { repositories, getUserRepositories } = useUserRepositories(user)
    // 直接使用搜索方法, 传入useUserRepositories方法返回的repositories列表
    // 返回查询条件, 和条件查询方法
    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)
    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: repositoriesMatchingSearchQuery,
      getUserRepositories,
      searchQuery,
    }
  },
  data () {
    return {
      filters: { ... }, // 3
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
  },
  methods: {
    updateFilters () { ... }, // 3
  }
}