Vue3.x 高阶 —— 组合式API

在 Vue3.x 版本Composition API 出现之前使用的 Vue2.x 版本的都是一些选项式的API,如有data,methods,computed 和 watch 等这些选项,在data选项中来去定义一个变量为msg,在 methods , computed 等这些选项中来去用一下,或者要来进行一个排查,排查变量msg的问题,可能需要到 这些选项当中去查询,如果这些选项下面的代码量一多的话混在一起可能维护起来相对比较困难了,所以在 Vue3.x 当中推出了 Composition API ,它不是选项式而是组合式的API,通过 setup() 来进行处理的。

setup()

如以下选择通过选项式API来操作,像使用data,methods或watch这些

javascript 复制代码
<div id="app"></div>

<script>
    const app = Vue.createApp({
        data(){
            return {
                msg: 'lhxz'
            }
        },
        template:`
            <div>
                {{msg}}
            </div>
        `
    })
</script>

在 Componsition API 组合式API中就尽可能不要再用像data,methods,watch等这些选项。使用setup()。

javascript 复制代码
<div id="app"></div>

<script>
    const app = Vue.createApp({
        setup(props, context){
            return {
                msg: 'lhxz',        // 属性
                sayHello: ()=>{     // 方法
                    console.log('Hello');
                }
            }
        },
        template:`
           <div>
                {{msg}}
                <button @click="sayHello">打招呼</button>
           </div>
        `
    })
</script>

使用setup() 同样可以完成,在setup()中可定义属性和方法返回,选项式API生命周期和组合式API生命周期之间的映射关系是发生了改变,这点在对比讲Vue2.x和Vue3.x的时候讲过了,如果使用CompositionAPI之后,先前的选项式API中的 beforeCreate 和 created 生命周期就没有了而是被setup()取代了, 同时在setup里面能不能拿到这个this,这个在之前的篇目也提及过了,是不能拿到这个this的,因为它没有被挂载,下面来一个例子:

javascript 复制代码
<div id="app"></div>

<script>
    const app = Vue.createApp({
        setup(){
            this.sayHi();
        },
        methods:{
            sayHi(){
                console.log('Hi');
            }
        },
        template:`
        <div>
            <button @click="sayHi">打招呼</button>
        </div>`
    })
</script>

可以看到报错误了,this.sayHi is not a function,setup()是在 beforeMount 被挂载之前就已经调用了setup() ,[ *提示:Vue的生命周期,beforeCreate,created[setup],beforeMount,.... ] ,此时的methods是还没有被创建出来,而在setup里面来去调用是不可能的,所以在setup中是不能够去调用任何生命周期当中的任何内容,包括了optionAPI选项式API中的一些东西,你会发现setup的执行是非常早的对吧。

那么反过来我可以在其他生命周期函数或者像optionAPi中的选项来去访问 setup 里面的一些东西呢?其实是可以。

javascript 复制代码
<div id="app"></div>

<script>
    const app = Vue.createApp({
        setup(){
            return{
                msg: 'lhxz'
            }
        },
        methods:{
            looking(){
                console.log(this.$options.setup())
            }
        },
        template:`
        <div>
            {{msg}}
            <button @click="looking">打招呼</button>
        </div>
        `
    })

    app.mount('#app')
</script>

以上就是Composition API里面的setup()的内容,那么学了组合式API之后就尽可能的少用选项式API中的内容,像上面我们在组合式API中来去使用选项式API的内容,虽然两者可以兼容,但是使用组合式API就不要再用选项式API了,如data和methods这些选项的使用。

ref

CompositionAPI 中 ref : 让基础类型的数据具备响应式。

在选项式API中就不需要,因为底层已经完成了具备响应式的特点,而组合式API则需要我们来去给数据包装成响应式,下面来看一下:

javascript 复制代码
<body>
    <div id="app"></div>

<script>
    const app = Vue.createApp({
        setup(props,context){
            let msg = 'Hello'
            return{
                msg
            }
        },
        template:`
        <div>
            {{msg}}
        </div>
        `
    }).mount('#app')
</script>
</body>

如果是选项式API的方式,那么可以在控制台上使用 app.msg 是可以拿到 msg ,以及使用app.msg = "xxxx" 的命令可以来去修改页面数据的,而现在使用组合式API就不起作用了。

