前言

上一节, 我们分析了官网中通用API的前三个, 后面两个我们慢慢分析. 从今天这一章开始,我们进入Vue3的核心, 即组合式API 的学习.

首先我们从组合式API 的入口开始分析, 理解setup钩子函数的使用

1. setup 钩子函数的认识

钩子函数, 相信学过vue2的同学都很熟悉了, vue2组件的生命周期钩子函数会在组件不同的生命周期阶段调用.

但钩子函数的本质, 其实就是组件选项对象的方法. 我们通常说的选项都是选项对象的属性.

vue3选项对象中新增了一个钩子函数, 即setup作为组合式API的入口, 也就是说我们以后学习的组合式API 都需要在setup 钩子函数中使用.

示例:

import { defineComponent } from "vue";

export default defineComponent({
  // 钩子函数
  setup() {
    return {};
  },
});

data钩子函数一样, setup函数返回一个数据对象.

不同的是,setup函数返回值除了可以是数据对象, 也可以是一个渲染函数.


2. setup基本使用

setup 钩子函数的使用,我们在上一章中也提及过:

  • setup函数可以返回一个渲染函数, 渲染函数返回通过h创建的vnode
  • setup函数可以返回一个对象, 对象的数据可以在模板上直接使用

因为之前的学习, 我们知道,只有组件实例对象上的数据才可以被模板直接使用,

接下来我们分析一下setup返回对象中数据在组件实例对象上是如何存放的.


2.1. setup 钩子函数返回对象

setup钩子函数 是vue3 新增的选项, 是组件内部使用组合式API的入口, 在setup() 函数中返回的对象会暴露给模板和组件实例, 其他选项也可以通过组件实例来获取setup() 暴露的属性

我们使用mounted钩子函数输出组件实例对象, 即this的值, 查看setup返回值是否在组件实例对象上

示例:

import { defineComponent } from "vue";

export default defineComponent({
  setup() {
    // 声明数据
    const msg = "hello";

    // 返回数据对象
    return { msg };
  },

  mounted() {
    console.log("this", this);
  },
});

控制台输出结果:

img

你应该很明确看到,在组件实例对象上, 具有setup返回的数据msg, 因此setup返回对象中的数据,可以直接在模板中使用


2.2. setup 钩子函数中不能通过this 获取组件对象

上个实例中我们在mounted钩子函数中通过this访问组件实例对象. 如果我们在setup中需要使用组件实例对象怎么办? , 我们第一反应应该是使用this来获取, 看示例

示例:

import { defineComponent, } from "vue";


export default defineComponent({
  setup() {
    console.log("this", this);
   
    // 声明响应式数据
    const msg = "hello ";

    // 返回数据对象
    return { msg };
  }
});

运行结果:

// 控制台输出结果
this undefined

通过控制台输出的结果, 你就会发现, 在setup中使用this无法获取到组件的实例对象.

主要原因在于:

setup 钩子先于组件实例化对象创建之前调用

记住这句话,很重要, 也就是说在执行setup钩子函数时, 组件对象还未创建完成. 就像vue2beforeCreate钩子函数一样, 无法通过this获取组件对象

如果你此时查看vue3文档中的组件生命周期图, 你就会发现setup钩子函数是在beforeCreate钩子函数之前调用的, beforeCreate钩子函数都无法通过this获取的上下文组件对象, setup可想而知

vue3组件生命周期图:

img


2.3. setup 钩子函数获取组件实例对象

在某些场景下, 我们可能需要通过组件对象访问组件上的数据, 但我们又无法通过this来获取到组件对象, 那该怎么办呢?

其实你不用担心, vue3给我们提供了一个全局的getCurrentInstance函数, 获取组件实例对象.

但需要注意的是getCurrentInstance获取的组件对象和this获取的组件对象不太一样.我们通过示例查看getCurrentInstance返回值

示例:

<template>
  <div>
    <div>{{ msg }}</div>
  </div>
</template>


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


export default defineComponent({
  name: 'app',
  // setup函数, vue3 组合式API的入口
  setup() {
    
    // 获取组件实例对象
    const instance = getCurrentInstance()
    console.log('instance', instance)
   
    // 声明响应式数据
    const msg = 'hello '

    // 返回数据对象
    return { msg }
  },
})
</script>

控制台输出结果:

//  组件示例对象 instance 具有一个ctx 数据
// ctx 上下文对象存储当前组件setup 暴露的数据
/*
  instance {
    //...
    ctx:{
     msg: 'hello'
    }
        }
*/

通过示例返回的结果, 你可以看到getCurrentInstance函数返回的组件实例对象中具有一个ctx上下文对象, 该对象就是你通过this访问的对象.

如果你要问为什么会有此变化. 这是vue3版本更新后向前兼容一种手段.

如果你阅读过vue3源码, 源码中为了兼容vue2选项式API 做了大量的兼容手段, 如果你有兴趣,可以查看我vue3源码的文章.

最后提醒一句:

如果你将来选择使用TypeScript开发, 建议尽量少用getCurrentInstance

主要原因在于getCurrentInstance 会丢失返回值的TS类型, 比如props, 具体视情况而定

至此, 你应该已经了解, setup 函数返回的对象中的数据会暴露给组件实例对象ctx 上下文对象中,

因此模板中就可以正常使用了

2.4. setup 钩子函数返回渲染函数

setup钩子函数可以返回一个数据对象, 还可以直接返回渲染函数,

渲染函数就是一个返回vnode的函数, vue3给我们提供了一个全局h函数, 用于创建vnode

示例:

import { defineComponent, h } from "vue";

export default defineComponent({
  setup() {
    // 返回一个渲染函数
    return () => h("h2", null, "hello world");
  },
});

页面渲染效果:

img

3. setup 钩子函数的参数

使用setup函数时, setup函数可以接收两个参数:

  1. props: 收集父组件向当前子组件传递的数据,
  2. context: 上下文对象

3.1. props 参数

setup 函数的第一个参数是组件的 props

讲到props参数,在使用时, 你需要注意一下事项:

  1. props 数据是响应式的,也就表示父组件修改了传入的值, 子组件使用的值也会发生同步更新
  2. props中的数据不能通过解构的方式使用, 因为会丢失响应性, 即父组件数据的变化, 不会触发子组件的变化

具体响应性的原理,我们之后通过学习响应式API时, 在详细分析

我们通过示例来理解setup函数的props参数

示例:

父组件

<template>
  <div>
		<!--   使用子组件,并传递props 参数    -->
    <jc-children :count="count"></jc-children>

		<!--     点击修改数据 -->
    <button @click="change">修改</button>
  </div>
</template>


<script  lang="ts">
import { defineComponent } from 'vue';
import JxChildren from './views/child.vue'

export default defineComponent({
  name: 'app',
  components: { JxChildren },
  setup() {
    // 声明相应式数据
    const count = ref(0)
    const change = () => {
      count.value++
    }
    return { msg, count, change }
  }

})
</script>

父组件中通过refAPI 创建一个具有响应性的数据.

子组件:

<template>
  <h1>点击次数:{{ count }}</h1>
</template>

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

export default defineComponent({
  props: {
    count: {
      type: Number
    }
  },
  setup(props) {
    console.log(props.count)
     return {props}
  }
})
</script>

在示例中, 因为子组件setup 函数第一参数props是响应数据, 所以当父组件cout 发生变化, 子组件setup 函数中props 就能够同步获取到最新的数据,并且触发视图更行

接下来,我们修改一下子组件中 获取props数据的用法, 通过解构的方式获取count数据

示例:

<template>
  <h1>点击次数:{{ count2 }}</h1>
  <h1>点击次数2:{{ count3 }}</h1>
</template>

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

export default defineComponent({
  props: {
    count: {
      type: Number
    }
  },
  setup(props) {
    // 1. 解构获取props中的数据
    const {count: count2} = props

    // 2. 赋值新的变量
    const count3 = props.count
     return {count2,count3}
  }
})
</script>

示例中通过解构获取到count2 或者 直接赋值给新的变量count3 , 你会发现他们都是非响应式数据,

即当父组件count 发生变化时, props 本身会更新最新数据, 但是count2, count3 不会同步更新, 因此视图不会发生变化

如果你确实需要解构赋值,并暴露数据响应特性, 可以使用只要要学习的API, 比如toRefs, toRef 两个API, 这两个API 后续在说

当然最佳的方式, 就是不要从props中解构数据, 直接通过props.xxx方式获取数据, 就不会丢失响应性.

3.2. context 上下文对象

传入 setup 函数的第二个参数是一个 setup 上下文对象。context上下文对象中暴露了一些常用的数据, 比如attrs, emit

我们可以在控制台输出, 查看context上下文对象中都具有哪些数据

示例:

<script lang="ts">
import { defineComponent } from 'vue';
  
