vue2面试题总结

Vue的⽣命周期详解
第⼀个问题:什么是Vue的⽣命周期?
通俗的来说,⼀个Vue实例从创建到销毁的这个过程,我们称之为Vue的⽣命周期。
整个流程?
开始创建⼀个Vue实例 —> 初始化Vue实例内部数据 —> 模板开始编译 —> 挂载并渲染
dom
—> 更新数据(可能有这个过程) —> Vue实例开始销毁 —> 实例销毁完毕
什么是钩⼦?
钩⼦,其实是钩⼦函数的简称,说的简单点,就是Vue实例的⽣命周期⾥,⾃动会触发
的函数,有的话就执⾏这个函数,这也就是我们常说的钩⼦。
 
 
详解触发钩⼦时已完成的操作及建议的操作
beforeCreate
实例初始化,将this指向Vue实例,此时data computed watch methods上的⽅法
和数据均不能访问
可在此处加⼊loading事件
 
created
实例创建完成,完成数据(data props computed)的初始化 ,可访问其中的数据。
此时未挂载dom,也不能访问Vue实例中的$el属性
没有dom,也就是说$ref为空数组了
可以对data数据进⾏操作,可进⾏⼀些请求,请求不易过多,避免⽩屏时间太⻓。
若在此阶段进⾏的 DOM 操作⼀定要放在 Vue.nextTick() 的回调函数中,dom更新
的时候,才会触发nextTick⾥的回调函数。
也可在此处结束loading
 
berofeMount
此时$el挂载完毕,并且把Vue实例中template部分编译成render函数,或者直接执
⾏render函数
使⽤render函数的结果和我们之前使⽤template解析出来的结果是⼀样的。
render函数是发⽣在beforeMount和mounted之间的,这也从侧⾯说明了,在
beforeMount的时候,$el还只是我们在HTML⾥⾯写的节点,然后到mounted的
时候,它就把渲染出来的内容挂载到了DOM节点上。这中间的过程其实是执⾏了
render函数的内容。
 
mounted
完成创建vm.$el,dom此时已经根据template编译的render函数,渲染完成
有了DOM且完成了双向绑定 可访问DOM节点及$ref属性
 
beforeUpdate(更新数据时触发)
数据更新之前,可在更新前访问现有的DOM,如⼿动移除添加的事件监听器
 
updated
完成虚拟DOM的重新渲染
组件DOM 已完成更新;
可执⾏依赖的dom 操作
注意:不要在此函数中操作数据,会陷⼊死循环的。
 
activated
在使⽤ vue-router 时,有时我们会使⽤ keep-alive 标签来保持组件的状态,避
免频繁发送请求。此时,created钩⼦就不会被重复调⽤了。因此,如果我们需要
在组件每次加载的时候进⾏某些操作,可以在activated这个钩⼦当中触发。
 
deactivated
被 keep-alive 标签包住的组件,被移除时触发。
 
 
beforeDestroy
实例销毁之前调⽤。在这⼀步当中,实例仍然完全可⽤。
可做⼀些删除提示,⽐如,"您确认离开此⻚⾯"
可⽤于销毁定时器,解绑全局事件或者销毁引⼊的插件对象
 
destroyed
Vue 实例销毁后调⽤。此时Vue实例⾥的数据解除绑定,所有的事件监听器也被移
除了
 

 

 
 
v-if和v-show的区别
v-if和v-show 都是指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值时才被渲染。
当 v-if 元素被触发,元素及其所包含的指令/组件都会销毁和重构。如果初始条件是假,那
么其内部的内容根本都不会被渲染。
v-if 可用于 <template> 使用,同时在使用时v-if比v-for 优先级高。
v-show 是通过设置内联样式的 display CSS 属性来工作 。当条件改变时,也会触发过渡效果。
v-show 不支持在 <template> 元素上使用,也不能和 v-else 搭配使用。
 
使用场景
v-if适用于权限判断的页面显示,显示和隐藏操作都会导致页面的重绘。而v-show适用于
用户切换的显示隐藏效果,例如列表搜索展开收起、折叠面板等
 
 

vue中祖孙组件间的通信之使用$attrs和$listeners的方式

$attrs的用法

  • 正常情况下:父组件通过v-bind绑定一个数据传递给子组件,子组件通过props接收到就可以在子组件的html中使用了。但是,如果父组件v-bind传递给子组件,子组件没有用props接收呢?
  • 注意:这个时候,父组件传递过来的数据就会被挂载(赋值)到这个子组件自带的对象$attrs上面,所以:
  • $attrs就是一个容器对象,这个容器对象会存放:父组件传过来的且子组件未使用props声明接收的数据
