Vue3的组合API——setup、响应式实现(ref、reactive)

setup

setup是Vue3中一个新的配置项,值为一个函数

setup的使用

  • 组件中所用到的:数据、方法等等,均要配置在setup中。
  • setup函数的两种返回值:
    (1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用
    (一定要到返回模板中才能读取出)
    (2)若返回一个渲染函数:则可以自定义渲染内容。
    如果返回的是渲染函数,那么返回值会直接渲染在页面上

eg:返回一个对象:
App.vue

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   {name}}</h2>
  <h2>年龄{
   
   {age}}</h2>
  <button @click="sayHello">说话</button>
</template>
<script>

export default {
      
      
  name: 'App',
  setup() {
      
      
    // 数据
    let name = "yang"
    let age = 18

    // 方法
    function sayHello() {
      
      
    //注意哦,这里的name前面就无需添加this了
      alert(`你好呀,我叫${ 
        name}`)
    }
    return {
      
      
      name,
      age,
      sayHello
    }
  }
}
</script>

在这里插入图片描述
eg2:
返回的是渲染函数

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   {name}}</h2>
  <h2>年龄{
   
   {age}}</h2>
  <button @click="sayHello">说话</button>
</template>

<script>
import {
      
      h} from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      
    // 数据
    let name = "yang"
    let age = 18

    // 方法
    function sayHello() {
      
      
      alert("你好呀")
    }

    // return {
      
      
    //   name,
    //   age,
    //   sayHello
    // }

    return () => h('h1','yang')

  }
}
</script>

在这里插入图片描述

setup的注意点

  • 尽量不要与Vue2.x配置混用
    在Vue3中然然可以编写Vue2的配置(data、methos、computed…),并且和setup有如下关系:
    Vue2.x配置(data、methos、computed…)中可以访问到setup中的属性、方法。
    但在setup中不能访问到Vue2.x配置(data、methos、computed…)。
    如果有重名, setup优先。
  • setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

响应式数据的实现:ref和reactive

在vue2中data配置中的属性会通过Object.defineProperty原理最终包装成响应式数据_data。
vue3中为我们提供了两种包装响应式数据的方法:ref和reactive

ref函数

注意这里的ref和vue2中的ref不一样,这里是一个ref函数

ref的引入

上面使用setup包裹页面数据,但是这样编写出的数据不是响应式的,即数据改变页面不会被重新加载。从vue2中我们知道一个数据要实现响应式一定有对应的响应式得getter和setter方法,在vue3中通过ref函数帮我们生成响应式的getter和setter。

ref的实现原理也是通过Object.defineProperty实现的

ref的作用

定义一个响应式的数据

基本类型ref的使用

  • 引入 :import { ref } from 'vue'
  • 格式:const xxx = ref(数据)(创建一个包含响应式数据的引用对象(reference对象,简称ref对象))
    ref负责将传入的数据包装成一个带有响应式的getter和setter的对象
    eg:
    在这里插入图片描述
    可以发现具体值在该RefImpl的.value属性上
  • 值的获取:
    js中:变量名.value
    模板中读取数据:不需要.value,直接:<div>{ {变量名}}</div>
    (因为vue3在模板中自动帮我们读取其中的.value)

例子:

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   { name }}</h2>
  <h2>年龄{
   
   { age }}</h2>
  <button @click="sayHello">hello</button>
  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {
      
       ref } from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      
    // 数据
    let name = ref('yang')
    let age = ref(18)

    // 方法
    function sayHello() {
      
      
      alert(`你好呀,我叫${ 
        name.value}`)
    }
    function changeInfo() {
      
      
      console.log(name)
      name.value = 'cheng',
      age.value = 20
    }

    return {
      
      
      name,
      age,
      sayHello,
      changeInfo
    }
  }
}
</script>

在这里插入图片描述

对象属性ref的使用

