你是否从还在焦虑从vue2过渡到vue3,那就看看这里(三)

151 阅读4分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第五天,点击查看活动详情

1.jpg

这篇文章主要记录vue3的基础知识点,提供给需要从vue2过渡到vue3的朋友,因为我也是需要从vue2过渡到vue3,所以在此记录学习过程,vue3知识点文章将会持续更新
祝愿看到文章的朋友身体健康;如果可以麻烦一键三连

shallowReactive和shallowRef函数

shallowReactive

shallow意为浅的,所以shallowReactive是针对引用类型的浅响应式,只处理最外层的属性为响应式,多层次的属性不做处理

<template>
    <!-- <div>{{obj.a}}</div> -->
    <div>{{obj.b.a}}</div>
    <button @click="clickBtn">点击按钮</button>
</template>


<script>
import { shallowReactive } from "vue"
export default {
    name: 'HelloWorld',
    setup() {
        const obj = shallowReactive({
            a:1,
            b:{
                a:1
            }
        })
        function clickBtn() {        
            // 对于最外层的属性是具有响应式的
            // obj.a++
            // 对于深层的属性是不具有响应式的
            obj.b.a++
        }
        return {obj,clickBtn}
    }
}
</script>

shallowRef

shallowRef只对基础数据类型做响应式,对引用数据类型不做响应式处理;所以如果是定义基础数据类型shallowRef和ref性质一样都是有响应式的,对于定义引用数据类型,操作数据中的某个值是不会有响应式的,但是如果直接替换数据.value是有响应式的

<template>
    <div>{{a}}-{{obj1.b.a}}</div>
    <button @click="clickBtn">点击按钮</button><br>
    <button @click="clickBtn1">点击按钮1</button>
</template>
<script>
import { shallowRef } from "vue"
export default {
    name: 'HelloWorld',
    setup() {
        let a = shallowRef(2)
        let obj1 = shallowRef({
            a:1,
            b:{
                a:1
            }
        })
        // 基本数据类型是有响应式的
        function clickBtn() {        
            a.value++
        }
        function clickBtn1() {   
            // 对于引用数据类型,只修改他的某个值是没有响应式的     
            obj1.value.a++
            // 修改整个value值是有响应式的
            obj1.value = {
                a:2,
                b:{
                    a:3
                }
            }
        }
        return {a,obj1,clickBtn,clickBtn1}
    }
}
</script>

总结:

  • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化使用shallowReactive
  • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换使用shallowRef
  • 如果两个以上的数据使用了shallowReactive会有问题,当操作让最外层的属性改变的时候,之前操作过的深层的属性也会跟着改变;如果不理解看下面代码;如果先点击按钮1页面不会改变(不具有响应式),再点击按钮2页面会改变(浅层具有响应式)并且按钮1之前的操作也会反应到页面
  • shallowRef也存在类似的问题,混合使用也有这个问题
  • 最新版本3.2.31也是存在这个问题的
  • 所以在数据量不是非常庞大的情况下不建议使用,如果哪个版本没有这个问题了我们就可以随便用了
<template>
    <div>{{obj.b.a}}-{{obj1.a}}</div>
    <button @click="clickBtn">点击按钮1</button><br>
    <button @click="clickBtn1">点击按钮2</button>
</template>
<script>
import { shallowReactive } from "vue"
export default {
    name: 'HelloWorld',
    setup() {
        const obj = shallowReactive({
            a:1,
            b:{
                a:1
            }
        })
        const obj1 = shallowReactive({
            a:1,
            b:{
                a:1
            }
        })
        function clickBtn() {        
            obj.b.a++
        }
        function clickBtn1() {        
            obj1.a++
        }
        return {obj,obj1,clickBtn,clickBtn1}
    }
}
</script>

readonly与shallowReadonly函数

意为只读(即不可修改)和浅只读(可修改深层的数据);刚好的和shallowReactive相反

用法:将响应式的数据转化为只读或者浅只读,或者直接定义引用类型为只读或者浅只读(创建的还是porxy对象),但是对于基础数据类型直接使用是没有用的const num = readonly(2)和 const num = 2没有区别

  • 对于基本数据类型,readonly和shallowReadonly意义是一样的和直接定义一个变量没有区别
  • 对于引用数据类型,使用readonly和shallowReadonly直接定义的数据都是不可以修改的,需要先使用reactive定义再转为shallowReadonly才可以修改深层次的数据