$attrs就是一个容器对象,这个容器对象会存放:父组件传过来的且子组件未使用props声明接收的数据
爷组件传递给孙组件的逻辑流程就是,通过爷组件首先传递给父组件,当然父组件不在props中接收,那么爷组件传递给父组件的数据就会存放到父组件的$attrs对象中里面了,然后,再通过v-bind="$attrs",再把这个$attr传递给孙组件,在孙组件中使用props就能接收到$attrs中的数据了,这样就实现了,祖孙之间的数据传递。

$Listeners的用法

使用$listeners可以实现孙组件的数据传递到爷组件中去,逻辑的话,也是用在中间的桥梁父组件上面去,我的理解就是$listeners可以将子组件emit的方法通知到爷组件。
具体实现
 
parent
<template>
    <div>
        <child 
            name="名字"
            @change="change"></child>
    </div>
</template>

<script>
    export default {
        methods: {
            change() {
                alert("孙组件传出来的时间")
            }
        },
    }
</script> 

 

child.vue

<template>
    <div>
        <grand-child 
             v-bind="$attrs" 
            v-on="$listeners"></grand-child>
    </div>
</template>

<script>  

</script> 
 
grandChild
<template>
    <div>
        名字:{{name}}
        <div @click="btn">
            点击事件
        </div>
    </div>
</template>

<script>
    export default {
     inheritAttrs :false,
       props: {
           name: {
               type: String,
               default: ''
           },
       },
       methods: {
           btn() {
               this.$emit('change')
           }
       },
    }
</script>

  

 

$set的用法

set 是 Vue.js 中的一个全局API,主要用于动态向响应式对象中添加新属性或者修改已有属性,并确保新属性也是响应式的。

如何使用 $set:

this.$set 方法接受三个参数:

  1. 目标对象target:需要添加属性的对象。
  2. 键名name/index:你要添加的属性名称或数字的索引。
  3. 值value:该属性的值或者数组索引的值。
new Vue({
  el: '#app',
  data: {
    userProfile: {
      name: 'Alice'
    }
  },
  mounted() {
    // 正确的添加响应式属性
    this.$set(this.userProfile, 'age', 25);
  }
});

  

 

vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式

它采用集中式存储管理应用的所有组件的状态

并以相应的规则保证状态以一种可预测的方式发生变化

所以vuex就是一个仓库,用来存放数据的。所以我们使用vuex一般会新建一个store文件夹,store单词的中文意思就是商店、仓库的意思

vuex的应用场景

  • 正常数据放到data里面就行了,免得麻烦,一般小项目都很少用到vuex,毕竟vuex的步骤稍微多一点
  • 一般公共数据放到vuex中会比较好,毕竟组件有自己的data可以存放数据,公共的变量如用户信息、token等。vuex存一份,localstorage再存一份,取数据直接从vuex里面取,取不到再从localstorage里面去取。
  • 跨很多组件层级的数据通信,也可以通过vuex去做管理,毕竟vuex就像一个对象一个,好多组件都指向这个对象,当这个vuex对象发生了变化,所有的组件中对应的内容都会发生变化,这样就能做到实时响应,一个变,大家都变

 

vuex使用

在上述代码中,我们已经在vuex中的state里面定义了一个msg属性

export default new Vuex.Store({
    state:{
        msg:'我是vuex哦'
    },
    // 等...
})

  

使用mounted中去取用vuex中的数据

<template>
  <div class="box">
      <h2>{{msg}}</h2>
  </div>
</template>
<script>
export default {
    data() {
        return { msg:'' }
    },
    mounted() {
        this.msg = this.$store.state.msg
    },
}
</script>

 

使用computed去取vuex中的数据

<template>
  <div class="box">
      <h2>{{msg}}</h2>
  </div>
</template>
<script>
export default {
    computed: {
        msg(){ return this.$store.state.msg }
    }
}
</script>

  

修改vuex中的数据

方法1 一action-->mutation-->state

组件代码如下

<template>
  <div class="box">
      <h2>{{msg}}</h2>
      <el-button @click="changeVuex">修改</el-button>
  </div>
</template>

<script>
export default {
    methods: {
        changeVuex(){
            this.$store.dispatch('actionsChange')
        },
    },
    computed: {
        msg(){ return this.$store.state.msg }
    }
}
</script>

