《前端面试题之 Vue 篇(第一集)》

目录

1、vue基本原理

Vue 的数据响应式是实现数据与视图自动同步更新的关键机制。

在 Vue 2.x 中,通过Object.defineProperty方法遍历data对象的属性,将它们转换为getter和setter形式。getter用于依赖收集,在组件渲染访问属性时,会把当前组件的watcher实例和该属性建立依赖关系;setter在属性值变化时触发,通知watcher重新计算并更新组件视图。但Object.defineProperty无法监听数组索引变化和对象新增、删除属性,需要借助Vue.set和Vue.delete来处理。

Vue 3.0 采用Proxy实现数据响应式。Proxy能直接代理整个对象,全面拦截各种操作,包括数组和对象的操作,无需重写数组方法。同时引入effect概念替代watcher,在依赖收集和更新上更加灵活高效,优化了响应式系统性能。

总结:Vue 的基本原理是通过数据响应式来监听数据变化,利用模板编译将模板转化为可执行的代码,借助组件化来构建复杂的应用结构,通过生命周期钩子函数来控制组件的各个阶段,使用虚拟 DOM 来高效地更新视图,从而为开发者提供了一种简洁、高效的构建用户界面的方式。

2、双向数据绑定的原理

Vue.js 的双向数据绑定通过数据劫持和发布订阅者模式实现,主要用Object.defineProperty()方法。原理分四步:

数据劫持(Observer):遍历数据对象及其子属性,给每个属性加上setter和getter。当数据变动触发setter时,就能监听到数据变化。

模板解析(Compile):解析模板指令,把变量替换成实际数据并渲染页面。同时给指令对应的节点绑定更新函数,这些节点就成了数据订阅者,数据变了就更新视图。

订阅者(Watcher):它是 Observer 和 Compile 通信的桥梁。实例化时加入属性订阅器 dep,有update()方法。数据变动时,dep 通知 Watcher,它调用update()方法,触发 Compile 绑定的回调更新视图。

MVVM:作为入口,整合 Observer、Compile 和 Watcher。Observer 监听数据,Compile 解析模板,Watcher 传递消息,最终实现数据变化更新视图,视图交互变化也能更新数据的双向绑定效果。

3、使用 Object.defineProperty() 来进行数据劫持有什么缺点?

使用Object.defineProperty()时,难以拦截通过下标修改数组数据的操作,如arr[0] = newValue,也无法有效监听数组的大部分操作,像push、pop等。这意味着当以这些方式改变数组时,无法触发组件重新渲染,致使页面数据无法及时更新。尽管 Vue 内部通过重写数组函数的手段解决了部分问题,但这本质是一种修补策略,并非根本性的解决方案。

在给对象动态新增属性时,Object.defineProperty()同样无法进行拦截。例如obj.newProp = ‘new value’,这一操作不会触发数据劫持,组件也不会重新渲染。这使得在开发过程中,对对象属性的动态管理变得棘手。

鉴于这些问题,Vue 3.0 弃用Object.defineProperty(),转而采用Proxy对对象进行代理。Proxy能全方位监听数据变化,然而它基于 ES6 语法,存在兼容性问题,在一些老旧浏览器中可能无法正常运行 。

4. MVVM、MVC、MVP的区别

MVC 模式
View(视图):就是用户能看到的界面,比如网页上的各种按钮、文本框、图片等,负责把数据显示给用户看。
Model(模型):用来存储数据和处理业务逻辑,像用户信息、订单数据等都放在这里,并且它知道怎么对这些数据进行增删改查等操作。
Controller(控制器):是 View 和 Model 之间的桥梁。当用户点击按钮或进行其他操作时,Controller 就会收到通知,然后它会根据用户的操作去调用 Model 里的方法来处理数据,最后再告诉 View 应该怎么更新界面。例如,用户点击 “登录” 按钮,Controller 就会去检查用户输入的账号密码是否正确(通过调用 Model 的方法),然后根据结果让 View 显示登录成功或失败的提示。

MVP 模式
View(视图):和 MVC 中的 View 类似,是用户看到的界面,负责展示数据和接收用户的操作。
Model(模型):也和 MVC 中的 Model 一样,存储数据和处理业务逻辑。
Presenter(呈现者):它是 View 和 Model 之间的中间人。Presenter 从 View 那里知道用户做了什么操作,然后去调用 Model 的方法处理数据,处理完后再根据结果告诉 View 怎么更新界面。与 MVC 不同的是,MVP 中 View 和 Model 没有直接联系,它们之间的交互都通过 Presenter 来进行,这样可以让 View 和 Model 更加独立,代码的复用性和可维护性更好。比如在一个新闻应用中,Presenter 会从 Model 获取新闻数据,然后决定在 View 上如何展示这些新闻,而 View 不用知道数据是从哪里来的,Model 也不用知道数据是怎么展示的。

MVVM 模式
View(视图):同样是用户看到的界面,负责数据的展示。
Model(模型):负责存储数据和业务逻辑。
ViewModel(视图模型):是关键的部分。它就像一个 “翻译官”,一边监听着 Model 里数据的变化,一旦数据有变化,就会通知 View 去更新界面;另一边,当用户在 View 上进行操作导致数据改变时,它也会把这些变化同步到 Model 中。这样,Model 和 View 之间就实现了双向数据绑定,开发者只需要关注数据的处理,不用像在 MVC 或 MVP 中那样手动去更新 View 或处理 View 和 Model 之间的交互。例如,在一个表单应用中,用户在输入框中输入内容,ViewModel 会自动把输入的内容同步到 Model 中,而当 Model 中的数据因为其他原因改变时,输入框中的内容也会自动更新。

5、v-if和v-show的区别

  • 手段:v-if是动态的向DOM树内添加或者删除DOM元素;v-show是通过设置DOM元素的display样式属性控制显隐

  • 编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换;

  • 编译条件:v-if是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始局部编译; v-show是在任何条件下,无论首次条件是否为真,都被编译,然后被缓存,而且DOM元素保留;

  • 性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗

  • 使用场景:v-if适合运营条件不大可能改变;v-show适合频繁切换;

6、v-if、v-show、v-html 的原理

  • v - if
    在 Vue 中,v - if 指令会调用 addIfCondition 方法。在生成虚拟节点(vnode)时,若条件不满足,对应节点会被忽略。到了渲染(render)阶段,由于之前生成 vnode 时已忽略该节点,所以不会对其进行渲染。比如 v - if=“show”,当 show 为 false 时,相关元素不会出现在最终页面中。
  • v - show
    v - show 指令会生成 vnode,在渲染过程中也会将其渲染为真实节点。不过,在渲染时会修改节点的 show 属性值(对应 CSS 中的 display 属性)来控制元素显示或隐藏。若 v - show=“show” 中 show 为 false,元素对应的 display 会被设置为 none 从而隐藏,但元素依然存在于 DOM 树中。
  • v - html
    v - html 指令执行时,会先移除目标节点下的所有子节点。然后调用 html 方法,通过 addProp 来添加 innerHTML 属性,本质就是将 v - html 绑定的值设置为节点的 innerHTML。例如 <div v - html=“htmlContent”>,会把 htmlContent 的内容渲染到
    中替换原有的内容。

7、MVVM

  • 概念:MVVM 是 Model - View - ViewModel 的缩写,是一种前端开发架构模式
    组成部分:
  • Model(模型):对应应用程序的数据部分,像 Vue 中 data 里的数据,负责存储和管理应用数据。
  • View(视图):即用户界面,也就是 DOM,是用户与应用交互的可视化部分。
  • ViewModel(视图模型):以 Vue 为例,是 Vue 的实例对象,作为连接 View 和 Model 的桥梁。
  • 核心机制 - 双向数据绑定:ViewModel 能监听 Model 数据变化,自动更新 View;当用户操作 View 时,ViewModel 也能监听到变化并通知 Model 进行改动,实现 View 和 Model 的自动同步,开发者无需手动操作 DOM 及过多关注数据状态,专注于业务逻辑。

8、v-for中key的作用

在这里插入图片描述

9、说一下Vue的生命周期

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

  1. beforeCreate(创建前):数据观测和初始化事件还未开始,此时 data 的响应式追踪、
    event/watcher 都还没有被设置,也就是说不能访问到data、computed、watch、methods上的
    方法和数据。
  2. created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
    3. beforeMount(挂载前):在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。此时还没有挂载html到页面上。
  3. mounted(挂载后):在el被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
  4. beforeUpdate(更新前):响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染。
  5. updated(更新后) :在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。此时 DOM已经根据响应式数据的变化更新了。调用时,组件 DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
  6. beforeDestroy(销毁前):实例销毁之前调用。这一步,实例仍然完全可用, this 仍能获取到实例。
  7. destroyed(销毁后):实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
  8. 另外还有 keep-alive 独有的生命周期,分别为 activated 和 deactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 activated 钩子函数。
  9. created和mounted的区别
    created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
    mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
  10. keep-alive是Vue的一个内置组件,用于包裹动态组件,可以缓存不活跃的组件实例,避免销毁它们。这样可以将组件切换时的状态保存在内存中,防止重复渲染DOM节点,从而减少加载时间和性能消耗,提高用户体验。

10、Vue 子组件和父组件执行顺序

  • 加载渲染过程:
  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted
  • 更新过程:
  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated
  • 销毁过程:
  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed

11、 一般在哪个生命周期请求异步数据

  1. 我们可以在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data已经创建,可以将服务端端返回的数据进行赋值。
  2. created:在渲染前调用,通常先初始化属性,然后做渲染。
  3. mounted:在模板渲染完成后,一般都是初始化页面后,在对元素节点进行操作。
  4. 请求的数据对DOM有影响,那么使用created。如果请求的数据对DOM无关,可以放在mounted。
  5. 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:能更快获取到服务端数据,减少页面加载时间,用户体验更好;SSR不支持 beforeMount 、mounted 钩子函数,放在 created 中有助于一致性。
  6. 拓展:SSR(Server - Side Rendering,服务端渲染)是一种将原本在 Vue 客户端进行的标签到 HTML 渲染工作转移至服务端完成,随后把生成的 HTML 直接返回给客户端的技术。其优势显著,在 SEO(搜索引擎优化)方面表现更佳,因为搜索引擎爬虫抓取页面时,能直接获取到完整的 HTML 内容,利于提高网站在搜索引擎中的排名;同时,首屏加载速度更快,用户无需等待客户端完成大量渲染工作,就能更快看到页面内容,提升了用户体验。然而,SSR 也存在一些缺点。从开发角度看,开发条件受到限制,服务器端渲染仅支持 beforeCreatecreated 这两个 Vue 生命周期钩子函数,这使得开发者在某些场景下的操作选择受限;在外部扩展库的使用上,当需要引入一些扩展库时,需要进行特殊处理,并且服务端渲染应用程序必须运行在 Node.js 环境中,增加了开发和部署的复杂性;从服务器层面来说,SSR 会给服务端带来更多的负载,因为服务端要承担原本客户端的部分渲染工作,对服务器的性能和资源要求更高。

12、vue中修饰符

  1. 事件修饰符:
.stop:阻止事件冒泡,使事件不会向上级元素传播。比如在一个嵌套的按钮结构中,阻止子按钮的点击事件冒泡到父元素。
.prevent:阻止事件默认行为,像阻止 <a> 标签的默认跳转行为。
.capture:使用事件捕获模式,内部元素触发的事件先在此处处理,而非按常规冒泡顺序。
.self:只有当 event.target 是当前绑定事件的元素时才触发事件,而非其子元素触发时也响应。
.once:事件只会触发一次,之后再触发无效,可用于一些初始化操作。
.passive:告诉浏览器可以立即触发事件默认行为,用于优化滚动事件性能等场景。
.native:将 Vue 组件当作原生 HTML 标签看待,用于给组件绑定原生 DOM 事件。
  1. 按键修饰符:
.keyup:在键盘按键抬起时触发事件。
.keydown:在键盘按键按下时触发事件。
  1. 系统修饰符:
.ctrl:与 Ctrl 键相关,用于组合键操作监听。
.alt:与 Alt 键相关。
.meta:在 Mac 系统对应 Command 键,在 Windows 系统对应 Windows 键。
  1. 鼠标修饰符:
.left:监听鼠标左键操作。
.right:监听鼠标右键操作。
.middle:监听鼠标中键操作。
  1. 表单修饰符:
.lazy:原本 input 事件在输入值改变时立即触发,.lazy 让其在输入完成(比如失去焦点)后触发,常用于表单数据处理。
.trim:自动删除用户输入内容前后的空格。
.number:将用户输入值转为数字类型,若输入不是数字则保留原值。

13、ElementUI中表单验证

  1. 在表单中加rules属性,然后再data里写校验规则;
  2. 内部添加规则;
  3. 自定义函数校验;
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

14、vue中组件通信

①props / $emit

父组件通过 props 向子组件传递数据,子组件通过 $emit 和父组件通信

  • 父组件
<template>
<div id="father">
<son :msg="msgData" :fn="myFunction"></son>
</div>
</template>
<script>
import son from "./son.vue";
export default {
name: father,
data() {
msgData: "父组件数据";
},
methods: {
myFunction() {
console.log("vue");
}
},
components: {
son
}
};
</script>
  • 子组件
<template>
<div id="son">
<p>{{msg}}</p>
<button @click="fn">按钮</button>
</div>
</template>
<script>
export default {
name: "son",
props: ["msg", "fn"]
};
</script>
  • 父组件
<template>
<div class="section">
<com-article :articles="articleList" @onEmitIndex="onEmitIndex"></comarticle>
<p>{{currentIndex}}</p>
</div>
</template>
<script>
import comArticle from './test/article.vue'
export default {
name: 'comArticle',
components: { comArticle },
data() {
return {
currentIndex: -1,
articleList: ['红楼梦', '西游记', '三国演义']
}
},
methods: {
onEmitIndex(idx) {
this.currentIndex = idx
}
}
}
</script>
  • 子组件
<template>
<div>
<div v-for="(item, index) in articles" :key="index"
@click="emitIndex(index)">{{item}}</div>
</div>
</template>
<script>
export default {
props: ['articles'],
methods: {
emitIndex(index) {
this.$emit('onEmitIndex', index) // 触发父组件的方法,并传递参数index
}
}
}
</script>

②eventBus事件总线($emit / $on )

eventBus 事件总线适用于父子组件、非父子组件等之间的通信,使用步骤如下:

(1)创建事件中心管理组件之间的通信。

// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()

(2)假设有两个兄弟组件 firstCom 和 secondCom :

<template>
<div>
<first-com></first-com>
<second-com></second-com>
</div>
</template>
<script>
import firstCom from './firstCom.vue'
import secondCom from './secondCom.vue'
export default {
components: { firstCom, secondCom }
}
</script>

(3)在 firstCom 组件中发送事件:

<template>
<div>
<button @click="add">加法</button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js' // 引入事件中心
export default {
data(){
return{
num:0
}
},
methods:{
add(){
EventBus.$emit('addition', {
num:this.num++
})
}
}
}
</script>

(4)在 secondCom 组件中接收事件:

<template>
<div>求和: {{count}}</div>
</template>
<script>
import { EventBus } from './event-bus.js'
export default {
data() {
return {
count: 0
}
},
mounted() {
EventBus.$on('addition', param => {
this.count = this.count + param.num;
})
}
}
</script>

在上述代码中,这就相当于将 num 值存贮在了事件总线中,在其他组件中可以直接访问。事件总线就相当于一个桥梁,不用组件通过它来通信。
虽然看起来比较简单,但是这种方法也有不变之处,如果项目过大,使用这种方式进行通信,后期维护起来会很困难。

③依赖注入(provide / inject)

这种方式就是Vue中的依赖注入,该方法用于父子组件之间的通信。当然这里所说的父子不一定是真正的父子,也可以是祖孙组件,在层数很深的情况下,可以使用这种方法来进行传值。就不用一层一层的传递了。
provide / inject 是Vue提供的两个钩子,和 data 、 methods 是同级的。并且 provide 的书写形式和 data 一样。
provide 钩子用来发送数据或方法
inject 钩子用来接收数据或方法
注意: 依赖注入所提供的属性是非响应式的。

  • 在父组件中
provide() {
return {
num: this.num
};
}
  • 在子组件中
inject: ['num']

还可以这样写,这样写就可以访问父组件中的所有属性:

// 父组件
provide() {
return {
app: this
};
}
data() {
return {
num: 1
};
}

// 子组件
inject: ['app']
console.log(this.app.num)

④ ref / $refs

这种方式也是实现父子组件之间的通信
ref : 这个属性用在子组件上,它的引用就指向了子组件的实例。可以通过实例来访问组件的数据和方法
(1)在子组件中:

export default {
data () {
return {
name: 'JavaScript'
}
},
methods: {
sayHello () {
console.log('hello')
}
}
}

在父组件中

<template>
<child ref="child"></component-a>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
mounted () {
console.log(this.$refs.child.name); // JavaScript
this.$refs.child.sayHello(); // hello
}
}
</script>

⑤$parent / $children

使用 $parent 可以让组件访问父组件的实例(访问的是上一级父组件的属性和方法)
使用 $children 可以让组件访问子组件的实例,但是, $children 并不能保证顺序,并且访问的数据也不是响应式的
需要注意

$children 的值是数组,而 $parent 是个对象;
通过 $parent 访问到的是上一级父组件的实例,可以使用 $root 来访问根组件的实例;
在组件中使用 $children拿到的是所有的子组件的实例,它是一个数组,并且是无序的;

在根组件 #app 上拿 $parent 得到的是 new Vue()的实例,在这实例上再拿 $parent 得到的是 undefined ,而在最底层的子组件拿 $children 是个空数组;

在子组件

<template>
<div>
<span>{{message}}</span>
<p>获取父组件的值为: {{parentVal}}</p>
</div>
</template>
<script>
export default {
data() {
return {
message: 'Vue'
}
},
computed:{
parentVal(){
return this.$parent.msg;
}
}
}
</script>

在父组件

// 父组件中
<template>
<div class="hello_world">
<div>{{msg}}</div>
<child></child>
<button @click="change">点击改变子组件值</button>
</div>
</template>
<script>
import child from './child.vue'
export default {
components: { child },
data() {
return {
msg: 'Welcome'
}
},
methods: {
change() {
// 获取到子组件
this.$children[0].message = 'JavaScript'
}
}
}
</script>

⑥ $attrs / $listeners

