头图

一、setup基本用法
1、setup()-组合API的入口函数

  • 为什么要用setup?
    在实际开发中,为了实现一个功能,其逻辑可能需要data,computed,methods, watch等几项共同协作才能实现,当组件变得更大时,逻辑关注点就会变得碎片化,不利于阅读和维护,因此在处理单个逻辑关注点时,将这些相关的代码配置在一起,就是setup的目的。
  • 怎么用?
    调用setup(),所有的组合API函数都在此使用,只在初始化时执行一次
    setup()如果返回对象,对象中的属性或方法,模板中可以直接使用
<template>
  <div>{{count}}</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';

export default defineComponent({
  name: 'App',
    setup(){
      const count = 10
      return {
        count
      }
  }
});
</script>

但直接在setup()中声明的变量并不是响应式的,需要使用ref()函数。
2、ref()
ref是一个函数,其作用为:定义一个基本类型的响应式数据
返回值是一个reference对象,对象中有一个value属性,用于保存该响应式数据的值
DOM模板中不需要使用.value属性的写法

<template>
  <div class="btn" @click="updateCount">update</div>
  <!-- DOM中使用ref定义的响应式数据时不需要加.value -->
  <div>{{count}}</div>
</template>

<script lang="ts">
//1、引入ref
import { defineComponent, ref } from 'vue'

export default defineComponent({
  name: 'App',
  setup() {
    //2、使用ref(),将count定义为一个响应式数据,参数是初始值
    const count = ref(0)
    function updateCount() {
      //使用数据时,调用该ref对象的value属性
      count.value++
    }
    return {
      count,
      updateCount
    }
  }
})
</script>

3、reactive()
当在setup中需要声明一个响应式的对象数据时,使用reactive()

<template>
  <div class="btn" @click="update">update</div>
  <div>
    <p>name: {{user.name}}</p>
    <p>age: {{user.age}}</p>
  </div>
</template>

<script lang="ts">
//1、引入reactive
import { defineComponent, reactive } from 'vue'

export default defineComponent({
  name: 'App',
  setup() {
    const obj = {
      name: 'Liane',
      age: 18
    }
    //2、调用reactive(),将目标对象作为参数传入
    const user = reactive(obj)
    console.log(user) //Proxy {name: "Liane", age: 18}
    function update() {
      user.age++
    }
    return {
      user,
      update
    }
  }
})
</script>

reactive()接收一个普通对象,然后返回该普通对象的响应式代理器对象。
内部基于ES6的Proxy实现,通过代理对象操作源对象,内部数据都是响应式的。


二、setup详解
1、setup执行时机

  • 在beforeCreate执行之前执行,此时组件对象还没有创建
  • this是undefined,不能通过this来访问data/computed/methods/props
  • 所有的compositionAPI相关的回调函数中也不可以使用this
<template>
  <h2>Chile{{msg}}</h2>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  name: 'App',
  props: ['msg'],
  //我们发现setup会在beforeCreate之前执行
  //this打印为undefined
  beforeCreate() {
    console.log('beforeCreate')
  },
  setup() {
    console.log('setup')
    console.log(this) //undefined
  }
})
</script>

2、setup的返回值

  • 一般都是返回一个对象,为模板提供数据,就是模板中可以直接使用此对象中所有属性/方法
  • 返回对象中的属性会与data函数返沪对象的属性合并成为组件对象的属性
  • 返沪对象中的方法会与methods中的方法合并成组件对象的方法
  • 若有重名,setup优先
<template>
  <h2>Chile{{msg}}</h2>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'App',
  props: ['msg'],
  data() {
    return {
      a: 0
    }
  },
  setup() {
    const b = ref(1)
    const fun1 = () => {
      return 'fun1'
    }
    return {
      b,
      fun1
    }
  },
  methods: {
    fun2() {
      return 'fun2'
    }
  },
  mounted() {
    console.log(this)
  }
})
</script>

image.png
注意:

  • 一般不用混合使用:methods中可以访问setup提供的属性和方法,但在setup中不能访问data和methods
  • setup不能是一个async函数:因为返回值不再是return的对象,而是promise,模板就看不到return的属性数据了

3、setup的参数
setup(props,context)
setup(props,{attrs,slots,emit})

  • props参数:包含组件props配置声明且传入了的所有props的对象
  • attrs参数:包含没有在props配置中声明的属性对象,相当于this.$attrs
  • slots参数:包含所有传入的插槽内容的对象,相当于this.$slots
  • emit参数:可以用来分发一个自定义事件,相当于this.$emit

定义一个子组件Child.vue

<template>
  <slot></slot>
  <h2 @click="fun">Child{{msg}}</h2>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
  name: 'App',
  props: ['msg'],
  setup(props, { attrs, emit }) {
    console.log(props) //Proxy {msg: 0}
    console.log(attrs) //Proxy {text: "child", __vInternal: 1, onClickChild: ƒ}
    const fun = (n: number) => {
      //使用emit分发一个自定义事件
      emit('clickChild', 2)
    }
    return {
      fun
    }
  }
})
</script>

在App中使用Child组件

<template>
  <div class="btn" @click="update">update</div>
  <!--    prop      attrs           自定义事件 -->
  <Child :msg="msg" text="child" @clickChild="changeChild" />
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Child from './components/Child.vue'

export default defineComponent({
  name: 'App',
  components: {
    Child
  },
  setup() {
    const msg = ref(0)
    const update = () => {
      msg.value++
    }
    //child组件自定义事件
    const changeChild = (n: number) => {
      msg.value += n
    }
    return {
      msg,
      update,
      changeChild
    }
  }
})
</script>

三、reactive和ref

  • 都是Vue3组合API中2个最重要的响应式API
  • ref用来处理基本类型数据,reactive用来处理对象(递归深度响应式)
  • 如果用ref处理对象或数组,内部会自动将对象/数组转换为reactive的代理对象
  • ref内部:通过给value属性添加getter/setter来实现对数据的劫持
  • reactive内部:通过使用proxy来实现对对象内部所有数据的劫持,并通过Reflect反射操作对象内部数据
  • ref的数据操作:在js中使用ref对象.value获取数据,在模板中可直接使用
<template>
  <div class="btn" @click="add">add</div>
  <div>name: {{user1.name}}
    <br>age:{{user1.age}}</div>
  <div>name: {{user2.name}}
    <br>age:{{user2.age}}</div>
</template>
<script lang="ts">
import {defineComponent,ref,reactive} from 'vue'
export default defineComponent({
  name:'App',
  setup(){
    const user1 = ref({
      name: 'Rose',
      age: 16
    })
    const user2 = reactive({
      name: 'Jack',
      age: 18
    })
    const add = ()=>{
      //使用ref创建的响应式对象,通过.value获取数据
      user1.value.age ++
      //使用reactive创建的响应式对象,可直接获取数据
      user2.age ++
    }
    return {
      user1,
      user2,
      add
    }
  }
})
</script>

Liane
16 声望2 粉丝