# Vue

lastUpdated: 2023-9-01

# 前言

Vue框架部分我们会涉及一些高频且有一定探讨价值的面试题,我们不会涉及一些非常初级的在官方文档就能查看的纯记忆性质的面试题,比如:

vue常用的修饰符?

vue-cli 工程常用的 npm 命令有哪些?

vue中 keep-alive 组件的作用?

首先,上述类型的面试题在文档中可查,没有比官方文档更权威的答案了,其次这种问题没有太大价值,除了考察候选人的记忆力,最后,这种面试题只要用过vue的都知道,没有必要占用我们的篇幅.

我们的问题并不多,但是难度可能会高一些,如果你真的搞懂了这些问题,在绝大多数情况下会有举一反三的效果,可以说基本能拿下Vue相关的所有重要知识点了.

# 你对MVVM的理解?

MVVM是什么?

MVVM 模式,顾名思义即 Model-View-ViewModel 模式。它萌芽于2005年微软推出的基于 Windows 的用户界面框架 WPF ,前端最早的 MVVM 框架 knockout 在2010年发布。

Model 层: 对应数据层的域模型,它主要做域模型的同步。通过 Ajax/fetch 等 API 完成客户端和服务端业务 Model 的同步。在层间关系里,它主要用于抽象出 ViewModel 中视图的 Model。

View 层:作为视图模板存在,在 MVVM 里,整个 View 是一个动态模板。除了定义结构、布局外,它展示的是 ViewModel 层的数据和状态。View 层不负责处理状态,View 层做的是 数据绑定的声明、 指令的声明、 事件绑定的声明。

ViewModel 层:把 View 需要的层数据暴露,并对 View 层的 数据绑定声明、 指令声明、 事件绑定声明 负责,也就是处理 View 层的具体业务逻辑。ViewModel 底层会做好绑定属性的监听。当 ViewModel 中数据变化,View 层会得到更新;而当 View 中声明了数据的双向绑定(通常是表单元素),框架也会监听 View 层(表单)值的变化。一旦值变化,View 层绑定的 ViewModel 中的数据也会得到自动更新。

MVVM的优缺点?

优点:

分离视图(View)和模型(Model),降低代码耦合,提高视图或者逻辑的重用性: 比如视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定不同的"View"上,当View变化的时候Model不可以不变,当Model变化的时候View也可以不变。你可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑

提高可测试性: ViewModel的存在可以帮助开发者更好地编写测试代码

自动更新dom: 利用双向绑定,数据更新后视图自动更新,让开发者从繁琐的手动dom中解放 缺点:

Bug很难被调试: 因为使用双向绑定的模式,当你看到界面异常了,有可能是你View的代码有Bug,也可能是Model的代码有问题。数据绑定使得一个位置的Bug被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。另外,数据绑定的声明是指令式地写在View的模版当中的,这些内容是没办法去打断点debug的

一个大的模块中model也会很大,虽然使用方便了也很容易保证了数据的一致性,当时长期持有,不释放内存就造成了花费更多的内存

对于大型的图形应用程序,视图状态较多,ViewModel的构建和维护的成本都会比较高

# 你对Vue生命周期的理解?

生命周期是什么

Vue 实例有一个完整的生命周期,也就是从开始创建、初始化数据、编译模版、挂载Dom -> 渲染、更新 -> 渲染、卸载等一系列过程,我们称这是Vue的生命周期。

各个生命周期的作用

生命周期 描述

beforeCreate 组件实例被创建之初,组件的属性生效之前

created 组件实例已经完全创建,属性也绑定,但真实dom还没有生成,$el还不可用

beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用

mounted el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用该钩子

beforeUpdate 组件数据更新之前调用,发生在虚拟 DOM 打补丁之前

update 组件数据更新之后

activited keep-alive专属,组件被激活时调用

deadctivated keep-alive专属,组件被销毁时调用

beforeDestory 组件销毁前调用

destoryed 组件销毁后调用

生命周期示意图

Vue父子组件生命周期执行顺序及钩子函数的个人理解

加载渲染过程

父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted

子组件更新过程

父beforeUpdate->子beforeUpdate->子updated->父updated

父组件更新过程

父beforeUpdate->父updated

销毁过程

父beforeDestroy->子beforeDestroy->子destroyed->父destroyed

