Vue(知识点总结)

Vue

对Vue的理解

Vue 是一个构建数据驱动的渐进性框架,目标是通过API实现 响应数据绑定视图更新

Vue的两大核心

  • 数据驱动
  • 组件系统

Vue生命周期

Vue实例从创建到销毁的过程

开始创建-beforeCreate
初始化数据-created
编译模板-beforeMount
挂载DOM-mounted
渲染-mounted
更新-beforeUpdate+updated
卸载-beforeDestroy+destroyed

在这里插入图片描述

生命周期的作用

生命周期中有多个事件钩子,能让开发者在控制整个vue实例的过程时更容易形成良好的逻辑判断

Vue 2 的8大生命周期钩子函数:

  1. beforeCreate
  2. created
  3. beforeMount
  4. mounted
  5. beforeUpdate
  6. updated
  7. beforeDestroy
  8. destroyed

Vue3的6大生命周期钩子函数:

beforeMount、onMounted、beforeUpdate、onUpdated、beforeUnmount、onUnmounted

用setup代替了beforeCreate、Created;destory改名成了unmount

createdmounted

createdmounted
调用时机模板渲染成 HTML 前模板渲染成 HTML 后
常见作用初始化属性值,再渲染成视图操作DOM节点
合适的调用接口时期组件渲染必须的,且请求耗时较短组件渲染不必要的,或者请求耗时较长

beforeDestroy()

当组件被销毁时(例如调用了 vm.$destroy() 方法),Vue.js 将会依次触发 beforeDestroy() 生命周期钩子函数

  1. 清除定时器和监听器:在组件销毁前清除定时器和监听器,避免内存泄漏和不必要的资源占用。
clearInterval(this.timer); // 清除定时器
this.timer = null; // 将定时器变量置为 null,释放内存

this.$off('eventName'); // 清除特定事件的监听器
this.$off(); // 清除所有事件的监听器
  1. 取消订阅事件:如果组件订阅了外部事件,需要在组件销毁前取消订阅,以避免在组件已销毁后继续接收事件。

  2. 清理非 Vue 实例的资源:例如关闭 WebSocket 连接、清理 Web Worker、释放资源等。

  3. 取消异步任务:如果组件中存在未完成的异步任务,需要在销毁前取消这些任务,以确保不会产生不必要的副作用。

  4. 其他清理工作:例如重置状态、清空缓存、解除绑定等。

总的来说,beforeDestroy() 生命周期钩子函数是在组件即将销毁时执行的最后一个钩子函数,用于执行一些清理和准备工作,确保组件销毁时的状态和资源得到正确处理,以提高页面性能并避免潜在的问题。

v-model

用于表单数据的双向绑定,其实是语法糖,背后做了两个操作:

  1. v-bind绑定value属性
  2. v-on绑定input事件

Vue响应式(双向数据)原理

通过 数据劫持 结合 发布订阅模式 的方式来实现

  • Vue2:通过Object.defineProperty()来劫持各个属性的settergetter,在数据变动时发布消息给订阅者,触发相应的监听回调
  • Vue3:数据劫持的方式更改为 ES6 中的 Proxy 对象,更加灵活且性能更好

虚拟DOM

虚拟DOM相对于浏览器所渲染出来的真实 DOM,在 React,Vue 等技术出现之前, 改变页面展示内容只能遍历查询 DOM 树找到需要修改的 DOM,然后修改样式行为或者结构,来更新UI,但是这种方式相当消耗计算资源,每次查询DOM几乎都需要遍历整棵DOM树,因此建立一个与真实DOM树对应的虚拟DOM对象(JavaScript对象),以对象嵌套的方式来表示DOM树,那么每次DOM的更改就变成了对象属性的更改,这样一来就能通过diff算法查找变化要比查询真实的DOM树的性能开销小

Vue渲染优先级

Vue通过内部函数_init处理,优先级:render()>template>el选项

  • render()
