Vue3.0 组合式 API

265 阅读5分钟

什么是组合式 API?

组合式 API 即vue3.0新增的 setup 组件选项, 在创建组件之前执行,一旦 props 被解析,并充当合成 API 的入口点.因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

// 之前版本
export default {
    beforeCreate () {
        console.log('this is beforeCreate')
    },
    created () {
        console.log('this is created')
    }
}

// 3.0 版本
export default {
    setup() {
        console.log('write beforeCreate and created code')
    }
}

由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。因此,你只能访问以下 property:

  • props
  • attrs
  • slots
  • emit 换句话说,你将无法访问以下组件选项:
  • data
  • computed
  • methods

简单用法

setup 选项应该是一个接受 props 和 context 的函数。

export default {
    props: {
        title: String
    },
    setup(props) {
        console.log(props.title)
    }
}

我们从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板.

<template> 
    <h1>This is {{title}}</h1>
</template>

<script>
import { ref } from 'vue'

export default {
    setup() {
        const title = ref('Vue3.0');
        return {
            title
        }
    }
}
</script>

上面代码在代码块之中我们使用 ref 创建了一个响应变量, 在 Vue 3.0 中,我们可以通过一个新的 ref 函数使任何响应式变量在任何地方起作用,其接受参数并返回它包装在具有 value property 的对象中,然后可以使用该 property 访问或更改响应式变量的值, 关于响应式可查看:响应性基础 API

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

setup 中访问组件周期钩子

setup中,你可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。

export default {
    setup() {
        // mounted
        onMounted(() => {
             console.log('Component is mounted!')
        })
    }
}

下表包含如何在 setup () 内部调用生命周期钩子:

选项式 APIHook inside setup
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeUnmountonBeforeUnmount
unmountedonUnmounted
errorCapturedonErrorCaptured
renderTrackedonRenderTracked
renderTriggeredonRenderTriggered

在上面我们已经将结果, beforeCreate 和 created 生命周期的方法应该转移到setup()函数中执行。

setup 中使用watch

我们可以从 Vue 导入 watch 函数, 该函数接受三个参数:

  • 一个响应式引用或我们想要侦听的 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'
export default {
    setup() {
        const counter = ref(0)
        watch(counter, (newValue, oldValue) => {
            console.log('The new counter value is: ' + counter.value)
        })
    }
}

setup 中使用computed

import { ref, computed } from 'vue'
export default {
    setup() {
        const counter = ref(0)
        const twiceTheCounter = computed(() => counter.value * 2)

        counter.value++
        console.log(counter.value) // 1
        console.log(twiceTheCounter.value) // 2
        
        twiceTheCounter.value++ // error
    }
}

上面代码在代码块之中我们使用 getter 函数,并为从 getter 返回的值返回一个不变的响应式 ref 对象, 所以,twiceTheCounter.value++ 产生了报错。我们也可以使用具有 get 和 set 函数的对象来创建可写的 ref 对象。

const count = ref(1)
const plusOne = computed({
  get: () => count.value + 1,
  set: val => {
    count.value = val - 1
  }
})

plusOne.value = 1
console.log(count.value) // 0

setup 中使用Provide/inject

在 setup() 中使用 provide 时,我们首先从 vue 显式导入 provide 方法。这使我们能够调用 provide 时来定义每个 property。

provide 函数允许你通过两个参数定义 property:

  1. property 的 name ( 类型)
  2. property 的 value
<!-- src/components/MyMap.vue -->
<template>
  <MyMarker />
</template>

<script>
import { provide } from 'vue'
import MyMarker from './MyMarker.vue

export default {
    components: {
        MyMarker
    },
    setup() {
        provide('location', 'North Pole')
        provide('geolocation', {
            longitude: 90,
            latitude: 135
        })
    }
}
</script>

在 setup() 中使用 inject 时,还需要从 vue 显式导入它。一旦我们这样做了,我们就可以调用它来定义如何将它暴露给我们的组件。 inject 函数有两个参数:

  1. 要注入的 property 的名称
  2. 一个默认的值 (可选)
<!-- src/components/MyMarker.vue -->
<script>
import { inject } from 'vue'

export default {
  setup() {
    const userLocation = inject('location', 'The Universe')
    const userGeolocation = inject('geolocation')

    return {
      userLocation,
      userGeolocation
    }
  }
}
</script>

为什么需要组合式 API?

在 vue3.0 之前的版本,我们可以通过创建 Vue 组件,将接口的可重复部分及其功能提取到可重用的代码段中,这使我们的应用程序在可维护性和灵活性方面走得更远。但光靠这一点可能是不够的,尤其是当你的应用程序变得非常大的时候——想想几百个组件。在处理如此大的应用程序时,共享和重用代码变得尤为重要。

假设在我们的应用程序中,我们有一个视图来显示某个用户的仓库列表。除此之外,我们还希望应用搜索和筛选功能。 该组件含有的功能有:

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

vue3.0 之前版本,我们的组件代码应该如下所示:

// 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 () {
      // 使用 `this.user` 获取用户仓库
    }, // 1
    updateFilters () { ... }, // 3
  },
  mounted () {
    this.getUserRepositories() // 1
  }
}

而如果我们使用vue3.0 组合式 API, 那我们的代码可以重构为以下的代码:

外部 API 获取该用户名的仓库,并在用户更改时刷新它

// 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 {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      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 {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: filteredRepositories,
      getUserRepositories,
      searchQuery,
      filters,
      updateFilters
    }
  }
}