Vue 3 框架

目录

注释:

一、API风格

1.1、选项式API

1.2、组合式API

二、setup和ref的基本使用

三、reactive的使用

四、vue3响应式的核心原理

五、setup的细节

六、reactive和ref的细节问题

七、计算属性与监视

扫描二维码关注公众号,回复: 15785601 查看本文章

八、Vue 3 中的生命周期

九、自定义hook函数

十、toRefs的使用

十一、ref的另一个作用:获取页面中的元素

十二、shallowReactive和shallowRef

十三、readonly和shallowReadonly

十四、toRaw和markRaw

十五、 toRef的使用及特点

十六、customRef的使用

十七、响应式数据的判断方法

十八、手写组合API


注释:

1、本文的特点?

      本文只列出Vue 3 中的新知识,且所有知识都写在下面代码中的注释里,大家可以结合这些代码和注释去学习这些新知识。

2、适合哪些人学习?

比较适合那些掌握Vue 2 和 基础TS的人群,这样学习起来就非常轻松。

3、提醒:

      我在第一节中是使用的setup语法糖,后面所有的代码都是没有使用setup语法糖的,使用的是setup函数。

      setup语法糖中,定义的变量在模板中是不需要暴露出去的,在模板中直接使用,大家看到后面就知道是什么意思了。

      Vue 3 中引入组件是不需要注册的,直接import引入,使用即可。剩下的基本和vue 2中的语法一样。

4、接下来的计划:

      准备总结数据库(MySQl)的知识,然后持续发布。

      总结Java的知识,然后持续发布。

5、寄语:引用撒贝宁老师的开场白

      时光总像“林花谢了春红”,脚步“太匆匆”。在追寻梦想的路上,“何妨吟啸且徐行”,这里有“荡胸生层云”的旷达,有“潇潇雨歇”的怅惋,有“长太息以掩涕兮”的悲悯,也有“悠悠我心”的真情!

      韶华易逝,沧海桑田。当古代的文人面对时光的流逝,既有“十年生死两茫茫”的深沉,也有“想当年金戈铁马,气吞万里如虎”的壮志,更有“花有重开日,人无在少年”的劝诫。无论“黄沙百战穿金甲,不破楼兰终不还”的决绝,“人生自古谁无死,留取丹心照汗青”的气节,还是“我自横刀向天笑,去留肝胆两昆仑”的慷慨,历史如尘烟消散,情感凝结成永恒。

     “非淡泊无以明志,非宁静无以致远”,人生就是一场不断完善自己的旅程,这一路上所有的经历,无论悲喜,都是为了成就一个更完美的自己。“东方欲晓,莫道君行早,踏遍青山人未老,风景这边独好。”你会发现,人生有诗意,永远是少年!


一、API风格


Vue的组件可以按两种不同的风格书写:选项式API和组合式API。

1.1、选项式API

Vue 2 中使用的式选项式API,我们可以用包含多个选项的对象来描述组件的逻辑,例如data、methods和mounted。选项式所定义的属性都会暴露在函数的this上,它会指向当前的组件实例。

选项式API代码:

<template>
  <div>
      <span>{
   
   {mag}}</span><br>
      <button @click="clickAddFun">+1</button>
  </div>
</template>

<script>
export default {
  // data () 返回的属性将会成为响应式的状态
  // 并且暴露在 this 上
  data () {
    return {
      msg: 0
    }
  },
  // vue 2 中的生命周期钩子
  // 这个函数就会在组件挂载完成后被调用
  mounted () {
    console.log(this.msg);
  },
  // methods是一些用来更改状态与触发更新的函数
  // 它们可以在模板中作为事件监听器绑定
  methods: {
    clickAddFun () {
      this.msg++;
    }
  }
}
</script>

<style>

</style>

1.2、组合式API

vue 3 中使用的是组合式API,组合式API的核心思想是直接在函数作用域内定义响应式状态变量,并将从多个函数中得到的状态组合起来处理复杂问题。这种形式更加自由,也需要你对vue的响应式系统有更深的理解才能高效使用。相应的,他的灵活性也使得组织和重用逻辑的模式更加强大。

通过组合式API,我们可以使用导入的API函数来描述组件逻辑。在单文件组件中,组合式API通常会与<script setup>搭配使用。这个setup是一个标识,告诉Vue需要在编译时进行一些处理,让我们可以更简洁地使用组合式API。比如,<script setup>中导入和顶层变量/函数都能够模板中直接使用。

组合式API代码:

<template>
    <span>{
   
   {msg}}</span><br>
    <button @click="clickAddFun">+1</button>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue'

// 定义一个number类型的响应式变量
const msg: number = ref(0);

// 用来修改状态、触发更新的函数
function clickAddFun ():number {
  return msg.value++;
}