ref当然也可以包装对象,那就有一个问题。他是否会包装对象中的数据,让对象中的数据也成为响应式的,这样对象中的数据改变才能重新渲染页面。
答案是:对象中的数据vue也帮我们设置成了响应式的但是不是通过ref实现的,是通过Proxy(ES6语法)实现的。(——vue3中封装了reactive函数来实现Proxy)
所以获取ref包装的对象的属性时: 对象.value.属性
(属性后面无需再加value了,属性不是用ref封装的)

eg:app.vue

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   { name }}</h2>
  <h2>年龄{
   
   { age }}</h2>
  <h2>工作种类{
   
   { job.type }}</h2>
  <h2>薪资{
   
   { job.salary }}</h2>
  <button @click="sayHello">hello</button>
  <br />
  <button @click="changeInfo">修改人的信息</button>
  <button @click="changeJob">修改工作信息</button>
</template>

<script>
import {
      
       ref } from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      
    // 数据
    let name = ref('yang')
    let age = ref(18)
    let job = ref({
      
      
      type: '前端工程师',
      salary:'30k'
    })

    // 方法
    function sayHello() {
      
      
      alert(`你好呀,我叫${ 
        name.value}`)
    }
    function changeInfo() {
      
      
      console.log(name)
      name.value = 'cheng',
      age.value = 20
    }
    function changeJob() {
      
      
      job.value.type = 'UI设计师'
      job.value.salary = '100k'
    }

    return {
      
      
      name,
      age,
      job,
      sayHello,
      changeInfo,
      changeJob
    }
  }
}
</script>

小结

  • 基本类型的数据:响应式依然是靠object.defineProperty()get 与set完成的。
  • 对象类型的数据:最外层使用的是object.defineProperty()get 与set,内部“求助”了Vue3.0中的一个新函数——reactive函数

reactive函数

reactive作用

作用:定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

reactive原理

直接源数据封装成Proxy代理对象,Proxy代理对象是响应式的

对象属性reactive使用

语法: const 代理对象 = reactive(源对象)
接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象)
通过代理对象操作源对象内部数据进行操作。
eg:

    let job = reactive({
    
    
      type: '前端工程师',
      salary:'30k'
    })
     console.log(job)

在这里插入图片描述

例子:
app.vue

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   { name }}</h2>
  <h2>年龄{
   
   { age }}</h2>
  <h2>工作种类{
   
   { job.type }}</h2>
  <h2>薪资{
   
   { job.salary }}</h2>
  <h2>c:{
   
   { job.a.b.c }}</h2>
  <button @click="sayHello">hello</button>
  <br />
  <button @click="changeInfo">修改人的信息</button>
  <button @click="changeJob">修改工作信息</button>
</template>

<script>
import {
      
       ref,reactive } from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      
    // 数据
    let name = ref('yang')
    let age = ref(18)
    let job = reactive({
      
      
      type: '前端工程师',
      salary: '30k',
      a:{
      
      
        b: {
      
      
          c:6
        }
      }
    })

    // 方法
    function sayHello() {
      
      
      alert(`你好呀,我叫${ 
        name.value}`)
    }
    function changeInfo() {
      
      
      console.log(name)
      name.value = 'cheng',
      age.value = 20
    }
    function changeJob() {
      
      
      // console.log(job)
      job.type = 'UI设计师'
      job.salary = '100k'
      job.a.b.c = 666
    }

    return {
      
      
      name,
      age,
      job,
      sayHello,
      changeInfo,
      changeJob
    }
  }
}
</script>

在这里插入图片描述
reactive定义的响应式数据是“深层次的”。

数组属性reactive使用

Proxy封装的数组,可以直接通过下标修改数据,同时实现响应式布局。
定义数据

 let hobby = reactive(['吃饭', '睡觉', '打豆豆'])

修改数据

    function changeHobby() {
    
    
      hobby[0] = 'study'
    } 

当数组数据改变可以引起页面重新渲染

例子,reactive实现上述ref例子

app.vue:

<template>
  <h1>app组件</h1>
  <h2>姓名{
   
   { person.name }}</h2>
  <h2>年龄{
   
   { person.age }}</h2>
  <h2>工作种类{
   
   { person.job.type }}</h2>
  <h2>薪资{
   
   { person.job.salary }}</h2>
  <h2>c:{
   
   { person.job.a.b.c }}</h2>
  <h3>hobby:{
   
   { person.hobby}}</h3>

  <button @click="changeInfo">修改人的信息</button>
</template>

<script>
import {
      
       ref,reactive } from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      

    let person = reactive({
      
      
      name: 'yang',
      age: 18,
      job: {
      
      
        type: '前端工程师',
        salary: '30k',
        a: {
      
      
          b: {
      
      
            c: 6
          }
        }
      },
      hobby: ['吃饭', '睡觉', '打豆豆']
    })
   
    function changeInfo() {
      
      
      person.name = 'cheng',
      person.age = 18,
      person.job.type = 'UI设计师'
      person.job.salary = '100k'
      person.job.a.b.c = 666
      person.hobby[0] = 'study'
   }
  
    return {
      
      
      person,
      changeInfo
    }
  }
}
</script>

在这里插入图片描述

vue3的响应式原理

vue2的响应式原理

  • 实现原理:
    (1)对象类型:通过 object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。
    (2)数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。
    eg:对象类型
    <script type='text/javascript'>
        let person ={
      
      
            name:'yang',
            age:18
        }

        // vue2的响应式原理
        let p ={
      
      }
        Object.defineProperty(p,'name',{
      
      
            get(){
      
      
                return person.name
            },
            set(value){
      
      
                console.log("name被修改了,触发了响应式")
                person.name = value
            }
        })
        Object.defineProperty(p,'age',{
      
      
            get(){
      
      
                return person.age
            },
            set(value){
      
      
                console.log("age被修改了,触发了响应式")
                person.age = value
            }
        })
    </script>
  • 存在问题:
    (1)新增属性、删除属性,界面不会更新。
    但是vue2中也可以解决:
    新增属性this.$set(this.person, 'sex','女')
    删除属性this.$delete(this.person, 'sex'')
    (2)直接通过下标修改数组,界面不会自动更新。
    但是vue2中也可以解决:
    修改数组hobby第0个元素:this.$set(this.person.hobby, 0,'学习')
    修改数组hobby第0个元素:this.person.hobby.splice(0,1,'学习')

vue3的响应式原理

vue3的响应式优点

vue2中存在的问题在vue3中都解决了:
(1)新增属性、删除属性,界面会更新。
(2)直接通过下标修改数组,界面会自动更新。

eg:实现添加sex属性和删除name属性

<template>
  <h1>app组件</h1>
  <h2 v-show="person.name">姓名:{
   
   { person.name }}</h2>
  <h2>年龄:{
   
   { person.age }}</h2>
  <h2 v-show="person.sex">性别:{
   
   { person.sex }}</h2>
  <h2>工作种类:{
   
   { person.job.type }}</h2>
  <h2>薪资:{
   
   { person.job.salary }}</h2>
  <h2>c:{
   
   { person.job.a.b.c }}</h2>
  <h3>hobby:{
   
   { person.hobby}}</h3>

  <button @click="changeInfo">修改人的信息</button>
  <button @click="addsex">添加一个sex属性</button>
  <button @click="deleteName">删除一个name属性</button>
</template>

<script>
import {
      
      reactive } from 'vue'
export default {
      
      
  name: 'App',
  setup() {
      
      

  //数据 
    let person = reactive({
      
      
      name: 'yang',
      age: 18,
      job: {
      
      
        type: '前端工程师',
        salary: '30k',
        a: {
      
      
          b: {
      
      
            c: 6
          }
        }
      },
      hobby: ['吃饭', '睡觉', '打豆豆']
    })

  //  方法
    function changeInfo() {
      
      
      person.name = 'cheng',
      person.age = 18,
      person.job.type = 'UI设计师'
      person.job.salary = '100k'
      person.job.a.b.c = 666
      person.hobby[0] = 'study'
    }
    function addsex() {
      
      
      person.sex = '女'
    }
    function deleteName (){
      
      
       delete person.name
    }
  
    return {
      
      
      person,
      changeInfo,
      addsex,
      deleteName
    }
  }
}
</script>

在这里插入图片描述

vue3的响应式原理

通过Proxy实现,Proxy是es6中的语法,是window身上的一个内置函数window.Proxy,可以直接使用
proxy的意思是代理,

  • 语法格式:const p =new Proxy(person,{})
    第一个参数:
    可以使p映射person的操作,即p代理着person,当p的值发生变化时person的值也会发生变化(和Object.defineProperty相似之处),而且增删改变化都可以被检测到(和Object.defineProperty不同之处,Object.defineProperty只能检测到的变化)。
    ——这就形成了数据代理,但是还没有完成响应式
    第二个参数:
    用于实现响应式,里面可以编写增删改操作的响应式
<script type='text/javascript'>
        let person ={
    
    
            name:'yang',
            age:18
        }
         // vue3的响应式原理
         const p =new Proxy(person,{
    
    
            // 读取p的属性的响应式
            get(target,propName){
    
    
                // target代表person源对象
                // propName代表读取的属性名
                console.log(`有人读取了person身上的${
      
      propName}属性`)

                // propName是一个变量需要使用数组形式读取
                return target[propName]
            },
             // 修改p或给p追加属性时的响应式
            set(target,propName,value){
    
    
                 console.log(`有人修改了了person身上的${
      
      propName}属性,我要去修改页面了`)
                target[propName] = value
            },
            // 删除p的属性时的响应式
            deleteProperty(target,propName){
    
    
                console.log(`有人删除了person身上的${
      
      propName}属性,我要去修改页面了`)
                return delete target[propName]
            }
         })


    </script>

vue3的响应式原理——reflect

Reflect也是ES6新增的一个属性,在Window身上,可以直接调用。

  • 作用:可以实现对对象属性的增删改查
        let person ={
    
    
            name:'yang',
            age:18
        }
        // 读取
        Reflect.get(person,"name")
        // 修改
        Reflect.set(person,"name","cheng")
        // 添加
        Reflect.set(person,"sex","男")
        // 删除
        Reflect.deleteProperty(person,"name")
  • Reflect身上也有 defineProperty 属性:
        let person ={
    
    
            name:'yang',
            age:18
        }
        Reflect.defineProperty(person,"school",{
    
    
            get(){
    
    
                return "nefu"
            },
            set(value){
    
    
                person.school = value
            }
        })
//Object写法
        /*Object.defineProperty(person,"school",{
            get(){
                return "nefu"
            },
            set(value){
                person.school = value
            }
        })*/       

Reflect.defineProperty 和 Object.defineProperty的区别:
Object.defineProperty对一个代理对象设置两个相同的属性会直接报错。
Reflect.defineProperty 对一个代理对象设置两个相同的属性不会报错,且以第一次设置的属性为准。

vue3的响应式原理的reflect写法(常用)

<script type='text/javascript'>
        let person ={
      
      
            name:'yang',
            age:18
        }
         // vue3的响应式原理
        const p =new Proxy(person,{
      
      
            // 读取p的属性的响应式
            get(target,propName){
      
      
                // target代表person源对象
                // propName代表读取的属性名
                console.log(`有人读取了person身上的${ 
        propName}属性`)

                // propName是一个变量需要使用数组形式读取
                return Reflect.get(target,propName)
            },
             // 修改p或给p追加属性时的响应式
            set(target,propName,value){
      
      
                console.log(`有人修改了了person身上的${ 
        propName}属性,我要去修改页面了`)
                Reflect.set(target,propName,value)
            },
            // 删除p的属性时的响应式
            deleteProperty(target,propName){
      
      
                console.log(`有人删除了person身上的${ 
        propName}属性,我要去修改页面了`)
                return Reflect.deleteProperty(target,propName)
            }
         })
 </script>

vue3的响应式原理就是通过Proxy代理对象Reflect反射对象实现的

reactive和ref的区别

  1. 从定义数据角度对比:
  • ref用来定义:基本类型数据。
  • reactive用来定义对象(或数组)类型数据。
  • 备注: ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。
  1. 从原理角度对比:
  • ref通过object.defineProperty()的get与set来实现响应式(数据劫持)。
  • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。
  1. 从使用角度对比:
  • ref定义的数据:操作数据需要.value,模板中读取数据时直接读取不需要.value 。
  • reactive定义的数据:操作数据与读取数据均不需要.value 。

我们最常用的还是 reactive,如果需要处理基本类型数据,直接封装成对象即可

setup的两个注意点

vue2知识回顾

  • 传递属性props:vue2中使用props传递的是属性,父组件在组件标签中传递属性,子组件使用props接受属性。
    父组件:
  <div>
    <Student name='张三' :age="20"/>
  </div>

子组件:

props: ['name','age'],

正常情况下由props接收从父组件传递管来的参数,并且将参数放在组件实例对象上。但是如果不接收的话会存储在组件实例对象上的$attrs属性上,如果接受了就不会存储在$attrs属性上了。

  • 插槽
    子组件设置<slot></slot>,父组件向里面传递html内容,在组件实例对象的$slots属性上存储着父组件向子组件传递的html内容

setup执行的时机

在beforeCreate之前执行一次,this是undefined。

setup的参数

setup的参数:

  • props:值为对象,包含组件外部传递过来,且组件内部声明接收了的属性。
    组件内必须使用props属性接受了的数据,setup的 props参数才能获取到,并且setup的 props参数是一个代理对象,即可以实现响应式。
    props:['msg','school'],
    setup(props) {
    
    
        console.log("---setup----", props)
        }

在这里插入图片描述

  • context: 上下文对象(是一个普通的对象)
    包含:
    (1)attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于实例对象中的this.$attrs
    组件中没有使用props声明接受的参数会存放在attrs中。
    eg:
    props:['msg'],//还传递了一个“school”参数
    setup(props,context) {
    
    
         console.log("---setup----", context.attrs)
        }

在这里插入图片描述
(2)slots:收到的插槽内容,相当于实例对象中this.$slots
(3)emit:触发自定义事件的函数,相当于实例对象中this.$emit
在vue3中为组件绑定自定义事件需要使用emits配置声明一下为该组件绑定的自定义事件。emits:['hello']
setup中触发事件: context.emit('事件名',参数)

EG:
app.vue:(绑定事件)

<template>
  <demo @hello="showHelloMsg"  msg="hello" school="nefu"></demo>
</template>

<script>
import Demo from './components/Demo.vue'
export default {
      
      
  name: 'App',
  components: {
      
       Demo },
  setup() {
      
      
    function showHelloMsg (value){
      
      
      alert(`${ 
        value}`)
    }
    return {
      
      
      showHelloMsg
    }
  }
}
</script>

Demo.vue(触发事件)

<template>
    <h1>Demo组件</h1>
    <h2 v-show="person.name">姓名:{
   
   { person.name }}</h2>
    <h2>年龄:{
   
   { person.age }}</h2>
    <button @click="test">测试触发Hello事件</button>
</template>

<script>
import {
      
       reactive } from 'vue'
export default {
      
      
    name: 'App',
    props: ['msg', 'school'],
    emits: ['hello'],
    setup(props,context) {
      
      
        // console.log("---setup----", context.attrs)
        console.log("---setup----", context.emit)
        //数据 
        let person = reactive({
      
      
            name: 'yang',
            age: 18,
        })
        // 方法
        function test (){
      
      
           context.emit('hello',666)
        }
        return {
      
      
            person,
            test
        }
    }
}
</script>

猜你喜欢

转载自blog.csdn.net/mantou_riji/article/details/125954725