https://blog.csdn.net/qq3401247010/article/details/81585411

# 异步请求适合在哪个生命周期调用?

官方实例的异步请求是在mounted生命周期中调用的,而实际上也可以在created生命周期中调用。

# Vue组件如何通信?

Vue组件通信的方法如下:

props/$emit+v-on: 通过props将数据自上而下传递,而通过$emit和v-on来向上传递信息。 EventBus: 通过EventBus进行信息的发布与订阅

vuex: 是全局数据管理库,可以通过vuex管理全局的数据流

$attrs/$listeners: Vue2.4中加入的$attrs/$listeners可以进行跨级的组件通信 provide/inject:以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效,这成为了跨组件通信的基础

还有一些用solt插槽或者ref实例进行通信的,使用场景过于有限就不赘述了。

详细可以参考这篇文章vue中8种组件通信方式,不过太偏门的通信方式根本不会用到,单做知识点了解即可

# computed和watch有什么区别?

computed:

computed是计算属性,也就是计算值,它更多用于计算值的场景

computed具有缓存性,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算

computed适用于计算比较消耗性能的计算场景 watch:

更多的是「观察」的作用,类似于某些数据的监听回调,用于观察props``$emit或者本组件的值,当数据变化时来执行回调进行后续操作

无缓存性,页面重新渲染时值不变化也会执行

小结:

当我们要进行数值计算,而且依赖于其他数据,那么把这个数据设计为computed

如果你需要在某个数据变化时做一些事情,使用watch来观察这个数据变化

# Vue是如何实现双向绑定的?

利用Object.defineProperty劫持对象的访问器,在属性值发生变化时我们可以获取变化,然后根据变化进行后续响应,在vue3.0中通过Proxy代理对象进行类似的操作。

// 这是将要被劫持的对象
const data = {
  name: '',
};

function say(name) {
  if (name === '古天乐') {
    console.log('给大家推荐一款超好玩的游戏');
  } else if (name === '渣渣辉') {
    console.log('戏我演过很多,可游戏我只玩贪玩懒月');
  } else {
    console.log('来做我的兄弟');
  }
}

// 遍历对象,对其属性值进行劫持
Object.keys(data).forEach(function(key) {
  Object.defineProperty(data, key, {
    enumerable: true,
    configurable: true,
    get: function() {
      console.log('get');
    },
    set: function(newVal) {
      // 当属性值发生变化时我们可以进行额外操作
      console.log(`大家好,我系${newVal}`);
      say(newVal);
    },
  });
});

data.name = '渣渣辉';
//大家好,我系渣渣辉
//戏我演过很多,可游戏我只玩贪玩懒月

let  obj = {};
Object.defineProperty(obj, 'name', {
    get:  function () {
        return  name;
    },
    set:  function (value) {
       document.getElementById('text').value = value;
       document.getElementById('show').innerHTML = value
    }
})
let  input = document.getElementById('text');
input.addEventListener('input', function (e) {
    let  text = e.target.value;
    obj.name = text;
})
// 发布者
class  Vue {
    constructor(options) {
        this.options = options;
        this.$data = options.data; // 获取数据
        this.$el = document.querySelector(options.el); // 获取app对象
        this._directive = {};
        this.Observer(this.$data)
        this.Compile(this.$el)
    }
    // 劫持数据
    Observer(data) {
        for (let  key  in  data) {
            this._directive[key] = []
            let  val = data[key]
            let  watch = this._directive[key]
            Object.defineProperty(this.$data, key, {
                get:  function () {
                    return  val
                },
                set:  function (newValue) {
                    if (newValue !== val) {
                        val = newValue;
                        watch.forEach(element  => {
                            element.update();
                        });
                    }
                }
            })
        }
 }
// 指令解析
Compile(el) {
    let  nodes = el.children;
    for (let  i = 0; i < nodes.length; i++) {
        let  node = nodes[i];
        //递归 把嵌套的元素都找出来 判断是否含有指令
        if (node.children.length) {
            this.Compile(node)
        }
        if (node.hasAttribute('v-text')) {
            let  attrValue = node.getAttribute('v-text')
            this._directive[attrValue].push(new  Watcher(node, this, attrValue, 'innerHTML'))
        }
        if (node.hasAttribute('v-model')) {
            let  attrValue = node.getAttribute('v-model');
            this._directive[attrValue].push(new  Watcher(node, this, attrValue, 'value'))
            node.addEventListener('input', () => {
                this.$data[attrValue] = node.value
            })
        }
        }
    }
}
// 订阅者
class  Watcher {
    constructor(el, vm, exp, attr) {
        this.el = el;
        this.vm = vm;
        this.exp = exp;
        this.attr = attr;
        this.update();
    }
    update() {
        this.el[this.attr] = this.vm.$data[this.exp]
    }
}