export default defineComponent({
  setup(props, ctx) {
    console.log('ctx', ctx)
    /*
     上下文对象
      ctx {
        attrs: (…) // 透传 Attributes(非响应式的对象,等价于 $attrs)
  			emit: (…) // 触发事件(函数,等价于 $emit)
  			expose: (exposed) => {…}  // 暴露公共属性(函数)
  			slots: (…)  // 插槽(非响应式的对象,等价于 $slots)
      }
    */

    return { }
  }
})
</script>

可以看到context对象具有attrs, emit, expose, slots等常用数据.

主要注意的时: 该上下文对象是非响应式的,可以安全地解构

因此在工作中, 推荐使用解构的方式获取上下文对象中具体的数据

示例:

export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

3.3. expose 暴露数据

expose 函数用于显式地限制该组件暴露出的属性,当父组件通过模板引用访问该组件的实例时,将仅能访问 expose 函数暴露出的内容:

简单理解就是, 原来父组件通过ref只要能获取到子组件对象, 就可以访问子组件中的所有数据,

expose函数的作用就是在子组件中明确限定父组件可以通过子组件对象访问的数据. 只有通过expose函数暴露出去数据, 父组件才能访问

示例:

未使用expose 时:

<template>
  <div>
		<!--   使用子组件,并传递props 参数    -->
    <jc-children ref="child"></jc-children>
  </div>
</template>


<script  lang="ts">
import { defineComponent } from 'vue';
import JxChildren from './views/child.vue'

export default defineComponent({
  name: 'app',
  components: { JxChildren },
  setup() {
    // 获取子组件实例对象
    const child = ref()

    // 查看组件实例对象
    nextTick(() => {
    	console.log('child', child)
      
    })
    return { child }
  }

})
</script>

子组件
<template>
  <h1>子组件</h1>
</template>

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

export default defineComponent({

  setup() {
     // 定义子组件数据
    const name = ref('张三')
    const age = ref(18)
    
    return { name, age}
  }
})
</script>

通过示例运行结果, 你就会发现, 在父组件中通过ref 获取子组件实例对象

在子组件实例对象上能看到子组件所有的数据

运行结果:

// console.log('child', child) 打印结果
/*
	child {
    age: (…)
    name: (…)
    $: (…)
    $attrs: (…)
    $data: (…)
    $el: (…)
    $emit: (…)
    $forceUpdate: (…)
    $nextTick: (…)
    $options: (…)
    $parent: (…)
    $props: (…)
    $refs: (…)
    $root: (…)
    $slots: (…)
    $watch: (…)
    _: (…)
  }
*/

如果在子组件中使用expose 指定暴露数据

子组件

<template>
  <h1>子组件</h1>
</template>

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

export default defineComponent({

  setup(props, {expose}) {
     // 定义子组件数据
    const name = ref('张三')
    const age = ref(18)

    // 子组件指定暴露age 数据
    expose({age})
    return { name, age}
  }
})
</script>

此时父组件获取子组件实例对象,打印时查看到的只有子组件暴露的数据

运行结果:

// console.log('child', child) 打印结果
/*
	child {
    age: (…)
  }
*/

也就是说, 只有expose函数参数对象中的数据,才会暴露出去.

4. 结语

至此, 关于setup钩子函数的使用,我们就已经讲完了, 你应该理解以下知识:

  1. setup钩子函数不能通过this获取组件对象, 而是通过getCurrentInstance函数获取
  2. setup钩子函数可以返回一个数据对象, 也可以直接返回渲染函数
  3. setup钩子函数接收两个参数, 即propscontext
  4. setup钩子函数中props参数不推荐使用解构方式获取数据, 会丢失数据的响应性
  5. setup钩子函数的context参数推荐使用解构方式获取数据, 因为context就是一个普通对象
  6. exposeAPI 函数暴露出去的数据, 才能在其他组件中通过组件对象访问

如果觉得这篇文章对你有帮助, 点赞关注不迷路, 你的支持是我的动力!

GitHub 加速计划 / vu / vue
207.54 K
33.66 K
下载
vuejs/vue: 是一个用于构建用户界面的 JavaScript 框架,具有简洁的语法和丰富的组件库,可以用于开发单页面应用程序和多页面应用程序。
最近提交(Master分支:2 个月前 )
73486cb5 * chore: fix link broken Signed-off-by: snoppy <michaleli@foxmail.com> * Update packages/template-compiler/README.md [skip ci] --------- Signed-off-by: snoppy <michaleli@foxmail.com> Co-authored-by: Eduardo San Martin Morote <posva@users.noreply.github.com> 4 个月前
e428d891 Updated Browser Compatibility reference. The previous currently returns HTTP 404. 5 个月前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