export default new Vuex.Store({
    strict:true,
    state:{
        msg:'我是vuex哦'
    },
    mutations:{
        // 这里第一个形参state就是仓库state,是可以访问到state里的msg的值,即 可以修改state
        // 第二个形参params是actions中传过来的数据
        mutationsChange(state,params){
            console.log(state,params);
            state.msg = params
        }
    },
    actions:{
        // 这里的形参store对象下面有commit方法
        // 可以去告知对应的mutations中的函数执行
        actionsChange(store){
            console.log(store);
            setTimeout(() => {
                store.commit('mutationsChange', '规范修改vuex')
            }, 500);
        }
    }
})

  

方法2 getter

getter中我们可以定义一个函数,这个函数我们用来修改state中的值的,函数接收一个参数state,这个state参数就是当前的state对象,通过这个参数可以加工state中的数据,加工好return出去,以供组件中使用

// vuex
export default new Vuex.Store({
    strict:true,
    state:{
        msg:'我是vuex哦'
    },
    getters:{
        gettersChange(state){
            return state.msg + '---getter修改state中的数据'
        }
    },
})

this.$store.getters.gettersChange

 

方法3 mapState辅助函数

第一步,假设vuex仓库中有三个数据,我们需要在组件上使用这三个数据

// store.js
export default new Vuex.Store({
    state:{
            msg1:'辅助函数一',
            msg2:'辅助函数二',
            msg3:'辅助函数三',
          },
}

第二步,从vuex插件中引入辅助函数 

import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

// 方式一,数组形式
computed: {
        ...mapState(['msg1','msg2','msg3'])
},
    
// 方式二, 对象形式(vuex模块化比较常用)
computed: {
    ...mapState({
        msg1: state=>state.msg1,
        msg2: state=>state.msg2,
        msg3: state=>state.msg3,
    })
}, 

<template>
    <div>
        <h1>{{msg1}}</h1>
        <h2>{{msg2}}</h2>
        <h3>{{msg3}}</h3>
    </div>
</template>

  

在组件中使用vuex模块化

<template>
  <div>
    <h2>{{ msg }}</h2>
  </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
  name: "CodeVue",
  computed: {
    // 正常方式
    msg(){
      return this.$store.state.vue.module// 找state里的vue模块下的module的值
    },
      
    // 使用辅助函数方式,这里用对象的写法
    ...mapState({
      msg:state=>state.vue.module// 找state里的vue模块下的module的值
    })
  }
};
</script>

 

不使用辅助函数

<template>
  <div>
    <h2>{{ msg }}</h2>
    <el-button @click="moduleChange">模块化修改值</el-button>
  </div>
</template>

<script>
export default {
  name: "CodeVue",
  computed: {
    ...mapState({
      msg:state=>state.vue.module
    })
  },
  methods: {
    moduleChange(){
      // 注意,直接提交对应模块的方法即可,commit会自动找到对应vuex下的方法
      this.$store.commit('moduleChange','我是参数')
    }
  },
};
</script>

 

使用辅助函数

<template>
  <div>
    <h2>{{ msg }}</h2>
    <!-- 我们在点击事件的语句中,把data中定义的参数带过去,去提交mutations -->
    <el-button @click="moduleChange(canshu)">模块化修改值</el-button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'
export default {
  name: "CodeVue",
  data() {
    return {
      canshu:'我是参数'
    }
  },
  computed: {
    ...mapState({
      msg:state=>state.vue.module
    })
  },
  methods: {
    ...mapMutations(['moduleChange'])
  },
};
</script>

写法如下

// 不使用辅助函数
moduleChange(){
    this.$store.commit('vue/moduleChange'); // 以斜杠分割,斜杠前写对应模块名,斜杠后写对应mutations中的方法名
}
    
// 使用辅助函数
...mapMutations('vue',['moduleChange']) // 以逗号分割,逗号前写模块名,逗号后是个数组,数组中放置对应mutations中的方法名

//3.别名状态下
...mapMutations({
    anotherName:'vue/moduleChange' // 和不使用辅助函数一样
}),

  

 

 

vue实例bus

 

vue实例bus的用法就相当于一个中间快递小哥,会帮我们在兄弟组件间进行数据传递。

1. EventBus 的基本使用:

创建一个 EventBus

创建一个 EventBus.js 文件(或直接在 main.js 中):

import Vue from 'vue';
export const EventBus = new Vue();

 

发送消息

// ComponentA.vue
<template>
  <div>
    <button @click="sendMessage">发送消息</button>
  </div>
</template>

<script>
import { EventBus } from './EventBus.js'; // 导入 EventBus

export default {
  methods: {
    sendMessage() {
      EventBus.$emit('message', '来自 ComponentA 的消息');
    }
  }
}
</script>

 

接收消息

// ComponentB.vue
<template>
  <div>
    <p>{{ message }}</p>
  </div>