<template>
    <div>{{a}}-{{obj1.b.a}}</div>
    <button @click="clickBtn">点击按钮</button><br>
    <button @click="clickBtn1">点击按钮1</button>
</template>
<script>
import { ref,reactive,shallowReadonly,readonly } from "vue"
export default {
    name: 'HelloWorld',
    setup() {
        let a = ref(2)
        // 如果直接使用shallowReadonly定义深层次的数据也不能修改
        // let obj1 = shallowReadonly({
        //     a:1,
        //     b:{
        //         a:1
        //     }
        // })
        // 需要先定义为reactive对象
        let obj1 = reactive({
            a:1,
            b:{
                a:1
            }
        })
        // a数据即使是浅只读也不可以修改
        a = shallowReadonly(a)
        // 对象如果是只读修改obj1.b.a也是没有用的
        // obj1 = readonly(obj1)
        // 如果是浅只读,修改obj1.b.a是响应式的,但是修改obj.a不是响应式的
        obj1 = shallowReadonly(obj1)
        function clickBtn() {        
            a.value = 3
        }
        function clickBtn1() {        
            // obj1.a++
            obj1.b.a++
        }
        return {a,obj1,clickBtn,clickBtn1}
    }
}
</script>

思考:

如果只是需要只读为什么不直接定义一个数据呢?

希望某个数据在某个时候变为只读吗

为什么生成的都是proxy对象,直接使用shallowReadonly定义改变了深层次的数据为什么只是数据改变但是没有响应到页面呢;为什么先使用reactive再使用shallowReadonly就可以响应到页面呢

shallowReadonly只做了数据劫持并没有做更新页面模板

toRaw和markRaw

toRaw

将一个由reactive或者redonly或者shallowReadonly定义的proxy对象转化为普通对象

对于由ref创建的数据是不能转化的(即响应式还在),但是可以转化数据的value

<template>
    <div>{{num}}</div>
    <div>{{person.name}}</div>
    <div>{{person.zy.old}}</div>
    <button @click="clickBtn">点击改变数据</button>
</template>
<script>
    import {ref,reactive,toRaw} from 'vue'
    export default {
        name: 'Demo',
        setup(){
            /** 如果使用ref定义的数据,不管是基础数据还是引用数据
            对其本身是不能转化的,但是可以转化其value **/
            // let num = ref(1)
            // let person = ref({
            //     name:'yfeng',
            //     age:18,
            //     zy:{
            //         old:'学生'
            //     }
            // })
            // person = toRaw(person.value)
            // num = toRaw(num.value)
            // console.log(num)
            // console.log(person)
            // 对
            let person = reactive({
                name:'yfeng',
                age:18,
                zy:{
                    old:'学生'
                }
            })
            person = toRaw(person)
            function clickBtn() {
                num.value = 2
                // person.name = "学习"
                // person.zy.old = 2
            }
            return {
                person,clickBtn,num
            }
        }
    }
</script>

markRaw

标记原始数据永远不会变成响应式的,特别注意对已经是响应式的数据没有任何作用

应用场景:

  1. 有些值不应被设置为响应式的,例如第三方类库等
  2. 当渲染具有不可变数据源的大列表时,跳过响应式转化可以提高性能

image.png

组件的双向绑定

vue2.x的写法有三种

方法一:在组件绑定v-mode,组件中用value接收props,用$emit('input')传出事件;因为v-model默认收集的就是value相值

方法二:在组件绑定v-mode,组件中定义model属性,在model中定义要接收的prop参数和需要传出的event事件,在props和传出方法中使用model中对应的值

方法三:在组件绑定属性的时候添加.sync语法糖,组件中照常接收props,传递事件的时候使用$emit(update:props属性,....)

使用组件

<template>
    <div>
        <!-- 方法一 -->
        <!-- <hello v-model="msg"></hello>{{msg}} -->
        <!-- 方法二 -->
        <!-- <hello v-model="msg"></hello>{{msg}} -->
        <!-- 方法三 -->
        <hello :title.sync="msg"/>{{msg}}
    </div>
</template>


<script>
    import hello from '../components/hello.vue'
    export default {
        name:'About',
        components: {hello},
        data () {
            return {msg:'1'}
        }
    }
</script>

定义组件

<template>
    <div class="hello">
        <!-- 方法一 -->
        <!-- <h1>{{value}}</h1> -->
        <!-- 方法二 -->
        <!-- <h1>{{title}}</h1> -->
        <button @click="click">点击</button>
    </div>
