Vue3新特性体验--中(Composition API)

wuchangjian2021-10-30 13:02:02编程学习

Vue3中的Composition API主要作用是便于整合代码,将实现相同功能的代码集中在一起,便于维护管理,Composition API新特性的入口-- setup()函数,该函数是为组件提供的新属性。造个简单的实例感受一下。

本篇实例代码在上篇的基础上进行修改:

Vue3新特性体验--上(内附简单实例,可直接使用)_前端菜小白leo的博客-CSDN博客

现在,我们在A组件实现一个小功能,点击按钮改变显示内容。

vue2中的写法:

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">修改名字</button>
  </div>
</template>

<script>
export default {
  name: 'A',
  data() {  //里面的数据具有响应性,通过Object.defineProperty()劫持getter、setter
    return {
      name: 'leo'
    }
  },
  methods: {
    changeName() {
      this.name = 'lion'
    }
  }
}
</script>

vue3中的写法:

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">修改名字</button>
  </div>
</template>

<script>
//导入ref函数
import {ref} from 'vue'
export default {
  name: 'A',
  setup(){
    //声明响应式数据name,初始值为'leo'
    let name = ref('leo')

    //创建方法changeName
    function changeName(){
      name.value = 'lion'
    }

    //暴露变量和方法,使其可以被外界访问
    return {
      name,
      changeName
    }
  }
}
</script>

页面展示:

在vue2中name和它的方法changeName是分开的,分别分布在data属性和methods属性,当实现一个功能时,代码是分开的。试想一下,如果这个组件里需要实现很多功能,那data中可能需要很多的数据,methods中也会构造很多的方法,代码分散,就会比较难找。在工作中确实存在这种情况,有时候找个变量很费时,特别是变量跟功能特别多的时候。而对比vue3,其将相同功能的逻辑组合在一块,如该例中name的声明与修改它的方法放一起,一眼就能看到某个功能的相关实现代码,方便功能代码的维护与管理。

setup

setup是Composition API中的入口函数,它只在初始化时执行一次,且在beforeCreate生命周期之前执行,验证一下:

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">修改名字</button>
  </div>
</template>

<script>
import {ref} from 'vue'
export default {
  name: 'A',
  beforeCreate(){
    console.log('beforeCreate执行')
  },
  setup(){
    console.log('setup执行')
    let name = ref('leo')
    function changeName(){
      name.value = 'lion'
    }
    return {
      name,
      changeName
    }
  }
}
</script>

在控制台先打印了setup()里面的内容,说明它在beforeCreate()之前执行,且只执行一次。这也代表着在setup执行的时候,组件对象还没有创建,组件实例对象this还不可用,不能通过this来访问data、computed、methods中的内容,所以不建议混合使用。

setup里面有两个参数props、context。

props:props为一个对象,子组件通过它拿到父组件传递下来的属性。

context:上下文对象,里面有三个属性attrs、slots、emit。attrs:获取当前组件模板标签上所有没有通过props接收的属性的对象。slots:包含所有传入的插槽内容的对象。emit:用来分发自定义事件的函数,可以进行父子组件的通信。(该内容不展开,可自行查阅深究)

ref

ref在vue3中定义一个具有响应性数据,可以是基本数据类型,也可以是对象。类似vue2中data中的响应式变量。

let name = ref('leo')

返回的是一个对象,真实值放于其value属性,不妨打印出来看看。

let name = ref('leo')
console.log(name,'ref')  //控制台打印,查看返回数据结构

 注意:在改变name(ref类型对象)的值的时候,不能直接“name = ”而需要“name.value = ”。

reactive

可以定义多个具有响应性的数据,接收一个对象,返回该对象的响应式代理器对象(Proxy),而且代理了该对象中所有的属性,不管该对象层级多深,所有的属性都具有响应性。对比vue2中让对象具有相应性的方式是遍历该对象中所有的属性,通过defineProperty()劫持属性的getter、setter,使其具有响应性。当存在多个对象,且对象层级很深,那么遍历所有属性使其都具有响应性,这是耗时间、耗资源的过程。vue3通过代理的方式改进了这个过程,提高了效率。