</template>

<script>
import { EventBus } from './EventBus.js'; // 导入 EventBus

export default {
  data() {
    return {
      message: ''
    };
  },
  created() {
    // 在组件创建时监听事件
    EventBus.$on('message', (msg) => {
      this.message = msg;
    });
  },
  destroyed() {
    // 组件销毁时移除事件监听
    EventBus.$off('message');
  }
}
</script>

  

 

 

 

keep-alive 的作用

keep-alive 主要用于包裹动态组件,使得这些组件在切换时被缓存,而不是销毁。具体的作用如下:

  • 缓存组件状态:当组件被缓存后,组件的状态、数据、DOM 等会被保留。当组件再次显示时,不需要重新初始化,而是从缓存中恢复状态。
  • 避免重新渲染:如果没有使用 keep-alive,当切换到其他视图时,组件会被销毁并重新渲染,使用 keep-alive 可以避免这个过程,提高性能。
  • 提升性能:对于有较大渲染开销的组件(如表单、列表、图表等),keep-alive 可以显著提升性能,减少重新渲染的次数。
  • keep-alive 还提供了一些属性来定制缓存行为,常见的属性包括:

keep-alive 还提供了一些属性来定制缓存行为,常见的属性包括:

include: 只缓存名称匹配的组件。

 <keep-alive :include="['ComponentA', 'ComponentB']"> <component :is="currentComponent"></component> </keep-alive>

 

exclude: 只排除名称匹配的组件。

exclude 属性则用于排除某些组件不被缓存:

<keep-alive :exclude="['ComponentC']"> <component :is="currentComponent"></component> </keep-alive>

 

max: 最多缓存的组件个数。

max 属性限制了缓存的组件数量,当缓存的组件超过这个数量时,最不常用的组件会被销毁:

<keep-alive :max="3"> <component :is="currentComponent"></component> </keep-alive>

 

使

 keep-alive 缓存的生命周期

activated:当组件从缓存中被激活时调用,通常用于恢复状态或执行某些操作。

deactivated:当组件被缓存时调用,通常用于暂停或清理一些操作,如停止定时器、解绑事件等。

示例:

<template>
  <div>
    <keep-alive>
      <component :is="currentComponent"></component>
    </keep-alive>
  </div>
</template>

<script>
export default {
  data() {
    return {
      currentComponent: 'ComponentA'
    };
  },
  methods: {
    switchComponent() {
      this.currentComponent = this.currentComponent === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    }
  }
};
</script>

<script>
export default {
  name: 'ComponentA',
  activated() {
    console.log('ComponentA 被激活');
  },
  deactivated() {
    console.log('ComponentA 被缓存');
  }
};
</script>

  

 

Vuex使用
Vuex 是 Vue.js 的状态管理库,用于集中管理应用的状态。在 Vuex 中有多个核心属性:`state`、`mutations`、`actions`、`getters`、`modules` 等,每个属性都有特定的使用场景。下面是各个 Vuex 属性在实际项目中的常见使用场景:

### 1. **`state`:存储应用的状态**

`state` 是 Vuex 中存储全局共享状态的地方,任何组件都可以访问和修改 `state` 中的数据。适用于需要跨多个组件共享和管理的数据。

#### 使用场景:
- **用户认证状态**:例如存储当前用户的登录信息(如用户 ID、Token、用户名等),让应用的其他部分根据这些信息调整界面展示。
  - **示例**:存储用户信息(如 `user` 对象)或权限数据(如 `isLoggedIn`)。
  
- **购物车信息**:在电商应用中,购物车数据通常存储在 `state` 中,所有页面组件都可以访问和修改购物车的商品列表、数量和价格等信息。
  - **示例**:存储 `cart` 数组,包含购物车中的商品。

- **全局配置**:比如语言设置、主题(深色/浅色模式)等,存储这些信息以便全局管理和调整。
  - **示例**:存储 `language` 或 `theme` 设置。

```javascript
const store = createStore({
  state: {
    user: null,
    cart: [],
    language: 'en'
  }
});
```

### 2. **`mutations`:同步修改状态**

`mutations` 是用来修改 `state` 中数据的唯一方式,Vuex 通过 `mutations` 来保证状态变更的可追踪性。`mutations` 是同步操作。

#### 使用场景:
- **更新用户数据**:用户登录后,需要更新用户的认证信息(如用户名、Token 等)。
  - **示例**:当用户登录时,通过 `setUser` mutation 更新用户信息。
  
- **修改UI状态**:在全局状态中管理 UI 的显示或隐藏,避免在多个组件中重复管理。
  - **示例**:更新一个加载状态(`isLoading`)或弹出框显示状态(`isModalOpen`)。

