Vue3+vite+Ts+pinia—第三章 ref-reactive-toRefs-toRef-标签ref

1,272 阅读9分钟

3.1 ref-基本类型响应式

        Vue2是把数据放在data里,在data里的数据,全都是响应式数据。而在Vue3是通过ref和reactive来实现响应式,这个ref跟标签的ref是不相同的,在后面会详细讲解标签ref。Vue3的理念是,它并不会把所有数据都设置成响应式,而是你需要哪个数据是响应式,就在哪个数据包裹一下ref(),让它变成响应式。

        1、引入ref

            import {ref} from 'vue'

        2、把需要响应式的变量包裹ref()

<template>
  <div class="person">
    <h2>姓名:{{name}}</h2>
    <h2>年龄:{{age}}</h2>
    <h2>地址:{{address}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
    <button @click="showTel">查看联系方式</button>
  </div>
</template>

<script lang="ts" setup name="Person">
    import {ref} from 'vue'

    let name = ref('张三')
    let age = ref(18)
    let tel = '13888888888'
    let address = '北京昌平区宏福苑·宏福科技园'
    
    console.log(name)
    console.log(age)

    // 方法
    function changeName() {
        name = 'zhang-san'
    }
    function changeAge() {
        age += 1
    }
    function showTel() {
        alert(tel)
    }
</script>

image.png

            在上面的示例中,只有name和age需要响应式,因此只给它们俩包裹ref()。包裹了ref()之后,它会变成了一个RefImpl实例对象,它里面有几个属性,但只有value才是我们能够使用的,带下划线的都是它内部属性。

            既然它已经变成了RefImpl实例对象,而且值是在它的value属性里,那么是否应该写成name.value读取呢?注意:在模板里不需要加.value,但在ts里则需要加上.value才能获取到值

// 方法
function changeName() {
    name.value = 'zhang-san'
}
function changeAge() {
    age.value += 1
}

        3、代码示例

3.2 reactive-对象类型响应式

        ref主要是针对基本类型的响应式,如果是对象类型我们可以使用reactive,用法跟ref一样,也是使用reactive把对象进行包裹。

3.2.1 对象响应式

        1、引入reactive

            import {reactive} from 'vue'

        2、把对象包裹reactive()

<template>
  <div class="person">
    <h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
    <button @click="changePrice">修改汽车的价格</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive} from 'vue'

  // 数据
  let car = reactive({brand:'奔驰',price:100})
  console.log(car)

  // 方法
  function changePrice(){
    car.price += 10
  }
</script>

image.png

            在上面的示例中,包裹了reactive()之后,对象就变成了响应式,而且它是一个Proxy实例对象,它里面的target属性就是存放着对象的属性值。

            注意:reactive定义的响应式是深层次的,就是哪怕你的对象很复杂,例如obj.a.b.c,它一样可以监听得到。

3.2.2 数组响应式

        数组响应式跟对象一样,因为数组也是属于对象范畴,使用的时候也直接包裹Reactive()即可。

<template>
  <div class="person">
    <h2>游戏列表:</h2>
    <ul>
      <li v-for="g in games" :key="g.id">{{g.name}}</li>
    </ul>
    <button @click="changeFirstGame">修改第一个游戏的名字</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive} from 'vue'

  // 数据
  let games = reactive([
    {id:'aysdytfsatr01',name:'王者荣耀'},
    {id:'aysdytfsatr02',name:'原神'},
    {id:'aysdytfsatr03',name:'三国志'}
  ])

  // 方法
  function changeFirstGame(){
    games[0].name = '流星蝴蝶剑'
  }
</script>

image.png

3.2.3 示例代码

3.3 ref-对象类型响应式

        reactive是只能定义对象类型,它不能定义基本类型。而ref不单是可以定义基本类型的响应式,它也可以定义对象类型。

        1、使用ref定义对象类型

            用ref定义对象类型跟基本类型是一样的,在修改值的时候也同样需要.value获取对象值再进行修改。

<template>
  <div class="person">
    <h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
    <button @click="changePrice">修改汽车的价格</button>
    <br>
    <h2>游戏列表:</h2>
    <ul>
      <li v-for="g in games" :key="g.id">{{g.name}}</li>
    </ul>
    <button @click="changeFirstGame">修改第一个游戏的名字</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

  // 数据
  let car = ref({brand:'奔驰',price:100})
  let games = ref([
    {id:'aysdytfsatr01',name:'王者荣耀'},
    {id:'aysdytfsatr02',name:'原神'},
    {id:'aysdytfsatr03',name:'三国志'}
  ])
  
  console.log(games)

  // 方法
  function changePrice(){
    car.value.price += 10
    console.log(car.value.price)
  }
  function changeFirstGame(){
    games.value[0].name = '流星蝴蝶剑'
  }