举个例子,创建一个爱好like对象,代理使其具有响应性。

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">改变内容</button>
    <div>
      <p>喜欢的电影:{{like.movie}}</p>
      <p>喜欢的音乐:{{like.music}}</p>
    </div>
  </div>
</template>

<script>
import {reactive, ref} from 'vue'
export default {
  name: 'A',
  setup(){
    let name = ref('leo')
    let like = reactive({     //对象具有响应性
      movie:'长津湖',
      music:'我爱你中国'
    })
    console.log(like,'like')
    function changeName(){
      name.value = 'lion'
      like.movie = '战狼2'
    }
    return {
      name,
      like,
      changeName
    }
  }
}
</script>

观察控制台打印结果,跟ref不一样,真实的值不在value中,而是在对象原本对应的属性中,因此修改值时候不需要加“.value”。

初始页面

 点击按钮,验证其相应性

computed

计算属性与vue2中的基本一致,举个例子:

计算属性的函数中如果只传入一个回调函数,表示一个get的过程

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">改变内容</button>
    <div>
      <p>{{content}}</p>
    </div>
  </div>
</template>

<script>
import {computed, reactive, ref} from 'vue'
export default {
  name: 'A',
  beforeCreate(){
    // console.log('beforeCreate执行')
  },
  setup(){
    let name = ref('leo')
    let like = reactive({
      movie:'长津湖',
      music:'我爱你中国'
    })
    console.log(like,'like')
    function changeName(){
      name.value = 'lion'
      like.movie = '战狼2'
    }
    let content = computed(()=>{
      return '我喜欢看:' + like.movie + ';喜欢听:' + like.music
    })
    console.log(content,'content')  //控制台打印,返回的数据结构
    return {
      name,
      like,
      content,
      changeName
    }
  }
}
</script>

通过控制台打印出来,发现与ref类型的数据结构一样,说明computed返回的是一个ref类型的数据。

注意:computed返回的ref类型的.value是只读属性,不允许修改。

function changeName(){
  name.value = 'lion'
  like.movie = '战狼2'
  content.value = '注意啦!!!' + content.value
}

 计算属性当然也可以传入一个对象,里面可以存在读、写方法,打印观察。

let content = computed({
  get(){
    return '我喜欢看:' + like.movie + ';喜欢听:' + like.music
  },
  set(newVal){
    like.movie = newVal
  }
})
console.log(content,'content')

watch

watch与vue2中的配置一致。都是包括要监听的数据、回调函数、监听配置。

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">改变内容</button>
    <div>
      <p>{{content}}</p>
      <span>{{newContent}}</span>
    </div>
  </div>
</template>

<script>
import {computed, reactive, ref, watch} from 'vue'
export default {
  name: 'A',
  beforeCreate(){
    // console.log('beforeCreate执行')
  },
  setup(){
    let name = ref('leo')
    let like = reactive({
      movie:'长津湖',
      music:'我爱你中国'
    })
    let newContent = ref('')
    console.log(like,'like')
    function changeName(){
      name.value = 'lion'
      like.movie = '战狼2'
    }
    watch(    //监听like对象,当movie跟music发生变化时执行回调
      like,
      ({movie,music})=>{
        newContent.value = '我更喜欢看:' + movie + ';更喜欢听:' + music
      }
    )
    let content = computed({
      get(){
        return '我喜欢看:' + like.movie + ';喜欢听:' + like.music
      },
      set(newVal){
        like.movie = newVal
      }
    })
    console.log(content,'content')
    return {
      name,
      like,
      content,
      newContent,
      changeName
    }
  }
}
</script>

初始页面

watch页面加载完毕默认不执行,希望立即执行,修改如下配置(deep:true,深度监听)

watch(
  like,
  ({movie,music})=>{
    newContent.value = '我更喜欢看:' + movie + ';更喜欢听:' + music
  },
  {immediate:true,deep: true}   //immediate:true立即执行
)

查看页面,此时newContent中的movie是初始值 