详细实现见Proxy比defineproperty优劣对比?

# Proxy与Object.defineProperty的优劣对比?

Proxy的优势如下:

Proxy可以直接监听对象而非属性

Proxy可以直接监听数组的变化

Proxy有多达13种拦截方法,不限于apply、ownKeys、deleteProperty、has等等是Object.defineProperty不具备的

Proxy返回的是一个新对象,我们可以只操作新的对象达到目的,而Object.defineProperty只能遍历对象属性直接修改

Proxy作为新标准将受到浏览器厂商重点持续的性能优化,也就是传说中的新标准的性能红利

Object.defineProperty的优势如下:

兼容性好,支持IE9

详细实现见Proxy比defineproperty优劣对比?

# 你是如何理解Vue的响应式系统的?

响应式系统简述:

任何一个 Vue Component 都有一个与之对应的 Watcher 实例。

Vue 的 data 上的属性会被添加 getter 和 setter 属性。

当 Vue Component render 函数被执行的时候, data 上会被 触碰(touch), 即被读, getter 方法会被调用, 此时 Vue 会去记录此 Vue component 所依赖的所有 data。(这一过程被称为依赖收集)

data 被改动时(主要是用户操作), 即被写, setter 方法会被调用, 此时 Vue 会去通知所有依赖于此 data 的组件去调用他们的 render 函数进行更新。

深入响应式系统

简述VUE的响应式原理

# 既然Vue通过数据劫持可以精准探测数据变化,为什么还需要虚拟DOM进行diff检测差异?

考点: Vue的变化侦测原理

前置知识: 依赖收集、虚拟DOM、响应式系统

现代前端框架有两种方式侦测变化,一种是pull一种是push

pull: 其代表为React,我们可以回忆一下React是如何侦测到变化的,我们通常会用setStateAPI显式更新,然后React会进行一层层的Virtual Dom Diff操作找出差异,然后Patch到DOM上,React从一开始就不知道到底是哪发生了变化,只是知道「有变化了」,然后再进行比较暴力的Diff操作查找「哪发生变化了」,另外一个代表就是Angular的脏检查操作。

push: Vue的响应式系统则是push的代表,当Vue程序初始化的时候就会对数据data进行依赖的收集,一但数据发生变化,响应式系统就会立刻得知,因此Vue是一开始就知道是「在哪发生变化了」,但是这又会产生一个问题,如果你熟悉Vue的响应式系统就知道,通常一个绑定一个数据就需要一个Watcher,一但我们的绑定细粒度过高就会产生大量的Watcher,这会带来内存以及依赖追踪的开销,而细粒度过低会无法精准侦测变化,因此Vue的设计是选择中等细粒度的方案,在组件级别进行push侦测的方式,也就是那套响应式系统,通常我们会第一时间侦测到发生变化的组件,然后在组件内部进行Virtual Dom Diff获取更加具体的差异,而Virtual Dom Diff则是pull操作,Vue是push+pull结合的方式进行变化侦测的.

# Vue为什么没有类似于React中shouldComponentUpdate的生命周期?

考点: Vue的变化侦测原理

前置知识: 依赖收集、虚拟DOM、响应式系统

根本原因是Vue与React的变化侦测方式有所不同

React是pull的方式侦测变化,当React知道发生变化后,会使用Virtual Dom Diff进行差异检测,但是很多组件实际上是肯定不会发生变化的,这个时候需要用shouldComponentUpdate进行手动操作来减少diff,从而提高程序整体的性能.

Vue是pull+push的方式侦测变化的,在一开始就知道那个组件发生了变化,因此在push的阶段并不需要手动控制diff,而组件内部采用的diff方式实际上是可以引入类似于 shouldComponentUpdate相关生命周期的,但是通常合理大小的组件不会有过量的diff,手动优化的价值有限,因此目前Vue并没有考虑引入shouldComponentUpdate这种手动优化的生命周期.