考虑一种场景,如果A是B组件的父组件,B是C组件的父组件。如果想要组件A给组件C传递数据,这种隔代的数据,该使用哪种方式呢?
如果是用 props/$emit 来一级一级的传递,确实可以完成,但是比较复杂;如果使用事件总线,在多人开发或者项目较大的时候,维护起来很麻烦;如果使用Vuex,的确也可以,但是如果仅仅是传递数据,那可能就有点浪费了。
针对上述情况,Vue引入了 $attrs / $listeners ,实现组件之间的跨代通信。
先来看一下 inheritAttrs ,它的默认值true,继承所有的父组件属性除 props 之外的所有属性;
inheritAttrs:false 只继承class属性 。

$attrs :继承所有的父组件属性(除了prop传递的属性、class 和 style ),一般用在子组件的子元素上。
$listeners :该属性是一个对象,里面包含了作用在这个组件上的所有监听器,可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)

A组件

<template>
<div id="app">
//此处监听了两个事件,可以在B组件或者C组件中直接触发
<child1 :p-child1="child1" :p-child2="child2" @test1="onTest1"
@test2="onTest2"></child1>
</div>
</template>
<script>
import Child1 from './Child1.vue';
export default {
components: { Child1 },
methods: {
onTest1() {
console.log('test1 running');
},
onTest2() {
console.log('test2 running');
}
}
};
</script>

B组件

<template>
<div class="child-1">
<p>props: {{pChild1}}</p>
<p>$attrs: {{$attrs}}</p>
<child2 v-bind="$attrs" v-on="$listeners"></child2>
</div>
</template>
<script>
import Child2 from './Child2.vue';
export default {
props: ['pChild1'],
components: { Child2 },
inheritAttrs: false,
mounted() {
this.$emit('test1'); // 触发APP.vue中的test1方法
}
};
</script>

C组件

<template>
<div class="child-2">
<p>props: {{pChild2}}</p>
<p>$attrs: {{$attrs}}</p>
</div>
</template>
<script>
export default {
props: ['pChild2'],
inheritAttrs: false,
mounted() {
this.$emit('test2');// 触发APP.vue中的test2方法
}
};
</script>

在上述代码中:
C组件中能直接触发test的原因在于 B组件调用C组件时 使用 v-on 绑定了 $listeners 属性
在B组件中通过v-bind 绑定 $attrs 属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)

⑦总结

(1)父子组件间通信

  1. 子组件通过 props 属性来接受父组件的数据,然后父组件在子组件上注册监听事件,子组件通过emit 触发事件来向父组件发送数据。
  2. 通过 ref 属性给子组件设置一个名字。父组件通过$refs组件名来获得子组件,子组件通过$parent 获得父组件,这样也可以实现通信。
  3. 使用 provide/inject,在父组件中通过 provide提供变量,在子组件中通过 inject 来将变量注入到组件中。不论子组件有多深,只要调用了 inject 那么就可以注入 provide中的数据。
    (2)兄弟组件间通信
  4. 使用 eventBus 的方法,它的本质是通过创建一个空的 Vue 实例来作为消息传递的对象,通信的组件引入这个实例,通信的组件通过在这个实例上监听和触发事件,来实现消息的传递。
  5. 通过$parent/$refs来获取到兄弟组件,也可以进行通信。
    (3)任意组件之间
  6. 使用 eventBus ,其实就是创建一个事件中心,相当于中转站,可以用它来传递事件和接收事件。
  7. 如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候采用上面这一些方法可能不利于项目的维护。这个时候可以使用 vuex ,vuex 的思想就是将这一些公共的数据抽离出来,将它作为一个全局的变量来管理,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。

15、keep-alive