OptionsAPI能够响应式的处理是因为底层做了处理而CompositionAPI没有,这个底层原理后续我们再详讲,那么CompositionAPI如何具备响应式的特点,那么就可以使用ref,ref可以让基础数据类型具备响应式的特点,下面我们来编写一下:

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 1.引入ref
            const {ref} = Vue
            // let msg = "Hello"
            let msg = ref('Hello');
            return{
                msg
            }   
        },
        template:`
        <div>
            {{msg}}
        </div>
        `
    }).mount('#app')
</script>
</body>

在讲Vue2到Vue3的过度的时候,就有提及到这个Vue3.x将这个ProxyAPI替代definedProretyAPI了,大家可以回顾一下。在optionsAPI当中我们知道具备这种响应式式通过代理的方式,它是通过代理的方式,definedProperty,那么CompositionAPI也是通过底层代理Proxy,它内部包装成一个对象 proxy({value: 'val'}) ,那么在 setup() 需要注意的是不能直接为msg赋值,而是msg.value,下面我们来演示一下:

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 1.引入ref
            const {ref} = Vue
            // let msg = "Hello"
            let msg = ref('Hello');
            
            // 两秒后来修改
            setTimeout(()=>{
                msg = 'HI'
            },2000)

            return{
                msg
            }   
        },
        template:`
        <div>
            {{msg}}
        </div>
        `
    }).mount('#app')
</script>
</body>

那么如果我们将setTimeout中的msg改为msg.value再来测试一下:

javascript 复制代码
...
setTimeout(()=>{ msg.value = "HI" },2000)
...

这个是需要注意的内容,当你使用CompositionAPI的时候需要用到这个响应式数据的时候可以使用这个ref,ref可以使得基础数据类型具备响应式的特点。当然了有基础数据类型,当然了还有引用数据类型,像数组对象这些,那么可以使用下面我们要讲的 reactive。

reactive

CompositionAPI 中 reactive : 让引用类型的数据具备响应式。

下面我们直接来一个例子就可以知道 reactive 的使用:

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 1.引入reactive
            const {reactive} = Vue
            // 对象
            let obj = reactive({name: 'syan', sex: '女'});
            // 数组
            let arr = reactive(['1','2','3','4'])

            return{
                obj,
                arr
            }   
        },
        template:`
        <div>
            <p>对象</p>
            <span>{{obj}}</span><br/>
            <span>名称:{{obj.name}}</span><br/>
            <span>性别:{{obj.sex}}</span><br/>
            
            <p>数组</p>
            <span>{{arr}}</span><br/>
            <span>arr[0] - {{arr[0]}}</span><br/>
            <span>arr[1] - {{arr[1]}}</span><br/>
        </div>
        `
    }).mount('#app')
</script>
</body>

下面来通过控制台进行修改以上的这些数据信息:

reactive的底层也是通过proxy,那么ref的转化是

proxy:("xxxx") ------ 》 proxy(value:"xxxx") 通过 ref.value

而reactive的转化是什么呢?

proxy:({xxx: "yyyy", xxxx: "yyyy"}) ------ 》 proxy({xxx: "yyyy", xxxx: "yyyy"})

数组同理;

那么下面我们来通过setTimeout来测试一下:这时我们就不需要和ref那样需要进行一个".value"的方式来去响应式的修改。

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 1.引入reactive
            const {reactive} = Vue
            // 对象
            let obj = reactive({name: 'syan', sex: '女'});
            // 数组
            let arr = reactive(['1','2','3','4'])

            setTimeout(()=>{
                obj.name = "zsen"
                arr[1] = '888'
            },2000)

            return{
                obj,
                arr
            }   
        },
        template:`
        <div>
            <p>对象</p>
            <span>{{obj}}</span><br/>
            <span>名称:{{obj.name}}</span><br/>
            <span>性别:{{obj.sex}}</span><br/>
            
            <p>数组</p>
            <span>{{arr}}</span><br/>
            <span>arr[0] - {{arr[0]}}</span><br/>
            <span>arr[1] - {{arr[1]}}</span><br/>
        </div>
        `
    }).mount('#app')
</script>
</body>

以上就是reactive的使用了!

readonly

readonly,字面意思 "只读" 的,在Vue2.x的时候我们就已经讲过了Vue的一个数据流它是一个单向数据流,从父组件到子组件传递信息,父组件可以更改子组件的数据,而子组件不能直接修改父组件的数据,而是通过请求父组件对自己数据的更改,那么在CompositionAPI当中数据具备响应式能够被修改,那么如何来去规避数据被修改呢?即父组件向子组件传递数据,子组件可以将数据拿来用但是不能对父组件的数据进行修改。