// vue 3 中的生命周期钩子
// 这个函数就会在组件挂载完成后被调用
onMounted (() => {
  console.log(msg);
  
})
</script>

<style>

</style>

以上就是组合式API和响应式API的对比,它们的功能是一模一样的,vue 2 中使用的JS,vue 3 中使用的是TS。

二、setup和ref的基本使用


<template>
    <h3>{
   
   {num}}</h3><br>
    <button @click="updateNumFun">+1</button>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue';

export default defineComponent({
  name: 'App',
  // setup是组合式API的入口函数,只在初始化执行一次
  // 一般都是返回一个对象,对象中的属性或方法,模板中可以直接使用

  // ref是一个函数
  // 作用:定义一个响应式的数据,返回的是一个Ref对象,对象中有一个value值,如果要对数据进行操作,就需要使用该对象调用value属性,进行数据的操作
  // 一般是用来定义基本类型的响应式数据
  
  setup () {
    // num的类型是Ref类型
    // html模板中是不需要使用.value的
    const num = ref(0);
    // 方法
    function updateNumFun () {
      console.log(num);
      // num是一个Ref对象,对象是不能进行++操作的
      num.value++;
    }
    // 返回时一个对象
    return {
      // 属性
      num,
      // 方法
      updateNumFun
    }
  }
});
</script>

<style>

</style>

三、reactive的使用


<template>
    <span>{
   
   {person.name}}今年{
   
   {person.age}}岁了,性别{
   
   {person.sex}}</span><br>
    <span>{
   
   {person.girl.name}}今年{
   
   {person.girl.age}}岁了,住在{
   
   {person.girl.address}}</span><br>
    <button @click="getMessageFun">改变信息</button>
</template>

<script lang="ts">
import { defineComponent, reactive } from 'vue';

export default defineComponent({
  name: 'App',
  // reactive
  // 作用:定义多个数据的响应式
  // const proxy = reactive(obj):接收一个普通对象然后返回该普通对象的响应式代理器对象
  // 响应式转换是"深层次的":会影响对象内部所有嵌套的属性内部基于ES6的proxy实现,通过代理对象操作源对象内部数据都是响应式的

  setup () {
    // const obj: any = { // 为了使用obj.sex = '男'不出现错误提示信息
    const obj = {  
      name: '王昭没有君啊',
      age: 22,
      girl: {
        name: '王子夭',
        age: 3
      }
    }
    // 把数据变成响应的数据
    // 返回的是一个proxy的代理对象,被代理的目标对象就是obj对象
    // person是代理对象,obj是目标对象
    // person的类型是proxy类型
    // 直接使用目标对象是不能更新目标对象的属性的,只能通过代理对象来更新数据
    const person = reactive<any>(obj)
    function getMessageFun () {
      // obj.age++; // 无变化,也就是不是响应式的
      // person.age++;
      // person.girl.age++;
      console.log(person);

      // person对象或者obj对象添加一个新的属性,哪一种方式会影响页面的更新
      // obj.sex = '男'; // 这种方式页面没有更新渲染
      person.sex = '男'; // 页面可以更新渲染,这个数据最终也添加到了obj对象

      // person对象或者obj对象移出一个属性,哪一种方式会影响页面的更新
      // delete obj.age; // 页面没有更新渲染,obj中确实没有了age这个属性
      // delete person.age; // 页面跟新渲染,obj中也没有了age这个属性

      // 总结:操作代理对象页面会发生渲染,且目标对象的数据也会随之变化

      person.girl.address = '武汉';

    }
    return {
      person,
      getMessageFun
    }
  }
});
</script>

<style>

</style>