.keep-alive是Vue的一个内置组件,用于包裹动态组件,可以缓存不活跃的组件实例,避免销毁它们。这样可以将组件切换时的状态保存在内存中,防止重复渲染DOM节点,从而减少加载时间和性能消耗,提高用户体验。(如果需要在组件切换的时候,保存一些组件的状态防止多次渲染,就可以使用 keep-alive 组件包裹需要保存的组件。
在这里插入图片描述
在这里插入图片描述

<component :is="currentView"> 中,‌:is是动态组件绑定的关键属性‌,用于指定当前需要渲染的组件名称或组件对象‌。

在这里插入图片描述
2‌. 动态控制缓存‌

<template>
  <keep-alive :include="cachedComponents">
    <router-view v-if="$route.meta.keepAlive" />
  </keep-alive>
  <router-view v-if="!$route.meta.keepAlive" />
</template>

<script>
export default {
  computed: {
    cachedComponents() {
      return ['ListPage', 'DetailPage']; // 指定需缓存的组件名
    }
  }
};
</script>

‌说明‌:通过 include 属性精准控制缓存范围‌。
在这里插入图片描述

16、axios封装

下载 创建实例 接着封装请求响应拦截器 抛出 最后封装接口
在这里插入图片描述
在这里插入图片描述

17、Vue中路由传参

(1)params方式

配置路由格式: /router/:id
传递的方式:在path后面跟上对应的值
传递后形成的路径: /router/123

①路由定义

//在APP.vue中
<router-link :to="'/user/'+userId" replace>用户</router-link>
//在index.js
{
path: '/user/:userid',
component: User,
},

②路由跳转

// 方法1:
<router-link :to="{ name: 'users', params: { uname: wade }}">按钮</router-link>
// 方法2:
this.$router.push({name:'users',params:{uname:wade}})
// 方法3:
this.$router.push('/user/' + wade)

③参数获取

通过 $route.params.userid 获取传递的值

(2)query方式

配置路由格式: /router ,也就是普通配置
传递的方式:对象中使用query的key作为传递方式
传递后形成的路径: /route?id=123

①路由定义

//方式1:直接在router-link 标签上以对象的形式
<router-link :to="{path:'/profile',query:{name:'why',age:28,height:188}}">档案
</router-link>
// 方式2:写成按钮以点击事件形式
<button @click='profileClick'>我的</button>
profileClick(){
this.$router.push({
path: "/profile",
query: {
name: "kobi",
age: "28",
height: 198
}
});
}

②路由跳转

// 方法1:
<router-link :to="{ name: 'users', query: { uname: james }}">按钮</router-link>
// 方法2:
this.$router.push({ name: 'users', query:{ uname:james }})
// 方法3:
<router-link :to="{ path: '/user', query: { uname:james }}">按钮</router-link>
// 方法4:
this.$router.push({ path: '/user', query:{ uname:james }})
// 方法5:
this.$router.push('/user?uname=' + jsmes)

③参数获取

通过$route.query 获取传递的值

(3)params和query的区别

在Vue-router中,paramsquery都用于在路由跳转时传递参数,但它们有以下一些区别:

传递方式

  • params:通过路由路径中的参数占位符来传递参数。例如,定义路由路径为/user/:id,那么可以在router.push方法中通过{ name: 'user', params: { id: 123 }}来传递参数,其中id就是参数名,123是参数值。
  • query:通过URL查询字符串来传递参数。例如,router.push({ path: '/user', query: { name: 'John', age: 30 }}),参数会以?name=John&age=30的形式附加在URL后面。

路由匹配

  • params参数是路由路径的一部分,必须在路由配置中定义对应的参数占位符,否则无法正确匹配路由。例如,/user/:id中的:id就是占位符,如果访问/user/而没有提供id参数,路由将无法匹配。
  • query:参数不属于路由路径的一部分,即使没有在路由配置中事先定义,也可以通过query传递参数。例如,路由配置为/user,仍然可以通过/user?name=John来访问,并且可以获取到name参数。

参数获取

  • params:在组件中可以通过this.$route.params来获取参数值。例如,this.$route.params.id就能获取到传递过来的id参数。
  • query:在组件中通过this.$route.query来获取参数值。例如,this.$route.query.name可以获取到name参数的值。

显示方式

  • params:参数会显示在路由路径中,是路径的一部分。例如,访问/user/123,其中123就是params参数。
  • query:参数显示在URL的查询字符串中,以?开头,多个参数之间用&连接。例如,/user?name=John&age=30nameagequery参数。

刷新页面时的表现

  • params:刷新页面时,params参数会保留在路由路径中,组件可以继续通过this.$route.params获取参数值。
  • query:刷新页面时,query参数也会保留在URL的查询字符串中,组件同样可以通过this.$route.query获取参数值。

用途场景

  • params:通常用于传递一些与路由路径紧密相关的参数,比如用户的id、文章的id等,这些参数是标识特定资源的重要信息,并且在路由导航中具有明确的语义。
  • query:适用于传递一些可选的、临时性的参数,例如搜索条件、分页参数等。这些参数不影响路由的基本路径,但可以根据不同的参数值来改变页面的展示内容或行为。

动态路由使用示例

要在路由配置里设置meat属性,扩展权限相关的字段,在路由导航守卫里通过判断这个权限标识,实现路由的动态增加和跳转。根据用户登录的账号,返回用户角色前端再根据角色,跟路由表的meta.role进行匹配把匹配搭配的路由形成可访问的路由。

<!DOCTYPE html>
<html lang="zh-CN">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>动态路由权限控制</title>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/vue-router@3.5.1/dist/vue-router.js"></script>
</head>

<body>
  <div id="app">
    <router-view></router-view>
  </div>

  <script>
    // 模拟后台返回的用户角色
    const userRole = 'admin';

    // 定义路由配置
    const routes = [
      {
        path: '/',
        name: 'Home',
        component: {
          template: '<div>首页</div>'
        },
        meta: {
          role: ['admin', 'user']
        }
      },
      {
        path: '/admin',
        name: 'Admin',
        component: {
          template: '<div>管理员页面</div>'
        },
        meta: {
          role: ['admin']
        }
      },
      {
        path: '/user',
        name: 'User',
        component: {
          template: '<div>普通用户页面</div>'
        },
        meta: {
          role: ['user']
        }
      },
      {
        path: '*',
        component: {
          template: '<div>404 页面</div>'
        }
      }
    ];

    const router = new VueRouter({
      mode: 'history',
      routes
    });

    // 全局前置守卫
    router.beforeEach((to, from, next) => {
      if (to.matched.length === 0) {
        // 如果路由不存在,跳转到 404 页面
        next({ path: '*' });
      } else {
        const routeMeta = to.meta;
        if (routeMeta.role && routeMeta.role.length > 0) {
          if (routeMeta.role.includes(userRole)) {
            // 用户角色匹配,允许访问
            next();
          } else {
            // 用户角色不匹配,跳转到首页
            next({ path: '/' });
          }
        } else {
          // 没有权限限制,直接访问
          next();
        }
      }
    });

    const app = new Vue({
      router
    }).$mount('#app');
  </script>
</body>

</html>

18、 路由的hash和history模式的区别

Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。

①hash模式

简介: hash模式是开发中默认的模式,它的URL带着一个#,例如:www.abc.com/#/vue,它的hash值就是 #/vue 。
特点:hash值会出现在URL里面,但是不会出现在HTTP请求中,对后端完全没有影响。所以改变hash值,不会重新加载页面。这种模式的浏览器支持度很好,低版本的IE浏览器也支持这种模式。hash路由被称为是前端路由,已经成为SPA(单页面应用)的标配。
原理: hash模式的主要原理就是onhashchange()事件

window.onhashchange = function(event){
console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}

使用onhashchange()事件的好处就是,在页面的hash值发生变化时,无需向后端发起请求,window就可以监听事件的改变,并按规则加载相应的代码。除此之外,hash值变化对应的URL都会被浏览器记录下来,这样浏览器就能实现页面的前进和后退。虽然是没有请求后端服务器,但是页面的hash值和对应的URL关联起来了。

②history模式

简介: history模式的URL中没有#,它使用的是传统的路由分发模式,即用户在输入一个URL时,服务器会接收这个请求,并解析这个URL,然后做出相应的逻辑处理。

特点: 当使用history模式时,URL就像这样:abc.com/user/id。相比hash模式更加好看。但是,history模式需要后台配置支持。如果后台没有正确配置,访问时会返回404

APIhistory api可以分为两大部分,切换历史状态和修改历史状态

修改历史状态:包括了 HTML5 History Interface 中新增的 **pushState() 和 replaceState()**方法,这两个方法应用于浏览器的历史记录栈,提供了对历史记录进行修改的功能。只是当他们进行修改时,虽然修改了url,但浏览器不会立即向后端发送请求。如果要做到改变url但又不刷新页面的效果,就需要前端用上这两个API。

切换历史状态: 包括 forward() 、 back() 、 go() 三个方法,对应浏览器的前进,后退,跳转操作。虽然history模式丢弃了丑陋的#。但是,它也有自己的缺点,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出404来。如果想要切换到history模式,就要进行以下配置(后端也要进行配置):

const router = new VueRouter({
mode: 'history',
routes: [...]
})

③两种模式对比

调用 history.pushState() 相比于直接修改 hash,存在以下优势:

  1. pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;
  2. pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;
  3. pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;
  4. pushState() 可额外设置 title 属性供后续使用。
  5. hash模式下,仅hash符号之前的url会被包含在请求中,后端如果没有做到对路由的全覆盖,也不会返回404错误;history模式下,前端的url必须和实际向后端发起请求的url一致,如果没有对用的路由处理,将返回404错误。
  6. hash模式和history模式都有各自的优势和缺陷,还是要根据实际情况选择性的使用。

④如何获取页面的hash变化

(1)监听$route的变化

// 监听,当路由发生变化的时候执行
watch: {
$route: {
handler: function(val, oldVal){
console.log(val);
},
// 深度观察监听
deep: true
}
},

(2)window.location.hash读取#值 window.location.hash 的值可读可写,读取来判断状态是否改变,写入时可以在不重载网页的前提下,添加一条历史访问记录。

19、Vue 单页应用(SPA)与多页应用(MPA)的区别

SPA单页面应用(SinglePage Web Application),指只有一个主页面的应用,一开始只需要加载一次js、css等相关资源。所有内容都包含在主页面,对每一个功能模块组件化。单页应用跳转,就是切换相关组件,仅仅刷新局部资源。

MPA多页面应用 (MultiPage Application),指有多个独立页面的应用,每个页面必须重复加载js、css等相关资源。多页应用跳转,需要整页资源刷新。

区别
1. 页面加载方式
单页应用:在首次加载时,会将 HTML、CSS、JavaScript 等资源一次性加载到浏览器中,后续页面的交互和内容更新主要通过 JavaScript 动态操作 DOM 来实现,不会重新加载整个页面。例如在一个 Vue 单页应用中,点击导航栏切换不同的视图,只是局部内容更新,浏览器地址栏可能会通过 history 或 hash 模式改变,但页面整体不会重新刷新。

多页应用:每一次页面跳转都需要向服务器重新请求新的 HTML 页面以及相关的资源(CSS、JavaScript 等)。比如从一个 HTML 页面跳转到另一个 HTML 页面,浏览器会重新发起请求,服务器返回新的页面内容,整个页面会重新加载和渲染。
2. 组件化与复用性
单页应用:高度组件化,组件可以在不同的视图中复用。Vue 单页应用通过 Vue 组件系统,将页面拆分成多个可复用的组件,如导航栏组件、侧边栏组件等,这些组件可以在不同的页面或视图中使用,提高了开发效率和代码的可维护性。
多页应用:**组件化程度相对较低,复用性较差。**不同页面之间的复用通常是通过模板引擎等方式实现部分代码的复用,但整体上不如单页应用的组件化灵活和高效。
3. 路由机制
单页应用:通常使用前端路由(如 Vue - Router )来管理页面的导航和视图切换。前端路由通过监听浏览器地址栏的变化(hash 变化或 history 模式下的 URL 变化),动态地渲染对应的组件,实现无刷新的页面切换。
多页应用:一般采用
传统的服务器端路由
,根据不同的 URL 请求,服务器返回不同的 HTML 页面。例如在一个基于 Node.js 和 Express 的多页应用中,服务器会根据请求的 URL 来确定返回哪个 HTML 页面以及相关的数据。
4. 搜索引擎优化(SEO)
单页应用:SEO 相对较差。由于页面内容是通过 JavaScript 动态生成的,搜索引擎爬虫在抓取页面时,可能无法获取到完整的页面内容,导致在搜索引擎中的排名不理想。不过现在也有一些解决办法,如服务端渲染(SSR)或预渲染等技术来改善单页应用的 SEO 问题。
多页应用:SEO 相对较好。因为每个页面都是独立的 HTML 文件,搜索引擎爬虫可以直接抓取到页面的内容,更容易被搜索引擎收录和排名。
5. 开发和维护成本
单页应用:开发初期成本较高,需要掌握前端路由、组件化开发等技术,并且在处理复杂业务逻辑和大型项目时,代码的管理和维护也有一定难度。但随着项目规模的扩大,其组件化和可复用性的优势会逐渐体现出来,能够提高开发效率。
多页应用:开发相对简单,适合小型项目或对 SEO 要求较高的项目。但在维护大型多页应用时,由于页面之间的耦合度较高,修改和扩展功能可能会比较麻烦。

20、Vue-router 导航守卫

有的时候,需要通过路由来进行一些操作,比如最常见的登录权限验证,当用户满足条件时,才让其进入导航,否则就取消跳转,并跳到登录页面让其登录。 为此有很多种方法可以植入路由的导航过程:全局的,单个路由独享的,或者组件级的。

  1. 全局前置/钩子:beforeEach、beforeResolve、afterEach
  2. 路由独享的守卫:beforeEnter
  3. 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

全局守卫

  1. beforeEach:全局前置守卫,在路由导航开始前被调用,可用于进行全局的路由拦截、权限验证等操作。例如,检查用户是否登录,如果未登录则重定向到登录页面。
  2. beforeResolve:在导航被确认之前,beforeResolve 守卫会被调用,它和 beforeEach 类似,但 beforeResolve 更适合在导航确认前进行一些异步操作的解析,比如获取组件所需的数据。
  3. afterEach:全局后置钩子,在路由导航完成后被调用,主要用于进行一些全局的后置处理,如页面标题的更新、埋点统计等,它不会接受 next 函数,也不会改变导航的流程。
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from './views/Home.vue'
import Login from './views/Login.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/login',
    name: 'Login',
    component: Login
  }
]

const router = new VueRouter({
  mode: 'history',
  routes
})

// 全局前置守卫 beforeEach
router.beforeEach((to, from, next) => {
  const isLoggedIn = localStorage.getItem('token') // 假设 token 存在表示已登录
  if (to.path === '/login' || isLoggedIn) {
    next()
  } else {
    next('/login')
  }
})