# Vue中的key到底有什么用?

key是为Vue中的vnode标记的唯一id,通过这个key,我们的diff操作可以更准确、更快速

diff算法的过程中,先会进行新旧节点的首尾交叉对比,当无法匹配的时候会用新节点的key与旧节点进行比对,然后超出差异.

diff程可以概括为:oldCh和newCh各有两个头尾的变量StartIdx和EndIdx,它们的2个变量相互比较,一共有4种比较方式。如果4种比较都没匹配,如果设置了key,就会用key进行比较,在比较的过程中,变量会往中间靠,一旦StartIdx>EndIdx表明oldCh和newCh至少有一个已经遍历完了,就会结束比较,这四种比较方式就是首、尾、旧尾新头、旧头新尾.

准确: 如果不加key,那么vue会选择复用节点(Vue的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的bug.

快速: key的唯一性可以被Map数据结构充分利用,相比于遍历查找的时间复杂度O(n),Map的时间复杂度仅仅为O(1).

# vue项目中用jsx语法

Vue框架并没有特意地去支持JSX,其实它也没必要去支持,因为JSX最后都会编译为标准的JavaScript代码。既然这样, 那Vue和JSX为什么能配合在一起使用呢? 很简单, 因为Vue支持虚拟DOM, 你可以用JSX或者其他预处理语言,只要能保证render方法正常工作即可。 Vue官方提供了一个叫做babel-plugin-transform-vue-jsx的插件来编译JSX, 现在介绍如何使用它:

打开你的webpack.config.js文件, 加入babel loader:

loaders: [   
    { test: /\.js$/, loader: 'babel', exclude: /node_modules/ }
]

2.新建或者修改你的.babelrc文件,加入 babel-plugin-transform-vue-jsx 这个插件

{
     "presets": ["es2015"],
     "plugins": ["transform-vue-jsx"]
 }

# 我们平时开发页面就是把页面拆成一个个组件,那么组件的拆分粒度是越细越好吗?

并不是。为什么呢?

每一个组件的挂载都需要执行初始化,渲染 vnode,patch 生成 DOM 的流程,而嵌套组件的挂载就是是一个递归挂载组件的流程,显然嵌套越深执行时间越长 https://mp.weixin.qq.com/s/FB5ME9kYARJs7h8FFbK6Rw

# vue中使用v-for时为什么不能用index作为key?

因为删除中间的元素,index会变,对应的前后节点需要更新

更新DOM的时候会出现性能问题

会发生一些状态bug

React 中的 key 也是如此

单页应用(SPA)如何进行SEO优化

前言

SEO(Search Engine Optimization),即搜索引擎优化。权威的解释是利用搜索引擎的规则提高网站在有关搜索引擎内的自然排名。具体到怎么实现,就是通过在网页上内嵌一些meta属性来达到更好地被搜索引擎爬取关键信息,从而提升自身的搜索排名。

# 单页应用(Single Page Application)

由Vue-Cli打包生成的网站的文件就是单页应用,它只有一个Index.html文件。显然如果需要对网站进行SEO优化,网站存在多个页面的情况,但是只有一个网页会存在meta标签,这对于需要进行SEO优化的需求者来说是完全不够的。那么这是否意味着我们需要放弃使用Vue、React之类的框架,而去使用原生的方式进行开发?答案是否定的。既然存在这样的需求,那么肯定有解决的方法!

Vue官网上提供了两种解决方案:

使用预渲染的方式对网页的路由指定模板

使用服务端渲染SSR

今天,我们只讲预渲染实现SEO优化

prerender-spa-plugin 插件 https://blog.csdn.net/qq_42049445/article/details/100135960

# VueX之actions与mutations的区别

actions

  1. 用于通过提交mutation改变数据
  2. 会默认将自身封装为一个Promise
  3. 可以包含任意的异步操作
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state) {
      state.count++
    }
  },
  actions: {
    increment (context) {
      context.commit('increment')
    }
  }
})
actions: {
  increment ({ commit }) {
    commit('increment')
  }
}

mutations

  1. 通过提交commit改变数据
  2. 只是一个单纯的函数
  3. 不要使用异步操作,异步操作会导致变量不能追踪
