Vue3.0 Composition API 介绍

vue3.0最近发布了,可是好像还没看到有中文的文档,于是本人根据英文的文档进行了简单的翻译,仅仅是想提高自己对文档的理解,同时也作个记录,将会缓慢更新,相信官方很快会推出正式的中文文档...... 如果发现文中哪里的翻译、描述不太对的,欢迎留言哈~~

 

Introduction

为什么要用Composition API?

Note:

阅读此部分内容之前,你需要先熟悉vue的基础以及如何创建组件

通过使用vue组件,我们可以将接口中的某些重复的部分,以合适的方式组合成可复用的代码,而使得我们的应用更具可维护性和灵活性。但当我们的应用变得越来越庞大时,例如调用了上百个组件,仅仅靠这是还不够的。在处理这种庞大的应用时,共享、复用代码变得尤为重要。

设想一下,我们要在一个应用中展示某个用户的仓库列表,并且要有搜索和过滤的能力。这个应用可能长这样:

// src/components/UserRepositories.vue

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  data () {
    return {
      repositories: [], // 1
      filters: { ... }, // 3
      searchQuery: '' // 2
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
    repositoriesMatchingSearchQuery () { ... }, // 2
  },
  watch: {
    user: 'getUserRepositories' // 1
  },
  methods: {
    getUserRepositories () {
      // using `this.user` to fetch user repositories
    }, // 1
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

 这个组件要实现这几个功能:

  1. 从外部的api中根据user name获取到仓库数据,并且当user变动时,刷新数据;
  2. 通过一个searchQuery字符串搜索仓库数据;
  3. 通过一个filters对象过滤仓库数据

通过vue组件原有的组件选项(data,computed,methods,watch)来组织逻辑在大部分场合或许已经够用。然而,随着组件变大,当中的逻辑关注点也会增多,这会使得组件难以被理解,尤其是对于组件开发者以外的人。

Vue3.0 Composition API 介绍_第1张图片

上图是一个vue组件的示例,里面的逻辑关注点通过颜色进行了分组。如此的碎片化使得组件难以理解,并且复杂,底层的逻辑关注点被分离的选项所遮掩。当我们要处理某个逻辑关注点时,我们就要不断地上下滚动,这就非常不方便了。因此我们希望可以把与某个逻辑关注点相关的代码放到一起,而这正式composition api所能够给我们带来的。

Composition API的基本

既然已经了解我们为什么要用composition api,那应该怎样用呢?首先,我们需要一个能够使用它的地方。在vue组件中,这个地方叫setup。

setup组件选项

setup是在props解析完成后、组件created之前被执行,作为composition api的入口。

Warm:

由于setup被执行的时候,组件示例还没有创建完成,因此在setup里面没有this。这意味着除了props,你无法使用组件内定义的其他任何属性,包括本地状态、计算属性和方法等。

选项setup是一个方法,它接收props和context作为参数,这我们稍后再作讨论。另外,在setup里面return的内容将会暴露给这个组件的除setup以外的剩下的部分以供调用,例如计算属性、方法、生命周期钩子等,以及组件的template模板。

现在我们将setup添加到组件之中:

// src/components/UserRepositories.vue

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup(props) {
    console.log(props) // { user: '' }

    return {} // anything returned here will be available for the rest of the component
  }
  // the "rest" of the component
}

现在我们进行第一步:提取第一个逻辑关注点(原始片段中标记为“1”的部分)。

  1. 从一个外部接口中获取某用户的仓库数据,并且当用户改变时刷新这个数据。

我们将从显而易见的部分开始:

  • 仓库列表
  • 更新仓库列表的方法
  • 返回这个列表与方法,使得它们能够被其他组件选项调用
// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'

// inside our component
setup (props) {
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories // functions returned behave the same as methods
  }
}

这是我们的起点,但它还不会正常运作,因为变量respositories不是响应式的。这意味着对于用户来说,仓库列表还是空的,我们现在来修复它。

通过ref创建的响应式变量

在vue3.0,我们可以在任何地方通过ref方法创建响应式变量,像这样:

import { ref } from 'vue'

const counter = ref(0)

ref接收参数并将这个参数封装成一个对象,这个对象里有一个value属性,该属性可被用来获取或改变这个响应式变量的值:

import { ref } from 'vue'

const counter = ref(0)

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

counter.value++
console.log(counter.value) // 1

将一个值封装在一个对象中,看似是没必要的,但要在拥有众多不同数据的javascript中保持表现一致,这是有需要的。这是因为在javascript中,基本数据类型(如Number,String)是通过值进行传递的,而非引用。

Vue3.0 Composition API 介绍_第2张图片

 

