Vue3基础-02:vue3中ref和reactive

4,073 阅读3分钟

1.改写计数器例子:

Vue2中的代码:

<template>
  <p>{{count}}</p>
  <button @click="increase">👍</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
  name: 'App',
  data(){
    return {
      count:0
    }
  },
  methods:{
    increase(){
      this.count++;
    }
  }
});
</script>

使用vue3改写后的代码:

  • setup:在生命周期之前运行的
  • 通过代码可以发现,increase函数改变count的值是改变的count对象的value,但template中展示值是直接通过count展示,而不是count.value
  • computed:是一个函数,接收一个参数:回调函数,回调函数中处理想要返回的值
<template>
  <p>{{count}}</p>
  <p>{{double}}</p>
  <button @click="increase">👍</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import {ref,computed} from "vue";
export default defineComponent({
  name: 'App',
  setup(){
    const count = ref(0);
    const increase = () => {
      count.value ++;
    }
    const double = computed(()=>{
      return count.value * 2;
    })
    return {
      count,increase,double
    }
  }
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

结果如下:

2.更进一步,reactive

1.使用reactive实现计数器:

  • reactive:reactive是一个函数,参数是一个对象;通过reactive包装后返回一个响应式的数据data

  • 因为computed的原因,需要使用interface去约束下data的类型,否则如下图:

    上图的data会报错,解决方案就是添加interface去约束,代码如下:

<template>
  <p>{{data.count}}</p>
  <p>{{data.double}}</p>
  <button @click="data.increase">👍</button>
</template>

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

interface DataProps {
  count:number;
  double:number;
  increase:() => void;
}
export default defineComponent({
  name: 'App',
  setup(){
    const data:DataProps = reactive({
      count:0,
      increase:()=>{
        data.count++;
      },
      double:computed(()=>{
        return data.count * 2
      })
    })
    return {
      data
    }
  }
});
</script>

2.无效的优化方案:

这里面看到data.count...等的很麻烦,想要在return {data}中对data进行解构或者es6的"..."展开操作符,但是解构后发现页面计数器失效,因为直接通过下面的方式:

//略
//页面点击无反应
return {
	...data
}

//或者
//页面点击后依然无反应
return {
	count:data.count,
	increase:data.increase,
	double:data.double
}

3.正确使用toRefs去优化:(响应式对象——->普通对象)

  • toRefs:使用一个reactive对象作为参数,返回一个普通的对象

  • 通过toRefs转化后的refData如下图,是一个Ref类型,然后展开refData即可。

  • 代码如下:

<template>
  <p>{{count}}</p>
  <p>{{double}}</p>
  <button @click="increase">👍</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import {ref,computed,reactive,toRefs} from "vue";
interface DataProps {
  count:number;
  double:number;
  increase:() => void;
}
export default defineComponent({
  name: 'App',
  setup(){
    const data:DataProps = reactive({
      count:0,
      increase:()=>{
        data.count++;
      },
      double:computed(()=>{
        return data.count * 2
      })
    })
    const refData = toRefs(data);
    return {
      ...refData
    }
  }
});
</script>

3.refreactive的区别:

上面使用了变量声明、ref、以及reactive三种方式去定义变量:

ref定义的数据,打印结果是一个被对象包裹的响应的数据,使用reactive的方式和纯变量声明的方式打印结果是一样的,这是什么原因呢?

在控制台输出一个警告信息, 提示100这个值不能被reactive创建。 官方也推荐我们在定义数据的时候,reactive定义复杂的数据类型的数据,ref推荐定义基本数据类型,所以如果要使用reactive定义基本数据类型的话,需要在reactive中将数据包装一下。

  • 但是使用reactive定义的数据和ref定义的数据打印结果有一些差异:
  • ref定义的数据打印结果需要.value才能获取到结果,而reactive则不需要

总结:

  • reactiveref 都是用来定义响应式数据的。 reactive更推荐去定义复杂的数据类型,不能直接解构,需要需要使用toRefs()转成普通对象后解构; ref 更推荐定义基本类型
  • refreactive 本质可以简单地理解为ref是对reactive二次包装ref定义的数据访问的时候要多一个.value
  • 使用ref定义基本数据类型ref也可以定义数组对象

4.对比vue2vue3的响应式

vue2的响应式:

  • 通过object.defineproperty()实现响应式的,由于这个方法的限制,对于对象上新增的属性是无能为力的; 对于数组则需要拦截它的原型方法来完成响应式

  • 对于对象:

  • 对于数组:

为此vue中还可以使用vm.$set方法。官网链接:cn.vuejs.org/v2/guide/re…

vue3的响应式:

  • 利用了proxy实现的响应式

    如果有一个data对象,那么使用definePropertyProxy拦截如下

// defineProperty
Object.defineProperty(data,"name",{
	get(){},
	set(){},
})

//Proxy
new Proxy(data,{
	get(key){},
	set(key,value){}
})
  • defineProperty是针对某个明确的属性的,对于新增的属性无能为力
  • Proxy更像是从更高的维度去,针对于整个data对象的。