const store = new Vuex.Store({
  state: {
    count: 1
  },
  mutations: {
    increment (state) {
      // 变更状态
      state.count++
    }
  }
})
store.commit('increment')

# vue2和vue3的区别

根节点不同 vue2中必须要有根标签

<template>
  <div class='form-element'>
      <h2> {{ title }} </h2>
  </div>
</template>

vue3中可以没有根标签,会默认将多个根标签包裹在一个fragement虚拟标签中,有利于减少内存。

<template>
  <div class='form-element'>
  </div>
  <h2> {{ title }} </h2>
</template>

组合式API和选项式API

Vue2与Vue3 最大的区别 — Vue2使用选项类型API(Options API)对比Vue3合成型API(Composition API)

旧的选项型API在代码里分割了不同的属性: data,computed属性,methods,等等。新的合成型API能让我们用方法(function)来分割,相比于旧的API使用属性来分组,这样代码会更加简便和整洁。

在vue2中采用选项式API,将数据和函数集中起来处理,将功能点切割了当逻辑复杂的时候不利于代码阅读。

export default {
  props: {
    title: String
  },
  data () {
    return {
      username: '',
      password: ''
    }
  },
  methods: {
    login () {
      // 登陆方法
    }
  },
  components:{
            "buttonComponent":btnComponent
        },
  computed:{
	  fullName(){
	    return this.firstName+" "+this.lastName;     
	  }
}
}

在vue3中采用组合式API,将同一个功能的代码集中起来处理,使得代码更加有序,有利于代码的书写和维护。

export default {
  props: {
    title: String
  },
  setup () {
    const state = reactive({ //数据
      username: '',
      password: '',
      lowerCaseUsername: computed(() => state.username.toLowerCase()) //计算属性
    })
     //方法
    const login = () => {
      // 登陆方法
    }
    return { 
      login,
      state
    }
  }
}

生命周期的变化 创建前:beforeCreate -> 使用setup()

创建后:created -> 使用setup()

挂载前:beforeMount -> onBeforeMount

挂载后:mounted -> onMounted

更新前:beforeUpdate -> onBeforeUpdate

更新后:updated -> onUpdated

销毁前:beforeDestroy -> onBeforeUnmount

销毁后:destroyed -> onUnmounted

异常捕获:errorCaptured -> onErrorCaptured

被激活:onActivated 被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。

切换:onDeactivated 比如从 A 组件,切换到 B 组件,A 组件消失时执行

v-if和v-for的优先级

在vue2中v-for的优先级高于v-if,可以放在一起使用,但是不建议这么做,会带来性能上的浪费

在vue3中v-if的优先级高于v-for,一起使用会报错。可以通过在外部添加一个标签,将v-for移到外层

diff算法不同

vue2中的diff算法:

遍历每一个虚拟节点,进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方。用patch记录的消息去更新dom

缺点:比较每一个节点,而对于一些不参与更新的元素,进行比较是有点消耗性能的。

特点:特别要提一下Vue的patch是即时的,并不是打包所有修改最后一起操作DOM,也就是在vue中边记录变更新。(React则是将更新放入队列后集中处理)。

vue3中的diff算法:

在初始化的时候会给每一个虚拟节点添加一个patchFlags,是一种优化的标识。

只会比较patchFlags发生变化的节点,进行识图更新。而对于patchFlags没有变化的元素作静态标记,在渲染的时候直接复用。

响应式原理不同 vue2通过Object.definedProperty()的get()和set()来做数据劫持、结合和发布订阅者模式来实现,Object.definedProperty()会遍历每一个属性。

vue3通过proxy代理的方式实现。

proxy的优势:不需要像Object.definedProperty()的那样遍历每一个属性,有一定的性能提升proxy可以理解为在目标对象之前架设一层“拦截”,外界对该对象的访问都必须通过这一层拦截。这个拦截可以对外界的访问进行过滤和改写。

当属性过多的时候利用Object.definedProperty()要通过遍历的方式监听每一个属性。利用proxy则不需要遍历,会自动监听所有属性,有利于性能的提升

相比于vue2.x,使用proxy的优势如下

defineProperty只能监听某个属性,不能对全对象监听

可以省去for in、闭包等内容来提升效率(直接绑定整个对象即可)

可以监听数组,不用再去单独的对数组做特异性操作 vue3.x可以检测到数组内部数据的变化