如果想让数据不被修改可以使用readonly规避数据修改。

toRefs

toRefs是针对reactive包装起来的引用数据类型,可以通过toRefs来将其结构出来。

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 引入toRefs
            const {reactive, toRefs} = Vue
             
            let obj = reactive({name: 'syan', sex: '女'});

            // 解构obj
            let { name , sex } = toRefs(obj);

            setTimeout(()=>{
                // 结构出来的属性也具有响应式的特点
                name.value = "lhxz",
                sex.value = '男'
            },2000)

            return{
                obj,
                name,
                sex
            }   
        },
        template:`
        <div>
            <p>对象</p>
            <span>{{obj}}</span><br/>
            <span>名称:{{name}}</span><br/>
            <span>性别:{{sex}}</span><br/>
        </div>
        `
    }).mount('#app')
</script>
</body>

父组件传递这样一个reactive的obj给到子组件,那么子组件可以通过这个toRefs来进行解构使用,同时toRefs解构的属性像案例中的name和sex属性都是具备响应式的特点的,需要注意的是解构之后进行赋值修改的时候需要 ".value" 的方式,来看一下toRefs()它到底做了什么工作,同上面的 ref,reactive 一样都是通过这个proxy,toRefs的转化是:

proxy({ name:"syan" ,sex: "女" })

-》 name: proxy({ value: "syan" })

-》 sex: proxy({ value: "女" })

以上就是toRefs()的一个使用,下面来看另外一个toRef()。

toRef

在上面我们看到了toRefs对reactive引用数据的解构,那么下面我们介绍另外一种情况:

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        setup(props,context){
            // 引入toRefs
            const {reactive, toRefs} = Vue
             
            let obj = reactive({name: 'syan', sex: '女'});

            // 解构obj
            let { name , sex ,age } = toRefs(obj);

            setTimeout(()=>{
                // 结构出来的属性也具有响应式的特点
                name.value = "lhxz",
                sex.value = '男',
                age.value = 18
            },2000)

            return{
                name,
                sex
            }   
        },
        template:`
        <div>
            <span>名称:{{name}}</span><br/>
            <span>性别:{{sex}}</span><br/>
            <span>年龄:{{age}}</span><br/>
        </div>
        `
    }).mount('#app')
</script>
</body>

父组件像子组件传递这样的一个obj,然后在子组件当中被toRefs所解构,那么这个age在obj当中是没有的,但是我可能在组件开发的过程中以为这个age是有的,那么name和sex属性是必要的,而age属性则可选的,那么我们可以通过toRef来处理。

以上的这块就toRef的一个使用了。

context

attrs