Vue.component('my-component', {
  render: function (createElement) {
    return createElement('h1', 'Hello Vue!')
  }
})
  • template
Vue.component('my-component', {
  template: '<h1>Hello Vue!</h1>'
})
  • el选项
new Vue({
  el: '#app',
  data: {
    message: 'Hello Vue!'
  }
})

<template>渲染过程

将template标签里面的内容编译成render函数
挂载实例:根据render函数的根节点递归生成虚拟DOM树
通过diff算法对比虚拟DOM,渲染到真实DOM
新的DOM操作使得DOM树发生改变

key的作用

为了高效的更新虚拟DOM

给虚拟DOM的每个节点 VNode 添加唯一 id,让其可以依靠 key,更快速准确地拿到 oldVnode 中对应的 VNode

【设置key不推荐使用index或者随机数,因为增删子项的时候损耗性能较大】

v-ifv-show

  • v-if在DOM层面决定元素是否存在,会引起重排重绘,变成true时触发前4个生命周期,变成false时触发beforeDestroy、destroyed
  • v-show在CSS层面决定是否将元素渲染出来(实际上该元素一直存在),不触发生命周期, 属于display 的切换(block/none)

v-ifv-for优先级

v-ifv-for
Vue 2优先
Vue 3优先

解决方案 :

  1. 父元素使用v-if,子元素使用v-for
  2. 使用计算属性computed
computed() {
    list() {
        return [1, 2, 3].filter(item => item !== 2);
    }
}

修饰符

.stop:阻止事件冒泡
.trim:去空格
.sync:简化子组件向父组件传值,写在父组件里面,监听子组件的传值

data是个函数并且返回一个对象 / data为什么要用return

因为一个组件可能会多处调用,每次调用会执行data函数并返回新的数据对象,因此这样可以避免多处调用之间的数据污染

Vue 如何监听键盘事件

  1. @keyup.方法
  2. addEventListener

组件

组件通信

父子祖孙/隔代兄弟
props / $emit
ref与 $parent / $children
$attrs / $listeners
provide / inject
EventBus($emit / $on)
Vuex、Pinia

localStorage/Cookie等都可以

  • 父子的传参及方法调用:props / $emit、$parent / $children、$ref
  • 祖孙的传参:provide / inject API、$attrs / $listeners
  • 兄弟的传参:Vuex、事件总线bus.js(Vue2)、mitt插件(Vue3)
  • 路由的传参:query、params
    在这里插入图片描述

使用$ref的弊端

因为$ref是操作真实DOM,所以组件嵌套过深/频繁调用子组件会造成性能问题

  1. 组件嵌套过深: 如果在嵌套层次很深的组件树中频繁使用 $ref 来访问子组件的真实 DOM,可能会导致性能下降。因为每次访问都需要遍历组件树,直到找到目标组件,这会增加额外的计算开销。

  2. 频繁调用子组件: 如果在父组件中频繁地使用 $ref 来操作子组件,特别是在循环中或者在大量数据的列表中使用,可能会造成性能问题。因为每次操作都会涉及到真实 DOM 的更新和重绘,这会消耗大量的计算资源。

另外,直接操作真实 DOM 也会破坏了 Vue 的响应式机制,因为 Vue 的响应式是基于虚拟 DOM 的,直接操作真实 DOM 可能会导致视图和数据的不一致。

父子组件的生命周期顺序

父组件created -> 子组件created -> 子组件mounted -> 父组件mounted

为何如此设计:父组件需要在created拿到初始化数据,通过props传递给子组件;父组件可能有多个子组件,所以需要等待所有子组件mounted

同理,更新 / 销毁也是如此:
父组件beforeUpdate / beforeDestroy-> 子组件beforeUpdate / beforeDestroy-> 子组件updated / destroyed-> 父组件updated / destroyed

动态组件

使用<component :is="判断条件"></component>

