做展柜在哪些网站找客户,单位建设网站用交印花税吗,最热门的短期培训课程,aspcms手机网站模板#x1f308;个人主页#xff1a;前端青山 #x1f525;系列专栏#xff1a;Vue篇 #x1f516;人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue3-组合式API 目录
组合式API
1.1 什么是组合式API
1.2 为什么使用它
1.2.1 更好的逻辑复用#…
个人主页前端青山 系列专栏Vue篇 人终将被年少不可得之物困其一生 依旧青山,本期给大家带来vue篇专栏内容:vue3-组合式API 目录
组合式API
1.1 什么是组合式API
1.2 为什么使用它
1.2.1 更好的逻辑复用#
1.2.2更灵活的代码组织#
1.2.3 更好的类型推导#
1.2.4 更小的生产包体积#
1.3 第一个组合式API的例子
setup()函数
2.1 基本使用
2.2 访问 Prop
2.3 Setup的上下文
2.4 与渲染函数一起使用
组合式API
1.1 什么是组合式API
组合式 API (Composition API) 是一系列 API 的集合使我们可以使用函数而不是声明选项的方式书写 Vue 组件。它是一个概括性的术语涵盖了以下方面的 API 响应式 API例如 ref() 和 reactive()使我们可以直接创建响应式状态、计算属性和侦听器。 生命周期钩子例如 onMounted() 和 onUnmounted()使我们可以在组件各个生命周期阶段添加逻辑。 依赖注入例如 provide() 和 inject()使我们可以在使用响应式 API 时利用 Vue 的依赖注入系统。
组合式 API 是 Vue 3 及 Vue 2.7 的内置功能。对于更老的 Vue 2 版本可以使用官方维护的插件 vue/composition-api。在 Vue 3 中组合式 API 基本上都会配合 script setup 语法在单文件组件中使用。
1.2 为什么使用它
1.2.1 更好的逻辑复用#
组合式 API 最基本的优势是它使我们能够通过组合函数来实现更加简洁高效的逻辑复用。在选项式 API 中我们主要的逻辑复用机制是 mixins而组合式 API 解决了 mixins 的所有缺陷。
组合式 API 提供的逻辑复用能力孵化了一些非常棒的社区项目比如 VueUse一个不断成长的工具型组合式函数集合。组合式 API 还为其他第三方状态管理库与 Vue 的响应式系统之间的集成提供了一套简洁清晰的机制例如 RxJS。
1.2.2更灵活的代码组织#
许多用户喜欢选项式 API 的原因是因为它在默认情况下就能够让人写出有组织的代码大部分代码都自然地被放进了对应的选项里。然而选项式 API 在单个组件的逻辑复杂到一定程度时会面临一些无法忽视的限制。这些限制主要体现在需要处理多个逻辑关注点的组件中这是我们在许多 Vue 2 的实际案例中所观察到的。
我们以 Vue CLI GUI 中的文件浏览器组件为例这个组件承担了以下几个逻辑关注点 追踪当前文件夹的状态展示其内容 处理文件夹的相关操作 (打开、关闭和刷新) 支持创建新文件夹 可以切换到只展示收藏的文件夹 可以开启对隐藏文件夹的展示 处理当前工作目录中的变更
这个组件最原始的版本是由选项式 API 写成的。如果我们为相同的逻辑关注点标上一种颜色那将会是这样
你可以看到处理相同逻辑关注点的代码被强制拆分在了不同的选项中位于文件的不同部分。在一个几百行的大组件中要读懂代码中的一个逻辑关注点需要在文件中反复上下滚动这并不理想。另外如果我们想要将一个逻辑关注点抽取重构到一个可复用的工具函数中需要从文件的多个不同部分找到所需的正确片段。
而如果用组合式 API 重构这个组件将会变成下面右边这样
现在与同一个逻辑关注点相关的代码被归为了一组我们无需再为了一个逻辑关注点在不同的选项块间来回滚动切换。此外我们现在可以很轻松地将这一组代码移动到一个外部文件中不再需要为了抽象而重新组织代码大大降低了重构成本这在长期维护的大型项目中非常关键。
1.2.3 更好的类型推导#
近几年来越来越多的开发者开始使用 TypeScript 书写更健壮可靠的代码TypeScript 还提供了非常好的 IDE 开发支持。然而选项式 API 是在 2013 年被设计出来的那时并没有把类型推导考虑进去因此我们不得不做了一些复杂到夸张的类型体操才实现了对选项式 API 的类型推导。但尽管做了这么多的努力选项式 API 的类型推导在处理 mixins 和依赖注入类型时依然不甚理想。
因此很多想要搭配 TS 使用 Vue 的开发者采用了由 vue-class-component 提供的 Class API。然而基于 Class 的 API 非常依赖 ES 装饰器在 2019 年我们开始开发 Vue 3 时它仍是一个仅处于 stage 2 的语言功能。我们认为基于一个不稳定的语言提案去设计框架的核心 API 风险实在太大了因此没有继续向 Class API 的方向发展。在那之后装饰器提案果然又发生了很大的变动在 2022 年才终于到达 stage 3。另一个问题是基于 Class 的 API 和选项式 API 在逻辑复用和代码组织方面存在相同的限制。
相比之下组合式 API 主要利用基本的变量和函数它们本身就是类型友好的。用组合式 API 重写的代码可以享受到完整的类型推导不需要书写太多类型标注。大多数时候用 TypeScript 书写的组合式 API 代码和用 JavaScript 写都差不太多这也让许多纯 JavaScript 用户也能从 IDE 中享受到部分类型推导功能。
1.2.4 更小的生产包体积#
搭配 script setup 使用组合式 API 比等价情况下的选项式 API 更高效对代码压缩也更友好。这是由于 script setup 形式书写的组件模板被编译为了一个内联函数和 script setup 中的代码位于同一作用域。不像选项式 API 需要依赖 this 上下文对象访问属性被编译的模板可以直接访问 script setup 中定义的变量无需一个代码实例从中代理。这对代码压缩更友好因为本地变量的名字可以被压缩但对象的属性名则不能。
1.3 第一个组合式API的例子
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title组合式API/title
/head
bodydiv idapp{{ count }}button clickadd加1/buttonbutton clickcount 10加10/button/div
/body
script src../lib/vue.global.js/script
script
Vue.createApp({data () {return {count: 10}},methods: {add () {this.count}}}).mount(#app)
const { ref } VueVue.createApp({setup () { // 组合式API的标识
const count ref(100)console.log(count.value)const add () {// ref 创建的状态修改是需要借助它的value属性count.value count.value 1}return { // 返回页面模版中需要使用的数据 count,add}}}).mount(#app)
/script
/html 体验提取公共部分 !DOCTYPE html
html langen
head
meta charsetUTF-8
meta http-equivX-UA-Compatible contentIEedge
meta nameviewport contentwidthdevice-width, initial-scale1.0
title组合式API/title
/head
body
div idapp{{ count }}button clickadd加1/button
/div
/body
script src../lib/vue.global.js/script
script
const { ref, onMounted, onUpdated } VueVue.createApp({setup () {
const count ref(0)
const add () {count.value count.value 1}
onMounted(() {document.title 点击了${count.value}次})
onUpdated(() {document.title 点击了${count.value}次})
return { count,add}}}).mount(#app)
function useCount () {const count ref(0)
const add () {count.value count.value 1}
return {count, add}
}
function useTitle (count) {onMounted(() {document.title 点击了${count.value}次})
onUpdated(() {document.title 点击了${count.value}次})
}
Vue.createApp({setup () {
const { count, add } useCount()
useTitle(count)
return { count,add}}
}).mount(#app)
/script
/html
setup()函数
setup() 钩子是在组件中使用组合式 API 的入口通常只在以下情况下使用 需要在非单文件组件中使用组合式 API 时。 需要在基于选项式 API 的组件中集成基于组合式 API 的代码时。
其他情况下都应优先使用 script setup 语法。
2.1 基本使用
我们可以使用响应式 API 来声明响应式的状态在 setup() 函数中返回的对象会暴露给模板和组件实例。其它的选项也可以通过组件实例来获取 setup() 暴露的属性
script
import { ref } from vue
export default {setup() {const count ref(0)
// 返回值会暴露给模板和其他的选项式 API 钩子return {count}},
mounted() {console.log(this.count) // 0}
}
/script
templatebutton clickcount{{ count }}/button
/template
请注意在模板中访问从 setup 返回的 ref 时它会自动浅层解包因此你无须再在模板中为它写 .value。当通过 this 访问时也会同样如此解包。 setup() 自身并不含对组件实例的访问权即在 setup() 中访问 this 会是 undefined。你可以在选项式 API 中访问组合式 API 暴露的值但反过来则不行。 headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title组合式API/title
/head
bodydiv idapp{{ count }}button clickadd加1/button/div
/body
script src../lib/vue.global.js/script
scriptconst { ref, onMounted } VueVue.createApp({setup () {const count ref(0)const add () {count.value 1}onMounted(() {console.log(1111)})return {count,add}},data () {return {count: 10}},methods: {add () {this.count 10}},mounted () {console.log(2222)}}).mount(#app)
/script 生命周期先执行 组合式API 后执行选项式API其余以组合式API为优先 2.2 访问 Prop
setup 函数的第一个参数是组件的 props。和标准的组件一致一个 setup 函数的 props 是响应式的并且会在传入新的 props 时同步更新。
{props: {title: String,count: Number},setup(props) {console.log(props.title)console.log(props.count)}
} 请注意如果你解构了 props 对象解构出的变量将会丢失响应性。因此我们推荐通过 props.xxx 的形式来使用其中的 props。 如果你确实需要解构 props 对象或者需要将某个 prop 传到一个外部函数中并保持响应性那么你可以使用 toRefs() 和 toRef() 这两个工具函数
{setup(props) {// 将 props 转为一个其中全是 ref 的对象然后解构const { title } toRefs(props)// title 是一个追踪着 props.title 的 refconsole.log(title.value)
// 或者将 props 的单个属性转为一个 refconst title toRef(props, title)}
}
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title组合式API/title
/head
bodydiv idappbutton clicknum加1/button {{ num }}my-root :numnum/my-root/div
/body
script src../lib/vue.global.js/script
template idroot
div{{ num }} -- {{ test }}/div
/template
scriptconst { ref, onMounted, computed } Vue
const Root {props: [num],template: #root,// setup (props) { // 千万不要对 props 解构// console.log(111)// return {// test: computed(() props.num) // 继续保持响应式// }// }setup ({ num }) {console.log(num)return {test: computed(() num) // 失去了响应式 - test的值不会发生改变}}}Vue.createApp({setup () {const num ref(10000)return { num }},components: {MyRoot: Root}}).mount(#app)
/script
/html
2.3 Setup的上下文
传入 setup 函数的第二个参数是一个 Setup 上下文对象。上下文对象暴露了其他一些在 setup 中可能会用到的值
{setup(props, context) {// 透传 Attributes非响应式的对象等价于 $attrsconsole.log(context.attrs)
// 插槽非响应式的对象等价于 $slotsconsole.log(context.slots)
// 触发事件函数等价于 $emitconsole.log(context.emit)
// 暴露公共属性函数console.log(context.expose)}
}
该上下文对象是非响应式的可以安全地解构
{setup(props, { attrs, slots, emit, expose }) {...}
}
attrs 和 slots 都是有状态的对象它们总是会随着组件自身的更新而更新。这意味着你应当避免解构它们并始终通过 attrs.x 或 slots.x 的形式使用其中的属性。此外还需注意和 props 不同attrs 和 slots 的属性都不是响应式的。如果你想要基于 attrs 或 slots 的改变来执行副作用那么你应该在 onBeforeUpdate 生命周期钩子中编写相关逻辑。
expose 函数用于显式地限制该组件暴露出的属性当父组件通过模板引用访问该组件的实例时将仅能访问 expose 函数暴露出的内容
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titlesetup上下文对象/title
/head
bodydiv idappmy-com refcomref classmyBox stylecolor: red idbox msghello msg my-eventgetDatatemplate #headerheader/templatedivcontent/divtemplate #footerfooter/template/my-com/div
/body
template idcomdivh1子组件/h1button clicksendData发送数据/buttonslot nameheader/slotslot/slotslot namefooter/slot/div
/template
script src../lib/vue.global.js/script
scriptconst { createApp, ref, onMounted } Vueconst Com {template: #com,setup (props, context) {// attrs 获取透传过来的值// slots 如果使用了插槽// emit 子组件给父组件传值// expose 子组件暴露给父组件可以调用的属性和方法 ---- options API ref获取子组件的实例
console.log(props)console.log(context.attrs) // ref 不在透传之列console.log(context.slots)const sendData () { // 子组件给父组件传值context.emit(my-event, 1000)}
// 自定义的属性和方法供给父组件使用const a ref(1)const b ref(2)const c ref(3)
const fn () {a.value 100}
// 暴露出去的是对象context.expose({a, b, fn})
return {sendData}}}
Vue.createApp({setup () {const getData (val) { // 接收子组件的值console.log(666, val)}
const comref ref() // comref 就是模版中refcomref
onMounted(() {console.log(com, comref.value) // {}console.log(a, comref.value.a) // 1console.log(b, comref.value.b) // 2console.log(c, comref.value.c) // undefined 因为没有暴露comref.value.fn()console.log(a, comref.value.a) // 100})
return {getData,comref}},components: {MyCom: Com}}).mount(#app)
/script
/html 在父组件通过ref获取子组件的实例的属性和方法的需求中需要注意 1.如果子组件是 选项式API组件基本不需要做任何操作 2.如果子组件是 组合式API组件需要通过 context.expose 暴露给父组件需要使用的属性和方法 3.如果父组件使用 选项式API, 可以通过 this.$refs.refName 访问到子组件想要你看到的属性和方法 4.如果父组件使用 组合式API,需要在setup中先创建 refName然后再访问子组件想要你看到的属性和方法const refName ref() refName.value.X 2.4 与渲染函数一起使用
setup 也可以返回一个渲染函数此时在渲染函数中可以直接使用在同一作用域下声明的响应式状态
{setup() {const count ref(0)return () h(div, count.value)}
}
返回一个渲染函数将会阻止我们返回其他东西。对于组件内部来说这样没有问题但如果我们想通过模板引用将这个组件的方法暴露给父组件那就有问题
我们可以通过调用 expose() 解决这个问题
{setup(props, { expose }) {const count ref(0)const increment () count.value
expose({increment})
return () h(div, count.value)}
}
!DOCTYPE html
html langen
headmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title渲染函数/title
/head
bodydiv idappbutton clickadd加1/buttonmy-child refchild/my-child/div
/body
script src../lib/vue.global.js/script
scriptconst { h, ref } Vueconst Child {// 写法1:// template: divchild/div// 写法2:// render () {// return [// h(div, child!)// ]// }// 写法3setup (props, { expose }) {const count ref(10)
const increment () {count.value 1}
expose({increment})
// 返回一个函数 函数返回 渲染函数的结果return () h(div, child!! count.value)}}
Vue.createApp({components: {MyChild: Child},setup () {const child ref()
const add () {child.value.increment()}
return {child,add}}}).mount(#app)
/script
/html