</script>

image.png

        2、观察ref实例对象

            凡是使用ref包裹之后,它都会变成RefImpl实例对象,但它的value值却是一个Proxy对象。也就是说,ref面对对象类型的时候,它的底层实际上还是由Reactive实现的。

image.png

        3、示例代码

3.4 ref对比reactive

        1、宏观角度

            (1)ref用来定义:基本类型数据、对象类型数据

            (2)reactive用来定义:对象类型数据

        2、区别(在下方有详述)

            (1)ref创建的变量必须使用.value(可以使用volar插件自动添加.value)。

            (2)reactive重新分配一个新对象,会失去响应式(可以使用Object.assign去整体替换)。

        3、使用原则

            (1)若需要一个基本类型的响应式数据,必须使用ref

            (2)若需要一个响应式对象,层级不深,refreactive都可以。

            (3)若需要一个响应式对象,且层级较深,推荐使用reactive

3.4.1 使用volar插件

        在使用ref变量的时候,每次都得手写一个.value,在vscode里提供了一个插件叫volar,其实这个插件在vite初始化工程的时候就会提示你安装的了。

image.png

        如果已经安装过,在vscode左侧的扩展按钮进入,就会看到Volar插件。

image.png

        启用这个Volar插件,在左侧工具栏选择"Manage"—>选择"setting"—>选择"Volar"—>勾选"Auto Insert: Dot value"选项。

image.png

        此时它就会自动识别,如果是ref变量,它会自动帮我们填入.value。

image.png

3.4.2 reactive的局限性

        使用reactive定义了响应式对象后,如果重新给它赋值一个新的对象,哪怕是reactive响应式对象,它也会失去响应式的功能。请观察以下changeCar方法,页面是不会发生任何变化。

<template>
  <div class="person">
    <h2>汽车信息:一辆{{car.brand}}车,价值{{car.price}}万</h2>
    <button @click="changeBrand">修改汽车的品牌</button>
    <button @click="changePrice">修改汽车的价格</button>
    <button @click="changeCar">修改汽车</button>
  </div>
</template>

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

  // 数据
  let car = reactive({brand:'奔驰',price:100})

  // 方法
  function changeBrand(){
    car.brand = '宝马'
  }
  function changePrice(){
    car.price += 10
  }
  function changeCar(){
    car = reactive({brand:'奥拓',price:1})
  }
</script>

bb47fc9f-96d9-4d61-b89c-40f01a7f9ae6.gif

        这里可以通过使用Object.assign()合并对象来解决,这个方法只是将后面的对象属性合并到第一个对象里,但并没有改变reactive关联的对象链。

<script lang="ts" setup name="Person">
  ......
  function changeCar(){
    // car = reactive({brand:'奥拓',price:1})
    Object.assign(car,{brand:'奥拓',price:1})
  }
</script>

3.4.3 ref赋值新对象

        对比reactive赋值新对象需要借助Object.assign(),ref并不需要,它直接对value属性赋值一个新的对象即可,响应式也毫无影响。因为ref的value不管是什么值,只要它发生改变,它就一定是响应式。

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

  // 数据
  let car = ref({brand:'奔驰',price:100})

  // 方法
  ......
  function changeCar(){
    car.value = {brand:'奥拓',price:1}
  }
</script>

b02d8c30-6440-4148-b2fd-c1b9d80a5a50.gif

        注意:关于ref重新赋值的问题,一定是要改变它的value值,如果赋一个新的ref值,例如let a = ref(0); a = ref(9) 这样它的响应式是失效的。

3.4.4 示例代码

3.5 toRefs与toRef

3.5.1 toRefs

         我们用reactive包裹了对象之后,它就变成了响应式了,假设此时我们使用解构赋值,要注意的一点是,解构赋值的变量,并不是响应式的,它只是一个普通的变量而已。

<script lang="ts" setup name="Person">
  let person = reactive({
    name:'张三',
    age:18
  })
  let {name,age} = person
</script>  

         此时toRefs就可以派上用场了,它的作用就是可以让对象的每一个属性解构出来都变成响应式:

<script lang="ts" setup name="Person">
  let person = reactive({
    name:'张三',
    age:18
  })
  let {name,age} = toRefs(person)
</script>  

         这样一来,name和age属性都变成了响应式,而且如果改变它们,会联动改变对象的属性值。下面我们输出name和person.name,发现它们的值是一样的,会联动修改。