四、vue3响应式的核心原理


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script>
        // vue3响应式的核心原理
        // 通过Proxy(代理):拦截对data任意属性的任意操作(handler处理器对象,有13种方法),比如说属性值的读写、属性的删除、属性的添加等
        // 通过Reflect(反射):动态对被代理对象的属性进行特定的操作

        // 定义一个目标对象
        const obj = {
            name: '王昭没有君啊',
            age: 22,
            children: {
                name: '王子夭',
                age: 3
            }
        }

        // 把目标对象变为代理对象
        // 参数1:obj target目标对象
        // 参数2:handler 处理器对象用来监视数据,及实现数据的操作
        const proxyObj = new Proxy(obj, {

            // handler里的get方法,用来获取目标对象中的数据
            // 参数1:target目标对象
            // 参数2:propetry要获取的属性
            get (target, propetry) { 
                return Reflect.get(target, propetry); // 通过Reflect里的get方法再把想要获取的数据反射出去
            },

            // handler里的set方法,用来给目标对象设置()添加数据或者修改数据的
            // 参数1:target目标对象
            // 参数2:propetry要设置或者修改的属性
            // 参数3:value要设置或者修改的属性值
            set (target, propetry, value) { 
                return Reflect.set(target, propetry, value); // 通过Reflect里的set方法再把想要获取的数据反射出去
            },

            // handler里的deleteProperty方法,用来删除目标对象中的数据
            // 参数1:target目标对象
            // 参数2:propetry要删除的属性
            deleteProperty (target, propetry) {
                return Reflect.deleteProperty(target, propetry); // 通过Reflect里的deleteProperty方法把删除后的数据反射出去
            }
        })
        // 想要获得name属性值
        // 第一步:通过handler里的get方法,获取到对应的数据
        // 第二步:然后再通过Reflect把想要获取的数据反射出去
        console.log(proxyObj.name); // 王昭没有君啊

        // 设置addresss属性
        // 第一步:通过handler里的set方法,设置对应的数据
        // 第二步:然后再通过Reflect把设置的数据反射出去
        proxyObj.address = '武汉';
        console.log(obj); // {name: '王昭没有君啊', age: 22, address:  '武汉', children: {name: '王子夭', age: 3}}

        // 修改name属性
        // 第一步:通过handler里的set方法,设置对应的数据
        // 第二步:然后再通过Reflect把设置的数据反射出去
        proxyObj.children.name = '王子苓';
        proxyObj.children.age = 1;
        console.log(obj); // {name: '王昭没有君啊', age: 22, address:  '武汉', children: {name: '王子苓', age: 1}}
        
        // 删除一维对象中的address属性,删除二维对象中的age属性
        // 第一步:通过handler里的deleteProperty方法,设置对应的数据
        // 第二步:然后再通过Reflect把删除后的数据反射出去
        delete proxyObj.address;
        delete proxyObj.children.age;
        console.log(obj); // {name: '王昭没有君啊', age: 22,  children: {name: '王子苓'}}
        // 由此可见proxy确实也是深度监听的
        
    </script>
</body>
</html>

五、setup的细节


<template>
    <span>{
   
   {age}}</span><br>
    <span>{
   
   {num}}</span>
</template>

<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue';
export default defineComponent({
  name: 'App',
  setup (props, context) {
    // setup的两个参数
    // props参数是一个proxy对象,里面有父组件传递的数据,并且是在子组件中使用props接收到的所有的属性,包含props配置声明且传入了所以属性的对象
    console.log(props);
    // context是一个proxy对象,里面有attrs对象(获取当前组件标签上所有属性的对象,但是该属性是在props中没有声明接收的所有的属性对象,相当于this.$attrs),emit方法(分发事件的),slot对象(插槽)
    console.log(context);
    const age = ref(3);
    const setupFun = () => {
      console.log('setup中的方法');
    }
    console.log('setup执行了', this); // 先执行 
    // setup的执行时机
    // setup先执行,beforeCreate后执行
    // 1、说明:setup是在beforeCreate生命周期之前就执行了,而且就执行一次,那也就意味着setup执行的时候,当前的组件还没有创建出来,所以组件实例对象this根本就不能使用
    // 2、this是undefined,也就意味着不能通过this再去调用props/data/computed/methods的相关内容
    // 3、其实所有的composition API相关回调函数中都不可以

    // setup的返回值
    // 1、setup返回值是一个对象,内部的属性和方法是可以直接给html模板使用的
    // 2、setup中的对象内部的属性和data函数中的属性都可以再html模板中使用
    return {
      age,
      setupFun
    }
  },
  data () {
    return {
      num:  3,
    }
  },
  beforeCreate () {
    console.log('beforeCreate执行了');  // 后执行
  },
  mounted () {
    console.log(this); 
    // this是一个proxy对象,在里面明显可以看到target对象里面有两个属性age和num,也有两个方法setupFun和methodsFun
    // 这也就说明:
    // 3、setup中的属性会和data函数中的对象中的属性合并为组件对象的属性
    // 4、setup中的方法会和methods对象中的方法合并为组件对象的属性

    // 注意:
    // 1、在vue3中不要混合使用data和setup,也不要混合只有methods和setup(尽管它是向下兼容的,我这里只是演示用的代码)
    // 2、在setup方法中是不能访问data和methods的,因为你要想访问data和methods,就必须要使用this,但是在vue3中我们是不能使用this的
    // 3、setup不能是一个async函数:因为返回值不再是return对象,而是一个promise,模板看不到return对象中的属性数据
  },
  methods: {
    methodsFun () {
      console.log('methods中的方法');
    }
  }
});
</script>

<style>

</style>

六、reactive和ref的细节问题


<template>
    <span>name: {
   
   {name}}</span><br>
    <span>child: {
   
   {child}}</span><br>
    <span>person: {
   
   {person}}</span><br>
    <button @click="clickUpdateFun">更新数据</button>