// 全局解析守卫 beforeResolve
router.beforeResolve((to, from, next) => {
  // 模拟异步操作,比如获取一些全局数据
  setTimeout(() => {
    next()
  }, 1000)
})

// 全局后置钩子 afterEach
router.afterEach((to, from) => {
  document.title = to.meta.title || '默认标题'
})

export default router

路由独享守卫

  1. beforeEnter:定义在路由配置中的守卫,只对当前路由生效。可以在路由配置中直接使用,用于对特定路由进行单独的权限验证或其他预处理操作。例如,某些特定页面需要特定权限才能访问,就可以在该路由的 beforeEnter 守卫中进行检查。
const routes = [
  {
    path: '/admin',
    name: 'Admin',
    component: () => import('./views/Admin.vue'),
    beforeEnter: (to, from, next) => {
      const isAdmin = localStorage.getItem('isAdmin') === 'true' // 假设 isAdmin 为 true 表示是管理员
      if (isAdmin) {
        next()
      } else {
        next('/')
      }
    }
  }
]

组件内的守卫

  1. beforeRouteEnter:在进入组件之前调用,不能访问组件实例 this,因为此时组件尚未创建。可以通过 next 函数传递参数给组件的 created 钩子函数,以便在组件创建后获取相关数据。
  2. beforeRouteUpdate:当路由参数发生变化时调用,例如从 /user/1 导航到 /user/2,组件实例会被复用,此时可以在 beforeRouteUpdate 中根据新的参数进行数据更新等操作。
  3. beforeRouteLeave:在离开当前组件时调用,可用于进行一些离开前的确认操作,如提示用户是否保存未保存的数据等,防止用户意外离开导致数据丢失。
// 在组件文件中(例如 Admin.vue)
export default {
  name: 'Admin',
  beforeRouteEnter(to, from, next) {
    // 不能访问 this,通过 next 传递参数给 created 钩子
    next((vm) => {
      vm.initData() // 假设组件有 initData 方法来初始化数据
    })
  },
  beforeRouteUpdate(to, from, next) {
    // 当路由参数变化时,比如从 /admin/1 到 /admin/2
    this.fetchNewData(to.params.id) // 假设组件有 fetchNewData 方法获取新数据
    next()
  },
  beforeRouteLeave(to, from, next) {
    const hasUnsavedChanges = this.hasUnsavedChanges() // 假设组件有 hasUnsavedChanges 方法判断是否有未保存更改
    if (hasUnsavedChanges) {
      if (window.confirm('你有未保存的更改,确定离开吗?')) {
        next()
      } else {
        next(false)
      }
    } else {
      next()
    }
  },
  methods: {
    initData() {
      // 初始化数据逻辑
    },
    fetchNewData(id) {
      // 根据新参数获取数据逻辑
    },
    hasUnsavedChanges() {
      // 判断是否有未保存更改的逻辑
      return false
    }
  }
}

21、Vue-router跳转和location.href区别

Vue - router跳转和location.href都可以实现页面的跳转,但它们在原理、使用场景和效果等方面存在一些区别,具体如下:

原理

  • Vue - router:是基于Vue.js的路由管理系统,通过HTML5History APIHash模式来实现页面的无刷新跳转。它会拦截路由的切换,根据路由配置来渲染相应的组件,在单页应用(SPA)中,只是替换DOM中特定的区域,而不是整个页面。
  • location.href:是浏览器原生的属性,用于获取或设置当前页面的URL。当使用location.href进行跳转时,浏览器会向服务器发送新的请求,加载新的页面,整个页面会进行重新加载。

页面效果

  • Vue - router:由于是局部更新,页面切换时不会出现整个页面重新加载的闪烁效果,用户体验更流畅,尤其是在切换多个页面时,性能优势明显。
  • location.href:每次跳转都会重新加载整个页面,包括页面的HTMLCSSJavaScript等资源,会出现短暂的白屏或闪烁,特别是在网络环境较差时,这种情况会更明显。

数据传递

  • Vue - router:有多种方式传递数据,如通过路由参数(paramsquery)、Vuex等状态管理工具或者使用provideinject来实现跨组件的数据传递。传递的数据可以是复杂的对象,并且在组件之间的传递相对方便和灵活。
  • location.href:主要通过URL的查询参数来传递简单的数据,即location.href = 'page2.html?name=John&age=30'这种形式。如果要传递复杂数据,需要先将数据进行序列化处理,在接收页面再进行反序列化,使用起来相对不太方便。

路由导航守卫

  • Vue - router:提供了丰富的导航守卫,如全局前置守卫beforeEach、全局后置钩子afterEach、路由独享的守卫beforeEnter以及组件内的守卫beforeRouteEnter等。这些守卫可以方便地进行权限验证、页面加载前的准备工作等。
  • location.href:没有内置的导航守卫机制,如果需要在跳转前进行一些验证或处理逻辑,需要自己编写额外的代码来实现,通常是通过window.onbeforeunload事件来进行一些简单的提示或确认操作。

使用场景

  • Vue - router:适用于单页应用中,实现页面之间的快速切换和动态路由加载,能够很好地管理页面的状态和组件的生命周期,提高用户体验和开发效率。
  • location.href:适用于传统的多页应用,或者在需要完全刷新页面、跳转到外部链接以及一些简单的页面跳转场景中使用。

22、$route 和$router 的区别

$route$router 是 Vue Router 中两个非常重要的对象,它们的区别如下:

概念

  • $route:表示当前激活的路由信息对象,包含了当前路由的路径、参数、查询参数等信息,是一个只读对象,它会随着路由的变化而自动更新。
  • $router是 Vue Router 的实例,用于管理路由的导航、路由的切换、路由的配置等操作,是一个可操作的对象,通过它可以调用各种方法来实现路由的跳转、前进、后退等功能。

用法

  • $route:主要用于获取当前路由的相关信息,在组件中通过 this.$route 来访问。例如,获取当前路由的路径可以使用 this.$route.path,获取路由参数可以使用 this.$route.params,获取查询参数可以使用 this.$route.query
  • $router:主要用于进行路由的导航和操作,在组件中通过 this.$router 来访问。例如,使用 this.$router.push 方法可以实现路由的跳转,this.$router.back 方法可以实现返回上一页的功能。

作用

  • $route:帮助开发者获取当前路由的详细信息,以便根据不同的路由情况进行相应的处理,比如根据路由参数来加载不同的数据,或者根据查询参数来进行页面的筛选等。
  • $router:负责整个路由系统的管理和控制,包括路由的初始化、路由的匹配、路由的切换等,它提供了一系列的方法来方便开发者实现页面之间的导航和交互。

23、Vue-Router 的懒加载如何实现

非懒加载

import List from '@/components/list.vue'
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})

懒加载

方案一(常用):使用箭头函数+import动态加载

const List = () => import('@/components/list.vue')
const router = new VueRouter({
routes: [
{ path: '/list', component: List }
]
})

方案二:使用箭头函数+require动态加载

const router = new Router({
routes: [
{
path: '/list',
component: resolve => require(['@/components/list'], resolve)
}
]
})

方案三:使用webpack的require.ensure技术,也可以实现按需加载。 这种情况下,多个路由指定相同的chunkName,会合并打包成一个js文件。

// r就是resolve
const List = r => require.ensure([], () => r(require('@/components/list')),
'list');
// 路由也是正常的写法 这种是官方推荐的写的 按模块划分懒加载
const router = new Router({
routes: [
{
path: '/list',
component: List,
name: 'list'
}
]
}))

24、 对前端路由的理解

早期前端,一个URL对应一个页面,页面切换伴随刷新,体验不佳,因需刷新页面才能重新请求数据。Ajax出现后,实现不刷新页面发起请求,催生了SPA(单页面应用)。SPA极大提升用户体验,可在不刷新页面时更新内容,但诞生之初存在问题:

  • 页面切换前后URL不变,SPA不知当前“进展到哪一步”,刷新页面,操作记录清零。
  • 仅有一个URL映射页面,不利于SEO(搜索引擎优化),搜索引擎无法全面收集信息。

为解决这些问题,前端路由应运而生。它能在单页面情况下,给SPA的各个视图匹配唯一标识,用户前进、后退触发的新内容会映射到不同URL,刷新页面时内容也不会丢失。

实现前端路由需解决两个问题:

  • 用户刷新页面时,浏览器按当前URL重新定位资源的操作对SPA不必要,会导致用户操作记录丢失,需拦截刷新操作,在前端消化。
  • 单页面应用对服务端而言只有一个URL和一套资源,要在不改变URL性质、不影响服务器识别的前提下,让前端感知URL变化,据此用JS生成不同内容 。

25、解决刷新后二次加载路由问题

window.location.reload():这是浏览器提供的方法,用于重新加载当前页面。在前端路由场景中,单纯使用它只是简单地刷新页面,可能会引发二次加载路由等问题,它并没有专门针对解决刷新后二次加载路由问题做处理,不是解决该问题的合适方法。

在 Vue Router 中,matcher 是一个非常核心的对象,它负责处理路由的匹配逻辑。当我们遇到刷新后二次加载路由的问题时,利用 matcher 重置是一种有效的解决方式,以下从原理到具体代码示例详细讲解:

原理