将值封装在对象中,使得我们可以安全地在整个应用中传递,而不必担心在传递的过程中失去响应式的特性。

Note: 

换一种说法,ref给我们的值带来响应式的引用,使用引用的概念将在通篇对composition api的描述中经常用到。

回到我们的例子,我们将创建一个响应式变量repositories:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories
  }
}

完成了!无论我们什么时候调用getUserRepositories,都会改变repositories,并且视图将会更新来反映这个改变。我们的组件现在看起来是这样的:

// src/components/UserRepositories.vue
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  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的方法,以及要设置一个watcher,在user prop改变时调用这个方法。

我们将利用生命周期钩子来开始。

在setup内注册生命周期钩子

与option api相比,为了使得composition api的特性更加齐全,我们也需要在setup内部注册生命周期钩子。这也许要归功于几个可以从vue中导出的新方法。composition api中的生命周期钩子的名字与option api中的相同,除了多了个前缀on。例如,option api中的mounted,在composition api中叫做onMounted。

这些方法接收一个回调函数,这些回调函数将在组件调用生命周期钩子的时候被执行。

现在我们将这些方法放到setup中:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

现在我们需要在user prop改变时作出响应。为了实现这个,我们将用到一个独立的watch方法。

通过watch来应对改变

Just like how we set up a watcher on the user property inside our component using the watch option, we can do the same using the watch function imported from Vue. It accepts 3 arguments:就像在组件中利用watch选项来建立一个user的侦听器一样,我们通过从vue中导入的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,这个侦听器就会被触发,并且调用回调函数(第二个参数)。在上面的例子中,将会在控制台输出“The new counter value is: 5”。

下面的options api的代码与上例是等价的:

export default {
  data() {
    return {
      counter: 0
    }
  },
  watch: {
    counter(newValue, oldValue) {
      console.log('The new counter value is: ' + this.counter)
    }
  }
}

想了解更多关于watch的细节,请参考in-depth指南

现在来看看怎么将它运用到我们的例子中:

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'

// in our component
setup (props) {
  // using `toRefs` to create a Reactive Reference to the `user` property of props
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // update `props.user` to `user.value` to access the Reference value
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // set a watcher on the Reactive Reference to user prop
  watch(user, getUserRepositories)

  return {
    repositories,
    getUserRepositories
  }
}

你或许注意到了在setup里面顶部使用了toRefs。这是为了确保侦听器能够对user prop的改变作出响应。

通过以上位置上的调整,我们已经将第一个逻辑关注点的全部内容移动到了同一个位置了。同样地,我们可以对第二个逻辑关注点––根据serchQuery进行过滤,进行同样的改动。这次我们将使用计算属性。

独立的computed

与ref和watch类似,计算属性也能在vue组件外部创建––通过从vue中导入的computed方法。现在我们一起回到我们的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的方法返回一个只读的响应式引用,这个引用是对一个类似与getter方法的回调函数的返回结果的响应式引用,这个回调函数作为computed方法的第一个参数。为了获取到这个新创建的computed变量,我们需要像ref一样,使用.value的属性。

现在我们将search功能移到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) {
  // using `toRefs` to create a Reactive Reference to the `user` property of props
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // update `props.user` to `user.value` to access the Reference value
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // set a watcher on the Reactive Reference to 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
  }
}

同样地,我们也可以对其他逻辑关注点这么做,但你或许会这样问––将所有东西都放到setup里面,不会使它变得非常大吗?这没错,因此我们在继续另外要做的事情前,我们需要将上面的代码提取到一个独立的composition方法中,现在我们先来创建一个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/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
  }
}

在将这两个功能分别放到两个文件中之后,我们可以在我们的组件中使用它们了。下面是实现方法:

// src/components/UserRepositories.vue
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'

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)

    return {
      // Since we don’t really care about the unfiltered repositories
      // we can expose the filtered results under the `repositories` name
      repositories: repositoriesMatchingSearchQuery,
      getUserRepositories,
      searchQuery,
    }
  },
  data () {
    return {
      filters: { ... }, // 3
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
  },
  methods: {
    updateFilters () { ... }, // 3
  }
}

此时你或许已经懂得了这个技巧了,因此我们快速跳到末尾,将剩下的过滤功能也进行迁移。我们无需研究具体的实现细节,因为那不是本篇教程的重点。

// 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 {
      // Since we don’t really care about the unfiltered repositories
      // we can expose the end results under the `repositories` name
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}

于是所有都完成了!

记住,以上我们只是学习到composition api的表层以及它所能给我们带来的好处。如果想学习更多关于composition api的,请参考in-depth指南。

你可能感兴趣的:(vue3.0中文文档,#,高级指南,vue,vue.js,javascript)