- **数据更新**:例如,更新购物车中商品的数量,移除商品等。
  - **示例**:`addToCart`、`removeFromCart` 之类的 mutation。

```javascript
const store = createStore({
  mutations: {
    setUser(state, user) {
      state.user = user;
    },
    addToCart(state, product) {
      state.cart.push(product);
    }
  }
});
```

### 3. **`actions`:异步操作**

`actions` 用于处理异步操作,通常是向服务器请求数据、提交表单或等待某些操作完成后,再通过调用 `mutations` 来修改 `state`。

#### 使用场景:
- **获取远程数据**:请求服务器端的用户数据、商品列表等。
  - **示例**:当应用加载时,发起请求获取用户数据,并更新 `state`。
  
- **处理表单提交**:在提交表单时,进行一些验证或与后端进行异步交互,然后通过 `mutations` 来修改应用状态。
  - **示例**:在提交购物车订单时,进行异步请求并在请求成功后更新购物车状态。

- **延时操作**:比如一些需要延时执行的操作(例如动画、轮询请求)通常在 `actions` 中处理。
  - **示例**:发起数据请求后,等待返回结果并处理。

```javascript
const store = createStore({
  actions: {
    async fetchUser({ commit }) {
      const response = await fetch('/api/user');
      const user = await response.json();
      commit('setUser', user);
    },
    async addToCart({ commit }, productId) {
      const response = await fetch(`/api/products/${productId}`);
      const product = await response.json();
      commit('addToCart', product);
    }
  }
});
```

### 4. **`getters`:计算派生状态**

`getters` 用于从 `state` 中派生出新的数据,通常用于进行计算、过滤、排序等操作。类似于组件中的计算属性(`computed`)。

#### 使用场景:
- **过滤数据**:例如,获取购物车中所有已选择的商品,或根据某些条件过滤用户数据。
  - **示例**:`cartItems` 计算已选商品,`userFullName` 拼接用户的完整名称。

- **计算派生值**:例如,计算购物车中商品的总价格、购物车商品数量等。
  - **示例**:获取购物车的总金额(`totalPrice`)或商品数量(`cartCount`)。

- **格式化数据**:对日期、金额等进行格式化显示。
  - **示例**:将时间戳格式化为可读日期,或将金额格式化为带有货币符号的字符串。

```javascript
const store = createStore({
  getters: {
    cartCount(state) {
      return state.cart.length;
    },
    cartTotal(state) {
      return state.cart.reduce((total, product) => total + product.price, 0);
    }
  }
});
```

### 5. **`modules`:模块化管理**

当应用变得复杂时,使用 `modules` 可以将 Vuex store 划分成多个模块,每个模块拥有自己的 `state`、`mutations`、`actions` 和 `getters`,使得管理状态变得更加清晰。

#### 使用场景:
- **大型应用拆分业务逻辑**:对于大型应用,使用模块化可以让每个模块负责独立的业务逻辑,如用户模块、购物车模块、订单模块等。
  - **示例**:将 `user`、`cart`、`product` 分拆成不同的模块。
  
- **避免 state 冲突**:当应用的状态过多时,将状态分隔到不同的模块中,有助于避免命名冲突或过于庞大的状态对象。

- **跨模块共享状态**:可以通过 `root` store 或 `actions` 跨模块访问和操作其他模块的 `state`。

```javascript
const store = createStore({
  modules: {
    user: {
      state: { user: null },
      mutations: { setUser(state, user) { state.user = user; } },
      actions: { fetchUser({ commit }) { /* fetch logic */ } }
    },
    cart: {
      state: { items: [] },
      mutations: { addToCart(state, product) { state.items.push(product); } },
      actions: { addProductToCart({ commit }, product) { /* async logic */ } }
    }
  }
});
```

### 总结

Vuex 属性的使用场景可以概括如下:
- **`state`**:用于存储全局共享的应用状态。
- **`mutations`**:用于同步修改 `state` 中的值,确保状态的可追溯性。
- **`actions`**:处理异步操作,发起 API 请求或执行延时任务,最后通过 `mutations` 修改 `state`。
- **`getters`**:计算派生状态,可以用于过滤、计算、格式化数据等。
- **`modules`**:将 store 分割成多个模块,以便更好地组织和管理复杂应用的状态。

通过合理利用 Vuex 的这些功能,能够让应用的状态管理更加清晰、易于维护。

  

vuex和pinia的区别

