新特性
组合式API
组合式API可以提供更好的复用性,与mixin
相比其带来的影响更小,mixin 的问题可以参考 react 文档中总结的 mixin 危害,其与 vue 中的 mixin 危害是大致相同的。
组合式 API 基础
与新的组合式 API 相对应,vue的文档中称过去的这种模式为选项式 API
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
|
export default { components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList }, props: { user: { type: String } }, data () { return { repositories: [], filters: { ... }, searchQuery: '' } }, computed: { filteredRepositories () { ... }, repositoriesMatchingSearchQuery () { ... }, }, watch: { user: 'getUserRepositories' }, methods: { getUserRepositories () { }, updateFilters () { ... }, }, mounted () { this.getUserRepositories() } }
|
而组合式 API 主要依靠setup
组件选项实现
新的 setup 组件选项在创建组件之前执行,一旦 props 被解析,并充当合成 API 的入口点, 注意,setup 中无法访问到 this,也就是说我们只能访问到到组件实例的 props。
在 setup 中我们可以使用以下api:
ref
只能用于原始值类似 data 用于创建响应式的变量,需要注意的是其中 value
才是变量真实的值,其将原始值也变为引用值方便实现响应式
reactive
用于引用值,返回对象的响应式副本,基于 proxy 实现,返回的代理不等于原始对象
watch
同选项式 API 的 watch
computed
同选项式 API 的 computed
- 生命周期钩子需要加
on
,例如 mounted
-> onMounted
Setup
接受两个参数
- props 组件的props,是响应式的,不能使用解构赋值
- contenxt 提供三个组件的 property :
attrs
, slots
, emit
可以使用解构赋值
attrs 和 slots 是有状态的对象,它们总是会随组件本身的更新而更新。这意味着你应该避免对它们进行解构,并始终以 attrs.x 或 slots.x 的方式引用 property。请注意,与 props 不同,attrs 和 slots 是非响应式的。如果你打算根据 attrs 或 slots 更改应用副作用,那么应该在 onUpdated 生命周期钩子中执行此操作。
在非模板中访问 Setup 中返回的经过 ref 处理的变量需要使用 *.value
,在模板中直接访问即可
setup
还可以返回一个渲染函数,可以使用同一作用域中声明的响应式状态
1 2 3 4 5 6 7 8 9 10 11 12
|
import { h, ref, reactive } from 'vue'
export default { setup() { const readersNumber = ref(0) const book = reactive({ title: 'Vue 3 Guide' }) return () => h('div', [readersNumber.value, book.title]) } }
|
生命周期钩子
选项式 API |
setup |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeUnmount |
onBeforeUnmount |
unmounted |
onUnmounted |
errorCaptured |
onErrorCaptured |
renderTracked |
onRenderTracked |
renderTriggered |
onRenderTriggered |
由于 setup 是围绕 beforeCreate 和 created 运行的,所以不用再 setup 中定义它们。
1 2 3 4 5 6 7 8
| export default { setup() { onMounted(() => { console.log('Component is mounted!') }) } }
|
提供 / 注入
使用 Provide
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <!-- 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>
|
使用注入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <!-- 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>
|
添加相应性
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
| <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template>
<script> import { provide, reactive, ref } from 'vue' import MyMarker from './MyMarker.vue
export default { components: { MyMarker }, setup() { const location = ref('North Pole') const geolocation = reactive({ longitude: 90, latitude: 135 })
provide('location', location) provide('geolocation', geolocation) } } </script>
|
修改响应式 property
建议尽可能,在提供者内保持响应式 property 的任何更改。
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
| <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template>
<script> import { provide, reactive, ref } from 'vue' import MyMarker from './MyMarker.vue
export default { components: { MyMarker }, setup() { const location = ref('North Pole') const geolocation = reactive({ longitude: 90, latitude: 135 })
provide('location', location) provide('geolocation', geolocation)
return { location } }, methods: { // 在此处更改 updateLocation() { this.location = 'South Pole' } } } </script>
|
如果需要在注入组件内修改数据,则最好提供一个方法来修改
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 40 41 42 43 44 45 46 47 48 49 50 51
| <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template>
<script> import { provide, reactive, ref } from 'vue' import MyMarker from './MyMarker.vue
export default { components: { MyMarker }, setup() { const location = ref('North Pole') const geolocation = reactive({ longitude: 90, latitude: 135 })
const updateLocation = () => { location.value = 'South Pole' }
provide('location', location) provide('geolocation', geolocation) // 提供一个修改方法 provide('updateLocation', updateLocation) } } </script>
<!-- src/components/MyMarker.vue --> <script> import { inject } from 'vue'
export default { setup() { const userLocation = inject('location', 'The Universe') const userGeolocation = inject('geolocation') // 需要时使用这个方法修改 const updateUserLocation = inject('updateLocation')
return { userLocation, userGeolocation, updateUserLocation } } } </script>
|
如果需要保护提供的数据不被篡改的话可以使用 readonly
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
| <!-- src/components/MyMap.vue --> <template> <MyMarker /> </template>
<script> import { provide, reactive, readonly, ref } from 'vue' import MyMarker from './MyMarker.vue
export default { components: { MyMarker }, setup() { const location = ref('North Pole') const geolocation = reactive({ longitude: 90, latitude: 135 })
const updateLocation = () => { location.value = 'South Pole' } // 在此处使用readonly provide('location', readonly(location)) provide('geolocation', readonly(geolocation)) provide('updateLocation', updateLocation) } } </script>
|
模板引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template> <div ref="root">This is a root element</div> </template>
<script> import { ref, onMounted } from 'vue'
export default { setup() { const root = ref(null)
onMounted(() => { console.log(root.value) })
return { root } } } </script>
|
由于我们使用了ref="root"
,所以
提供 / 注入
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
| const app = Vue.createApp({})
app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide: { user: 'John Doe' }, template: ` <div> {{ todos.length }} <!-- 模板的其余部分 --> </div> ` })
app.component('todo-list-statistics', { inject: ['user'], created() { console.log(`Injected property: ${this.user}`) } })
|
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
| app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide: { todoLength: this.todos.length }, template: ` ... ` })
app.component('todo-list', { data() { return { todos: ['Feed a cat', 'Buy tickets'] } }, provide() { return { todoLength: this.todos.length } }, template: ` ... ` })
|
这种形式的注入传递的变量和对象是非响应式的,如果需要可以传递 ref 和 reactive 变量
1 2 3 4 5 6 7 8
| app.component('todo-list', { provide() { return { todoLength: Vue.computed(() => this.todos.length) } } })
|