context 可以拿到 attrs 、solts、emit,attrs拿到的就是这个no-props,父传子可以在props中拿到,下面来简单看一下:

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        template:`
        <div>
            <zj-compt></zj-compt>
        </div>
        `
    })

    // 子组件
    app.component('zj-compt',{
        setup(props,context){
            console.log(context)
            const { attrs, solts, emit } = context
            console.log(attrs)
        },
        template:`
             <div style="border:1px solid red"> 我是子组件 </div>
        })

    app.mount('#app')
</script>
</body> `

在子组件上设置 sex="女" ,通过context中的attrs来拿到:

javascript 复制代码
<script>
    const app = Vue.createApp({
        template:`
        <div>
            <zj-compt sex="女"></zj-compt>
        </div>
        `
    })

    // 子组件
    app.component('zj-compt',{
        setup(props,context){
            console.log(context)
            const { attrs, solts, emit } = context
            console.log(attrs)
            console.log(attrs.sex)
        },
    ...

可以看到效果拿到了,在子组件上的 template 来使用原先自己的 style,当然这里我们在 <zj-compt> 上添加一个style属性,然后通过attrs来被子组件获取,接着通过$attrs.属性即可使用。

javascript 复制代码
<body>
    <div id="app"></div>
<script>
    const app = Vue.createApp({
        template:`
        <div>
            <zj-compt sex="女" style="border: 2px solid blue"></zj-compt>
        </div>
        `
    })

    // 子组件
    app.component('zj-compt',{
        setup(props,context){
            console.log(context)
            const { attrs, solts, emit } = context
            console.log(attrs)
            console.log(attrs.sex)
            console.log(attrs.style)
        },
        template:`
            <div :style="$attrs.style"> 我是子组件 </div>
        `
    })

    app.mount('#app')
</script>
</body>

父组件向子组件传递一些no-props的东西如 sex=val 这些通过attrs来去获取,attrs.属性可以打印出对应的值,在使用的使用需要使用 $attrs.属性。


slots

context中的第二个属性slots,应该不陌生,源于插槽,通过它来拿到插槽内中的一些元素来让我们进行操作,如先来看一下:

javascript 复制代码
<body>
    <div id="app">
        <l-h>
            <button type="button">按钮</button>
        </l-h>
        <l-h>
            <input type="text" placeholder="输入....">
            <button type="button">提交</button>
        </l-h>
        <l-h>
            <img src="https://www.baidu.com/img/flexible/logo/pc/result.png" alt="图片">
        </l-h>
    </div>

    <!-- 模板 -->
    <template id="box">
        <div style="border: 5px solid green;">
            <slot>默认内容</slot>
        </div>
    </template>

<script>
    const LHXZ = {
        template: '#box'
    }

    const app = Vue.createApp({
        components: {
            'l-h': LHXZ
        }
    })
    
    app.mount('#app')
</script>
</body>

下面来看看如何在setup()函数里面来去使用:

javascript 复制代码
<script>
    const LHXZ = {
        setup(props, context){
            // const {attrs, solts, emit} = context
            const {slots} = context
            console.log(slots)      // proxy
            console.log(slots.default()) // 具体取出
            console.log(slots.default()[0]) // 具体取出下标为[1]
        },
        template: '#box'
    }
....

同时还可以拿到它的Props属性对象内容:

javascript 复制代码
console.log(slots.default()[0].props) // 具体取出下标为[1]的props

通过slots可以拿到插槽内的一些元素,拿到这些元素可供我们实现页面一些数据可用,下面来来看emit,主要是自定义事件。


emit

下面先来一个自定义事件,从父组件到子组件,Vue是单项数据流,但可以通过请求父组件对自身进行修改。

javascript 复制代码
<body>
    <!-- 父组件 -->
    <div id="app" style="width: 200px; height: 200px; background-color: red;">
        <l-h @out-click="handleFunc"></l-h>
    </div>

    <!-- #small -->
    <template id="small">
        <div style="width:100px; height: 100px; background-color: rebeccapurple">
            <button @click="btnClick">按钮</button>    
        </div>
    </template>

<script>
    // 子组件
    const LHXZ = {
        // 待写

        methods:{
            btnClick(){
                alert('点击');
                const dataObj = {
                    name: 'Gridfriend',
                    info: 'Good Night!'
                };
                this.$emit('outClick', dataObj)
            }
        },
        template: '#small'
    }


    // 父组件
    const app = Vue.createApp({
        data() {
            return {
                msg: "INFO-信息"
            }
        },
        components: {
            'l-h': LHXZ
        },
        methods:{
            handleFunc(item){
                console.log('子组件中按钮发生点击,触发父组件');
                console.log(item)
            }
        }
    })

    app.mount('#app')
</script>
</body>

这种方式也是我们之前使用 optionsAPI 选项式API采用的方式,现在使用CompositionAPI呢也就没有了这些methods选项了,而是就一个setup()函数来进行操作了。

下面我们来就子组件中的内容来进行修改,如下(可以和注释的地方进行一个对比):

javascript 复制代码
<script>
    // 子组件
    const LHXZ = {
        setup(props,context) {
            
            const { emit } = context

            // 处理业务逻辑
            function btnClick() {
                alert('点击');
                const dataObj = {
                    name: 'Gridfriend',
                    info: 'Good Night!'
                };
                // this.$emit('outClick', dataObj)
                emit('outClick', dataObj)
            }

            return {
                btnClick
            }
        },
        // methods:{
        //     btnClick(){
        //         alert('点击');
        //         const dataObj = {
        //             name: 'Gridfriend',
        //             info: 'Good Night!'
        //         };
        //         this.$emit('outClick', dataObj)
        //     }
        // },
        template: '#small'
    }

以上就是关于context中的 attrs / slots / emit 的用法了。

相关推荐
崔庆才丨静觅41 分钟前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60611 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅2 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅2 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment2 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅3 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊3 小时前
jwt介绍
前端
爱敲代码的小鱼3 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax