vue面试题
# vue面试题
# vue响应式原理
# vue2 响应式原理
- Object.defineProperty()来进行数据劫持,针对对象特定属性通过设置getter和setter方法来劫持数据,当读取属性时会触发 getter函数;当view视图层发生变化时,会触发Object.defineProperty()中的setter方法。
# vue3 响应式原理
- Proxy 是针对object类型数据的所有属性都添加get和set方法进行监听,在实现嵌套数据时,更简单,原生支持数组类的监听。
# vue优化方式
- 减少v-if的活用v-show
- v-for遍历添加key属性
- 将组件进行切割(vue的更新是组件粒度的,将耗时任务单独拆分成一组件,父组件数据变化时只会重新渲染父组件,耗时组件并不会渲染,这样性能会更好)
- 使用keep-alive缓存组件
- 区分computed和watch
- 图片资源懒加载
- 动态加载组件,异步加载组件
- 使用防抖和节流
- 开启Gzip压缩
- 使用cdn缓存
# 图片加载优化
- 减少文件体积大小,减少图片资源请求,
- 预加载
var images = new Array();
function preload() {
for (var i = 0; i < preload.arguments.length; i++) {
images[i] = new Image();
images[i].src = preload.arguments[i];
}
}
preload('1.png', '2.png', '3.png');
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# MVC 和 MVVM 的区别
- 没有很大的区别,只是 MVC 是单项数据绑定,只绑定了数据,数据更改然后视图跟着渲染,而 MVVM 实现了双向数据绑定,在基础上多了一层视图更改,对应数据绑定也跟着更改。要从 MVC 到 MVVM 其实不难,一个 onchange 事件搞定,所以没有很大区别。
# 怎样理解单项数据流
- 这个概念出现在组件通讯。父组件是通过 prop 把数据传递到子组件的,但是这个 prop 只能由父组件修改,子组件不能修改,否则会报错。子组件想修改时,只能通过 $emit 派发一个自定义事件,父组件接收后,由父组件修改。
# vue 生命周期
- onErrorCapture 捕获后代组件传递的错误时调用。
- onActivated 激活时(适用于 keep-alive 缓存的组件)
- onDeactivated 解散时调用(适用于 keep-alive 缓存的组件)
# 父子组件生命周期执行顺序
- 父组件 beforeCreated
- 父组件 created
- 父组件 beforeMounted
- 子组件 beforeCreated
- 子组件 created
- 子组件 beforeMounted
- 子组件 mounted
- 父组件 mounted
# 当父组件引入了mixin之后
- mixin beforeCreated
- 父组件 beforeCreated
- mixin created
- 父组件 created
- mixin beforeMounted
- 父组件 beforeMounted
- 子组件 beforeCreated
- 子组件 created
- 子组件 beforeMounted
- 子组件 mounted
- mixin mounted
- 父组件 mounted
# vue的传值方式
- $emit
- 路由传参
- query传参(地址栏显示参数)
- name和path都能用
- 地址栏会显示参数
- params传参(地址栏不显示参数)
- 跳转只能用name ,不能用path
- 地址栏不显示参数
- query传参(地址栏显示参数)
- vuex
- Eventbus
- localStore
- provide/inject
- $ref
- this.$refs.children 、 this.$refs.parents
# 导航守卫
全局守卫
- beforeEach (全局前置守卫,路由跳转前触发)
- beforeResolve (全局解析守卫,在所有组件内守卫和异步组件被解析后触发)
- afterEach (路由跳转完成后触发)
路由守卫
- beforeEnter (路由独享守卫)
组件守卫
- beforeRouteEnter
- beforeRouteUpdate
- beforeRouteLeave
参数
- to 即将要进入的目标路由对象
- from 即将要离开的路由对象
- next 是否可以进入某个具体路由,或者是某个具体路由的路径
# 从A页面跳转B页面时的生命周期
- A: beforeRouteLeave (组件守卫)
- beforeEach (全局守卫)
- beforeEnter (路由独享守卫)
- B: beforeRouteEnter (组件守卫)
- beforeResolve (全局解析守卫)
- afterEach (全局守卫)
- B: beforeCreate
- B: created
- B: beforeMount
- A: beforeDestroy
- A: destroyed
- B: mounted
# 路由的两种模式
- hash 模式
- history 模式
- abstract 模式(用来在不支持浏览器API的环境中,充当fallback)
# vue的history和hash的实现原理
- hash是通过
window.onHashChange()
事件去监听的 - history 是通过
window.history.pushState(null,null,path)
(新增历史记录)、window.history.replaceState(null,null,path)
(替换历史记录)在不进行刷新的情况下,操作浏览器的历史记录
# keep-alive 原理(重要)
- 通过缓存vnode实现,在渲染的时候对比组件的name(通过include和 exclude控制),命中缓存时就从缓存中读取。
- 问题: 使用keep-alive 的时候数据传递过去不会更新
- 会触发 activated deactivated
- 内存超限时通过LRU算法来管理
# nextTick 原理(重要)
- 在修改数据时,会开启一个异步队列,将watcher的update 更新操作缓存在同一事件循环中,同一个watcher只会被推入队列一次(通过watcherId去重),然后再下一事件循环中刷新队列,执行代码,这样做能减少频繁不必要的更新,提升更新渲染效率。
- 原理: nextTick 内部通过一系列异步api尝试将回调函数放在异步队列里, 包括 promise、mutationObserver、setImmediate、setTimeout。
- 所有的回调方法都会放在callbacks数组里,最后通过flushCallbacks 方法遍历 callbacks数组来依次执行回调。
- vue的dom更新是异步的,本身也是使用的 nextTick。
# nextTick 使用场景(重要)
- 获取数据更新后的dom
- 在created中进行dom操作
- 获取元素的宽高
# vue 模板编译过程(重要)
- 将模板编译为抽象语法树 AST(多叉树),将AST转化为渲染函数,执行渲染函数生成VDOM,将VDOM映射为真实DOM。
# computed 和 watch 的区别
- 计算属性是自动监听依赖值得变化,而动态返回内容,监听是一个过程,在监听的值变化时,可以触发一个回调,并做一些事情。
- 所以区别来源于用法,只是需要动态值,那就用计算属性;需要知道值的改变后执行业务逻辑,采用 watch,用反或混用虽然可行,但都是不正确的用法。
# mixins合并规则
合并规则:data数据和methods之类的对象类型的值会遍历合并,冲突时以组件内优先;钩子函数合并时都会执行,先执行mixins里的。
# Vue.extend()
- 用于拓展组件生成一个构造器,通过new构造函数生成一个新的组件实例,类似组件继承。(在vue3.0里废弃了)。
# Vue.use()
- 实际调用了该插件的install方法,当引入的插件有install时需要调用。
# Suspense 组件
- 作用: 可以在组件树上层等待下层的多个嵌套异步依赖项时,渲染一个加载状态 通过 #fallback 的形式。
# teleport
- 作用: 将组件内部的一部分模板“传送”到该组件dom结构的最外层。
# vue中的插槽
- 具名插槽
// 组件
<slot name="default" />
<template v-slot:default> </template>
// 或者
<template #default> </template>
1
2
3
4
5
6
7
2
3
4
5
6
7
- 作用域插槽
<!-- <MyComponent> 的模板 -->
<div>
<slot :text="greetingMessage" :count="1"></slot>
</div>
<MyComponent v-slot="slotProps">
{{ slotProps.text }} {{ slotProps.count }}
</MyComponent>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# $attrs 和 $listeners
- 透传 Attributes 是指由父组件传入,且没有被子组件声明为 props 或是组件自定义事件的 attributes 和事件处理函数。
- 你可以通过 inheritAttrs: false 来禁用这个默认行为。
# watch 和 watchEffect的区别
- watch 需要传入侦听的数据源。而watchEffect是自动收集数据源作为依赖
- watchEffect 在初始化时就会执行一次。而 watch的话,只有设置了
immediate: true
时,才会在初始化时监听。
# watchEffect
- 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行
- 设置 flush: post 将会使侦听器延迟到组件渲染后执行。
- 返回值是一个用来停止该副作用的函数。
// 副作用清除
watchEffect(async (onCleanup) => {
const { response, cancel } = doAsyncWork(id.value)
// `cancel` 会在 `id` 更改时调用
// 以便取消之前
// 未完成的请求
onCleanup(cancel)
data.value = await response
})
// 停止侦听器
const stop = watchEffect(() => {})
//当不需要此侦听器时:
stop()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 父组件可不可以监听子组件的生命周期
- 使用$emit 去监听
<!-- 父组件 -->
<template>
<div>
<child-component @mounted="handleDoSomething"></child-component>
</div>
</template>
<script>
export default Vue.component("HelloWorld", {
...
methods:{
handleDoSomething(data){
console.log('监听到子组件生命周期钩子函数mounted时,触发该回调',data)
}
},
components:{
"child-component":ChildComponent
}
});
</script>
<!-- 子组件 -->
<script>
export default {
...
mounted(){
this.$emit('mounted','mounted 触发了')
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
- 使用@hook (原理:就是父子组件通信的基础上(方法1),添加@hook,形成了对应生命周期函数的自动发布,方法1每次都是手动执行发布)
- @hook 使用场景:通过监听子组件的生命周期函数来处理业务,例如监听子组件loading,数据渲染到页面的之前让页面 loading。mounted 之后停止 loading。beforeUpdata 时开始 loading。updatad 之后停止 loading
<!-- 父组件 -->
<template>
<div>
<child-component @hook:mounted="handleDoSomething"></child-component>
</div>
</template>
<script>
export default Vue.component("HelloWorld", {
...
methods:{
handleDoSomething(data){
console.log('监听到子组件生命周期钩子函数mounted时,触发该回调',data)
}
},
components:{
"child-component":ChildComponent
}
});
</script>
<!-- 子组件 -->
<script>
export default {
mounted() {
const timer = setInterval(() => { ... }, 1000);
this.$once('hook:beforeDestroy', () => clearInterval(timer);)
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# vue 中的自定义指令
# 指令的生命周期
- created (在绑定元素的 attribute 前 或事件监听器应用前调用)
- beforeMount (在元素被插入到 DOM 前调用)
- mounted [vue3常用](在绑定元素的父组件及他自己的所有子节点都挂载完成后调用)
- beforeUpdate (绑定元素的父组件更新前调用)
- updated (在绑定元素的父组件,及他自己的所有子节点都更新后调用)
- beforeUnmount (绑定元素的父组件卸载前调用)
- unmounted(绑定元素的父组件卸载后调用)
# mounted或inserted参数
- el (指令绑定到的元素。这可以用于直接操作 DOM)
- binding 值
- value (传递给指令的值)
- oldValue (之前的值)
- arg(传递给指令的参数 (如果有的话)。例如在 v-my-directive:foo 中,参数是 "foo"。)
- vnode (代表绑定元素的底层 VNode)
# vue.directive('指令', {}) 使用场景
- 输入框自动聚焦
- 下拉菜单(点击下拉菜单区域外时,隐藏菜单)
- 相对时间转换
- 按钮级权限授权
# 输入框自动聚焦
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
<input v-focus>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 下拉菜单
点击下拉菜单本身不会隐藏菜单 点击下拉菜单以外的区域隐藏菜单
Vue.directive('clickoutside', {
bind(el, binding) {
function documentHandler(e) {
if (el.contains(e.target)) {
return false
}
if (binding.expression) {
binding.value(e)
}
}
el.__vueMenuHandler__ = documentHandler
document.addEventListener('click', el.__vueMenuHandler__)
},
unbind(el) {
document.removeEventListener('click', el.__vueMenuHandler__)
delete el.__vueMenuHandler__
}
})
new Vue({
el: '#app',
data: {
show: false
},
methods: {
handleHide() {
this.show = false
}
}
})
<div class="main" v-menu="handleHide">
<button @click="show = !show">点击显示下拉菜单</button>
<div class="dropdown" v-show="show">
<div class="item"><a href="#">选项 1</a></div>
<div class="item"><a href="#">选项 2</a></div>
<div class="item"><a href="#">选项 3</a></div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 相对时间转换
类似微博、朋友圈发布动态后的相对时间,比如刚刚、两分钟前等等
<span v-relativeTime="time"></span>
new Vue({
el: '#app',
data: {
time: 1565753400000
}
})
Vue.directive('relativeTime', {
bind(el, binding) {
// Time.getFormatTime() 方法,自行补充
el.innerHTML = Time.getFormatTime(binding.value)
el.__timeout__ = setInterval(() => {
el.innerHTML = Time.getFormatTime(binding.value)
}, 6000)
},
unbind(el) {
clearInterval(el.innerHTML)
delete el.__timeout__
}
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# vue按钮级权限怎么去做?
- 可以使用 vue的自定义指令去做。
封装 permissions.js 文件,设置全局的自定义指令
// 封装 permissions.js 文件,设置全局的自定义指令
import Vue from 'vue';
// 检测是否有权限
// 使用Vue.directive声明自定义指令btn-key
export const buttonPermissions = Vue.directive('btn-key',{
/**
* inserted:被绑定元素插入父节点时调用
* el:指令所绑定的元素,可以用来直接操作 DOM
* binding.value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
*/
inserted(el,binding){
let buttonKey = binding.value;
// 代表某个元素需要通过权限验证
if(buttonKey){
let key = checkKey(buttonKey)
if(!key){//没有权限
el.remove() //删除按钮
}
}else{
throw new Error('缺少唯一指令')
}
},
})
// 检测传入的元素key是否可以显示
function checkKey(key) {
// 获取权限数组
let permissionData = sessionStorage.getItem("permissionData") ? sessionStorage.getItem("permissionData") : [] ;
//如果传入的元素key不在权限数组里,则不可显示
let index = permissionData.indexOf(key)
if(index > -1) {
return true;
}else{
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
在main.js 中去挂载
import {buttonPermissions} from './common/permissions'
1
2
2
# 在 Vue 中怎么检测数组变化?
- vue 重写了这几个方法, push pop shift unshift sort reserve splice
- this.$set
# 在什么场景下会用到 slot
- 频繁的替换或更新时,可以使用 slot
- slot 相当于传递了一个组件给子组件
# Vue 的缺点
- 首页白屏的情况
- 为什么会有这种情况?
- Vue 在启动应用时,Vue 会对组件中的 data 和 computed 中状态值通过 Object.defineProperty 方法转化成 set,get 访问属性, 以便对数据变化进行监听。这一过程都是在启动应用时完成的,这也势必导致页面启动阶段比非 JS 驱动(如 jQuery 应用)的页面要慢一些
# Vue 怎么封装一个复用性比较高的组件
封装:求同存异
- 相同部分, 封装在一起
- 不同部分
- 有两种方式:
- 当不同部分变化的种类比较多时: 1.由外部来处理,可以通过插槽的方式,将不同的部分插入进去。
- 当种类只有固定的几种时: 2.由内部来处理;写一个方法,将种类传进来 ,通过 v-if 的形式判断,渲染其中的一种,
- 有两种方式:
# vue2中为什么 Vue.use 要在 new Vue()之前调用
在new Vue(options)时首先会执行this._init进行初始化,将Vue上的属性和options进行合并,然后在进行事件、生命周期等的初始化。beforeCreate,created生命周期的hook函数也是在这里进行调用
如果Vue.use在new Vue()之后执行,this._init()时你使用的插件的内容还没有添加到Vue.options.components、Vue.options.directives、Vue.options.filters等属性中。所以新初始化的Vue实例中也就没有插件内容 Vue.prototype._init 中合并 options
Vue.prototype._init = function (options?: Object) {
const vm: Component = this
// a uid
vm._uid = uid++
let startTag, endTag
...
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
...
// 挂载到dom上
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
}
//如果Vue.use在new Vue()之后执行,this._init()时你使用的插件的内容还没有添加到Vue.options.components、Vue.options.directives、Vue.options.filters等属性中。所以新初始化的Vue实例中也就没有插件内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 封装的公共样式 Less
- 将变量提取为 variable.less 文件。将通用颜色挂载写入, 并通过 webpack 挂载到全局
style-resources-loader
- 封装 单行溢出,多行溢出,1px 问题
- 设置一些通用的东西,去掉浏览器默认样式等
# 为什么 vue3 的打包体积比 vue2.x 的小了很多?
有一部分原因是 tree shaking,删除了一部分全局的方法
vue 将功能进行模块化, 在需要的时候去引入它
全局的 API tree shaking
- 受到影响的 API
- nextTick
- observable(用 Vue.reactive 替换)
- version
- compile
- set
- delete
- vue3 中所有的组件都是按需加载的,你想用某个组件 比如 nextTick,ref,toRefs, watch, computed 等 , 都是可以按需加载的。
- 受到影响的 API
composition api
- ref 接受一个内布置并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值得单个 property
value
- reactive 返回的是一个对象的响应式副本,直接就可以读取。
- toRefs 可以将解构的数据转化成响应式
- reactive + toRefs 形式,可以转化为跟 vue2.x 相似的写法
- isRef 判断当前值是否是一个 Ref 对象
- ref 接受一个内布置并返回一个响应式且可变的 ref 对象。ref 对象具有指向内部值得单个 property
proxy
# 在升级过程中 vue ,有的第三方库有没有遇到过什么问题?
- vue-count-to 这个第三方库不支持 ,需要将插件下载下来,手动更改一下 将 beforeDestory 修改为 beforeUnmount
# Vue3 和 vue2.62 的区别有哪些?
- 数据响应式的区别:vue3 使用 proxy,vue2 使用的是 Object.defaineProperty()
- 全局的 API 重构为 可以按需引入的实例
- 插件的安装方式不太一样,vue3 是要 createApp(App) 之后才可以 use 插件。而 vue2.x 通过 Vue.use() 就可以安装插件了。
- 一些普通的更改和一些不兼容语法的更改。
- 比如生命周期的重命名
- router 中写法的更改
- v-model 的更改
- composition API 的新增 setup()
- style 标签中 vars 的添加
数据绑定的区别, vue2 使用的是 Object.defineProperty() ,Vue3 使用的是 Proxy 代理
# 样式中的 /deep/ 变成了 ::deep
- 样式中增加了 vars
<script>
export default {
data() {
return {
color: 'red'
};
}
};
</script>
<style vars="{ color }">
.text {
color: var(--color);
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# main.js 中的区别
import App from './App.vue';
const app = createApp(App);
/**
* 1.不能通过 vue.prototype 挂载全局属性
* 如果想要在vue3中挂载全局属性,可以通过 app.config.globalProperties.$axios = axios 进行挂载全局属性, 官方不推荐挂载全局的属性
*
* 2. 创建方式不一样
* vue2中创建实例的方式: new Vue({}), vue 2可以在实例化之前去 .use() 使用vue插件
* 在vue3中创建实例 const app = createApp(App), vue3中 你必须先 createApp() 之后 ,才可以去.use() vue 的插件
*/
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# router 中的差别
// ========= 区别 1 ==========
// vue 2中的router
import Vue from 'vue'
import Router from 'vue-router'
export const constantRoutes = []
const createRouter = () =>
new Router({
mode: 'history'
routes: constantRoutes
})
export default createRouter
// vue3中的router
import { createRouter, createWebHashHistory } from 'vue-router'
const routes = []
const router = new createRouter({
history: createWebHashHistory(),
routes: routes
})
export default router
// ========= 区别 2 ==========
// *匹配所有页面的微小差别
// vue2中匹配所以页面的写法
const routes = [
{
path:'*',
redirect: '/404',
hidden: true
}
]
// vue3中匹配所有页面的写法
const routes = [
{
path:'/:pathMatch(.*)*',
redirect: '/404',
hidden: true
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 异步组件的使用(defineAsyncComponent)新增
// vue2中的异步组件
Vue.component('async-example', function (resolve, reject) {
setTimeout(function () {
resolve({
template: '<div>I am async!</div>'
});
}, 1000);
});
// 一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:
Vue.component('async-webpack-example', function (resolve) {
// 这个特殊的 `require` 语法将会告诉 webpack
// 自动将你的构建代码切割成多个包,这些包
// 会通过 Ajax 请求加载
require(['./my-async-component'], resolve);
});
// 可以在工厂函数中返回一个promise
Vue.component('async-webpack-example', () => import('./my-async-component'));
// 组件局部使用的时候
new Vue({
components: {
'my-component': () => import('./my-async-component')
}
});
// vue3的异步组件
// defineAsyncComponent 方法可以接收一个对象,加载成功的时候的组件,正在加载时的组件和加载失败时候的组件,还有一些配置
import { defineAsyncComponent } from 'vue';
export default {
components: {
toolBox: defineAsyncComponent(() => import('./components/toolbox'))
}
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 新增 Suspense 组件
<template>
<Suspense>
// 正常结果的组件
<template #default>
<async-comp />
</template>
// 还没有请求完成时显示的组件
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
</template>
<script>
import { onErrorCaptrued } from 'vue';
export default {
name: 'App',
setup() {
// 可以捕获异常的钩子函数
onErrorCaptured((error) => {
console.log(error);
});
}
};
</script>
// 异步组件
<template>
<div>
这是个异步组件,结果为
{{ result }}
</div>
</template>
<script>
export default {
async setup() {
const resData = await getData();
console.log(res);
return { result: resData.data };
}
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# 新增 Teleport 组件
可以将组件挂载在 DOM 的任何地方
// index.html
<div id="model"></div>
// 在组件中使用 有一个to属性
<teleport to="#model">
<model-component />
</teleport>
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 插槽的区别
vue3 中只保留了两种插槽,修改了 slot-scoped, 一种是具名插槽, 一种是带作用域的插槽
- 注意点
- 默认插槽的缩写语法不能和具名插槽混用,这样会导致作用域不明确。
// =========== 方式一 (具名插槽) ============
// 设置具名插槽
<template>
<slot name="header">
</template>
// 使用插槽
<template>
// 使用 v-slot:header 或使用简写 #header
<template #header>
</template>
</template>
// =========== 方式二 (作用域插槽) ============
// 设置作用域插槽(可以拿到插槽内的数据去渲染)
<template>
<div v-for="item in items">
<slot name="header" :item="item">
</div>
</template>
// 使用插槽, 点后面的属性跟的是自定义属性
<template>
<template #header="slotProps">
{{ slotProps.item }}
</template>
</template>
// =========== 如果只有一个默认插槽的时候 ============
// 使用
<template>
<div v-slot="slotProps">
// 要插入的内容
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# Proxy 和 Object.defineProperty()的区别
- Proxy 代理的是整个对象,Object.defineProperty 只代理对象中的某一个属性
- 对象上定义新属性时,Proxy 可以监听到,Object.defineProperty 监听不到
- 数组上新增删除修改时,Proxy 可以监听到, Object.defineProperty 监听不到
- Proxy 不兼容 IE, Object.defineProperty 不兼容 IE8 及以下