删除数组用 delete 和 Vue.delete 的区别

  • delete:只是被删除数组成员变为 empty / undefined,其他元素键值不变
  • Vue.delete:直接删了数组成员,并改变了数组的键值
    (对象是响应式的,确保删除能触发更新视图,这个方法主要用于避开 Vue 不能检测到属性被删除的限制)

计算属性computed和属性检测watch

cpmputed:当且仅当计算属性依赖的 data 改变时才会自动计算

computedwatch
首次运行× (可以添加{immediate:true}实现首次运行)
默认依赖深度(推荐使用)浅度
调用时在模板渲染只需修改元数据
合适性筛选、不可异步(因为有return)开销较大、异步操作
特征根据页面变化而变化(用于计算)监听页面状态而变化(用于监听)
场景购物车结算功能商品是否选中

Vue组件封装过程

Vue.extend 创建组件
Vue.component 注册组件
// 创建组件构造器
var MyComponent = Vue.extend({
  data() {
    return {
      message: 'Hello, Vue!'
    };
  },
  template: '<div>{{ message }}</div>'
});

// 注册组件
Vue.component('my-component', MyComponent);

// 创建 Vue 实例并挂载到页面上
new Vue({
  el: '#app'
});

v-for后使用this.$refs报错domundefined

组件初始化到第一次渲染完成的mounted周期里,只是渲染了组件模板的静态数据,并没有初始化动态绑定的dom,所以在mounted周期里面操作获取不到dom

解决方法:

  1. 把this.$nextTick放在获取到v-for绑定的数据并赋值之后,也就是触发响应式更新之后再进行操作
mounted() {
  this.$nextTick(() => {
    // 在 DOM 渲染完成后执行操作
    console.log(this.$refs);
  });
}
  1. 把操作dom的操作放到updated生命周期里,但是这样每次更新视图都会触发该操作
updated() {
  // 在组件数据更新后执行操作
  console.log(this.$refs);
}

this.$nextTick()

对DOM的操作最好都放在这里, 因为像created()生命周期时执行的话, 会报错domundefined, 该DOM还未渲染

process.nextTick()this.$nextTick()

process.$nextTick()是在所有微任务前执行,是 Node.js 中的一个机制
this.$nextTick()是在所有微任务后执行,是 Vue.js 中的方法,它用于等待 Vue 组件的 DOM 更新完成后再执行回调。它通常会在 Vue 组件中使用,确保在 DOM 更新后执行操作

scoped CSS的实现原理(Vue2)

  1. 通过PostCSS给所有dom都添加了唯一的动态属性
  2. 通过PostCSS也给css选择器额外添加对应的属性选择器,来选择组件中的dom

module CSS的实现原理(Vue 3)

  1. 局部作用域: 在 Vue 3 中,通过在 <style> 标签中添加 module 属性,可以将样式声明为局部作用域的。这意味着这些样式只会应用于当前组件的 DOM 元素,不会影响到其他组件或全局样式。

  2. 自动生成唯一标识符: 在编译过程中,Vue 3 会自动生成一个唯一标识符(通常是哈希值),并将其作为 CSS 类名的一部分。这个唯一标识符会与当前组件的样式关联起来,确保样式只应用于当前组件的 DOM 元素。

  3. 样式模块化: Vue 3 的 module 样式允许在样式表中使用类似于 CSS Modules 的语法,例如使用 :global 来声明全局样式,或者使用 :local 来声明局部样式。这样可以更加灵活地管理样式,并且可以避免命名冲突和样式污染。

  4. 动态导出类名: 在组件的 JavaScript/TypeScript 代码中,可以通过导入样式模块并使用其导出的类名来应用样式。这样可以确保类名的一致性,并且可以在模板中动态地应用样式。

插槽

  1. 默认插槽(Default Slot): 这是最基本的插槽类型。当组件内部没有具名插槽时,所有没有被包裹在具名插槽中的内容都会被放置在默认插槽中。
<template>
  <div>
    <slot></slot>
  </div>