1. 设计理念和目标

  • Vuex:

    • Vuex 是 Vue.js 官方的状态管理库,最初设计用于 Vue 2,并为 Vue 2.x 提供了一个集中式的状态管理解决方案。
    • 它的设计目标是通过“单一数据源”来管理整个应用的状态,遵循严格的“mutation”和“action”规则,以确保状态的可预测性和调试能力。
  • Pinia:

    • Pinia 是 Vue 3 专为 Vue 3 设计的状态管理库,是 Vuex 的现代替代品,旨在提供更简洁、灵活、易用的状态管理方式。
    • Pinia 提供了更好的类型推导支持,更简单的 API,且完全支持 Vue 3 的 Composition API。

2. 支持的 Vue 版本

  • Vuex:

    • Vuex 最初是为 Vue 2.x 设计的,但也提供了 Vue 3 的支持。对于 Vue 3,Vuex 仍然有效,但它并不完全利用 Vue 3 的新特性,比如 Composition API 和响应式 API。
  • Pinia:

    • Pinia 是为 Vue 3 和 Composition API 设计的,充分利用了 Vue 3 的新特性,能够更好地与 Vue 3 集成,推荐在 Vue 3 项目中使用。

 

3. API 风格

  • Vuex:

    • Vuex 采用了传统的基于 store 对象的管理方式,需要定义 statemutationsactions 和 getters
    • 操作状态时需要通过 commit(触发 mutation)和 dispatch(触发 action),使得 API 相对冗长。
// Vuex 示例
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    incrementAsync({ commit }) {
      setTimeout(() => {
        commit('increment');
      }, 1000);
    }
  },
  getters: {
    doubleCount(state) {
      return state.count * 2;
    }
  }
});

 

Pinia:

  • Pinia 使用 Composition API 风格,提供了更简洁、声明式的 API,状态和计算属性(getters)直接在 store 中声明,并且可以直接访问 state 和 getters,无需使用 commit 和 dispatch
  • // Pinia 示例
    import { defineStore } from 'pinia';
    
    const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0
      }),
      actions: {
        increment() {
          this.count++;
        },
        incrementAsync() {
          setTimeout(() => {
            this.count++;
          }, 1000);
        }
      },
      getters: {
        doubleCount: (state) => state.count * 2
      }
    });
    

      

4. 响应性

  • Vuex:

    • 在 Vuex 中,state 是响应式的,但你需要通过 mutations 来修改状态。这个过程使得 Vuex 的更新机制相对复杂。
    • 如果直接在组件中修改 state,Vuex 不会自动反应更新,必须通过 mutation 进行状态的修改。
  • Pinia:

    • Pinia 中的状态是响应式的,直接在 store 中修改 state 即可自动触发视图更新,无需通过额外的 mutations 或 actions。
    • Pinia 更好地支持 Vue 3 的响应式系统,状态直接绑定在 Vue 组件中时,变更会自动反应。

5. 类型支持

  • Vuex:

    • Vuex 对类型支持较弱,尤其是 Vuex 4.x 在 Vue 3 中,虽然有类型支持,但在一些复杂的应用中,类型推导可能并不总是完美。
  • Pinia:

    • Pinia 从一开始就为 TypeScript 提供了良好的支持,类型推导更加准确和友好。你可以直接在 store 中定义类型,Pinia 会自动推导出相关类型。

 

6. 模块化

  • Vuex:

    • Vuex 提供了模块化机制,可以将状态拆分成多个模块,每个模块有自己的 statemutations 和 actions
// Vuex 模块化示例
const store = new Vuex.Store({
  modules: {
    counter: {
      state: () => ({ count: 0 }),
      mutations: {
        increment(state) {
          state.count++;
        }
      }
    }
  }
});

  

Pinia:

  • Pinia 也支持模块化,每个 store 都是独立的模块,可以方便地按需导入和使用。
  • // Pinia 模块化示例
    import { defineStore } from 'pinia';
    
    const useCounterStore = defineStore('counter', {
      state: () => ({
        count: 0
      }),
      actions: {
        increment() {
          this.count++;
        }
      }
    });
    

      

7. 插件支持

  • Vuex:

    • Vuex 支持插件,可以通过 Vuex 插件来增强其功能。
  • Pinia:

    • Pinia 也支持插件,插件可以用来处理持久化、日志记录、状态恢复等功能,但相比 Vuex 的插件系统更轻量。

8. 持久化

  • Vuex:

    • 在 Vuex 中,持久化通常需要使用第三方库(如 vuex-persistedstate)来实现状态的持久化。
  • Pinia:

    • Pinia 可以通过插件轻松实现状态持久化,官方推荐使用 Pinia 的持久化插件来保存状态。

