Composition API
也叫组合式API,是Vue3.x的新个性。
通过创立 Vue 组件,咱们能够将接口的可重复部分及其性能提取到可重用的代码段中。仅此一项就能够使咱们的应用程序在可维护性和灵活性方面走得更远。然而,咱们的教训曾经证实,光靠这一点可能是不够的,尤其是当你的应用程序变得十分大的时候——想想几百个组件。在解决如此大的应用程序时,共享和重用代码变得尤为重要
艰深的讲:
没有Composition API
之前vue相干业务的代码须要配置到option的特定的区域,中小型我的项目是没有问题的,然而在大型项目中会导致前期的维护性比较复杂,同时代码可复用性不高。Vue3.x中的composition-api就是为了解决这个问题而生的
compositon api提供了以下几个函数:
setup
ref
reactive
watchEffect
watch
computed
toRefs
- 生命周期的
hooks
一、setup组件选项
新的setup
组件选项在创立组件之前执行,一旦props
被解析,并充当合成API
的入口点
提醒:
因为在执行setup
时尚未创立组件实例,因而在setup
选项中没有this
。这意味着,除了props
之外,你将无法访问组件中申明的任何属性——本地状态、计算属性或办法。
应用 setup
函数时,它将承受两个参数:
props
context
让咱们更深刻地钻研如何应用每个参数
1. Props
setup
函数中的第一个参数是props
。正如在一个规范组件中所冀望的那样,setup
函数中的props
是响应式的,当传入新的prop
时,它将被更新
// MyBook.vueexport default { props: { title: String }, setup(props) { console.log(props.title) }}
留神:
然而,因为props
是响应式的,你不能应用ES6
解构,因为它会打消prop
的响应性。
如果须要解构 prop,能够通过应用 setup
函数中的 toRefs
来平安地实现此操作。
// MyBook.vueimport { toRefs } from 'vue'setup(props) { const { title } = toRefs(props) console.log(title.value)}
2. 上下文
传递给setup
函数的第二个参数是context
。context
是一个一般的 JavaScript 对象,它裸露三个组件的 property
// MyBook.vueexport default { setup(props, context) { // Attribute (非响应式对象) console.log(context.attrs) // 插槽 (非响应式对象) console.log(context.slots) // 触发事件 (办法) console.log(context.emit) }}
context
是一个一般的 JavaScript 对象,也就是说,它不是响应式的,这意味着你能够平安地对context
应用 ES6 解构
// MyBook.vueexport default { setup(props, { attrs, slots, emit }) { ... }}
attrs
和slots
是有状态的对象,它们总是会随组件自身的更新而更新。这意味着你应该防止对它们进行解构,并始终以attrs.x
或slots.x
的形式援用 property。请留神,与props
不同,attrs
和slots
是非响应式的。如果你打算依据attrs
或slots
更改利用副作用,那么应该在onUpdated
生命周期钩子中执行此操作。
3. setup组件的 property
执行 setup
时,组件实例尚未被创立。因而,你只能拜访以下 property:
props
attrs
slots
emit
换句话说,你将无法访问以下组件选项:
data
computed
methods
4. ref reactive 以及setup联合模板应用
在看setup
联合模板应用之前,咱们首先得晓得ref
和 reactive
办法。
如果 setup
返回一个对象则能够在模板中绑定对象中的属性和办法,然而要定义响应式数据的时候能够应用ref
, reactive
办法定义响应式的数据
谬误写法:
<template>{{msg}}<br><button @click="updateMsg">扭转etup中的msg</button><br></template><script>export default { data() { return { } }, setup() { let msg = "这是setup中的msg"; let updateMsg = () => { alert("触发办法") msg = "扭转后的值" } return { msg, updateMsg } },}</script><style lang="scss">.home { position: relative;}</style>
正确写法一:
ref用来定义响应式的 字符串、 数值、 数组、Bool
类型
import { ref} from 'vue'
<template>{{msg}}<br><br><button @click="updateMsg">扭转etup中的msg</button><br><br><ul> <li v-for="(item,index) in list" :key="index"> {{item}} </li></ul><br></template><script>import { ref} from 'vue'export default { data() { return { } }, setup() { let msg = ref("这是setup中的msg"); let list = ref(["马总", "李总", "刘总"]) let updateMsg = () => { alert("触发办法"); msg.value = "扭转后的值" } return { msg, list, updateMsg } },}</script><style lang="scss">.home { position: relative;}</style>
正确写法二:
reactive 用来定义响应式的对象
import { reactive } from 'vue'
<template>{{msg}}<br><br><button @click="updateMsg">扭转setup中的msg</button><br><br><ul> <li v-for="(item,index) in list" :key="index"> {{item}} </li></ul><br>{{setupData.title}}<br><button @click="updateTitle">更新setup中的title</button><br><br></template><script>import { reactive, ref} from 'vue'export default { data() { return { } }, setup() { let msg = ref("这是setup中的msg"); let setupData = reactive({ title: "reactive定义响应式数据的title", userinfo: { username: "张三", age: 20 } }) let updateMsg = () => { alert("触发办法"); msg.value = "扭转后的值" } let updateTitle = () => { alert("触发办法"); setupData.title = "我是扭转后的title" } return { msg, setupData, updateMsg, updateTitle } },}</script><style lang="scss">.home { position: relative;}</style>
阐明:要扭转ref定义的属性名称须要通过 属性名称.value
来批改,要扭转reactive
中定义的对象名称能够间接
5. 应用 this
在setup()
外部,this
不会是该沉闷实例的援用,因为setup()
是在解析其它组件选项之前被调用的,所以setup()
外部的this
的行为与其它选项中的this
齐全不同。这在和其它选项式 API 一起应用setup()
时可能会导致混同
二、toRefs - 解构响应式对象数据
把一个响应式对象转换成一般对象,该一般对象的每个property
都是一个ref
,和响应式对象property
一一对应
<template><div> <h1>解构响应式对象数据</h1> <p>Username: {{username}}</p> <p>Age: {{age}}</p></div></template><script>import { reactive, toRefs} from "vue";export default { name: "解构响应式对象数据", setup() { const user = reactive({ username: "张三", age: 10000, }); return { ...toRefs(user) }; },};</script>
当想要从一个组合逻辑函数中返回响应式对象时,用 toRefs 是很无效的,该 API 让生产组件能够 解构 / 扩大(应用 …
操作符)返回的对象,并不会失落响应性:
function useFeatureX() { const state = reactive({ foo: 1, bar: 2, }) // 对 state 的逻辑操作 // .... // 返回时将属性都转为 ref return toRefs(state)}export default { setup() { // 能够解构,不会失落响应性 const { foo, bar } = useFeatureX() return { foo, bar, } },}
三、computed - 计算属性
<template><div> <h1>解构响应式对象数据+computed</h1> <input type="text" v-model="firstName" placeholder="firstName" /> <br> <br> <input type="text" v-model="lastName" placeholder="lastName" /> <br> {{fullName}}</div></template><script>import { reactive, toRefs, computed} from "vue";export default { name: "解构响应式对象数据", setup() { const user = reactive({ firstName: "", lastName: "", }); const fullName = computed(() => { return user.firstName + " " + user.lastName }) return { ...toRefs(user), fullName }; },};</script>
四、readonly “深层”的只读代理
传入一个对象(响应式或一般)或 ref,返回一个原始对象的只读代理。一个只读的代理是“深层的”,对象外部任何嵌套的属性也都是只读的
<template> <div> <h1>readonly - “深层”的只读代理</h1> <p>original.count: {{original.count}}</p> <p>copy.count: {{copy.count}}</p> </div></template><script>import { reactive, readonly } from "vue";export default { name: "Readonly", setup() { const original = reactive({ count: 0 }); const copy = readonly(original); setInterval(() => { original.count++; copy.count++; // 报正告,Set operation on key "count" failed: target is readonly. Proxy {count: 1} }, 1000); return { original, copy }; },};</script>
五、watchEffect
在响应式地跟踪其依赖项时立刻运行一个函数,并在更改依赖项时从新运行它。
<template><div> <h1>watchEffect - 侦听器</h1> <p>{{data.count}}</p> <button @click="stop">手动敞开侦听器</button></div></template><script>import { reactive, watchEffect} from "vue";export default { name: "WatchEffect", setup() { const data = reactive({ count: 1, num: 1 }); const stop = watchEffect(() => console.log(`侦听器:${data.count}`)); setInterval(() => { data.count++; }, 1000); return { data, stop }; },};</script>
六、watch 、watch 与watchEffect区别
比照watchEffect
,watch
容许咱们:
- 懒执行,也就是说仅在侦听的源变更时才执行回调;
- 更明确哪些状态的扭转会触发侦听器从新运行;
- 拜访侦听状态变动前后的值
更明确哪些状态的扭转会触发侦听器从新运行
<template><div> <h1>watch - 侦听器</h1> <p>count1: {{data.count1}}</p> <p>count2: {{data.count2}}</p> <button @click="stopAll">Stop All</button></div></template><script>import { reactive, watch} from "vue";export default { name: "Watch", setup() { const data = reactive({ count1: 0, count2: 0 }); // 侦听单个数据源 const stop1 = watch(data, () => console.log("watch1", data.count1, data.count2) ); // 侦听多个数据源 const stop2 = watch([data], () => { console.log("watch2", data.count1, data.count2); }); setInterval(() => { data.count1++; }, 1000); return { data, stopAll: () => { stop1(); stop2(); }, }; },};</script>
拜访侦听状态变动前后的值
<template><div> <h1>watch - 侦听器</h1> <input type="text" v-model="keywords" /></div></template><script>import { ref, watch} from "vue";export default { name: "Watch", setup() { let keywords = ref("111"); // 侦听单个数据源 watch(keywords, (newValue, oldValue) => { console.log(newValue, oldValue) }); return { keywords }; },};</script>
懒执行,也就是说仅在侦听的源变更时才执行回调
<template><div> <h1>watch - 侦听器</h1> <p>num1={{num1}}</p> <p>num2={{num2}}</p></div></template><script>import { ref, watch, watchEffect} from "vue";export default { name: "Watch", setup() { let num1 = ref(10); let num2 = ref(10); // 侦听单个数据源 watch(num1, (newValue, oldValue) => { console.log(newValue, oldValue) }); watchEffect(() => console.log(`watchEffect侦听器:${num2.value}`)); return { num1, num2 }; },};</script>
七、组合式api生命周期钩子
你能够通过在生命周期钩子后面加上 “on” 来拜访组件的生命周期钩子。
下表蕴含如何在 setup () 外部调用生命周期钩子:
选项式 API | Hook inside setup |
---|---|
beforeCreate | 不须要* |
created | 不须要* |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeUnmount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTriggered |
因为setup
是围绕beforeCreate
和created
生命周期钩子运行的,所以不须要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该间接在setup
函数中编写
export default { setup() { // mounted onMounted(() => { console.log('Component is mounted!') }) }}
八、Provider Inject
通常,当咱们须要将数据从父组件传递到子组件时,咱们应用 props。设想一下这样的构造:你有一些深嵌套的组件,而你只须要来自深嵌套子组件中父组件的某些内容。在这种状况下,你依然须要将 prop 传递到整个组件链中,这可能会很烦人
对于这种状况,咱们能够应用provide
和inject
对父组件能够作为其所有子组件的依赖项提供程序,而不论组件层次结构有多深。这个个性有两个局部:父组件有一个provide
选项来提供数据,子组件有一个inject
选项来开始应用这个数据
1. 非组合式api中的写法
<!-- src/components/MyMap.vue --><template> <MyMarker /></template><script>import MyMarker from './MyMarker.vue'export default { components: { MyMarker }, provide: { location: 'North Pole', geolocation: { longitude: 90, latitude: 135 } }}</script><!-- src/components/MyMarker.vue --><script>export default { inject: ['location', 'geolocation']}</script>
2. 组合式api中的写法
Provider:
在setup()
中应用provide
时,咱们首先从vue
显式导入provide
办法。这使咱们可能调用provide
时来定义每个property
provide
函数容许你通过两个参数定义 property
:
property
的name
(<String>
类型)property
的value
应用 MyMap
组件,咱们提供的值能够按如下形式重构:
<!-- src/components/MyMap.vue --><template> <MyMarker /></template><script>import { provide } from 'vue'import MyMarker from './MyMarker.vueexport default { components: { MyMarker }, setup() { provide('location', 'North Pole') provide('geolocation', { longitude: 90, latitude: 135 }) }}</script>
Inject:
在setup()
中应用inject
时,还须要从vue
显式导入它。一旦咱们这样做了,咱们就能够调用它来定义如何将它裸露给咱们的组件。
inject
函数有两个参数:
- 要注入的
property
的名称 - 一个默认的值 (可选)
应用 MyMarker
组件,能够应用以下代码对其进行重构:
<!-- 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>
Provider Inject 响应性
父组件:
import { provide, ref, reactive} from 'vue'setup() { const location = ref('北京') const geolocation = reactive({ longitude: 90, latitude: 135 }) const updateLocation = () => { location.value = '上海' } provide('location', location); provide('geolocation', geolocation); return { updateLocation } }
<button @click="updateLocation">扭转location</button>
子组件:
import { inject } from 'vue'export default { setup() { const userLocation = inject('location', 'The Universe') const userGeolocation = inject('geolocation') return { userLocation, userGeolocation } }}</script>