</template>
  1. 具名插槽(Named Slots): 通过给插槽设置名称,可以在父组件中将内容插入到特定的插槽中。具名插槽使得父组件可以在一个单独的插槽中插入不同的内容。
<template>
  <div>
    <slot name="header"></slot>
    <slot></slot>
    <slot name="footer"></slot>
  </div>
</template>

在父组件中使用:

<template>
  <MyComponent>
    <template v-slot:header>
      <h1>This is header</h1>
    </template>
    <p>This is main content</p>
    <template v-slot:footer>
      <p>This is footer</p>
    </template>
  </MyComponent>
</template>
  1. 作用域插槽(Scoped Slots): 作用域插槽允许父组件向子组件传递数据。子组件可以在插槽中接收来自父组件的数据,并根据这些数据来渲染内容。
<template>
  <div>
    <slot :user="user"></slot>
  </div>
</template>

在父组件中使用:

<template>
  <MyComponent>
    <template v-slot="{ user }">
      <p>{{ user.name }}</p>
      <p>{{ user.age }}</p>
    </template>
  </MyComponent>
</template>

这样,user 对象就会从父组件传递到子组件,并在子组件的作用域中可用。

Vue3

ref():监听简单数据类型 reactive():监听复杂数据类型

ref():适用任何类型,会返回一个包裹对象,需要使用.value才能拿到里面的值【更通用常见】
reactive():只适用对象(包括数组、Map、Set)

reactive响应的注意点

import { reactive, ref } from 'vue';
 
// 这个时候你就会发现当前数据不会具有响应:原因是因为当前的 proxy对象 已经被替换为 原始对象
let play = reactive({ a: 0 })
play = { a: 1 }
console.log(play);  //{a: 1}
 
 
// 解决1 :在重新替换的时候给新的值也加上 reactive()
let play = reactive({ a: 0 })
play = reactive({ a: 1 })
console.log(play);  //Proxy {a: 1}
 
 
// 解决2 :使用 ref 代替 reactive 
// 原因:ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value
let play = ref({ a: 0 })
play.value = { a: 1 }
console.log(play);
 
// 以下的解构也不会具有响应式
 
let n = play.a
n++  //不影响原始的 play
 
let { a } = play
a++  // 不会影响原始的 play

Vue优化

  • 组件拆分
  • 使用keep-alive
  • 优化渲染性能
  1. 合理使用v-ifv-show
  2. 使用v-for避免手动操作DOM
  3. 合理使用computed和缓存重复计算
  • 状态管理优化
  1. 使用Vuex管理状态,避免组件间传递大量props
  2. 合理使用Getter和Mutation,保持状态管理的可维护性
  • 图片懒加载
  • 路由懒加载
  • 使用骨架屏
  • 使用SSR(服务器端渲染)

localstorage vuex和pinia的异同

vue3为什么要出hooks 跟普通的封装函数有什么区别

  1. 状态管理: Vue 3 的 Hooks 具有更强大的状态管理能力,可以直接使用响应式状态,无需手动进行状态管理。而普通的封装函数可能需要通过参数传递状态或者通过闭包来管理状态。

  2. 生命周期钩子: 在 Vue 3 中,Hooks 可以模拟类组件中的生命周期钩子,比如 onMounted, onUpdated, onUnmounted 等,使得在函数式组件中可以处理生命周期相关的逻辑。

  3. 逻辑复用: Hooks 的设计目的是为了更好地复用逻辑。使用 Hooks 可以将组件中的状态和副作用逻辑提取到可复用的函数中,然后在组件中进行调用,而普通的封装函数可能只能实现逻辑的封装,但不够灵活和易用。

  4. 性能优化: Vue 3 的 Hooks 在内部做了一些性能优化,比如利用了 JavaScript 的闭包特性来保持状态的独立性,从而避免了一些潜在的性能问题。

