Skip to content

Vue3使用setup、ref和reactive

1. 初识setup函数

  1. 理解: 它是Vue3中新的一个配置项, 值为函数
  2. setup是所有的Composetion API(组合API)"表演的舞台"
  3. 组件中所用到: 数据方法等等,均要配置到setup中。
  4. setup函数的两种返回值:
    • 若返回一个对象,则对象中的属性、方法,在模版中局可以直接使用。(重点关注!)
    • 若返回一个渲染函数:则可以自定义渲染内容。(了解)
  5. 注意点:
    1. 尽量不要与Vue2.x配置混用,有下面奇怪的情形出现:
      • Vue2.x配置(data,methods、computed...)中可以访问到setup中的属性,方法。
      • 但在setup中不能访问到Vue2.x配置(data, methods、computed...)。
      • 如果有重名,setup优先
    2. setup不能是一个async函数,因为返回值不再是return的对象,而是promise, Vue不会自动解析then中的值,自然模版看不到return对象中的属性。

    提示

    例外:用到<Suspense>和异步组件配合使用时可以return一个Promise实例对象,即此时是异步的

2. setup函数深入理解

  1. setup执行的时机:在beforeCreate之前执行一次,this的值是undefined
  2. setup的参数:
    • props: 值为对象,包含组件外部传递过来,且组件内部声明接收了的属性
    • context(上下文对象):
      • attrs: 值为对象,包含组件外部传递过来,但没有在props配置中声明的属性
      • slots: 收到的插槽内容,相当于this.$slots
      • emit: 分发自定义事件的函数, 相当于this.$emit

在Vue3中:

js
<script>
  export default {
    name: 'Demo',
    beforeCreate(){
        console.log('------------beforeCreate-------------')
    },
    // Vue3中提供两个参数方便调用,避免没有this使用影响,需要重点关注props和context
    setup(props, context){ 
        console.log('------------setup-------------', this);
    }
  }
</script>

执行结果: Alt text

提示

因此在Vue3中不用关心是否能写箭头函数了,因为已经没有了this,箭头函数中现在不用写this避免undefined出现。

  1. context参数attrs源自Vue2的$attrs, context参数slots源自Vue2中的$slots

2.1 回顾Vue2中的attrs和slots

$attrs用来保存父组件传入子组件的属性信息,子组件$attrs会自动接收,它和props互斥,在props中定义的会在$attrs移除 在Vue2中About.vue代码如下:

vue
<template>
  <div>
   <About msg="早上好" name="jack">
     <span>在广州打工</span>
     <span>在成都打工</span>
   </About>
  </div>
</template>

可以查看控制台打印About.vue的实例对象查看上面的属性: 其中$slots中存放了两个<span>的虚拟节点, default表示为默认插槽。 Alt text 带名字的插槽传入

vue
<template>
  <div>
   <About msg="早上好" name="jack">
     <template v-slot:test1>
       <span>在广州打工</span>
     </template>
     <template v-slot:test2>
       <span>在成都打工</span>
     </template>
   </About>
  </div>
</template>

Alt text 可见出现两个带有名称的插槽在$slots上面。

2.2 分析props对象

在Vue3中父组件App.vue传入的数据:"你好啊"

vue
<template>
  <Demo hello="你好啊"></Demo>
</template>

<script>
  import { ref, reactive} from 'vue'
  import Demo from './components/Demo.vue'
  export default {
    name: 'App',
    components: {Demo},
    setup(props, context) {
      
    } 
}   
</script>
vue
<template>
    <div>{{ hello }}</div>
</template>

<script>
import { ref } from 'vue'

export default {
    name: 'Demo',
    setup(props, context) {
        console.log('------------setup-------------', props);
    }
}
</script>

运行会发现警告:提示我们没有使用传值变量hello,分析console打印发现props里面什么也没有,说明Vue3中参数props传入进来并没有自动包含携带父传子数据 Alt text 这时需要props属性,Vue3中依然有效,修改Demo.vue:

vue
<template>
    <div>{{ hello }}</div>
</template>

<script>
import { ref } from 'vue'

export default {
    name: 'Demo',
    props: ['hello'], 
    setup(props, context) {
        console.log('------------setup-------------', props);
    }
}
</script>

运行警告消失, props数据出来了: Alt text

2.2 分析context对象

打印context对象如下,有四大属性attrs、emit、expose、slots Alt text

vue
<template>
    <div>{{ hello }}</div>
    <button @click="invokeWalk">触发自定义事件</button>
    <slot></slot>
</template>

<script>
import { ref } from 'vue'
export default {
    name: 'Demo',
    props: ['hello'],
    emits: ['walkCode'],  // Vue3需要在子组件中申明使用自定义函数,不然会有警告
    setup(props, context) {
        console.log('------------context-------------', context)
        console.log('attrs', context.attrs); // 由于配置props: ['hello'],attrs中也就没有了hello值
        console.log('slots', context.slots);  // slots不会随着子组件中配置了<slot></slot>,就会消失里面的值
        console.log('emit', context.emit);   
        let invokeWalk = ()=>{
            context.emit('walkCode', 'jack')  
        }
        return {
            invokeWalk
        }    
    }
}
</script>
vue
<template>
  <Demo hello="你好啊" @walkCode="goWalkCode">
    <span>我是自定义Html代码</span>
  </Demo>
</template>

<script>
  import { ref, reactive} from 'vue'
  import Demo from './components/Demo.vue'
  export default {
    name: 'App',
    components: {Demo},
    setup(props, context) {
      let goWalkCode=(name)=>{
        alert(`继续搞java, ${name}`)
      }
      return {
        goWalkCode
      }
    } 
}
</script>

2.3 setup返回数据误区

  1. 不具有响应式,只在第一次页面正常显示,点击按钮页面不变。
js
return {
  name: person.name, 
  age: person.age, 
  salary: person.job.j1.salary, 
}
  1. 虽然具有响应式,但是数据已经没有和person关联,person的数据不再受到变化,响应式的数据只是return产生的新数据,不能在返回数据中加入ref。
js
return {
  name: ref(person.name), 
  age: ref(person.age), 
  salary: ref(person.job.j1.salary) 
}

3. ref函数

不同于Vue2中的ref类似id的效果,获取组件实例对象,Vue3中的ref是一个函数

  1. 作用: 定义一个响应式的数据
  2. 语法: const xxx = ref(initValue)
  • 创建一个包含响应式数据的引用对象(reference对象)
  • JS中操作数据: xxx.value
  • 模版中数据读取:不需要.value得到值,直接{{xxx}}即可,Vue3读取模版插值的时候会判断是RefImpl类型自动读取value属性。
  • 接收的数据可以是: 基本类型、也可以是对象类型。
  • 基本类型的数据: 响应式依然是靠Object.defineProperties()的get与set的实现, 是RefImpl类型数据
  • 对象类型的数据: 内部"求助"了Vue3.x中的新函数--reactive函数, 是Proxy类型数据

4. reactive函数

  • 作用: 定义一个对象类型的响应式数据(基本类型不要使用它,要用ref函数)
  • 语法: const 代理对象 = reactive(源对象)接收一个对象(或数组), 返回一个代理对象,返回一个代理对象(Proxy的实例对象, 简称proxy对象)
  • reactive定义的响应式数据是深层次的
  • 内部基于ES6的proxy实现,通过代理对象操作源对象内部数据进行操作。

5. 对比ref和reactive

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