</template>


<script>
export default {
    name: 'Hello',
    // 方法二才需要
    model:{
      prop:'title',
      event:'changeTitle'
    },
    props: {
      // 方法一使用value接收
      // value:String,
      // 方法二照常接收
      // title:String,
      // 方法三照常接收
      title:String
    },
    methods: {
        click(){  
            // 方法一使用input传出
            // this.$emit('input',this.value+'1')
            // 方法二  
            // this.$emit('changeTitle',this.value+'1')
            // 方法三
            this.$emit('update:title',this.title+'1')
        }
    }
}
</script>

vue3.x的写发有两种

第一种:在组件绑定v-mode,组件中用modelValue接收props,用**$emit('update:modelValue')传出事件**;vue3.x组件上使用v-model,默认使用modelValue接收参数,这样value就可以用作其他

第二种:同vue2.x的.sync,只是不使用.sync修饰符,直接使用v-model:属性值即可,props接收属性值,使用$emit('update:属性值')传出;使用方法二可以绑定多个v-model

使用组件

<template>
    <div>
        <!-- 方法一 -->
        <!-- <HelloWorld v-model="msg"></HelloWorld> -->
        <!-- 方法二 -->
        <HelloWorld v-model:title="msg"></HelloWorld>
    </div>
</template>


<script>
    import HelloWorld from '../components/HelloWorld.vue'
    export default {
        name:'About',
        components: {HelloWorld},
        data () {
            return {msg:'1'}
        }
    }
</script>

定义组件

<template>
    <div class="hello">
        <!-- 方法一 -->        
        <!-- <div class="hello">{{modelValue}}</div> -->
        <!-- 方法二 -->
        <div class="hello">{{title}}</div>
        <button @click="add"> 点击拼接字符串</button>
    </div>
</template>


<script>
export default {
    name: 'Hello',
    props: {
      // 方法一使用modelValue接收
      // modelValue:String,
      // 方法二照常接收
      title:String
    },
    methods: {
        click(){  
            // 方法一使用input传出
            // this.$emit('update:modelValue',this.modelValue+'1')
            // 方法二
            this.$emit('update:title',this.title+'1')
        }
    }
}
</script>

总结:

  • vue3.x舍弃了model属性,也不用value作为props默认接收属性了,舍弃了.sync修饰符
  • vue3.x都使用$emit('update:属性')传出事件,保持了统一性,这样input事件也可以另作他用了

祖孙组件之间传值

祖组件使用provide向后代组件提供数据;后代组件使用inject接收数据

// 祖组件
<template>
    <div>provide:{{person.name}}</div>
    <Demo></Demo>
    <button @click="clickBtn">点击改变数据</button>
</template>
<script>
    import Demo from "./demo.vue"
    import {reactive,provide} from 'vue'
    export default {
        name: 'provide',
        components:{Demo},
        setup(){
            let person = reactive({
                name:'yfeng',
                age:18,
                zy:{
                    old:'学生'
                }
            })
            provide('p',person)
            function clickBtn() {
                person.name = '学生1'
            }
            return {
                person,clickBtn
            }
        }
    }
</script>
// 子组件
<template>
    <vccInject></vccInject>
    <div>child:{{person.name}}</div>
</template>
<script>
import vccInject from "./inject.vue"
import { inject } from "vue"
export default {
    name: 'demo',
    components: {
        vccInject
    },
    setup() {
        let person = inject('p')
            return {
                person
            }
    }
}
</script>
// 祖孙组件
<template>
    <div>inject:{{person.name}}</div>
</template>
<script>
    import {inject} from 'vue'
    export default {
        name: 'inject',
        setup(){
            let person = inject('p')
            return {
                person
            }
        }
    }
</script>

祖组件定义了的数据使用了provide暴露之后,在后代组件任意地方使用inject都可以接收,这样就不用使用props一层一层的传递参数了;数据都是响应式的,在祖组件中改变,后代组件也会跟着改变

tips

Maximum call stack size超出最大调用栈;在写上面例子的时候报了这个错误,查看原因是由于我在引用组件的页面,将当前组件的组件名和引用组件的组件名写成一样的了

image.png

image.png

2.x 全局 API(Vue)3.x 实例 API (app)
Vue.config.xxxxapp.config.xxxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalPorperties
filter(全局和组件内部)移除