9. 性能

  • Vuex:

    • Vuex 在小型项目中足够使用,但随着项目的增大,Vuex 的性能和复杂度可能会成为瓶颈,尤其是在状态更新频繁的情况下。
  • Pinia:

    • Pinia 更加高效,特别是在 Vue 3 的响应式系统下,Pinia 在性能方面做了很多优化,尤其是在大规模应用中表现更好。

 

特性VuexPinia
支持的 Vue 版本 Vue 2 和 Vue 3 仅支持 Vue 3
API 风格 基于 mutation 和 action 基于 Composition API 和简洁的 API
类型支持 支持,但不如 Pinia 完善 更好的类型推导支持
响应式支持 需要通过 mutations 修改 完全支持 Vue 3 的响应式系统
模块化 支持模块化,但较为复杂 支持模块化,简单易用
插件支持 支持插件 支持插件
持久化 需要第三方插件支持

内置持久化插件支持

 

 

 

 

 

 

 

 

 

 

 

计算属性computed和watch的区别

 computed通常用于计算属性(计算属性仅会在响应式依赖更新时才会重新计算),不适用异步请求
 watch对于数据是深度监听,监听任何逻辑,支持异步操作,可以回调函数指向异步任务
 总结:computed 能做的,watch 都能做,反之不行。不过,能用 computed 的尽量用 computed。
 
 
 路由守卫有哪些,作用是什么

1. 全局守卫

全局守卫是应用于整个 Vue Router 的守卫,它们会在每次路由跳转时触发。全局守卫包括:

  • beforeEach:在路由跳转之前调用。
  • beforeResolve:在路由解析阶段之前调用(用于更复杂的路由守卫场景)。
  • afterEach:在路由跳转之后调用。

2. 路由独享守卫

路由独享守卫是配置在某个特定路由中的守卫,它只会在该路由触发时执行。包括:

  • beforeEnter:在进入路由之前调用。

3. 组件内守卫

组件内守卫是用在 Vue 组件中的守卫,它们是通过组件生命周期钩子来控制的。包括:

  • beforeRouteEnter:在路由进入前调用。
  • beforeRouteUpdate:在路由参数或路径更新时调用。
  • beforeRouteLeave:在离开路由时调用。

路由守卫的作用

  1. 权限验证与身份认证 在用户访问某些页面之前,你可以检查用户是否已登录或是否有权限访问该页面。如果没有权限或未登录,通常会重定向到登录页面或显示提示信息。

  2. 数据加载 在某些情况下,进入某个页面时需要加载一些动态数据。你可以在路由守卫中发起异步请求,确保数据在页面渲染之前被加载完毕。

  3. 路由重定向 有时你可能希望根据某些条件重定向用户到另一个路由。例如,基于用户的角色权限,可能需要将用户从普通页面重定向到管理页面,或反之。

  4. 清理工作 在离开页面时,你可能需要执行一些清理工作,比如注销事件监听、清除定时器或取消 API 请求等。组件的 beforeRouteLeave 守卫可以帮助你在路由跳转时进行清理。

  5. 动画与过渡控制 你可以在路由守卫中管理页面跳转的过渡效果。例如,在用户跳转到某个页面之前,触发一个页面加载动画,然后在页面加载完成后切换视图。

代码解析

1. 全局守卫 beforeEach

这是最常用的守卫类型,可以应用于所有路由。它在导航发生之前被调用,可以用来做全局性的权限检查或数据预加载。

router.beforeEach((to, from, next) => {
  // to: 即将进入的目标路由对象
  // from: 当前导航正要离开的路由
  // next: 一个回调函数,用来通知Vue Router是否继续进行导航
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!store.getters.isLoggedIn) {
      next({
        path: '/login',
        query: { redirect: to.fullPath }
      })
    } else {
      next()
    }
  } else {
    next()
  }
})

 

2、全局解析守卫 beforeResolve

 这个守卫在全局前置守卫之后,但在组建守卫和异步路由组件加载之前被调用。

router.beforeResolve((to, from, next) => {
  // 类似于 beforeEach,但是允许访问组件实例 `this`
  // 因为当守卫被调用时,组件实例已经被创建了
})

  

3. 全局后置钩子

 这些钩子在导航完成后被调用,不带有 next 函数,因此不能影响导航本身。
router.afterEach((to, from) => {
  // ...
})

  

4. 路由独享守卫

可以在路由配置中直接定义,只作用于该路由。

const routes = [
  {
    path: '/secret',
    component: SecretComponent,
    beforeEnter: (to, from, next) => {
      // ...
    }
  }
]

  

5. 组件内的守卫