</template>

<script lang="ts">
import { defineComponent, ref, reactive } from 'vue';
export default defineComponent({
  name: 'App',
  // 1、reactive和ref是vue3中composition API中两个最重要的响应式API
  // 2、ref是用来处理基本类型数据,reactive用来处理引用类型数据(深度响应式)

  setup () {
    const name = ref('王昭没有君啊');
    const child = reactive({
      name: '王子夭',
      age: 3,
      hobby: {
        sports: '足球'
      }
    });
    // 3、ref也是可以用来设置对象、数组的,但是内部会自动将对象、数组转为reactive的代理对象
    // 这也我们一般不用ref设置对象的原因
    const person = ref({
      name: '王子苓',
      age: 1,
      hobby: {
        sports: '羽毛球'
      }
    })
    // 更新数据,以下的的都能成功改变
    function clickUpdateFun () {
      console.log(person); // 经过了reactive的处理,形成了一个proxy类型的对象
      console.log(person.value.hobby); // 也是一个proxy类型的对象
      name.value = '王昭君';
      child.age = 2;
      child.hobby.sports = '乒乓球';
      person.value.age = 2;
      person.value.hobby.sports = '篮球'
    }
    // 4、ref内部:通过value属性添加getter/setter来实现对数据的劫持
    // 5、reactive内部:通过使用proxy来实现对象内部所有的数据劫持,并通过Reflect操作对象内部数据
    // 6、ref的数据操作:在js中需要.value,在模板中不需要(内部解析模板时会自动添加.value)
    return {
      name,
      child,
      person,
      clickUpdateFun
    }
  }
});
</script>

<style>

</style>

七、计算属性与监视


<template>
    <h4>信息录入</h4><br>
    姓名:<input type="text" v-model="person.name" placeholder="请输入姓名"><br>
    年龄:<input type="text" v-model="person.age" placeholder="请输入年龄"><br>
    <h4>计算属性和监视的演示</h4>
    姓名:<input type="text" v-model="firstName"><br>
    姓名:<input type="text" v-model="secondName"><br>
    姓名:<input type="text" v-model="thirdName"><br>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, computed, watch, watchEffect } from 'vue';
export default defineComponent({
  name: 'App',

  setup () {
    const person = reactive({
      name: '王昭没有君啊',
      age: 22
    });

    // 通过计算属性实现第一个姓名的显示
    // 需求:上面录入信息发生变化,第一个姓名显示就发生变化

    // 计算属性中的函数只传入一个回调函数的话,就表示是get
    // 返回的是一个Ref类型的对象
    const firstName = computed(() => {
      return person.name;
    })

    // 通过计算属性实现第二个姓名的显示
    // 需求:上面录入信息发生变化,第二个姓名显示就发生变化,并且我们在input框中修改第二个姓名,上面录入的信息也会相应发生变化

    // computed要想有get和set操作就必须要传入一个对象
    const secondName = computed({
      get () {
        return person.name;
      },
      set (val:string) {
        person.name = val;
      }
    })

    // 通过监听实现第三个姓名的显示
    const thirdName = ref('');
    watch (person, (person) => {
      thirdName.value = person.name;
    },{immediate: true,deep: true})
    // immediate 默认会执行一次watch监听
    // deep开启深度监视

    // watchEffect也是监视,不要配置immediate,本身默认就会进行监视(默认执行一次watch监听)
    // watchEffect(() => {
    //   thirdName.value = person.name;
    // })

    // watchEffect监视thirdName,在input框中修改第三个姓名,上面录入的信息也会相应发生变化
    watchEffect(() => {
      person.name = thirdName.value;
    })

    // watch是可以监听多个数据的

    // watch([person.name, person.age, thirdName], () => {
        // 只有thirdName发生变化了,才会打印111
        // 因为person.name, person.age不是响应式的数据
      console.log(111);
    // })
    // 要使用watch监视非响应式的数据的时候,代码需要改动
    watch([() => person.name, () => person.age, thirdName], () => {
      console.log(111);
    })
    return {
      person,
      firstName,
      secondName,
      thirdName
    }
  }
});
</script>

<style>

</style>

八、Vue 3 中的生命周期


<template>
    <h3>vue3中的生命周期</h3>  
</template>

