> 文章列表 > 【前端面试专题】【5】Vue3

【前端面试专题】【5】Vue3

【前端面试专题】【5】Vue3

Vue3 比 Vue2 有什么优势?

  • 性能更好(后面详细讲)
  • 体积更小
  • 更好的 ts 支持
  • 更好的代码组织
  • 更好的逻辑抽离
  • 更多新功能

Vue3 生命周期

Option API

  • beforeDestroy 改为 beforeUnmount
  • destroyed 改为 unmounted
  • 其他沿用 Vue2 的生命周期
<!-- App.vue -->
<template><life-cycles v-if="flag" :msg="msg" /><button @click="msg = Date.now()">change msg</button><button @click="flag = !flag">hide</button>
</template><script>
import LifeCycles from './components/LifeCycles.vue'
export default {components: {LifeCycles},data() {return {msg: 'vue3',flag: true}}
}
</script>
<!-- LifeCycles.vue -->
<template><div>生命周期{{ msg }}</div>
</template><script>
export default {name: 'LifeCycles',props: ['msg'],beforeCreate() {console.log('beforeCreate')},created() {console.log('created')},beforeMount() {console.log('beforeMount')},mounted() {console.log('mounted')},beforeUpdate() {console.log('beforeUpdate')},updated() {console.log('updated')},// beforeDestroy 改名beforeUnmount() {console.log('beforeUnmount')},// destroyed 改名unmounted() {console.log('unmounted')}
}
</script> 

Composition API

<!-- LifeCycles.vue -->
<template><div>生命周期{{ msg }}</div>
</template><script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
export default {name: 'LifeCycles',props: ['msg'],// 等于 beforeCreate 和 createdsetup() {console.log('setup')onBeforeMount(() => {console.log('onBeforeMount')})onMounted(() => {console.log('onMounted')})onBeforeUpdate(() => {console.log('onBeforeUpdate')})onUpdated(() => {console.log('onUpdated')})onBeforeUnmount(() => {console.log('onBeforeUnmount')})onUnmounted(() => {console.log('onUnmounted')})}
}
</script> 

Composition API 对比 Options API

Composition API 带来了什么

  • 更好的代码组织
  • 更好的逻辑复用
  • 更好的类型推导

在这里插入图片描述

如何选择

  • 不建议共用,会引起混乱
  • 小型项目,业务逻辑简单,用 Options API
  • 中大型项目,逻辑复杂,用 Composition API

理解 ref、toRef、toRefs

ref

  • 生成值类型的响应式数据
  • 可用于模板和 reactive
  • 通过 .value 修改值

代码示例:

<template><div><h1>ref</h1><div>{{ ageRef }}</div><div>{{ details.name }}</div><p ref="eleRef">文字</p></div>
</template><script>
import { ref, reactive, onMounted } from 'vue'
export default {name: 'ref',setup() {const ageRef = ref(22) // 值类型响应式const nameRef = ref('Jae')const details = reactive({name: nameRef,country: 'China',province: 'XXX',city: 'XXXX'})setTimeout(() => {console.log('ageRef: ', ageRef.value) // .value 获取值ageRef.value = 24 // .value 修改值nameRef.value = 'Jack'}, 1500)const eleRef = ref(null)onMounted(() => {console.log(eleRef.value)})return {ageRef,details,eleRef}}
}
</script> 

在这里插入图片描述

toRef

  • 针对一个响应式对象(reactive 封装)的 prop
  • 创建一个 ref,具有响应式
  • 两者保持引用关系

代码示例:

<template><div><h1>toRef</h1><div>{{ ageRef }}</div><div>{{ details.name }} - {{ details.age }}</div></div>
</template><script>
import { reactive, toRef } from 'vue'
export default {name: 'toRef',setup() {const details = reactive({age: 22,name: 'Jae'})const ageRef = toRef(details, 'age')setTimeout(() => {details.age = 24 // ageRef 也会跟着变}, 1500)setTimeout(() => {ageRef.value = 30 // details.age 也会跟着变}, 2500)return {details,ageRef}}
}
</script> 

在这里插入图片描述

toRefs

  • 将响应式对象(reactive 封装)转换为普通对象
  • 对象的每个 prop 都是对应的 ref
  • 两者保持引用关系

代码示例:

<template><div><h1>toRefs</h1><div>{{ age }}-{{ name }}</div></div>
</template><script>
import { reactive, toRefs } from 'vue'
export default {name: 'toRefs',setup() {const details = reactive({age: 22,name: 'Jae'})const detailsRefs = toRefs(details) // 将响应式对象,变为普通对象return detailsRefs}
}
</script> 

在这里插入图片描述
有同学可能会有疑问,不就是为了能在模板直接使用 {{ age }}{{ name }} 吗,那直接 return { ...details } 解构出来不就好了,这里可以自己试一下,使用这种写法,然后设置一个定时器,改变 age 和 name 的值,可以发现页面中并没有发生变化。这是因为解构会丢失响应性

为什么解构属性会出现丢失响应式的问题呢?又该如何解决呢?可以参考一下这篇文章

简单来说就是 Vue3 使用 Proxy 来实现响应式,属性被解构出来后不再生效,解决方式就是通过 toRefs 来解决

最佳使用方式

  • 用 reactive 做对象的响应式,用 ref 做值类型的响应式
  • setup 中返回 toRefs(details),或者 toRef(details, ‘xxx’)
  • 合成函数返回响应式对象时,使用 toRefs

示例:合成函数返回响应式对象

funciton useFunction() {const details = reactive({x: 1,y: 2})//...return toRefs(details) // 返回时转换为 ref
}
export default {setup() {const { x, y } = userFunction() // 可以在不失去响应式的情况下解构return { x, y }}
}

进阶 ref、toRef、toRefs

为什么需要 ref

  • 返回值类型,会丢失响应式
  • 在setup、computed、合成函数中,都有可能返回值类型

代码示例:

<template><div><h1>ref</h1><div>{{ age }}-{{ name }}</div></div>
</template><script>
import { reactive} from 'vue'
export default {name: 'ref',setup() {let age = 22setTimeout(() => {age = 24 // 页面上不会有任何变化}, 1500)let details = reactive({name: 'Jae'})setTimeout(() => {details.name = 'Jack' // 页面上不会有任何变化}, 1500)return {age, // 值类型不具有响应式...details // 解构,相当于返回值类型,值类型不具有响应式}}
}
</script> 

为什么需要 .value

  • ref 是一个对象(为了不丢失响应式,而值类型不具有响应式),value 存储值,可以理解成 ref = { value: ‘xxx’ }
  • 通过 .value 属性的 get 和 set 实现响应式
  • 用于模板、reactive 时,不需要 .value(经过 Vue 编译),其他情况都需要

为什么需要 toRef 和 toRefs

  • 初衷:在不丢失响应式的情况下,把对象数据分散/扩散(解构)
  • 前提:针对的是响应式对象(reactive 封装的)非普通对象
  • 注意:toRef 和 toRefs 并不是创造响应式,而是延续响应式

Vue3 升级了哪些重要的功能

  • createApp
  • emit 属性
  • 生命周期
  • 多事件
  • Fragment
  • 移除 .sync
  • 异步组件的写法
  • 移除 filter
  • Teleport
  • Suspense
  • Composition API

createApp

// vue2.x
const app = new Vue({ ... })
Vue.use(...)
Vue.mixin(...)
Vue.component(...)
Vue.directive(...)// vue3
const app = Vue.createApp({ ... })
app.use(...)
app.mixin(...)
app.component(...)
app.directive(...)

emits 属性

<!-- 父组件 -->
<HelloWorld :msg="msg" @onSay="say" />
// 子组件
export default {name: 'HelloWorld',props: { msg: String },emits: ['onSay'], // 声明需要 emit 的事件名setup(props, { emit }) {emit('onSay', 'hello')}
}

多事件处理

<!-- 在 methods 里定义 func1 和 func2 两个函数 -->
<button @click="func1($event), func2($event)">提交
</button>

Fragment

template 中可以是多节点:

<!-- vue2.x -->
<template><div class="container"><h1>title</h1><p>...</p></div>
</template><!-- vue3 -->
<template><h1>title</h1><p>...</p>
</template>

移除 .sync

<!-- vue2.x -->
<MyCom :title.sync="title" /><!-- vue3 -->
<MyCom v-model:title="title" />

异步组件

<!-- vue2.x -->
new Vue({components: {'my-com': () => import('./myCom.vue')}
})<!-- vue3 -->
import { createApp, defineAsyncComponent } from 'vue'createApp({components: {AsyncComponent: defineAsyncComponent(() => {import('./myCom.vue')})}
})

移除 filter

<!-- vue2.x -->
<div>{{ message | formate }}</div><div :id="rowId | formaId"></div>

Teleport

<button @click="ifOpen = true">全屏</button><!-- teleport 弹窗,父元素是body -->
<teleport to="body"><div v-if="ifOpen" class="model"><div>teleport 弹窗</div><button @click="ifOpen = false">关闭</button></div>
</teleport>

Suspense

<Suspense><!-- 具有深层异步依赖的组件 --><template #default><my-com /> <!-- 异步组件 --></template><!-- 在 #fallback 插槽中显示 “loading” --><template #fallback>loading...</template>
</Suspense>

Composition API

  • reactive
  • ref 相关
  • readonly
  • watch、watchEffect
  • setup
  • 生命周期钩子函数

Composition API 实现逻辑复用

  • 抽离逻辑代码到一个函数
  • 函数命名约定为 useXxx 格式
  • 在 setup 中引用 useXxx 函数

代码示例:

<!-- App.vue -->
<template><life-cycles v-if="flag" /><button @click="flag = !flag">切换显示</button>
</template><script>
import HelloWord from './components/HelloWord.vue'
export default {components: {HelloWord},data() {return {flag: true}}
}
</script>
<!-- HelloWord.vue -->
<template><div><h1>mouse position {{ x }} {{ y }}</h1></div>
</template><script>
import useMousePosition from './useMousePosition'
export default {name: 'MousePosition',setup() {const { x, y } = useMousePosition()return {x,y}}
}
</script>
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'function useMousePosition() {const x = ref(0)const y = ref(0)function update(e) {x.value = e.pageXy.value = e.pageYconsole.log(x.value, y.value)}onMounted(() => {console.log('useMousePosition mounted')window.addEventListener('mousemove', update)})onUnmounted(() => {console.log('useMousePosition unMounted')window.removeEventListener('mousemove', update)})return { x, y }
}export default useMousePosition

在这里插入图片描述

Vue3 如何实现响应式

回顾 Object.defineProperty

在之前【前端面试专题】【4】Vue2 原理 有提到过 vue2 通过 Object.defineProperty 实现响应式的原理和缺陷,当时给出的总结是使用 Object.defineProperty 有这么几个缺点:

  • 深度监听,需要递归到底,一次性计算量大
  • 无法监听新增属性/删除属性
  • 无法原生监听数组,需要特殊处理

那么 vue3 使用的 Proxy 是否解决了这些问题呢?

Proxy 基本使用

const data = {name: 'Jae',age: 22,details: {address: 'XXX'}
}const proxyData = new Proxy(data, {get(target, key, receiver) {const result = Reflect.get(target, key, receiver)console.log('get:', key)return result // 返回结果},set(target, key, val, receiver) {const result = Reflect.set(target, key, val, receiver)console.log('set:', key, val)console.log('result:', result)return result // 是否设置成功},deleteProperty(target, key) {const result = Reflect.deleteProperty(target, key)console.log('delete property:', key)console.log('result:', result)return result // 是否删除成功}
})

在这里插入图片描述
改一下测试用的 data,我们看下数组的表现如何:

const data = [1, 2, 'c']

在这里插入图片描述
我们可以看到当我们向数组中 push 一项时,触发了很多 get 与 set,这些触发真的都是有必要的吗?比如触发了 set: length 4 的更新,在触发 set: 3 1 的时候 length 已经是4了,此时再 set 一次没什么意义,因此可以进行一些优化:

get(target, key, receiver) {// 只处理本身(非原型)的属性const ownKeys = Reflect.ownKeys(target)if (ownKeys.includes(key)) {console.log('get:', key) // 监听}const result = Reflect.get(target, key, receiver)return result // 返回结果
},
set(target, key, val, receiver) {// 重复的数据不处理const oldVal = target[key]if (val === oldVal) return trueconst result = Reflect.set(target, key, val, receiver)console.log('set:', key, val)return result // 是否设置成功
}

在这里插入图片描述

Reflect

const obj = {name: 'Jae',age: 22
}Reflect.has(obj, 'a')
console.log(obj) // true,相当于 'a' in obj
Reflect.deleteProperty(obj, 'age')
console.log(obj) // { name: 'Jae' } 相当于 delete obj.age

Proxy 实现响应式

// 创建响应式
function reactive(target = {}) {if (typeof target !== 'object' || target === null) {// 不是对象或数组,返回return target}// 代理配置const proxyConf = {get(target, key, receiver) {// 只处理本身(非原型)的属性const ownKeys = Reflect.ownKeys(target)if (ownKeys.includes(key)) {console.log('get:', key) // 监听}const result = Reflect.get(target, key, receiver)return result // 返回结果},set(target, key, val, receiver) {// 重复的数据不处理const oldVal = target[key]if (val === oldVal) return trueconst result = Reflect.set(target, key, val, receiver)console.log('set:', key, val)return result // 是否设置成功},deleteProperty(target, key) {const result = Reflect.deleteProperty(target, key)console.log('delete property:', key)console.log('result:', result)return result // 是否删除成功}}// 生成代理对象const observed = new Proxy(target, proxyConf)return observed
}// 测试数据
const data = {name: 'Jae',age: 22,details: {country: 'China'}
}const proxyData = reactive(data)

测试一下:

在这里插入图片描述
输出结果都没什么问题,就是在获取 proxyData.details.country 的时候,只触发了 get: details,并没有触发 get: country,也就是深度监听还需要处理:

get(target, key, receiver) {const ownKeys = Reflect.ownKeys(target)if (ownKeys.includes(key)) {console.log('get:', key)}const result = Reflect.get(target, key, receiver)return reactive(result) // 再包一层,深度监听
}

在这里插入图片描述
对比一下 vue2 通过 Object.defineProperty 时候,创建响应式的时候一进入就开始递归每一层数据,而使用 Proxy,只是在需要的时候,也就是 get 中才进行递归,并没有一次性递归到底。

最后优化一下,判断是新增的属性还是旧的属性:

set(target, key, val, receiver) {// 重复的数据不处理const oldVal = target[key]if (val === oldVal) return trueconst ownKeys = Reflect.ownKeys(target)if (ownKeys.includes(key)) {console.log('已有的key:', key)} else {console.log('新增的key:', key)}const result = Reflect.set(target, key, val, receiver)console.log('set:', key, val)return result // 是否设置成功
}

在这里插入图片描述
总结:

  • 深度监听,性能更好
  • 可监听 新增/删除 属性
  • 可监听数组变化
  • Proxy 无法兼容所有浏览器,无法 polyfill

v-model 参数的用法

先来回顾一下 Vue2 中 .sync 的用法,参考官方文档:

在这里插入图片描述
而在 Vue3 中去除了 .sync 的写法,改用 v-model 参数写法:

在这里插入图片描述

<myComponent v-model:title="bookTitle" /><!-- 是以下的简写: --><myComponent :title="bookTitle" @update:title="bookTitle = $event" />

代码示例:

<!-- 父组件 -->
<template><div><p>{{ name }}-{{ age }}</p><user-infov-model:name="name"v-model:age="age"/></div>
</template><script>
import { reactive, toRefs } from 'vue'
import UserInfo from './UserInfo.vue'
export default {name: 'Vmodel',components: {UserInfo},setup() {const details = reactive({name: 'Jae',age: 22})return toRefs(details)}
}
</script>
<!-- 子组件 -->
<template><input type="text" :value="name" @input="$emit('update:name', $event.target.value)" /><input type="text" :value="age" @input="$emit('update:name', $event.target.value)" />
</template><script>
export default {name: 'UserInfo',props: {name: String,age: Number}
}
</script>

在这里插入图片描述

watch 和 watchEffect 的区别

  • 两者都可以监听 data 属性变化
  • watch 需要明确监听哪个属性
  • watchEffect 会根据其中的属性,自动监听其变化

代码示例:

<template><div><p>watch and watchEffect</p><p>{{ numberRef }}</p><p>{{ name }}-{{ age }}</p></div>
</template><script>
import { reactive, ref, toRefs, watch, watchEffect } from 'vue'
export default {name: 'Watch',setup() {const numberRef = ref(10)const details = reactive({name: 'Jae',age: 22})watch(numberRef, (newNum, oldNum) => {console.log('ref watch', oldNum, newNum)}, {immediate: true // 在初始化的时候就监听})// setTimeout(() => {//   numberRef.value = 200// }, 1500)watch(() => details.age, (newAge, oldAge) => {console.log('details watch', newAge, oldAge)})setTimeout(() => {details.age = 25}, 1500)return {numberRef,...toRefs(details)}}
}
</script>

在这里插入图片描述

// 初始化时会执行一次,收集要监听的数据
watchEffect(() => {console.log('details.name', details.name)
})setTimeout(() => {details.name = 'Jack'
}, 1500)setTimeout(() => {details.age = 25
}, 1500)

在这里插入图片描述

setup 中如何获取组件实例

  • 在 setup 和 其他 Composition API 中没有 this
  • 可通过 getCurrentInstance 获取当前示例
  • 若使用 Options API 可照常使用 this

代码示例:

<template><div><p>get instance</p><p>{{ name }}</p></div>
</template><script>
import { getCurrentInstance, onMounted } from 'vue'
export default {name: 'GetInstance',data() {return {name: 'Jae'}},setup() {console.log('this', this)const instance = getCurrentInstance()console.log('instance', instance)console.log('data name', instance.data.name)onMounted(() => {console.log('onMounted name', instance.data.name)})}
}
</script>

在这里插入图片描述

Vue3 为什么比 Vue2 快

Proxy 响应式

前面已经提到过,对比 Vue2 使用的 Object.defineProperty 有何优势

PatchFlag(静态标记)

  • 编译模板,动态节点做标记
  • 标记,分为不同的类型,如 text props
  • diff 算法时,可以区分静态节点,以及不同类型的动态节点

可以在这个网站可以看到 Vue3 模板编译的函数:

在这里插入图片描述
在这里插入图片描述

hoistStatic(静态提升)

  • 将静态节点的定义,提升到父作用域,缓存起来
  • 多个相邻的静态节点,会被合并起来
  • 拿空间换时间的优化策略

在这里插入图片描述
把 hoistStatic 打开:

在这里插入图片描述
在这里插入图片描述

cacheHandler(事件监听缓存)

在这里插入图片描述
把 cacheHandler 打开:

在这里插入图片描述

在这里插入图片描述
缓存事件即:在遇到事件的时候如果没有该缓存函数则定义一个缓存函数,后面触发该事件的时候就不用再定义了

SSR 优化

  • 静态节点直接输出,绕过了 vdom
  • 动态节点还是需要动态渲染

在这里插入图片描述
把 SSR 打开:

在这里插入图片描述

在这里插入图片描述

tree-shaking

模板编译时,根据不同的情况,引入不同的 API

Vite

  • 一个前端打包工具,Vue 作者发起的项目
  • 借助 Vue 的影响力,发展较快,和 webpack 竞争
  • 开发环境下无需打包,启动快

Vite 为什么启动快

  • 开发环境下使用 ES6 Module,无需打包
  • 生产环境使用 rollup,速度差别不是很大

Vue3 和 JSX

Vue3 中 JSX 的基本使用

使用之前需要安装依赖 npm i @vitejs/plugin-vue-jsx -D ,然后在 vite.config.js 中引入:

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'export default defineConfig({plugins: [vue(), vueJsx()]
})

接下来就可以使用了:

<!-- template 版本 -->
<template><div><p>Demo {{ numRef }}</p></div>
</template><script>
import { ref } from 'vue'
export default {name: 'Demo',setup() {const numRef = ref(100)return {numRef}}
}
</script>
<!-- Vue3 JSX 版本 -->
<!-- 记得写上 lang -->
<script lang="jsx">
import { ref } from 'vue'export default {setup() {const numberRef = ref(200)return () => (<p>Demo { numberRef.value }</p>)}
}
</script>
// JSX 版本
import { defineComponent, ref } from "vue"
import Child from './Child'export default defineComponent({setup() {const numberRef = ref(300)return () => (<><p>demo1 { numberRef.value }</p><Child name="Jae" /></>)}
})
// Child.jsx
import { defineComponent } from "vue"export default defineComponent({props: ['name'],setup(props) {return () => (<p>Child { props.name }</p>)}
})

在这里插入图片描述

JSX 和 template 的区别

  • 语法上有很大的区别
  • 本质相同
  • 具体示例:插值、自定义组件、属性和事件、条件和循环

语法区别:

  • JSX 本质就是 js 代码,可以使用 js 的任何能力
  • template 只能嵌入简单的 js 表达式,其他需要指令,如 v-if、v-for
  • JSX 已经称为 ES 规范,template 还是 Vue 的自家规范

归纳:

  • 在 tempalate 中,插值是用 {{ }},而在 JSX 中,插值是用 { }
  • 在 JSX 引入自定义组件的时候,不能使用小写以及驼峰写法
  • 在 template 中,属性可以写成动态类型的,比如 <Child :name="name" />,而 JSX 中没有这种冒号的写法,需要动态的话也只能在括弧中写 <Child age={age.value + 10} />
  • 在 template 中,事件可以写成 <Child @click="handleClick" />,而 JSX 中应该写成 <Child onClick={handleClick} />
  • 在 template 中,条件可以写成 <Child v-if="flag" />,而 JSX 中应该写成 { flag.value && <Child /> }
  • 在 template 中,循环 可以写成 <div v-for="item in list" :key="item.id">{{ item.name }}</div>,而 JSX 中应该写成 {list.map(item => <div>{item.name}</div>)}

JSX 和 slot

  • slot 是 Vue 发明的概念,为了完善 template 的能力
  • slot 本身的使用并不简单,特别是作用域 slot 较难以理解

实现普通插槽

代码示例:

<!-- index.vue -->
<template><tabs default-active-key="1" @change="onTabsChange"><tab-panel key="1" title="title1"><div>tab panel content 1</div></tab-panel><tab-panel key="2" title="title2"><div>tab panel content 2</div></tab-panel><tab-panel key="3" title="title3"><div>tab panel content 3</div></tab-panel></tabs>
</template><script>
import Tabs from './Tabs.jsx'
import TabPanel from './TabPanel.vue'
export default {components: {Tabs,TabPanel},methods: {onTabsChange(key) {console.log('tab changed: ', key)}}
}
</script>
<!-- TabPanel.vue -->
<template><slot></slot>
</template><script>
export default {name: 'TabPanel',props: ['key', 'title']
}
</script>
// Tabs.jsx
import { ref } from 'vue'
export default {name: 'Tabs',props: ['defaultActiveKey'],emits: ['change'],setup(props, context) {const children = context.slots.default()const titles = children.map(panel => {const { key, title } = panel.props || {}return {key, title}})// 当前 actKeyconst actKey = ref(props.defaultActiveKey)function changeActKey(key) {actKey.value = keycontext.emit('change', key)}// jsxconst render = () => <><div>{ /* 渲染 buttons */ }{ titles.map(titleInfo => {const { key, title } = titleInforeturn <buttonkey={key}style={{ color: actKey.value === key ? 'blue' : '#333' }}onClick={() => changeActKey(key)}>{title}</button>})}</div><div>{ /* 渲染内容 */ }{children.filter(panel => {const { key } = panel.props || {}if (actKey.value === key) return truereturn false})}</div></>return render}
}

在这里插入图片描述

实现作用域插槽

回顾一下 Vue 中的作用域插槽:常用于子组件将 data 中的值传到 slot 中,供父组件使用:

<!-- index.vue -->
<template><Child><template v-slot="slotProps"><p>父组件传值</p><p>获取子组件 data:{{ slotProps }}</p></template></Child>
</template><script>
import Child from './Child.vue'
export default {components: {Child}
}
</script>
<template><p>Child</p><slot :msg="msg"></slot>
</template><script>
export default {data() {return {msg: '我是Child组件中data的msg'}}
}
</script>

在这里插入图片描述
这对于 Vue 的初学者或是很久没使用过作用域插槽的人来说是非常不友好的,因为这是 Vue 中自定的语法规范,很容易遗忘。接下来让我们看看使用 JSX 如何完成作用域插槽的功能:

// index.jsx
import { defineComponent } from "vue"
import Child from './Child.jsx'export default defineComponent({setup() {function render(msg) {return <p>msg: { msg }</p>}return () => (<><p>父组件</p><Child render={render}></Child></>)}
})
// Child.jsx
import { defineComponent, ref } from "vue"export default defineComponent({props: ['render'],setup(props) {const msgRef = ref('作用域插槽Child')return () => (<p>{props.render(msgRef.value)}</p>)}
})

在这里插入图片描述

script setup

基本使用

  • 顶级变量、自定义组件,可以直接用于模板
  • 可以正常使用 ref reactive computed 等能力
  • 和其他 <script> 同时使用

代码示例,Vue 版本需要大于等于 3.2.0:

<!-- index.vue -->
<template><div @click="addCount">{{ countRef }}</div><div>{{ details.name }}</div><Child />
</template><script lang='ts' setup>
import { ref, reactive } from 'vue'
import Child from './Child.vue'const countRef = ref(100)
function addCount() {countRef.value ++
}const details = reactive({name: 'Jae'
})
</script> 
<!-- Child.vue -->
<template><div>Child</div>
</template><script lang='ts' setup></script> 

在这里插入图片描述

属性和事件

  • defineProps
  • defineEmits

代码示例:

<!-- index.vue -->
<template><Child name="Jae" :age="25" @change="onChange" @delete="onDelete" />
</template><script lang='ts' setup>
import Child from './Child.vue'function onChange(val) {console.log(val)
}
function onDelete(val) {console.log(val)
}
</script> 
<template><div>Child - name: {{ props.name }}, age: {{ props.age }}</div><button @click="$emit('change', 'changeeeee')">change</button><button @click="handleClickDelete">delete</button>
</template><script lang='ts' setup>
// 定义属性
const props = defineProps({name: {type: String,default: 'Jack'},age: {type: Number,default: 22}
})// 定义事件
const emit = defineEmits(['change', 'delete'])function handleClickDelete() {emit('delete', 'deleteeeee')
}
</script> 

在这里插入图片描述

defineExpose

  • 暴露数据给父组件

代码示例:

<!--index.vue-->
<template><Child ref="childRef" />
</template><script setup>
import { ref, onMounted } from 'vue'
import Child from './Child.vue'const childRef = ref(null)
onMounted(() => {// 拿到 Child 组件中的一些数据console.log(childRef.value.num1) // 100console.log(childRef.value.num2) // 200
})
</script> 
<template><div>Child</div>
</template><script setup>
import { ref } from 'vue'const num1 = ref(100)
const num2 = ref(200)defineExpose({num1, num2
})
</script>