export default {
  beforeRouteEnter (to, from, next) {
    // ...
  }
}

  

 beforeRouteUpdate: 在当前路由改变,但是该组件被复用时调用
export default {
  beforeRouteUpdate (to, from, next) {
    // ...
  }
}

  

 

 如何实现css样式隔离

1、在 Vue、Svelte 等前端框架中,scoped 是一种简单的样式隔离方式。它通过为组件的每个样式自动加上特定的作用域标识符,使得样式只应用于该组件。
<template>
  <div class="card">
    <p>This is a card component</p>
  </div>
</template>

<script>
export default {
  name: 'Card'
};
</script>

<style scoped>
.card {
  background-color: lightgray;
  padding: 10px;
}
</style>

2、使用 CSS 模块化

/* Button.module.css */
.button {
  background-color: blue;
  color: white;
  padding: 10px;
  border: none;
}

 

 

 递归组件

创建递归组件(TreeItem.vue)

<template>
  <ul>
    <li v-for="(node, index) in data" :key="node.id">
      <span>{{ node.name }}</span>
      <!-- 如果该节点有子节点,则递归渲染 -->
      <tree-item v-if="node.children && node.children.length" :data="node.children"></tree-item>
    </li>
  </ul>
</template>

<script>
export default {
  name: 'TreeItem',
  props: {
    data: {
      type: Array,
      required: true
    }
  }
}
</script>

<style scoped>
ul {
  list-style-type: none;
  padding-left: 20px;
}
li {
  margin: 5px 0;
}
</style>

  

使用递归组件

<template>
  <div>
    <tree-item :data="treeData" />
  </div>
</template>

<script>
import TreeItem from './components/TreeItem.vue';

export default {
  components: {
    TreeItem
  },
  data() {
    return {
      treeData: [
        {
          id: 1,
          name: 'Root 1',
          children: [
            { id: 2, name: 'Child 1-1', children: [] },
            { id: 3, name: 'Child 1-2', children: [
                { id: 4, name: 'Child 1-2-1', children: [] }
              ] 
            }
          ]
        },
        {
          id: 5,
          name: 'Root 2',
          children: [
            { id: 6, name: 'Child 2-1', children: [] }
          ]
        }
      ]
    }
  }
}
</script>

<style>
/* 可以根据需要设置父组件的样式 */
</style>

  

 vue是如何监听对象和数组变化
对象使用: this.$set
this.$set(this.obj,'name','小白')
 
数组:
1、使用Vue内置的响应式系统;2、使用Vue提供的数组变异方法;3、使用$watch侦听器。
 
Vue.js通过其内置的响应式系统,能够自动检测数组的变化并更新视图
new Vue({
  el: '#app',
  data: {
    items: [1, 2, 3, 4, 5]
  }
});

  

Vue.js覆盖了数组的7个变异方法,这些方法可以直接触发视图更新。这些方法包括:

  1. push()
  2. pop()
  3. shift()
  4. unshift()
  5. splice()
  6. sort()
  7. reverse()
 
Vue.js提供了$watch方法,用于侦听数据的变化。对于数组,$watch可以用来监听整个数组的变化或数组内元素的变new Vue({
  el: '#app',
  data: {
    items: [1, 2, 3]
  },
  watch: {
    items: {
      handler(newVal, oldVal) {
        console.log('数组变化了:', newVal, oldVal);
      },
      deep: true // 深度监听
    }
} });

 自定义指令如何实现

可以通过vue.directive 来定义一个自定义指令

// main.js
import Vue from 'vue';

Vue.directive('focus', {
  // 当绑定元素插入到 DOM 中时调用
  inserted(el) {
    // 聚焦元素
    el.focus();
  }
});

new Vue({
  el: '#app',
  render: h => h(App)
});

template使用

<template>
  <div>
    <input v-focus />
  </div>
</template>

  

自定义指令的生命周期钩子

自定义指令有一些钩子函数,允许你在不同的阶段进行操作。常用的钩子函数有:

  1. bind:指令和元素绑定时调用,仅调用一次。
  2. inserted:指令所在元素被插入父节点时调用。
  3. update:绑定的值变化时调用。
  4. componentUpdated:指令所在的组件更新时调用。
  5. unbind:指令和元素解绑时调用。

可以通过 Vue.directive 全局注册或者在组件中局部注册。指令的钩子函数提供了灵活性,使得你可以根据不同的需求对 DOM 元素进行操作。常见的场景包括动态样式调整、表单验证、权限等。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

 

posted @ 2024-12-10 09:03  键盘侠客  阅读(783)  评论(0)    收藏  举报