点击按钮,movie改变,watch监听到变化,newContent的值相应改变

扩展:watch监听多个数据,使用一个数组

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">改变内容</button>
    <div>
      <p>{{content}}</p>
      <span>{{newContent}}</span>
    </div>
  </div>
</template>

<script>
import {computed, reactive, ref, watch} from 'vue'
export default {
  name: 'A',
  beforeCreate(){
    // console.log('beforeCreate执行')
  },
  setup(){
    let name = ref('leo')
    let like = reactive({
      movie:'长津湖',
      music:'我爱你中国'
    })
    let newContent = ref('')
    console.log(like,'like')
    function changeName(){
      name.value = 'lion'
      like.movie = '战狼2'
    }
    watch(
      [name,()=>like.movie,()=>like.music],
      ([newName,newMovie,newMusic])=>{
        newContent.value = newName + '更喜欢看:' + newMovie + ';更喜欢听:' + newMusic
      },
      {immediate:true,deep:true}
    )
    let content = computed({
      get(){
        return '我喜欢看:' + like.movie + ';喜欢听:' + like.music
      },
      set(newVal){
        like.movie = newVal
      }
    })
    console.log(content,'content')
    return {
      name,
      like,
      content,
      newContent,
      changeName
    }
  }
}
</script>

初始页面

 点击按钮,实现watch对多数据进行监听

 watchEffect

与watch不一样的是它不用直接指定要监视的数据,而是通过初始时候执行一次,判断在回调函数中使用了哪些响应式数据,从而收集需要监听的数据。

将watch中的代码:

watch(
  [name,()=>like.movie,()=>like.music],
  ([newName,newMovie,newMusic])=>{
    newContent.value = newName + '更喜欢看:' + newMovie + ';更喜欢听:' + newMusic
  },
  {immediate:true,deep:true}
)

改成用watchEffect形式:

watchEffect(
  ()=>{
    newContent.value = name.value + '更喜欢看:' + like.movie + ';更喜欢听:' + like.music
  }
)

验证页面展示效果一致。

toRefs

在该实例中,我们在模板中使用like对象的属性的时候,通过“like.”的形式使用,如果页面大量使用该属性,这样写未免有些麻烦,如果通过es6解构,那么就会失去其响应性。而toRefs支持将一个reactive对象的所有原始属性转换为响应式的ref属性,ref在模板使用时直接使用对象,如该实例中的name、newContent。

使用toRefs,控制台打印使用前与使用后like对象的变化。

<template>
  <div class="a-style">
    <p>我是A组件</p>
    <p>我的名字:{{name}}</p>
    <button @click="changeName">改变内容</button>
    <div>
      <span>{{newContent}}</span>
      <!-- <p>不使用toRefs:{{like.movie}}、{{like.music}}</p> -->
      <p>使用toRefs:{{movie}}、{{music}}</p>
    </div>
  </div>
</template>

<script>
import {reactive, ref, toRefs, watchEffect} from 'vue'
export default {
  name: 'A',
  setup(){
    let name = ref('leo')
    let like = reactive({
      movie:'长津湖',
      music:'我爱你中国'
    })
    console.log(like,'like')
    let newContent = ref('')
    function changeName(){
      name.value = 'lion'
      like.movie = '战狼2'
    }
    watchEffect(
      ()=>{
        newContent.value = name.value + '更喜欢看:' + like.movie + ';更喜欢听:' + like.music
      }
    )
    let newLike = toRefs(like)   //使用toRefs,将proxy对象转成ref对象
    console.log(newLike,'newLike')
    return {
      name,
      //like,  //不使用toRefs
      ...newLike,  //扩展运算符,返回每一个proxy对象中属性对应的ref对象
      newContent,
      changeName
    }
  }
}
</script>

页面显示正常,效果与未使用toRefs时一致。 

附vue2与vue3的生命周期对比(图片源自:一文学会使用Vue3_hugo233的博客-CSDN博客)

总结:主要通过简单实例体验vue3的新特性Composition API。

发表评论    

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。