插槽方式不同

在vue2中

匿名插槽

子组件:

<div>
	<slot></slot>
</div>

父组件:

<child>
  <span>我是插槽插入的内容</span>
</child>

具名插槽 子组件:

<div>
  <slot name="person"></slot>
</div>

父组件:

<child>
  <span slot="person">我是插槽插入的内容</span>
</child>

作用域插槽:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。不过,我们可以在父组件中使用slot-scope 特性从子组件获取数据

<div>
  <slot :data="data"></slot>
</div>

父组件:

<child>
  <span slot-scope="data">我是插槽插入的内容</span>
</child>

在vue3中

匿名插槽:和在vue2中一样

具名插槽:

子组件:

<div>
  <slot name="person"></slot>
</div>

父组件:

<child>
  <template v-slot:person>
    <span>我是插槽插入的内容</span>
  </template>
</child>

作用域插槽: 子组件:

<div>
  <slot :data="data"></slot>
</div>

父组件:


//<child>
  // <span #data>我是插槽插入的内容</span>  === <span #default="{data}">我是插槽插入的内容</span>
//</child>

总结:

具名插槽使用方式不同:vue2使用slot='',vue3使用v-slot:''

作用域插槽使用方式不同:vue2中在父组件中使用slot-scope="data"从子组件获取数据,vue3中在父组件中使用 #data 或者 #default="{data}"获取

# 浅析Vue3中setup语法糖

# 1. setup语法糖 (opens new window)简介

直接在script标签中添加setup属性就可以直接使用setup语法糖了。
使用setup语法糖 (opens new window)后,不用写setup函数;组件只需要引入不需要注册;属性和方法也不需要再返回,可以直接在template模板中使用

<template>
    <my-component @click="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
	import {ref} from 'vue';
	import myComponent from '@/component/myComponent.vue';
	//此时注册的变量或方法可以直接在template中使用而不需要导出
	const numb = ref(0);
	let func = ()=>{
		numb.value++;
	}
</script>

# 2. setup语法糖中新增的api

defineProps:子组件接收父组件中传来的props
defineEmits:子组件调用父组件中的方法
defineExpose:子组件暴露属性,可以在父组件中拿到

# 2.1 defineProps

父组件代码

<template>
	<my-component @click="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
	import {ref} from 'vue';
	import myComponent from '@/components/myComponent.vue';
	const numb = ref(0);
	let func = ()=>{
		numb.value++;
	}
</script>

子组件代码

<template>
	<div>{{numb}}</div>
</template>
<script lang="ts" setup>
	import {defineProps} from 'vue';
	defineProps({
		numb:{
			type:Number,
			default:NaN
		}
	})
</script>

# 2.2 defineEmits

子组件代码

	<template>
		<div>{{numb}}</div>
		<button @click="onClickButton">数值加1</button>
	</template>
	<script lang="ts" setup>
		import {defineProps,defineEmits} from 'vue';
		defineProps({
			numb:{
				type:Number,
				default:NaN
			}
		})
		const emit = defineEmits(['addNumb']);
		const onClickButton = ()=>{
			//emit(父组件中的自定义方法,参数一,参数二,...)
			emit("addNumb");
		}
	</script>

父组件代码

	<template>
		<my-component @addNumb="func" :numb="numb"></my-component>
	</template>
	<script lang="ts" setup>
		import {ref} from 'vue';
		import myComponent from '@/components/myComponent.vue';
		const numb = ref(0);
		let func = ()=>{
			numb.value++;
		}
	</script>

# 2.3 defineExpose

组件暴露出自己的属性,在父组件中可以拿到。示例: 子组件

<template>
  <div>
    我是子组件
  </div>
</template>

<script setup>
  import {defineExpose,reactive,ref} from 'vue'
  let ziage=ref(18)
  let ziname=reactive({
    name:'赵小磊'
  })
  //暴露出去的变量
  defineExpose({
    ziage,
    ziname
  })
</script>

父组件

<template>
  <div class="die">
    <h3 @click="isclick">我是父组件</h3>
    <zi-hello ref="zihello"></zi-hello>
  </div>
</template>

<script setup>
  import ziHello from './ziHello'
  import {ref} from 'vue'
  const zihello = ref()

  const isclick = () => {
    console.log('接收ref暴漏出来的值',zihello.value.ziage)
    console.log('接收reactive暴漏出来的值',zihello.value.ziname.name)
  }
