vueX 的理解与应用
通过定义和隔离状态管理中的各种概念并通过强制规则维持视图和状态间的独立性,我们的代码将会变得更结构化且易维护。
主要包括以下几个模块:
State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
Getter:允许组件从 Store 中获取数据,mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性。
Mutation:是唯一更改 store 中状态的方法,且必须是同步函数。
Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作。
Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中。
const store = createStore({
state () {
return {
count: 5
}
},
mutations: {
increment (state) {
state.count++
}
}
})
const app = createApp({ /* 根组件 */ })
app.use(store)
可以通过 store.state 来获取状态对象,并通过 store.commit 方法触发状态变更:
store.commit('increment')
console.log(store.state.count) // -> 6
在 Vue 组件中, 可以通过 this.$store 访问store实例。现在我们可以从组件的方法提交一个变更:
methods: {
increment() {
this.$store.commit('increment')
console.log(this.$store.state.count)
}
}
由于 store 中的状态是响应式的,在组件中调用 store 中的状态简单到仅需要在计算属性中返回即可。触发变化也仅仅是在组件的 methods 中提交 mutation。
1.state
const store = new Vuex.Store({
state: {
count: 0
}
})
2.getters
const store = new Vuex.Store({
state: {
count: 0
},
getters: {
doubleCount(state) {
return state.count * 2
}
}
})
3.Mutation
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
},
add(state, payload) {
state.count += payload
}
}
})
4.actions
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
},
actions: {
incrementAsync(context) {
setTimeout(() => {
context.commit('increment')
}, 1000)
}
}
})
5.modules
const store = new Vuex.Store({
modules: {
cart: {
state: {
items: []
},
mutations: {
addItem(state, item) {
state.items.push(item)
}
},
actions: {
addAsyncItem(context, item) {
setTimeout(() => {
context.commit('addItem', item)
}, 1000)
}
}
}
}
})
computed 和 watch 的区别和运用的场景?
区别
- computed 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算。
- watch 没有缓存性,更多的是「观察」的作用,仅在数据源确实改变时才会触发回调。
运用的场景
- computed 当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算。比如大量的计算,我们可以缓存值,不必每次都重新计算,节约性能。
- watch 当我们需要在数据变化时执行异步或开销较大的操作时,我们可以通过 watch 去监听各种类型的值,如数字、数组、对象等,也可以对比新旧值来来判断是否执行某些操作。
Vue2.x 组件通信有哪些方式?
- 父子组件之间通信
props/$emit
父组件通过props的方式向子组件传递数据,而通过$emit子组件可以向父组件通信。$parent/children获取父子组件实例provide/inject
父组件中通过provide来提供变量, 然后再子组件中通过inject来注入变量。 注意: 这里不论子组件嵌套有多深, 只要调用了inject那么就可以注入provide中的数据,而不局限于只能从当前父组件的props属性中回去数据。ref/refs
ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例,可以通过实例直接调用组件的方法或访问数据 使用时 this.$refs.child
- 非父子组件之间通信(兄弟组件、隔代关系组件等)
- Vuex
eventBus
作用:eventBus又称为事件总线,在vue中可以使用它来作为沟通桥梁的概念, 就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件, 所以组件都可以通知其他组件。
缺点:eventBus也有不方便之处, 当项目较大,就容易造成难以维护的灾难localStorage/sessionStorage
这种通信比较简单,缺点是数据和状态比较混乱,不太容易维护。
通过window.localStorage.getItem(key)获取数据
通过window.localStorage.setItem(key,value)存储数据$attrs、$listenersProvide、inject
1.初始化
// event-bus.js
import Vue from 'vue'
export const EventBus = new Vue()
2.发送事件
<template>
<div>
<button @click="add">添加</button>
</div>
</template>
<script>
import {EventBus} from './event-bus.js'
export default {
data(){
return{
num:1
}
},
methods:{
add(){
EventBus.$emit('addition', {
num:this.num++
})
}
}
}
3.接收事件
<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>
4.移除事件监听者
import { eventBus } from 'event-bus.js'
EventBus.$off('addition', {})
说一下 v-if 和 v-show 的区别
- v-if 条件不成立时不会创建 Dom, 适用于运行时很少改变条件,不需要频繁切换条件的场景。
- v-show 通过 css 样式控制(display),适用于需要非常频繁切换条件的场景。
为什么 v-for 和 v-if 不建议用在一起使用
- 当 v-for 和 v-if 处于同一个节点时,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。如果要遍历的数组很大,而真正要展示的数据很少时,这将造成很大的性能浪费。
- 这种场景建议使用 computed,先对数据进行过滤。
组件中的 data 为什么是一个函数?
- 一个组件可能会被多次引用,也就会创建实例,本质上这些实例用的都是一个构造函数
- 如果 data 是对象的话,对象是属于引用类型,会影响所有的实例,所以为了保证多个实例之间的数据隔离, data 就必须为函数。
子组件为什么不可以修改父组件传递的 Prop ?/怎么理解 vue 的单向数据流?
- vue 提倡单项数据流,即父组件的 props 更新会流向子组件,反之则不行。
- 这是为了防止意外的改变父组件的状态,从而使应用数据流难以理解。
- 如果破坏了单项数据流,当应用复杂时,处理起来成本会非常高。
v-model 是如何实现双向绑定的?
- v-model 是用来在表单控件或组件上创建双向数据绑定的
- 它的本质是 v-on 和 v-bind 的语法糖
- 在一个组件上使用 v-model 默认会为组件绑定名为 value 的 props 和名为 input 的事件
nextTick 的作用以及大概实现原理
- 关键点:确保我们操作的是更新后的 DOM; 这样做可以避免频繁的 DOM 操作,提高性能
- 背景:vue.js 采用异步更新机制来提高渲染效率,当我们修改数据时,Vue 不会立即更新 DOM,而是将 DOM 更新操作放到一个异步队列中,等到下一次事件循环时再执行。这样做可以避免频繁的 DOM 操作,提高性能
- 问题:但是,由于 vue 的异步更新机制,当我们修改数据后,如果想要立即获取更新后的 DOM,可能会出现获取到的是更新前的 DOM 的情况。这时就需要使用 Vue.nextTick() 方法
- 原理:Vue.nextTick() 方法的实现原理是基于浏览器的异步任务队列,采用微任务优先的方式。 当我们修改数据时,Vue 会将 DOM 更新操作放到一个异步任务队列中,等待下一次事件循环时执行。而 Vue.nextTick() 方法则是将一个回调函数推入到异步任务队列中,等待 DOM 更新完成后执行。 具体实现方式有以下几种:
1.在 Vue2.x 中,如果浏览器支持 Promise,则会优先使用;
2.如果不支持 Promise,则会使用原生的 setTimeout 方法模拟异步操作;
3.如果浏览器支持 MutationObserver,Vue 会使用 MutationObserver 监听 DOM 更新,并在 DOM 更新完成后执行回调函数。
4.使用 setlmmediate: 在 lE 中,setlmmediate 方法可以用来延迟异步执行任务。在 Vue2.x 中,如果浏览器支持 setlmmediate,则会优先使用 setlmmediate,否则会使用 setTimeout。
Vue.nextTick() 可以将回调函数推入到异步队列中,在 DOM 更新完成后执行。这样就可以确保我们操作的是更新后的 DOM,而不是更新前的 DOM。比如在某些情况下需要获取某个元素的尺寸、位置等属性时,如果不使用 Vue.nextTick(),可能会获取到错误的结果。
Vue 事件绑定原理是什么?
- 原生事件是通过 addEventListener 绑定给真实元素的,组件事件绑定是通过 vue 自定义的 $on 实现的
说一下虚拟 Dom 以及 key 属性的作用
- 由于在浏览器中操作 Dom 是很昂贵的,频繁的操作 Dom ,会产生一定的性能问题,这就是虚拟 Dom 产生的原因。
- 虚拟 DOM 的实现原理主要包括以下 3 部分:
- 用 JavaScript 对象模拟真实 DOM 树,对真实 DOM 进行抽象;
- diff 算法 — 比较两棵虚拟 DOM 树的差异;
- pach 算法 — 将两个虚拟 DOM 对象的差异应用到真正的 DOM 树。
- key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速,更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快
为什么不建议用 index 作为 key ?
Vue中不建议使用数组元素的 index 作为 key ,是因为在数组的操作过程中,如果使用 index 作为 key ,可能会导致渲染错误或性能问题。
问题解释:
- 渲染错误:如果数组进行了排序、push、pop、shift、unshift 等操作,那么元素的 index 可能会发生变化,但是它们的 identity(即它们在 DOM 中的位置)可能不会改变。如果依然使用 index 作为 key,Vue 将无法正确地识别这些元素,可能会导致渲染错误。
- 性能问题:如果使用 index 作为 key ,每次重排时,Vue 都需要更新所有元素的 key,这可能会导致不必要的性能开销。
解决方法:
- 使用唯一且稳定的 ID:如果数组中的元素对象具有唯一且稳定的ID属性,应使用这个 ID 作为 key 。
- 使用特殊字符串:如果数组中的元素没有唯一 id ,可以使用特殊字符串作为 key ,但这个字符串必须保证在列表中唯一。
- 使用 v-for 的特殊变量:在 Vue 2.2.0+,可以直接使用 v-for 输出的特殊变量
(index, item)作为 key,如果列表渲染的是简单的列表项,这种方式是可行的。