vue2中Options API的弊端

在Vue2中,我们 编写组件的方式是Options API

<script>
export default {
	// 定义数据
    data() {
        return {};
    },
    // 声明周期钩子
    created() {},
    mounted() {},
    // 定义方法
    methods: {},
    // 组件
    components:{},
    // 过滤器
    filter: {},
    // 计算属性
    computed: {},
    // 自定义指令
    directives: {}
};
</script>

但是这种代码有一个很大的弊端:组件逻辑分散,不利于阅读和交流
在这里插入图片描述
如何解决呢?这也是Composition API要做的事情

Vue3的Composition API

为了开始使用Composition API,我们需要有一个可以实际使用它(编写代码)的地方,在vue组件中,setup函数就是这个位置。

setup

一个组件选项,在组件被创建之前,props 被解析之后执行。它是组合式 API 的入口。

<script>
export default {
    setup() {
    	console.log(this)	// undefined
        console.log(111);
    },
    beforeCreate() {
        console.log(222);
    },
    created() {
        console.log(333);
    },
};
</script>
浏览器输出的顺序:111 --> 222 --> 333

注意:setup函数它比声明周期钩子beforeCreate()执行的还要早,所以在里面是获取不到this

setup的参数(很重要)

它一般会有两个参数:propscontextcontext它里面包含三个属性

context三个属性:
attrs:所有的非prop的属性;
slots:父组件传递过来的插槽(这个在以渲染函数返回时会有作用);
emit:当我们组件内部需要发出事件时会用到emit(因为我们不能访问this,所以不可以通过 this.$emit发出事件);

父子组件传值

# 父组件
<template>
    <div class=''>
        <my msg="'uu盘'" id="123" @getMsg="get"></my>
    </div>
</template>

<script>
import my from '@/components/my'
export default {
    setup() {
        const get = (val)=>{
            console.log(val);   // '子组件的数据'
        }
		
		// 必须要有返回值,才能在template中使用
        return {
            get
        }
    },
    components:{
        my
    }
};
</script>
# 子组件
<script>
export default {
    props:["msg"],
    setup(props,{attrs,slots,emit}) {
        console.log(props); // {msg: 'uu盘'}
        console.log(attrs); // {id: '123'}
        emit('getMsg','子组件的数据')
    }
};
</script>

响应式api

举个例子:

<template>
    <div class=''>
        <div>{{num}}</div>
        <button @click="add">+</button>
        <button @click="sub">-</button>
    </div>
</template>

<script>
export default {
    setup() {
        let num = 0
        const add = () =>{
            console.log(num);
            num++
        }
        const sub = () =>{
            console.log(num);
            num--
        }
        return {
            num,
            add,
            sub
        }
    }
};
</script>

当我们点击按钮进行加减时,数据已经改变,但是页面视图并没有改变
这是因为对于一个定义的变量来说,默认情况下,Vue并不会跟踪它的变化,来引起界面的响应式操作,所以我们要把数据变成响应式数据.

reactive

import { reactive } from 'vue'
setup(){
    // 本质是转成proxy对象
    let state = reactive({
        age:1
    })
    function handler(){
        state.age++
    }
    return {state,handler}
}
// 使用:
{{state.age}}
<button @click="handler">点击修改</button>

如果我们传入一个基本数据类型vue会给我们报警告:所以vue3为我们提供了另外一种api(ref)

setup() {
	let num = reactive(0)
}

在这里插入图片描述
ref

import { ref } from 'vue'
setup(){
    const state = ref(1)
    function handler(){
        state.value = 2
    }
    return {
        state,handler
    }
}
// 使用:
在模板中引入ref的值时,vue会自动帮助我们进行解包操作,所以我们并不需要在模板中通过state.value的方式来使用
{{state}}
<button @click="handler">点击修改</button>

toRefs

toRefs可以解构reactive对象的属性,分解对象的单个属性 并保留属性的响应式。

import { toRefs } from 'vue'
export default {
    setup(){
         let state = reactive({
			name:'uu盘',
			data:[1,2,3,4]
        })
         ......
         return {
             ...toRefs(state)
         }
    }
}
# 使用
<span v-for="(item,i) in data" :key="item"></span>

toRef

如果我们只希望转换一个reactive对象中的属性为ref, 那么可以使用toRef的方法