// 自定义hook
import { ref, onMounted, onUnmounted } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);

  function increment() {
    count.value++;
  }

  function decrement() {
    count.value--;
  }

  // 在组件挂载时增加计数器值
  onMounted(() => {
    console.log('Component mounted');
  });

  // 在组件卸载时打印计数器值
  onUnmounted(() => {
    console.log('Component unmounted. Count:', count.value);
  });

  return {
    count,
    increment,
    decrement
  };
}
<template>
  <div>
    <h2>Counter: {{ counter.count }}</h2>
    <button @click="counter.increment">Increment</button>
    <button @click="counter.decrement">Decrement</button>
  </div>
</template>

<script>
import { useCounter } from './useCounter';

export default {
  setup() {
    const counter = useCounter();

    return {
      counter
    };
  }
};
</script>

Vue CLI

src目录每个文件夹和文件的用法

  1. assets文件夹是放静态资源;
  2. components是放组件;
  3. router是定义路由相关的配置;
  4. app.vue是一个应用主组件;
  5. main.js是入口文件

static和assets的区别

原理:webpack 如何处理静态资源

staticassets
内容类库项目资源
资源直接引用被 webpack 打包

(static放别人家的资源,assets放自己家的资源)

引入第三方库

  1. 绝对路径直接引入
  2. 在 webpack 中配置 alias [ˈeɪliəs]
  3. 在 webpack 中配置 plugins [ˈplu:genz]

Vue-Router

router、routes、route

router: 路由对象
routes: 路由配置项
route: 当前路由信息

hash模式和history模式

hash模式history模式
浏览器支持版本所有支持 HTML5 新 API 的现代浏览器
URL中是否带 #×
刷新 / # 后面的hash变化仅根据hash重新加载重新往服务器发起请求
适用场景兼容性要求较高 、不需要考虑 SEO 和页面刷新时的状态保持对SEO 有较高要求、接受需要服务器端额外配置