Vue Router 在创建实例时,会根据传入的路由配置(routes)生成一个 matcher 对象。matcher 内部维护了路由的映射关系,包括路径到组件的映射等信息。当页面发生路由跳转或者刷新时,matcher 会根据当前的 URL 来匹配对应的路由配置,从而确定应该渲染哪个组件。

当页面刷新时,可能由于一些状态的丢失或者变化,导致 matcher 的匹配逻辑出现异常,进而引发二次加载路由等问题。通过重新创建一个新的路由实例,获取其 matcher,并将原路由实例的 matcher 替换为新的 matcher,可以重置路由的匹配规则,使得路由在刷新后能够按照预期的方式进行匹配和加载。

代码示例

假设我们使用 Vue 3 + Vue Router 4 来构建项目,以下是具体的代码实现步骤:

  1. 创建路由配置文件(router.js
import { createRouter, createWebHistory } from 'vue-router';
// 定义路由组件(这里假设是简单的 Vue 组件,实际开发中从相应文件导入)
const Home = { template: '<div>Home Page</div>' };
const About = { template: '<div>About Page</div>' };

// 定义路由配置
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  }
];

// 创建路由实例
const router = createRouter({
  history: createWebHistory(),
  routes
});

// 导出重置路由的函数
export function resetRouter() {
  const newRouter = createRouter({
    history: createWebHistory(),
    routes
  });
  router.matcher = newRouter.matcher;
}

export default router;

在上述代码中,首先创建了一个常规的路由实例 router,并定义了一些简单的路由配置。然后定义了 resetRouter 函数,该函数重新创建了一个新的路由实例 newRouter,并将原路由实例 routermatcher 替换为 newRoutermatcher

  1. 在应用中使用重置路由函数
    可以在应用的入口文件(比如 main.js)或者相关组件中,在合适的时机调用 resetRouter 函数。例如,在应用挂载时调用:
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { resetRouter } from './router';

const app = createApp(App);

app.use(router);

// 在应用挂载前调用重置路由函数
resetRouter();

app.mount('#app');

也可以在某些特定的组件生命周期钩子函数中调用,比如在 App.vue 组件的 mounted 钩子中:

<template>
  <div id="app">
    <router-view></router-view>
  </div>
</template>

<script>
import { onMounted } from 'vue';
import { resetRouter } from './router';

export default {
  setup() {
    onMounted(() => {
      resetRouter();
    });
    return {};
  }
};
</script>

通过上述方式,在页面刷新或者应用初始化等场景下,就可以利用 matcher 重置来解决可能出现的二次加载路由问题,确保路由的正常匹配和页面的正确渲染。

26、Vuex 刷新数据丢失问题

Vuex 刷新数据丢失原因:Vuex 是 Vue 的状态管理模式,其数据存储在内存中。页面刷新时,整个 Vue 应用重新初始化,内存中的 Vuex 数据随之丢失,所以页面也会丢失依赖 Vuex 的数据。
解决方法一:利用浏览器缓存cookie、localStorage、sessionStorage 都是浏览器提供的存储机制。cookie 一般用于存储少量数据且会随 HTTP 请求发送到服务器;localStorage 存储的数据长期有效,除非手动删除;sessionStorage 存储的数据仅在当前会话(浏览器标签页关闭即失效)有效。将 Vuex 中的数据存储到这些地方,在页面刷新后可以从缓存中读取恢复数据。
解决方法二:刷新时重新请求数据
监听浏览器的刷新事件,在刷新前将重要数据存入 sessionStorage 作为临时备份。页面刷新后,重新向服务器请求数据,若请求成功,将数据存入 Vuex 来更新应用状态;若请求失败,可使用 sessionStorage 中备份的数据来维持页面基本显示。

27、computed 和 watch 的区别

  1. 定义与本质
    • computed计算属性,本质上是一个基于其他响应式数据的函数,但使用时更像属性。它用于基于一个或多个 data 中的响应式数据进行复杂计算,返回一个派生数据。比如有 data 中的 widthheight 属性,可通过 computed 创建 area 属性计算面积。
    • watch侦听器,用于监听 data 中数据或其他响应式数据的变化。当被监听的数据发生变化时,执行相应的回调函数来完成特定操作,比如数据更新、触发其他函数等。
  2. 缓存机制
    • computed 支持缓存。只有其依赖的属性值发生变化时,计算属性才会重新计算,否则直接返回缓存结果。例如,一个计算属性依赖于两个数据,只要这两个数据不变,多次访问该计算属性都不会重新计算,从而提高性能。
    • watch 不支持缓存。只要被监听的数据发生变化,无论变化前后的值是否相同,都会触发其回调函数执行。
  3. 异步操作
    • computed 不支持异步操作。因为它的设计目的是根据现有数据同步计算并返回一个结果,异步操作会使返回值无法及时确定,不符合其特性。
    • watch 可以进行异步操作。在其回调函数中,开发者可以方便地使用 async/awaitPromise 等进行异步任务,如在数据变化后发起网络请求获取新数据。
  4. 初始加载行为
    • computed 在组件首次加载时就开始监听其依赖的数据,并计算出结果。例如,在组件创建时,相关计算属性就会被计算并缓存。
    • watch 在初始加载时不会立即执行回调函数,仅在被监听的数据发生变化时才触发。不过,可通过设置 immediate: true 选项,使其在组件创建时立即执行一次回调函数,并同时开始监听数据变化。
  5. 返回值要求
    • computed 定义的函数中必须有 return 语句,用于返回计算后的结果,因为它要提供一个派生的数据值供使用。
    • watch 的回调函数不要求有返回值,主要用于执行各种操作,比如更新其他数据、调用方法等,重点并非返回特定值。

28、Vuex有哪几种属性,以及使用场景

  1. 使用场景:Vuex 是 Vue.js 的状态管理模式,适用于大型单页面应用中,多个组件需要共享和管理状态的场景。如用户的个人信息在多个页面展示和使用、购物车模块在不同页面要保持数据一致、订单模块在处理订单流程中多组件间数据交互等场景,使用 Vuex 可集中管理状态,方便组件间共享和更新。
  2. 属性
    • state:用于存储应用的状态数据,是数据源存放地,组件可通过 this.$store.state 访问其中数据。
    • getters:类似计算属性,从 state 中的基本数据派生出来的数据,可对 state 数据进行过滤、处理等操作,多个组件可复用,提升代码可维护性。
    • mutations:提交更新数据的方法,必须是同步操作,用于直接修改 state 中的数据,且遵循 Vue 的响应式原理。
    • actions:用于提交 mutations 来修改数据,与 mutations 不同的是,它可以包含异步操作,如发起网络请求等,在异步操作完成后再通过提交 mutations 来更新状态。
    • modules:实现 Vuex 的模块化,将不同功能的状态管理拆分到不同模块,每个模块有自己的 stategettersmutationsactions,便于管理大型应用复杂的状态逻辑。

29、mutationaction 区别

在 Vuex 中,mutationaction 是两个重要的概念,它们有着不同的职责和特性,以下是更清晰的对比及示例:

功能和职责

  • mutation:专注于直接修改 state,是 Vuex 中修改 state 的唯一推荐途径。它的主要作用是对 state 进行同步的状态变更操作,确保状态变化的可预测性。例如,在一个计数器应用中,增加计数器的值就可以通过 mutation 来实现。
  • action:主要处理业务逻辑代码以及异步操作,如发起 API 请求获取数据等。它不能直接操作 state,而是通过提交 mutation 来间接修改 state。例如,在获取用户信息的场景中,action 负责发起获取用户信息的网络请求,在请求成功后再提交 mutation 来更新 state 中的用户信息。

执行方式

  • mutation:必须是同步执行的。因为如果 mutation 是异步的,就很难追踪状态的变化,无法保证状态的可预测性。
  • action:可以执行异步操作,比如使用 async/await 或者 Promise 来处理异步任务。在异步操作完成后,通过提交 mutation 来更新 state

触发顺序

在视图更新流程中,一般是先触发 actionaction 执行完相应的业务逻辑(可能包含异步操作)后,再触发 mutation 来修改 state,从而导致视图的更新。

参数不同

  • mutation:接收的第一个参数是 state,它包含了 store 中的所有数据。通过对 state 的直接操作来实现状态的变更。
  • action:接收的第一个参数是 contextcontext 是一个与 store 实例具有相同方法和属性的上下文对象,它是 state 的父级,包含了 stategetterscommitdispatch 等。通过 contextaction 可以访问 stategetters,并提交 mutation

代码示例

以下是一个简单的 Vuex 应用示例,展示了 mutationaction 的使用:

// 引入 Vue 和 Vuex
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 定义 store
const store = new Vuex.Store({
  state: {
    count: 0,
    user: null
  },
  mutations: {
    // 增加计数器的 mutation
    INCREMENT(state) {
      state.count++
    },
    // 更新用户信息的 mutation
    SET_USER(state, payload) {
      state.user = payload
    }
  },
  actions: {
    // 异步增加计数器的 action
    async incrementAsync({ commit }) {
      await new Promise((resolve) => setTimeout(resolve, 1000))
      commit('INCREMENT')
    },
    // 获取用户信息的 action
    async fetchUser({ commit }) {
      // 模拟异步获取用户信息
      const response = { name: 'John', age: 30 }
      commit('SET_USER', response)
    }
  }
})

export default store