import { toRef } from 'vue'
export default {
    setup(){
         let state = reactive({
			name:'uu盘',
			data:[1,2,3,4]
        })
        let data = toRef(state,'data')
		// 修改data数据
		const handler = ()=>{
			data.value[0] = 'uu盘'
		}
		return {
			data,
			handler
		}
    }
}
# 使用
<span v-for="(item,i) in data" :key="item"></span>
<button @click="handler">点击修改</button>

总结:

  1. 我们一般用reactive声明对象,用ref 用来声明基本数据类型和数组,不能用来声明对象
  2. 我们通过toRefstoRef将reactive解构,它会变成ref数据,如果想要修改要加value(name.value = ‘哈哈哈’)

readonly

接受一个对象 (响应式或纯对象) 或 ref 并返回原始对象的只读代理。只读代理是深层的:任何被访问的嵌套 property 也是只读的。

import { readonly,ref,reactive } from 'vue'
setup() {
	// 1. 接受一个普通对象
	let obj1 = readonly({name:'uu盘'})
	obj1.name = 'hhh'					// 不能修改,vue会报警告
	// 2. 接受一个reactive对象
	let obj1 = reactive({name:'uu盘'})
	let obj2 = readonly(obj1)
	obj2.name = 'hhh'					// 不能修改,vue会报警告
	// 3. 接受一个ref对象
	let obj1 = ref(0)
	let obj2 = readonly(obj1)
	obj2.value = 2						// 不能修改,vue会报警告
}

经过readonly返回的对象是不能被修改的,本质上就是readonly返回的对象的setter方法被劫持了

computed

当我们处理一个数据受多个数据影响时,我们就可以使用计算属性

import { computed,ref } from 'vue'
export default {
    setup() {
        let firstName = ref('诸葛')
        let lastName = ref('孔明')
        // computed返回一个不可变的响应式 ref 对象。
        let fullName = computed(()=>firstName.value+ ' ' + lastName.value)

        const change = () =>{
            firstName.value = '周'
            // fullName.value = 'uu 盘' 
        }
        return {
            fullName,
            change
        }
    }
};
</script>

我们是不能修改computed返回的值,vue会给我们报一个警告:(fullName.value = 'uu 盘' )
在这里插入图片描述
如果我们想要修改计算属性,可以用对象的形式进行书写并给这个对象设置setter方法getter方法,返回一个可读可写的ref对象。

import { computed,ref } from 'vue'
export default {
    setup() {
        let firstName = ref('诸葛')
        let lastName = ref('孔明')

        // 如果要修改计算属性
        let fullName = computed({
            get:()=>firstName.value + ' ' + lastName.value,
            set(newVal) {
                firstName.value = newVal.split(' ')[0]
                lastName.value = newVal.split(' ')[1]
            }
        })
        
        const change = () =>{
            // firstName.value = '周'
            fullName.value = 'uu 盘'
        }

        return {
            fullName,
            change
        }
    }
};

watchEffect

当监听某些响应式数据变化时,我们希望执行某些操作,这个时候我们可以使用watchEffect;
watchEffect传入的函数会被立即执行一次,并在执行的过程中会收集依赖,并且只有当依赖发生变化的时候,才会执行。

import { ref,watchEffect } from 'vue'
export default {
    setup() {
        let age = ref(10)
        let str = ref('uu盘')
        // 自动收集响应式的依赖,只要变量在函数体里面出现,就会自动收集
        watchEffect(()=>{
            console.log('age:' + age.value,'str:' + str.value);
        })
        const change = ()=>{
            age.value++
        }

        return {
            change
        }
    }
};

点击按钮,因为age发生改变,所以watchEffect传入的函数才会执行:
在这里插入图片描述
watchEffect的停止监听

如果在某些情况下我们希望停止监听,这时候我们可以使用watchEffect的停止监听
watchEffect返回值是一个函数,执行这个函数就可以停止监听

import { ref,watchEffect } from 'vue'
export default {
    setup() {
        let age = ref(10)
        let str = ref('uu盘')
        相当于定时器会返回一个id 用来清除定时器
        let stop = watchEffect(()=>{
            console.log('age:' + age.value,'str:' + str.value);
        })
        const change = ()=>{
            age.value++
            if(age.value > 18) {
                stop()
            }
        }
        return {
            change
        }
    }
};