路由实现原理

  • 利用URL中的hash(“#”)
  • 利用History interface在HTML5中新增的方法

传参

  • name 传参
  • URL 传参
  • <router-link>的to传参
  • 用 path 匹配路由,通过 query 传参

二级路由 / 嵌套路由

使用路由导航<router-link>和路由容器<router-view>,配置children

路由守卫触发流程

  1. 不同组件(A组件跳转到B组件)
触发导航
调用A组件的路由守卫beforeRouteLeave
调用全局路由前置守卫beforeEach
调用B路由独享守卫beforeEnter
解析异步路由组件B
调用B的组件内路由守卫beforeRouteEnter
调用全局路由解析守卫beforeResolve
确认导航
调用全局路由钩子afterEach
渲染B组件DOM

keep-alive

keep-alive用于保存组件的渲染状态

不希望组件被重新渲染影响用户体验和降低性能,而是希望组件可以缓存下来,维持当前的状态,这时候就可以用到keep-alive组件

(通常搭配生命周期activated使用)

keep-alive是一个抽象组件:它自身不会渲染一个DOM元素,也不会出现在父组件链中;使用keep-alive包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。

Vuex

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
简单来说就是:应用遇到多个组件共享状态时,使用vuex。
在这里插入图片描述

vuex的流程

页面通过 mapAction 异步提交事件到 action
action 通过 commit 把对应参数同步提交到 mutation
mutation 修改 state 中对应的值
通过 getter 传递对应值
在页面的 computed 中通过 mapGetter 来动态获取 state 中的值

vuex属性

mapAction:State , Getter , Mutation , Action , Module
你对 Vuex 属性的理解很到位。下面我会稍微扩展一下:

  1. state(状态):Vuex 中的 state 是存储应用程序中的所有变量和状态的地方。它类似于组件中的 data 属性,但是它是全局的,可以在应用程序的任何地方访问。

  2. getter(获取器):Getter 允许组件从 store 中获取数据,并对数据进行一些处理后返回。它可以看作是 store 中的计算属性,用于派生出一些状态。

  3. mutation(变更):Mutation 是 Vuex 中唯一允许修改 state 的地方。它是同步的函数,用于修改 state 中的数据。由于 mutation 必须是同步的,因此它们应该尽量保持简单和高效。

  4. action(动作):Action 可以包含任意异步操作,例如向服务器发送请求。它类似于 mutation,但是可以包含任意异步操作。Action 是通过提交 mutation 来间接修改 state 的。

  5. modules(模块):Modules 允许将 store 分割成模块化的结构,每个模块都有自己的 state、getter、mutation 和 action。这样可以使得应用程序的结构更清晰,方便管理和维护。Modules 也可以嵌套,形成复杂的层次结构。

页面刷新后store的state不存在

可以安装插件让其实现持久化

ajax 写在 methods 中还是 actions 中

  1. 仅在请求组件内使用:写在组件的 methods 中
  2. 在其他组件复用:写在 vuex 的 actions 中
    (包装成 promise 返回,在调用处用 async await 处理返回的数据)

Vue项目

首屏加载优化

你列出了一些优化首屏加载速度的方法,下面我会对每个方法进行简要的解释:

  1. 服务器渲染(SSR):

    • 通过在服务器端生成 HTML 再发送到前端进行解析,可以减少客户端渲染的时间,提高页面加载速度和 SEO 效果。
  2. 骨架屏(Skeleton Screen):

    • 骨架屏是一种页面加载优化的技术,它会在页面加载时先展示页面的大致框架,然后再逐步加载细节内容,从而提升用户的加载体验。
  3. 图片懒加载:

    • 图片懒加载是一种延迟加载图片的技术,当图片进入可视区域时再进行加载,可以减少首屏加载时的网络请求和页面渲染时间。
  4. Vue-Router 路由懒加载:

    • 使用 Vue-Router 的路由懒加载功能,可以将页面组件按需加载,只在需要时才加载相应的组件,从而减少首屏加载时的资源请求和时间。
  5. 使用异步组件动态导入:

    • Vue.js 支持使用异步组件来实现动态导入,可以将组件按需加载,只在需要时才加载组件的代码,从而提高页面加载速度。
  6. 分包:

    • 将页面中的资源拆分为多个包,按需加载,可以减少首屏加载时的资源大小和加载时间,提高页面的加载速度。

通过使用以上方法,可以有效地优化页面的首屏加载速度,提升用户的加载体验和页面性能。

Webpack

构建流程

初始化参数
开始编译
确定入口
编译模块
完成编译
输出资源
输出完成

Webpack和Vite

打包的方式以及首次加载时间会有一些不同

Webpack: (全打包)

  • Webpack是一个传统的打包工具,它会将所有模块打包成一个或多个 bundle 文件,这些文件通常包含了整个应用程序的代码和依赖。这意味着首次加载时需要下载和解析所有的代码,因此首次加载时间可能会较长。

Vite: (模块打包)

  • Vite采用了一种不同的开发模式,称为ES模块(ESM)原生开发。在开发模式下,Vite不会将所有代码打包成一个大的 bundle 文件,而是会保持模块的原始状态,每个模块都是一个独立的文件。这样,在首次加载时,浏览器只需要请求并加载当前页面所需的模块,而不需要下载整个应用程序的代码。这通常会导致更快的首次加载时间。

不过,在生产环境下,Vite也可以进行打包,将所有模块打包成一个或多个 bundle 文件,以便于部署。这时,首次加载时间可能会比开发模式长一些,因为需要下载和加载更多的代码

工作遇到的问题

组件循环依赖问题

  1. 放在合适的生命周期如created
  2. 使用组件懒加载
// ComponentA.vue
export default {
  created() {
    import('./ComponentB.vue').then(ComponentB => {
      // 处理 ComponentB
    });
  }
}
  1. 把循环依赖的组件设置成全局
// main.js
import Vue from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';

Vue.component('ComponentA', ComponentA);
Vue.component('ComponentB', ComponentB);
  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

刘泽宇Developer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值