在上述代码中:

  • mutations 中的 INCREMENT 方法用于同步增加计数器的值,SET_USER 方法用于更新用户信息。
  • actions 中的 incrementAsync 方法是一个异步操作,延迟 1 秒后提交 INCREMENT mutation 来增加计数器的值;fetchUser 方法模拟获取用户信息的异步操作,获取到数据后提交 SET_USER mutation 来更新 state 中的用户信息。

30、 Vuex 和 localStorage 的区别

Vuex和localStorage是两种在前端开发中用于存储数据的不同机制,它们的主要区别如下:

数据存储位置

  • Vuex:数据存储在内存中,是一种基于JavaScript对象的状态管理模式,仅在当前页面会话期间有效。当页面刷新或关闭时,存储在Vuex中的数据会被清除。
  • localStorage:数据存储在浏览器的本地存储中,属于客户端存储的一种方式。即使页面刷新、关闭浏览器或重新打开计算机,数据也会保留,直到被主动删除。

数据作用域

  • Vuex:主要用于在Vue.js应用程序内部进行状态管理,实现组件之间的数据共享和传递。它的作用域是整个Vue应用,通过在组件中使用 this.$store 来访问和修改数据。
  • localStorage:是浏览器提供的一种全局存储机制,在同一个域名下的所有页面都可以访问和操作相同的 localStorage 数据。不同的浏览器标签页之间也可以共享 localStorage 数据,但如果是不同域名,则无法访问。

数据类型支持

  • Vuex:可以存储各种类型的数据,包括对象、数组、字符串、数字等,因为它是基于JavaScript对象来存储数据的,能够很好地支持复杂的数据结构。
  • localStorage:只能存储字符串类型的数据。如果要存储其他类型的数据,需要先将其转换为字符串(通常使用 JSON.stringify() 方法),在获取数据时再进行解析(使用 JSON.parse() 方法)。

数据更新和响应性

  • Vuex:具有响应式更新的特性,当 state 中的数据发生变化时,依赖该数据的组件会自动重新渲染,以更新视图展示。这是通过Vue.js的响应式系统实现的,使得数据的更新和视图的同步非常方便和高效。
  • localStorage:本身不具备响应式。当 localStorage 中的数据发生变化时,不会自动触发页面的更新。如果需要在数据更新后更新页面,需要手动监听 storage 事件或者在相关代码中进行数据获取和视图更新的操作。

应用场景

  • Vuex:适用于管理应用程序中的复杂状态,如用户登录状态、购物车数据、多步骤表单数据等,特别是当这些数据需要在多个组件之间频繁共享和交互时。它能够方便地进行数据的集中管理和更新,提高代码的可维护性和可扩展性。
  • localStorage:常用于存储一些持久化的数据,如用户的登录凭证、网站的配置信息、用户的浏览历史等。这些数据在用户下次访问网站时仍然需要使用,并且不需要实时响应数据的变化。

31、Vuex和单纯的全局对象有什么区别?

Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit)
mutation。这样可以方便地跟踪每一个状态的变化,从而能够实现一些工具帮助更好地了解我们的应用。

Vuex和单纯全局对象区别如下:

  • 管理机制:Vuex是集中式状态管理,通过state等概念按规则管理更新数据,流向清晰。全局对象管理松散,无规则限制数据修改,易混乱难追踪。
  • 响应式:Vuex基于Vue响应式系统,state变化时Vue组件自动重渲染更新视图。全局对象无此特性,数据变化需手动更新视图。
  • 模块化:Vuex支持模块化,各模块有独立状态管理,便于组织复杂逻辑。全局对象无内置模块化机制,需手动模拟,易出命名冲突。
  • 调试维护:Vuex可借devtools等工具跟踪状态变化,利于调试,结构清晰,维护性好。全局对象因修改随意、缺跟踪机制,调试维护困难。

32、为什么 Vuex 的 mutation 中不能做异步操作?

Vuex的 mutation 中不能做异步操作,主要有以下原因:

数据追踪与调试困难

  • Vuex的设计理念是让状态的变化可预测和易于追踪。mutation 中的同步操作使得状态的改变是即时的,开发者可以清晰地知道在某个时间点状态是如何变化的。
  • 如果在 mutation 中进行异步操作,就无法准确知道状态何时会被更新,这会导致调试变得困难,难以追踪状态变化的顺序和时机。

响应式更新问题

  • Vue的响应式系统是基于数据的同步更新来实现的。当 mutation 同步修改 state 时,Vue能够及时检测到数据的变化,并更新相关的视图。
  • mutation 中存在异步操作,可能会在视图更新之后才改变状态,导致视图与状态不同步,出现数据不一致的情况。

违背单一职责原则

  • mutation 的主要职责是对 state 进行简单的同步修改,保持数据操作的原子性和确定性。
  • 异步操作通常涉及到更复杂的逻辑,如网络请求、定时器等,将其放在 mutation 中会使 mutation 的职责不单一,代码逻辑变得复杂且难以维护。

为了处理异步操作,Vuex提供了 actionaction 可以包含异步操作,在异步操作完成后通过提交 mutation 来更新状态,这样可以将异步逻辑与状态更新逻辑分离,更好地遵循Vuex的设计原则。

33、Vuex响应式处理

Vuex 是 Vue.js 的状态管理模式,用于集中管理 Vue 应用的状态。以下是关于 Vuex 响应式处理的全面解释:

Vuex 与 Vue 组件的基本交互差异

在普通的 Vue 组件里,我们可以直接在组件的 methods 中定义方法,并通过事件绑定(比如 @click)来触发这些方法,以此实现一些交互逻辑,比如按钮点击后改变组件内的局部数据。

而在 Vuex 中,由于它是集中管理应用状态的工具,遵循特定的数据流动规则,不能像在 Vue 组件的 methods 那样直接触发方法来修改状态。Vuex 有一套自己处理状态变化的流程。

Vuex 处理异步操作与状态变化的流程

当我们需要处理异步操作(比如发起网络请求获取数据)时,在 Vue 组件中,我们会通过 dispatch 方法来触发 actions 中定义的方法。actions 主要用于处理异步任务,它可以包含异步代码,比如使用 async/await 或者 Promise 来处理网络请求。

actions 中完成异步操作并获取到结果后,并不会直接修改 state(Vuex 的状态存储对象),而是通过 commit 方法来触发 mutations 中定义的方法。mutations 是唯一可以直接修改 state 的地方,并且它必须是同步操作。这样设计是为了保证状态变化的可预测性和可追踪性,便于开发者调试和理解应用状态的变化。

mutations 修改了 state 中的值后,Vuex 借助 Vue 的响应式系统,会自动检测到 state 的变化。如果有组件依赖于这些被修改的 state 数据,那么相关组件就会重新渲染,从而实现将更新后的数据展示在视图上。这中间,getters 起到了类似计算属性的作用,它可以对 state 中的数据进行加工、过滤等处理,然后提供给组件使用,方便组件获取到符合特定需求的数据来更新视图。

Vuex 的安装与使用

在 Vue 项目中使用 Vuex,首先要通过 Vue.use(vuex) 来安装 Vuex。这一步实际上是调用了 Vuex 的 install 方法,该方法内部做了一些初始化和配置工作,其中包括通过 applyMixin(vue) 这种方式,在 Vue 的实例上进行了一些混入操作,使得在任意 Vue 组件内部都可以通过 this.$store 来访问到 Vuex 的 store 对象。通过这个 store 对象,组件就可以读取 state 中的数据、触发 actions 以及获取 getters 的计算结果等,从而实现与 Vuex 管理的状态进行交互。

Vuex 响应式的本质

Vuex 的 state 之所以具有响应式,是因为它借助了 Vue 的响应式机制。Vue 在初始化数据时,会对 data 中的属性进行劫持(通过 Object.defineProperty 等技术),当这些属性的值发生变化时,Vue 能够感知到变化并通知相关的组件进行更新。Vuex 的 state 本质上也是利用了类似的原理,将 state 存储到 Vue 实例组件相关的响应式数据结构中(可以理解为某种形式的 data 中),所以当 state 发生变化时,依赖它的 Vue 组件就能够像依赖普通的组件 data 一样,自动感知到变化并进行相应的更新操作,以保证视图和状态的一致性。

34、Vue和Jquery区别

  1. 原理
    • Vue 基于数据绑定,通过响应式系统实现数据与视图的自动同步。它会对数据进行劫持(如使用 Object.defineProperty 等技术),当数据变化时,自动更新对应的 DOM 元素,开发者更关注数据的变化。
    • jQuery 是先获取 DOM 元素,然后对获取到的 DOM 进行各种操作,如修改样式、绑定事件等,它更侧重于直接操作 DOM。
  2. 着重点
    • Vue 是数据驱动的框架,以数据为核心,视图随着数据的变化而更新,开发者主要处理数据的逻辑,代码的可维护性和可扩展性较好。
    • jQuery 着重于页面的交互效果和 DOM 操作,对于一些简单的页面特效和交互处理较为便捷,但在大型项目中,随着 DOM 操作的增多,代码可能会变得复杂和难以维护。
  3. 操作
    • Vue 通过组件化的方式构建应用,有自己的模板语法、指令等,操作数据和视图主要通过组件的属性、方法以及 Vue 提供的 API 来实现。
    • jQuery 主要使用其封装的各种函数来操作 DOM,如 $(selector).action() 的形式,对 DOM 进行增删改查等操作。
  4. 未来发展
    • Vue 作为现代前端框架,不断发展和更新,生态系统丰富,有大量的插件和组件库,适用于构建大型单页面应用和复杂的前端应用,在现代前端开发中应用广泛且前景良好。
    • jQuery 随着前端技术的发展,在一些简单的项目或者对兼容性要求极高的项目中仍有使用,但在大型、高性能要求的项目中逐渐被现代前端框架取代,其发展速度相对缓慢。