<script lang="ts">
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount,onUnmounted } from 'vue';
export default defineComponent({
  name: 'App',
  // 1、vue2的生命周期  vue3的生命周期
  // beforeCreate      setup
  // created           setup
  // beforeMount       onBeforeMount
  // mounted           onMounted
  // beforeUpdate      onBeforeUpdate
  // updated           onUpdated
  // beforeDestroy     onBeforeUnmount
  // destroyed         onUnmounted

  // 2、vue3中使用setup替代了vue2中的beforeCreate、created  
  // 3、vue2中的beforeDestroy、destroyed在vue3中已经改了名字,改为了onBeforeUnmount、onUnmounted
  // 4、vue3中生命周期的使用和vue2中的使用场景一模一样(我在《vue框架》(详细讲解vue2)这篇文章中已经详细讲解过了生命周期的具体内容,有想要了解生命周期的可以去看一下这篇文章,这里不作过多介绍了)
  setup () {
    // 挂载前
    onBeforeMount (() => {

    }),
    // 挂载后
    onMounted (() => {

    }),
    // 更新前
    onBeforeUpdate (() => {

    }),
    // 更新后
    onUpdated (() => {

    }),
    // 销毁前
    onBeforeUnmount (() => {

    }),
    // 销毁后
    onUnmounted (() => {

    })
    return {
     
    }
  }
});
</script>

<style>

</style>

九、自定义hook函数


// 自定义hook函数
// 1、自定义hook函数的作用:是用来在vue3中封装一些可复用功能函数的
// 2、自定义hook函数的优势:很清楚复用功能代码的来源,更清楚易懂

// 举例:自定义一个hook函数,将其写入hook.ts文件中,这样你就可以在你的任何页面中调用这个公共方法了
import { reactive } from 'vue'
export default function () {
    interface Person {
        name: string;
        age: number;
    }
    const person: Person = reactive({
        name: '王昭没有君啊',
        age: 22
    })
    return person;
}

十、toRefs的使用


<template>
    <span>name:{
   
   {name}}</span><br>
    <span>age:{
   
   {age}}</span><br>
</template>

<script lang="ts">
import { defineComponent, reactive, toRefs } from 'vue'
export default defineComponent({
  name: 'App',
  // toRefs:可以把一个响应式的对象转换为普通对象,该对象中的每一个属性都是一个ref
  // 它会把你结构出来的值通过toRefs包裹后还是响应式的
  setup () {
    const person = reactive({
      name: '王昭没有君啊',
      age: 22
    })
    // 写法一
    //const state = toRefs(person);

    // 写法二
    const {name, age} = toRefs(person);

    setInterval(() => {
      // 使用这种方式state.age.value++,修改age属性
      // 说明此时的age就是一个ref对象
      // 写法一
      // state.age.value++;

      // 写法二
      age.value++;
    }, 1000)

    // 从打印的结果上来看,也都是ref类型的对象
    // console.log(state);
    return {
      // 写法一
      //...state,

      // 写法二
      name,
      age
    }
  }
});
</script>

<style>

</style>

十一、ref的另一个作用:获取页面中的元素


<template>
    <input type="text" ref="input">
</template>

<script lang="ts">
import { defineComponent, ref, onMounted} from 'vue';
export default defineComponent({
  name: 'App',
  // 当页面加载完毕后,页面文本框自动获取焦点
  setup () {
    // 默认为空,当页面加载完毕后,说明组件已经存在,然后获取文本框元素
    const input = ref<HTMLElement | null>(null);
    onMounted(() => {
      if (input.value) {
        console.log(input.value);
        input.value.focus()
      }
    })
    return {
      input
    }
  }
});
</script>

<style>

</style>

十二、shallowReactive和shallowRef


<template>
    <span>{
   
   {person1}}</span><br>
    <span>{
   
   {person2}}</span><br>
    <span>{
   
   {person3}}</span><br>
    <span>{
   
   {person4}}</span><br>
    <button @click="updateFun">更改数据</button>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, shallowReactive, shallowRef } from 'vue';
export default defineComponent({
  name: 'App',
  // 1、shallowReactive:只处理了对象内外层属性的响应式(浅响应)
  // 2、shallowRef:只处理了value的响应式,不进行对象的reactive处理(浅响应)
  // 什么时候用浅响应式呢?
  // 一般情况下使用ref和reactive即可
  // 如果有一个对象数据,结构比较深,但变化只是需要外层属性变化,使用shallowReactive
  // 如果一个对象数据,后面会产生新的对象来替代,使用shallowRef
  setup () {
    // reactive深度响应
    const person1 = reactive({
      name: '王昭没有君啊',
      age: 22,
      hobby: {
        sports: '足球'
      }
    })
    // shallowReactive浅响应
    const person2 = shallowReactive({
      name: '王昭君',
      age: 22,
      hobby: {
        sports: '足球'
      }
    })
    // ref深响应
    const person3 = ref({
      name: '王子夭',
      age: 3,
      hobby: {
        sports: '足球'
      }
    })
    // shallowRef浅响应
    const person4 = shallowRef({
      name: '王子苓',
      age: 1,
      hobby: {
        sports: '足球'
      }
    })
    function updateFun () {
      // 数据都发生改变
      // person1.age++;
      // person1.hobby.sports = '观看足球比赛'

      // 只有年龄发生变化
      // person2.age++;
      // person2.hobby.sports = '观看足球比赛'

      // 数据都发生改变
      // person3.value.age++;
      // person3.value.hobby.sports = '观看足球比赛'

      // 只有年龄发生变化
      // person4.value.age++;
      // person4.value.hobby.sports = '观看足球比赛'
    }
    return {
      person1,
      person2,
      person3,
      person4,
      updateFun
    }
  }
});
</script>