<template>
  <div class="person">
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}},{{nl}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>
<script lang="ts" setup name="Person">
  import {reactive,toRefs} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    age:18
  })
  let {name,age} = toRefs(person)

  // 方法
  function changeName(){
    name.value += '~'
    console.log(name.value,person.name)
  }
  function changeAge(){
    age.value += 1
  }
</script>

f81a077a-4102-468c-bb9f-cd128beb92d1.gif

         包裹了toRefs()之后,整个对象里的各个属性值都变成了ref对象,我们可以通过输出toRefs(person)进行观察:

image.png

3.5.2 toRef

         toRef作用其实跟toRefs是相似的,toRefs是把整个对象的所有属性都变成了响应式,而toRef是指定对象的某一个属性变成响应式。它传两个参数,第一个是对象,第二个是属性名。

         例如下面例子的nl变量,它是使用toRef解构age属性出来的,结果也同样是响应式:

<template>
  <div class="person">
    <h2>姓名:{{person.name}}</h2>
    <h2>年龄:{{person.age}},{{nl}}</h2>
    <button @click="changeName">修改名字</button>
    <button @click="changeAge">修改年龄</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {reactive,toRefs,toRef} from 'vue'

  // 数据
  let person = reactive({
    name:'张三',
    age:18
  })

  // 使用toRefs从person这个响应式对象中,解构出name、age,且name和age依然是响应式的
  // name和age的值是ref类型,其value值指向的是person.name和person.age
  let {name,age} = toRefs(person)
  let nl = toRef(person,'age')

  // 方法
  function changeName(){
    name.value += '~'
    console.log(name.value,person.name)
  }
  function changeAge(){
    age.value += 1
  }
</script>

ba391153-f0e2-447d-951a-50df04436f36.gif

3.5.3 示例代码

3.6 标签ref

         了解完Vue3的响应式的ref之后,再来看看我们比较熟悉的标签的ref。其实Vue2和Vue3的标签ref使用方式是一样的,不过在获取ref元素的时候稍微有点区别。

3.6.1 使用标签ref

        1、Vue2

            在Vue2使用ref直接在html标签上使用,然后通过this.$refs来获取即可。

<template>
  <div class="person">
    <h1>中国</h1>
    <h2 ref="title2">北京</h2>
    <h3>尚硅谷</h3>
  </div>
</template>

<script>
export default {
  mounted() {
    console.log(this.$refs.title2)
  }
}
</script>

        2、Vue3

            vue3在模板上的使用跟vue2是一样的,但获取ref元素的时候,它需要用ref类型先把ref标签保存起来,然后再使用。

<template>
  <div class="person">
    <h1>中国</h1>
    <h2 ref="title2">北京</h2>
    <h3>尚硅谷</h3>
    <button @click="showLog">点我输出h2这个元素</button>
  </div>
</template>

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

  // 创建一个title2,用于存储ref标记的内容
  let title2 = ref()

  function showLog(){
    console.log(title2.value)
  }
</script>

3.6.2 defineExpose的使用

        ref标签不只是用在普通的html元素,它还可以应用在组件标签上。假设这里存在一对父子组件,在父组件里对子组件标签使用ref的话,它获取的是子组件的示例。

        父组件:

<template>
  <button @click="showLog">测试</button>
  <Person ref="child"/>
</template>

<script lang="ts" setup name="App">
  import Person from './components/Person.vue'
  import {ref} from 'vue'

  let child = ref()

  function showLog(){
    console.log(child.value)
  }
</script>

        子组件:

<template>
  <div class="person">
    ......
  </div>
</template>

<script lang="ts" setup name="Person">
  let a = ref(0)
  let b = ref(1)
  let c = ref(2)
</script>

image.png

        这里子组件有a、b、c3个属性,在父组件获取子组件的时候,按理说它是可以拿到子组件的所有属性的,因为它已经把子组件的示例获取到了。

        实际上父组件只拿到子组件的示例,而属性一个都拿不到。这是Vue3对子组件的一个保护机制,相当于一个人的隐私,如果想查看就得先得到对方的同意。这里可以使用defineExpose进行对属性的对外暴露,这样外部才可以访问内部属性。

        子组件:

<script lang="ts" setup name="Person">
  import {ref} from 'vue'

  // 创建一个title2,用于存储ref标记的内容
  let title2 = ref()
  let a = ref(0)
  let b = ref(1)
  let c = ref(2)

  function showLog(){
    console.log(title2.value)
  }

  defineExpose({a,b,c})
</script>

image.png