35、Vue中遍历全局的方法有哪些

  1. forEach:普通遍历数组,对每个元素执行回调,无返回值。示例:
arr.forEach(function(item, index, arr) {
  console.log(item, index);
});
  1. map:遍历数组并对元素统一操作,返回新数组。示例:
var newarr = arr.map(function(item) {
  return item + 1;
});
  1. filter:查找符合条件的元素,返回新数组。示例:
arr.filter(function(item) {
  if (item > 2) {
    return false;
  } else {
    return true;
  }
});
  1. findIndex:查询符合条件的元素,返回索引。示例:
arr.findIndex(function(item) {
  if (item > 1) {
    return true;
  } else {
    return false;
  }
});
  1. every:遍历数组,所有元素符合条件才返回 true,遇到不符合则停止。示例:
arr.every(function(item) {
  return item > 0;
});
  1. some:遍历数组,只要有一个元素符合条件就返回 true。示例:
arr.some(function(item) {
  return item % 2 === 0;
});

36、Vuex简单使用

代码示例:包含了 Vuex 的使用,包括使用辅助函数和不使用辅助函数的情况,同时对 Vuex 的五大模块(statemutationsactionsgettersmodules)进行了展示,并对能否直接修改 state 以及直接调用 mutations 方法进行了说明,代码中添加了详细注释:

目录结构

src
└── store
    ├── index.js
    └── modules
        ├── user.js
        └── cart.js

src/store/modules/user.js(用户模块)

// 用户模块

// 定义用户模块的 state,这里存储用户信息,初始值为 null
export const state = {
  user: null
}

// 定义用户模块的 mutations,用于修改 state,这里定义了一个设置用户信息的 mutation
export const mutations = {
  SET_USER(state, payload) {
    state.user = payload;
  }
}

// 定义用户模块的 actions,用于处理异步操作或者复杂逻辑,然后提交 mutations
export const actions = {
  async fetchUser({ commit }) {
    // 模拟异步获取用户信息,这里使用一个固定的对象模拟从服务器获取的数据
    const response = { name: 'Bob', age: 30 };
    // 提交 SET_USER mutation,将获取到的用户信息更新到 state 中
    commit('SET_USER', response);
  }
}

// 定义用户模块的 getters,用于从 state 中派生一些数据,这里定义了一个获取用户信息的 getter
export const getters = {
  getUser: state => state.user
}

src/store/modules/cart.js(购物车模块)

// 购物车模块

// 定义购物车模块的 state,存储购物车商品列表,初始为空数组
export const state = {
  cart: []
}

// 定义购物车模块的 mutations,用于修改购物车相关的 state,这里定义了一个添加商品到购物车的 mutation
export const mutations = {
  ADD_ITEM_TO_CART(state, item) {
    state.cart.push(item);
  }
}

// 定义购物车模块的 actions,处理添加商品到购物车的逻辑,然后提交 mutations
export const actions = {
  addItem({ commit }, item) {
    // 提交 ADD_ITEM_TO_CART mutation,将商品添加到购物车
    commit('ADD_ITEM_TO_CART', item);
  }
}

// 定义购物车模块的 getters,从购物车 state 中派生数据,这里定义了一个获取购物车商品数量的 getter
export const getters = {
  cartItemCount: state => state.cart.length
}

src/store/index.js(Vuex 入口文件)

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

// 使用 require.context 自动获取 modules 目录下所有符合条件(以.js 结尾)的文件
const files = require.context("./modules", false, /\.js$/);
// 定义一个对象用于存储合并后的模块内容
let modules = {
  state: {},
  mutations: {},
  actions: {},
  getters: {}
};

// 遍历获取到的所有模块文件
files.keys().forEach((key) => {
  // 将每个模块的 state 合并到总的 state 中
  Object.assign(modules.state, files(key)["state"]);
  // 将每个模块的 mutations 合并到总的 mutations 中
  Object.assign(modules.mutations, files(key)["mutations"]);
  // 将每个模块的 actions 合并到总的 actions 中
  Object.assign(modules.actions, files(key)["actions"]);
  // 将每个模块的 getters 合并到总的 getters 中
  Object.assign(modules.getters, files(key)["getters"]);
});

// 创建 Vuex 的 store 实例,传入合并后的模块内容
const store = new Vuex.Store(modules);

export default store;

组件中不使用辅助函数的示例(以 App.vue 为例)

<template>
  <div id="app">
    <p>用户姓名: {{ user }}</p>
    <p>购物车商品数量: {{ cartItemCount }}</p>
    <button @click="fetchUser">获取用户信息</button>
    <button @click="addItemToCart">添加商品到购物车</button>
  </div>
</template>

<script>
import store from "./store";

export default {
  computed: {
    // 从 store 的 state 中获取用户信息,这里通过模块名和 state 中的属性名来获取
    user() {
      return store.state.user;
    },
    // 从 store 的 getters 中获取购物车商品数量,这里通过模块名和 getters 中的方法名来获取
    cartItemCount() {
      return store.getters.cartItemCount;
    }
  },
  methods: {
    // 调用用户模块的 fetchUser action 来获取用户信息
    fetchUser() {
      store.dispatch('fetchUser');
    },
    // 调用购物车模块的 addItem action 来添加商品到购物车
    addItemToCart() {
      const newItem = { name: '商品 Y', quantity: 1 };
      store.dispatch('addItem', newItem);
    }
  }
};
</script>

全局引入:在 Vue 项目的 main.js 文件中引入并使用上述已经配置好的 Vuex 的 store 非常简单,以下是具体的代码示例及说明:

假设你的项目结构如下:

src
├── main.js
└── store
    ├── index.js
    └── modules
        ├── user.js
        └── cart.js

main.js 文件的内容如下:

import Vue from 'vue'
import App from './App.vue'
// 引入在 store 目录下 index.js 中导出的 store 实例
import store from './store'

// 关闭 Vue 在生产环境下的提示信息,提高性能
Vue.config.productionTip = false

// 创建 Vue 实例,并将 store 实例挂载到 Vue 实例上
new Vue({
  store, // 将 store 挂载到 Vue 实例,这样在所有组件中都可以通过 this.$store 访问
  render: h => h(App)
}).$mount('#app')

在上述代码中:

  1. 首先通过 import store from './store' 语句引入了在 store/index.js 中创建并导出的 store 实例。
  2. 然后在创建 Vue 实例时,将 store 作为一个选项传入,即 store 选项。这样做之后,在 Vue 应用的所有组件中,都可以通过 this.$store 来访问 Vuex 的 store,进而使用 store 中的 stategettersactions 等,实现组件与 Vuex 状态管理的交互。

这样配置后,整个 Vue 应用就能够使用 Vuex 进行状态管理了。

组件中使用辅助函数的示例(以另一个组件 AnotherComponent.vue 为例)

<template>
  <div>
    <p>用户姓名: {{ user }}</p>
    <p>购物车商品数量: {{ cartItemCount }}</p>
    <button @click="fetchUser">获取用户信息</button>
    <button @click="addItemToCart">添加商品到购物车</button>
  </div>
</template>

<script>
import { mapState, mapGetters, mapActions } from "vuex";

export default {
  computed: {
    // 使用 mapState 辅助函数,将用户模块的 user state 映射到当前组件的计算属性 user 上
   ...mapState({
      user: 'user.user'
    }),
    // 使用 mapGetters 辅助函数,将购物车模块的 cartItemCount getter 映射到当前组件的计算属性 cartItemCount 上
   ...mapGetters({
      cartItemCount: 'cart.cartItemCount'
    })
  },
  methods: {
    // 使用 mapActions 辅助函数,将用户模块的 fetchUser action 映射到当前组件的方法 fetchUser 上
   ...mapActions({
      fetchUser: 'user.fetchUser'
    }),
    // 使用 mapActions 辅助函数,将购物车模块的 addItem action 映射到当前组件的方法 addItemToCart 上
   ...mapActions({
      addItemToCart: 'cart.addItem'
    })
  }
};
</script>

关于能否直接修改 state 和调用 mutations 的说明

  • 能否直接修改 state 中的值:不可以直接修改 state 中的值。在 Vuex 中,state 是存储应用状态的地方,为了保证状态变化的可预测性和可追踪性,必须通过提交 mutations 来修改 state。如果直接修改 state,会破坏 Vuex 的单向数据流原则,导致难以调试和维护。
  • 能否直接调用 mutations 中的方法:一般情况下,不建议在组件中直接调用 mutations 中的方法。对于同步操作,可以在组件中通过 this.$store.commit('mutationName', payload) 来调用 mutations;但对于异步操作,应该先在 actions 中处理异步逻辑,然后在 actions 中通过 commit 来触发 mutations,这样可以将异步操作和状态修改逻辑分离,使代码结构更加清晰和易于维护。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值