<style>

</style>

十三、readonly和shallowReadonly


<template>
    <span>{
   
   {state2}}</span>
    <button @click="updateFun">更改数据</button>
</template>

<script lang="ts">
import { defineComponent, reactive, readonly, shallowReadonly } from 'vue';
export default defineComponent({
  name: 'App',
  // 1、readonly:深度只读数据,对象中的数据都是只读的,无法修改
  // 2、shallowReadonly:浅度只读数据,对象中外层数据是只读的,无法修改,但是深层次的数据是可以修改的
  setup () {
    const person = reactive({
      name: '王昭没有君啊',
      age: 22,
      hobby: {
        sports: '足球'
      }
    })
    const state = readonly(person);
    const state2 = shallowReadonly(person);
    function updateFun () {
      // 报错,因为readonly是只读的数据,而且是深度只读
      // state.name = '王昭君';
      // state.hobby.sports = '观看足球比赛';

      // 报错,因为shallowReadonly是只读的数据
      // state2.name = '王昭君';
      // 修该成功,说明shallowReadonly是浅只读
      state2.hobby.sports = '观看足球比赛';
    }
    return {
      person,
      state,
      state2,
      updateFun
    }
  }
});
</script>

<style>

</style>

十四、toRaw和markRaw


<template>
    <span>{
   
   {person}}</span><br>
    <button @click="clickFun1">测试toRaw</button><br>
    <button @click="clickFun2">测试markRaw</button><br>
</template>

<script lang="ts">
import { defineComponent, reactive, toRaw, markRaw } from 'vue';
export default defineComponent({
  name: 'App',

  // 1、toRaw:toRaw会把代理对象变为普通对象,数据变化,页面不变化
  // 2、markRaw:markRaw标记的对象数据,从此以后都不能再成为代理对象了,只能返回对象本身

  setup () {
    interface Person {
      name: string;
      age: number;
      hobby?: string;
    }
    const person: Person = reactive({
      name: '王昭没有君啊',
      age: 22
    })
    function clickFun1 () {
      // 页面不发生变化
      // 这是因为toRaw把代理对象变为普通对象
      const toRawData = toRaw(person);
      toRawData.name = '王昭君';
    }
    function clickFun2 () {
      // 多次点击改变数据,页面只发生了一次变化
      // 这是因为markRaw标记的对象数据,从此以后都不能再成为代理对象了,只能返回对象本身
      const markRawData = markRaw(person);
      markRawData.hobby += '观看足球比赛';
    }
    return {
      person,
      clickFun1,
      clickFun2
    }
  }
});
</script>

<style>

</style>

十五、 toRef的使用及特点


<template>
    <span>{
   
   {person}}</span><br>
    <span>{
   
   {age}}</span><br>
    <span>{
   
   {name}}</span><br>
    <button @click="updateFun">更新数据</button>
</template>

<script lang="ts">
import { defineComponent, reactive, toRef, ref } from 'vue'
export default defineComponent({
  name: 'App',
  setup () {
    const person = reactive({
      name: '王昭没有君啊',
      age: 22
    })

    // 把响应式数据person对象中的某个属性age变为了ref对象
    const age = toRef(person, 'age');

    // 把响应式对象中的某个属性使用ref进行包装,变成了一个ref对象
    const name = ref(person.name);

    // 打印出来都是ref对象
    console.log(age);
    console.log(name);

    // 更新数据
    const updateFun = () => {
      // 修改person里的age,不仅person会变化,toRef转化为的age也跟着变化
      // person.age += 1;

      // 修改toRef转化为的age,person里的age也会发生变化
      // 说明toRef和person是有关联的,也可以理解为双向绑定的,会互相影响其变化
      // age.value += 1;

      // 修改ref包装后的属性,只有name发生变化,而person不会发生变化
      // 说明ref只是拷贝了一份person
      // name.value += '啊';
    }
    return {
      person,
      age,
      name,
      updateFun
    }
  }
});
</script>

<style>

</style>

十六、customRef的使用


<template>
    <input type="text" v-model="value"><br>
    <span>{
   
   {value}}</span>
</template>