</script>

# Vue3 ref、reactive、toRef、toRefs的区别

vue3.0里给数据添加响应式有很多api可用,有时傻傻分不清,分享一下个人的理解。

一、reactive reactive 用于为对象添加响应式状态。 接收一个js对象作为参数,返回一个具有响应式状态的副本。

  • 获取数据值的时候直接获取,不需要加.value
  • 参数只能传入对象类型
import { reactive } from 'vue'
// 响应式状态
const state = reactive({
  count: 0
})
// 打印count的值
console.log(state.count)

二、ref ref 用于为数据添加响应式状态。 由于reactive只能传入对象类型的参数,而对于基本数据类型要添加响应式状态就只能用ref了,同样返回一个具有响应式状态的副本。

  • 获取数据值的时候需要加.value。
  • 参数可以传递任意数据类型,传递对象类型时也能保持深度响应式,所以适用性更广。
  • vue 3.0 setup里定义数据时推荐优先使用ref,方便逻辑拆分和业务解耦。
import { ref } from 'vue'

// 为基本数据类型添加响应式状态
const name = ref('Neo')

// 为复杂数据类型添加响应式状态
const state = ref({
  count: 0
})

// 打印name的值
console.log(name.value)
// 打印count的值
console.log(state.value.count)

三、toRef toRef 用于为源响应式对象上的属性新建一个ref,从而保持对其源对象属性的响应式连接。 接收两个参数:源响应式对象和属性名,返回一个ref数据。 例如使用父组件传递的props数据时,要引用props的某个属性且要保持响应式连接时就很有用。

  • 获取数据值的时候需要加.value
  • toRef后的ref数据如果是复杂类型数据时,不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据
import { defineComponent, toRef } from 'vue'

export default defineComponent({
  props: [title],
  
  setup (props) {
    // 创建变量myTitle
    const myTitle = toRef(props, 'title')

    console.log(myTitle.value)
  }
})

四、toRefs toRefs 用于将响应式对象转换为结果对象,其中结果对象的每个属性都是指向原始对象相应属性的ref。 常用于es6的解构赋值操作,因为在对一个响应式对象直接解构时解构后的数据将不再有响应式,而使用toRefs可以方便解决这一问题。

  • 获取数据值的时候需要加.value
  • toRefs后的ref数据如果是复杂类型数据时,不是原始数据的拷贝,而是引用,改变结果数据的值也会同时改变原始数据
  • 作用其实和 toRef 类似,只不过 toRef 是对一个个属性手动赋值,而 toRefs 是自动解构赋值。
import { defineComponent, toRefs } from 'vue'

export default defineComponent({
  props: [title],
  
  setup (props) {
    // 使用了解构赋值语法创建了变量myTitle
    const { myTitle } = toRefs(props)

    console.log(myTitle.value)
  }
})

五、结语 尽量不要混着用,reactive 和 ref 选一种,toRef 和 toRefs 选一种,不然代码会很乱。 推荐 ref 和 toRefs 一把梭。

# vue3 Hooks和vue2 mixin区别

  1. 语法和用法:
  • vue3 Hooks:Hooks 是 Vue 3 引入的一种新的函数式编程的方式,使用函数的方式定义和使用。
  • vue2 mixin:Mixin 是在 Vue 2 中引入的一种对象混入机制,通过对象的方式进行定义和应用。
  1. 组合性和灵活性:
  • vue3 Hooks:Hooks 允许开发者根据逻辑功能来组合和复用代码,可以将相关的逻辑和状态封装为自定义的 Hook 函数。每个 Hook 可以独立存在,可以在不同的组件之间复用。
  • vue2 mixin:Mixin 在组件中的属性和方法会与组件本身的属性和方法进行合并,可能会导致命名冲突或不可预料的行为。
  1. 响应式系统:
  • vue3 Hooks:Composition API 使用了一个新的响应式系统,可以通过 reactive 和 ref 来创建响应式数据,这样可以更精确地控制组件的更新和依赖追踪。
  • vue2 mixin:Mixin 在响应式方面可能没有 VUE3 Hooks 灵活,而且 Vue 2.x 中使用的 ES6 对象和 Vue 3 的 Composition API 有一定的区别。

# Comment area