在这里插入图片描述
watchEffect清除副作用

如果我们在开发中我们在监听函数里面进行网络请求,就比如:我们监听的值多次发生改变,它都会触发网络请求,这样对我们的服务器压力特别大。所以我们就可以用到watchEffect清除副作用
在我们给watchEffect传入的函数被回调时,其实可以获取到一个参数:onInvalidate

import { ref,watchEffect } from 'vue'
export default {
    setup() {
        let age = ref(10)
        let name = ref('uu盘')
        let str = ref('hahahaha')

        let stop = watchEffect((onInvalidate)=>{
            const time = setTimeout(() => {
                console.log('发送请求...');
            }, 2000);


            onInvalidate(()=>{
                console.log('onInvalidate');
                clearTimeout(time)
            })

            console.log('age:' + age.value,'str:' + str.value);
        })

    
        const change = ()=>{
            age.value++
            if(age.value > 18) {
                stop()
            }
        }

        return {
            age,
            change
        }
    }
};

在这里插入图片描述
对于终止axios请求:request.cancel()

setup中使用ref获取dom节点

在vue2中我们可以通过this.$refs.xxx获取到我们想要的dom节点,那么在vue3中因为setup里面获取不到this,如何获取到dom节点?
其实非常简单,我们只需要定义一个ref对象,绑定到元素或者组件的ref属性上即可;

import { watchEffect,ref } from 'vue'
export default {
    setup() {
        const title = ref(null)

        watchEffect(()=>{
            console.log(title.value);
        })

        return {
            title
        }
    }
};

在这里插入图片描述
我们会发现打印结果打印了两次:

  • 这是因为setup函数在执行时就会立即执行传入的函数,这个时候DOM并没有挂载,所以打印为null;
  • 而当DOM挂载时,会给title的ref对象赋值新的值,函数会再次执行,打印出来对应的元素;

如果我们希望在第一次的时候就打印出来对应的元素呢?
watchEffect有副作用,DOM挂载或者更新之前就会触发,我们可以通过flush: post在dom更新后运行函数,确保模板引用与dom保持同步,并引入正确的元素。

import { watchEffect,ref } from 'vue'
export default {
    setup() {
        const title = ref(null)

        watchEffect(()=>{
            console.log(title.value);
        },{
            flush:"post"
        })


        return {
            title
        }
    }
};

在这里插入图片描述
watch

watch允许我们:

  • 懒执行副作用(第一次不会直接执行);
  • 更具体的说明当哪些状态发生变化时,触发侦听器的执行;
  • 访问侦听状态变化前后的值;

watch可以访问新值和旧值,watchEffect不能

监听单个数据源

import { watch,ref,reactive } from 'vue'
export default {
    setup() {
        const state = reactive({
            name:'uu盘',
            age:18,
            obj:{
                sex:'nv'
            }
        })
        const num = ref(0)

        watch(()=>state.name,(newV,oldV)=>{
            console.log(newV,oldV);    // hhh uu盘
        })

        watch(num,(newV,oldV)=>{
            console.log(newV,oldV); // 10  0
        })

        const handler = () =>{
            state.name = 'hhh'
            num.value = 10
        }

        return {
            handler
        }

    }
};

监听多个数据源

import { watch,ref,reactive } from 'vue'
export default {
    setup() {
        const state = reactive({
            name:'uu盘',
            age:18,
            obj:{
                sex:'nv'
            }
        })
        const num = ref(0)
        watch([num,()=>state.name],([newV1,newV2],[oldV1,oldV2])=>{
            console.log(newV1,oldV1);
            console.log(newV2,oldV2);
        })

        const handler = () =>{
            state.name = 'hhh'
            num.value = 10
        }

        return {
            handler
        }

    }
};

watch的选项
如果我们希望监听一个深层的监听,那么依然需要设置deep:true,如果想要立即执行可以添加immediate:true

import { watch,ref,reactive } from 'vue'
export default {
    setup() {
        const state = reactive({
            name:'uu盘',
            age:18,
            obj:{
                sex:'nv'
            }
        })
        const num = ref(0)

        watch(()=>state,(value)=>{
            console.log(value);
        },{
            deep:true,
            immediate:true
        })

        const handler = () =>{
            state.obj.sex = '88'
        }

        return {
            handler
        }

    }
};
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