<script lang="ts">
import { defineComponent, customRef } from 'vue'
  // customRef:用于自定义一个ref,可以显示的控制以来追踪和出发响应,接受一个工厂函数,两个参数分别用于追踪的track与用于触发的tigger,并返回与一个带有set和get属性的参数
  
  // ref和customRef的区别:
  // ref响应式数据本身是立即响应的,而customRef它是可以自定义响应时间的

// 自定义hook防抖函数
// value传入的数据的类型不确定,要用泛型,time是防抖的时间间隔,默认是500ms
function setTimeFun<T>(value: T, time = 500) {
  let timeID: number;
  return customRef ((track, tigger) => {
    return {
      // 返回数据
      get () {
        // 告诉vue追踪数据
        track ()
        return value;
      },
      // 设置数据
      set (newValue: T) {
        // 清除定时器
        clearTimeout(timeID);
        // 开启定时器
        timeID = setTimeout(() => {
          value = newValue;
          // 告诉vue更新页面
          tigger();
        }, time)
      }
    }
  })
}
export default defineComponent({
  name: 'App',
  setup () {
    // const value = ref('abc');
    const value = setTimeFun('abc', 1000);
    return {
      value
    }
  }
});
</script>

<style>

</style>

十七、响应式数据的判断方法


<template>
    <h4>响应式数据的判断方法</h4> 
</template>

<script lang="ts">
import { defineComponent, ref, reactive, readonly, isRef, isReactive, isReadonly, isProxy } from 'vue'
export default defineComponent({
  name: 'App',
  setup () {
    // isRef:检查一个值是否为一个ref对象
    console.log(isRef(ref({}))); // true
    
    // isReactive:检查一个对象是否是由reactive创建的响应式代理
    console.log(isReactive(reactive({}))); // true
    
    // isReadonly:检查一个对象是否是由readonly创建的只读代理
    console.log(isReadonly(readonly({}))); // true
    
    // isProxy:检查一个对象是否由reactive或者readonly方法创建的代理
    console.log(isProxy(reactive({}))); // true
    console.log(isProxy(readonly({}))); // true
    return {
      
    }
  }
});
</script>

<style>

</style>

十八、手写组合API


index.js文件代码:

// 手写shallowReactive(浅监听)和reactive(深监听)

// 第二步:定义一个handler处理对象
const handler = {
    // 获取数据
    get (target, prop) {
        console.log(prop);
        if (prop === '_is_reactive') {
            return true;
        }
        console.log('执行了get函数');
        return Reflect.get(target, prop)
    },
    // 设置数据/修改数据
    set (target, prop, value) {
        console.log('执行了set函数');
        return Reflect.set(target, prop, value);
    }, 
    // 删除数据
    deleteProperty (target, prop) {
        console.log('执行了deleteProperty函数');
        return Reflect.deleteProperty(target, prop);
    },
}

// 第一步:定义一个shallowReactive函数,传入一个目标对象
function shallowReactive (target) {
    // 判断传入的目标对象是不是Object类型(数组,对象)
    if (target && typeof target === 'object') {
        return new Proxy(target, handler)
    }
    // 如果传入的数据是基本数据类型,就直接返回
    return target
}

// 第一步:定义一个reactive函数,传入一个目标对象
function reactive (target) {
    // 判断传入的目标对象是不是Object类型(数组,对象)
    if (target && typeof target === 'object') {
        // 对数组或者对象中所有的数据进行reactive的递归处理
        // 如果传入的数据是数组
        if (Array.isArray(target)) {
            // 遍历数组
            target.forEach((item, index) => {
                target[index] = reactive(item);
            })
        // 如果传入的数据是对象
        } else {
            // 遍历对象
            Object.keys(target).forEach(key => {
                target[key] = reactive(target[key]);
            })
        }
        return new Proxy(target, handler)
    }
    // 如果传入的数据是基本数据类型,就直接返回
    return target
}

// 手写shallowReadonly(浅只读)和readonly(深只读)

// 第二步:定义一个handler处理对象
const readHandler = {
    get (target, prop) {
        if (prop === '_is_readonly') {
            return true;
        }
        console.log('执行了读取数据');
        return Reflect.get(target, prop);
    },
    set (target, prop, value) {
        console.warn('只能读取数据,不能修改或者添加数据');
        return true;
    },
    deleteProperty (target, prop) {
        console.warn('只能读取数据,不能删除数据');
        return true;
    },
}

// 第一步:定义一个shallowReadonly函数
function shallowReadonly (target) {
    if (target && typeof target === 'object') {
        return new Proxy(target, readHandler);
    }
    return target
}
// 定义一个readonly函数
function readonly (target) {
    if (target && typeof target === 'object') {
        if (Array.isArray(target)) {
            target.forEach((item, index) => {
                target[index] = readonly(item);
            })
        } else {
            Object.keys(target).forEach(keys => {
                target[keys] = readonly(target[keys]);
            })
        }
        return new Proxy(target, readHandler);
    }
    return target
}

// 手写shallowRef和ref

// 定义一个shallowRef函数
function shallowRef (target) {
    return {
        // 把target数据保存起来
        _value: target,
        get value () {
            console.log('执行了get函数');
            return this._value;
        },
        set value (val) {
            console.log('执行了set函数');
            return this._value = val
        }
    }
}
// 定义一个ref函数
function ref (target) {
    target = reactive(target);
    return {
        _is_ref: true, // 标识当前对象是ref对象
        // 把target数据保存起来
        _value: target,
        get value () {
            console.log('执行了get函数');
            return this._value;
        },
        set value (val) {
            console.log('执行了set函数');
            return this._value = val;
        }
    }
}

// 定义一个函数isRef,判断当前对象是不是ref对象
function isRef (obj) {
    return obj && obj._is_ref;
}
// 定义一个函数isReactive,判断当前对象是不是reactive对象
function isReactive (obj) {
    return obj && obj._is_reactive;
}
// 定义一个函数isReadonly,判断当前对象是不是readonly对象
function isReadonly (obj) {
    return obj && obj._is_readonly;
}
// 定义一个函数isRroxy,判断当前对象是不是reactive对象或者redonly对象
function isProxy (obj) {
    return isReactive (obj) || isReadonly (obj);
}

index.html文件代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <script src="./index.js"></script>
    <script type="text/javascript">
        // 测试 手写的shallowReactive和reactive
        const person = shallowReactive({
            name: '王昭没有君啊',
            hobby: {
                sports: '足球'
            }
        })
        // 执行了get函数,执行了set函数
        // person.name += '啊';
        // 只执行了get函数
        // person.hobby.sports = '观看足球比赛';
        // 执行了get函数,执行了deleteProperty函数
        // delete person.name;
        // 只执行了get函数
        // delete person.hobby.sports;
        // 说明当前是浅劫持

        const state = reactive({
            name: '王昭没有君啊',
            hobby: {
                sports: '足球'
            }
        })
        // 执行了get函数,执行了set函数
        // state.name += '啊';
        // 执行了get函数,执行了set函数
        // state.hobby.sports = '观看足球比赛';
        // 执行了get函数,执行了deleteProperty函数
        // delete state.name;
        // 执行了get函数,执行了deleteProperty函数
        // delete state.hobby.sports;
        // 说明当前是深劫持


        // 测试 手写的shallowReadonly和readonly

        const person2 = shallowReadonly({
            name: '王昭没有君啊',
            hobby: ['足球', '编程']
        })
        // 执行了读取数据
        // console.log(person2.name);
        // 只能读取数据,不能修改或者添加数据
        // person2.name = '王昭没有君啊';
        // 只能读取数据,不能删除数据
        // delete person2.name;
        // 执行了读取数据
        // 只读的,但是不可以深层次的修改
        // console.log(person.hobby[1] = '观看足球比赛');
        // 只读的,但是不可以深层次的删除
        // delete person.hobby[1];
        // 说明是浅劫持

        const state2 = readonly({
            name: '王昭没有君啊',
            hobby: ['足球', '编程']
        })
        // 执行了读取数据
        // console.log(state2.name);
        // 只能读取数据,不能修改或者添加数据
        // state2.name = '王昭没有君啊';
        // 只能读取数据,不能删除数据
        // delete state2.name;
        // 执行了读取数据
        // 只读的,可以深层次的修改
        // console.log(state2.hobby[1] = '观看足球比赛');
        // 只读的,可以深层次的删除
        // delete state2.hobby[1];
        // 说明是深劫持


        // 测试 手写的shallowRef和ref
        const person3 = shallowRef({
            name: '王昭没有君啊',
            hobby: {
                sports: '足球'
            }
        })
        // 执行了get函数
        // console.log(person3.value);
        // 执行了set函数
        // person3.value = '';
        // 只执行了get函数
        // person3.value.hobby = '';
        // 说明是浅劫持

        const state3 = ref({
            name: '王昭没有君啊',
            hobby: {
                sports: '足球'
            }
        })
        // 执行了get函数
        // console.log(state3.value);
        // 执行了set函数
        // state3.value = '';
        // 执行了get函数,执行了set函数
        // state3.value.hobby = '1';
        // 说明是深劫持

        console.log(isRef(ref({})));
        console.log(isReactive(reactive({})));
        console.log(isReadonly(readonly({})));
        console.log(isProxy(reactive({})));
        console.log(isProxy(readonly({})));
    </script>
</body>
</html>

猜你喜欢

转载自blog.csdn.net/weixin_58448088/article/details/127583911
今日推荐