21天筑基期--Vue系列
Vue
Vue 是一个构建数据驱动的 Web 界面的渐进式框架。 Vue 的目标是通过尽可能简单的 API 实现响应的数据绑定和组合的视图组件。核心是一个响应的数据绑定系统。 关于 Vue 的优点,主要有响应式编程、组件化开发、虚拟 DOM。
Vue3 setup使用参考
Vue3 script setup 语法糖,就问你甜不甜 - 掘金 (juejin.cn)
数据驱动(MVVM)
MVVM 是一种设计思想
- Model:模型层,负责处理业务逻辑以及和服务器端进行交互
- View:视图层:负责将数据模型转化为UI展示出来,可以简单的理解为HTML页面
- ViewModel:视图模型层,用来连接Model和View,是Model和View之间的通信桥梁
Vue生命周期
关于 Vue 生命周期的变化,可以从下表直观地了解:
Vue 2 生命周期 | Vue 3 生命周期 | 执行时间说明 |
---|---|---|
beforeCreate | setup | 组件创建前执行 |
created | setup | 组件创建后执行 |
beforeMount | onBeforeMount | 组件挂载到节点上之前执行 |
mounted | onMounted | 组件挂载完成后执行 |
beforeUpdate | onBeforeUpdate | 组件更新之前执行 |
updated | onUpdated | 组件更新完成之后执行 |
beforeDestroy | onBeforeUnmount | 组件卸载之前执行 |
destroyed | onUnmounted | 组件卸载完成后执行 |
errorCaptured | onErrorCaptured | 当捕获一个来自子孙组件的异常时激活钩子函数 |
## Vue 的路由实现 |
解释 hash 模式和 history 模式的实现原理
后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。
history 模式的实现,主要是 HTML5 标准发布的两个 API,pushState 和 replaceState,这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。
两种模式的区别:
- 首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
- 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
- 在兼容性上,hash 可以支持低版本浏览器和 IE
说一下 router与router 与 router与route 的区别
$route 对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query 对象等。
- $route.params: 一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
- route.query:一个key/value对象,表示URL查询参数。例如对于路径/foo?user=1,则有route.query:一个 key/value 对象,表示 URL 查询参数。例如对于路径 /foo?user=1,则有 route.query:一个key/value对象,表示URL查询参数。例如对于路径/foo?user=1,则有route.query.user == 1,如果没有查询参数,则是个空对象。
- $route.hash:当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。
- $route.fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径。
- $route.matched:数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
- $route.name:当前路径名字
- $route.meta:路由元信息
$route 对象出现在多个地方:
- 组件内的 this.$route 和 route watcher 回调(监测变化处理)
- router.match(location) 的返回值
- scrollBehavior 方法的参数
- 导航钩子的参数,例如 router.beforeEach 导航守卫的钩子函数中,to 和 from 都是这个路由信息对象。
$router 对象是全局路由的实例,是 router 构造方法的实例。
$router 对象常用的方法有:
- push:向 history 栈添加一个新的记录
- go:页面路由跳转前进或者后退
- replace:替换当前的页面,不会向 history 栈添加一个新的记录
vueRouter 有哪几种导航守卫?
- 全局前置/钩子:beforeEach、beforeR-esolve、afterEach
- 路由独享的守卫:beforeEnter
- 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
解释一下 vueRouter 的完整的导航解析流程是什么
一次完整的导航解析流程如下:
- 1.导航被触发。
- 2.在失活的组件里调用离开守卫。
- 3.调用全局的 beforeEach 守卫。
- 4.在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
- 5.在路由配置里调用 beforeEnter。
- 6.解析异步路由组件。
- 7.在被激活的组件里调用 beforeRouteEnter。
- 8.调用全局的 beforeResolve 守卫(2.5+)。
- 9.导航被确认。
- 10.调用全局的 afterEach 钩子。
- 11.触发 DOM 更新。
- 12.用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
路由守卫
由配置中对需要登录权限的页面添加一个meta:{isRequireLogin:true}
,表示跳转到该页面后需要进行登录权限验证
之后在路由守卫中(to, from, next)
三个参数中的to
获取到meta.isRequireLogin
,如果有且为真,则需要对该页面进行登录校验,
// 路由守卫,登录判断,以及主子页面之间的切换
router.beforeEach((to, from, next) => {
const { requiredLogin } = to.meta;
});
然后再从本地存储localstore
中获取login,看看用户有没有登录,如果登录了,则能获取到login且为真,之后放行,执行next
,如果不能获取,则表示用户没有登录,那么就跳转到登录页面
router.beforeEach((to, from, next) => {
const { requiredLogin } = to.meta;
const isLogin = localStorage.getItem("isLogin");
// 判断是否已经登录并是否页面需要登录权限,如果是,跳转到登录页面,若否,则放行
if (!isLogin && requiredLogin) {
next("login");
} else {
next();
}
});
v-show与v-if
v-show
与 v-if
的作用效果是相同的(不含v-else),都能控制元素在页面是否显示
- 当表达式为
true
的时候,都会占据页面的位置 - 当表达式都为
false
时,都不会占据页面位置v-if
与v-show
都能控制dom
元素在页面的显示
v-if
相比 v-show
开销更大的(直接操作dom
节点增加与删除)
如果需要非常频繁地切换,则使用 v-show 较好
如果在运行时条件很少改变,则使用 v-if 较好
v-if和v-for不建议一起用
v-if
指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 true
值的时候被渲染
v-for
指令基于一个数组来渲染一个列表。v-for
指令需要使用 item in items
形式的特殊语法,其中 items
是源数据数组或者对象,而 item
则是被迭代的数组元素的别名
在 v-for
的时候,建议设置key
值,并且保证每个key
值是独一无二的,这便于diff
算法进行优化
- 永远不要把
v-if
和v-for
同时用在同一个元素上,带来性能方面的浪费(每次渲染都会先循环再进行条件判断) - 如果避免出现这种情况,则在外层嵌套
template
(页面渲染不生成dom
节点),在这一层进行v-if判断,然后在内部进行v-for循环
<template v-if="isShow">
<p v-for="item in items">
</template>
- 如果条件出现在循环内部,可通过计算属性
computed
提前过滤掉那些不需要显示的项
computed: {
items: function() {
return this.list.filter(function (item) {
return item.isShow
})
}
}
Vue组件间通信方式
整理vue
中8种常规的通信方案
props传递数据
- 适用场景:父组件传递数据给子组件
- 子组件设置
props
属性,定义接收父组件传递过来的参数 - 父组件在使用子组件标签中通过字面量来传递值
$emit 触发自定义事件
- 适用场景:子组件传递数据给父组件
- 子组件通过
$emit触发
自定义事件,$emit
第二个参数为传递的数值 - 父组件绑定监听器获取到子组件传递过来的参数
Chilfen.vue
this.$emit('add', good)
Father.vue
<Children @add="cartAdd($event)" />
ref
- 父组件在使用子组件的时候设置
ref
- 父组件通过设置子组件
ref
来获取数据 父组件
<Children ref="foo" />
this.$refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据
EventBus
- 使用场景:兄弟组件传值
- 创建一个中央时间总线
EventBus
- 兄弟组件通过
$emit
触发自定义事件,$emit
第二个参数为传递的数值 - 另一个兄弟组件通过
$on
监听自定义事件
Bus.js
// 创建一个中央时间总线类
class Bus {
constructor() {
this.callbacks = {}; // 存放事件的名字
}
$on(name, fn) {
this.callbacks[name] = this.callbacks[name] || [];
this.callbacks[name].push(fn);
}
$emit(name, args) {
if (this.callbacks[name]) {
this.callbacks[name].forEach((cb) => cb(args));
}
}
}
// main.js
Vue.prototype.$bus = new Bus() // 将$bus挂载到vue实例的原型上
// 另一种方式
Vue.prototype.$bus = new Vue() // Vue已经实现了Bus的功能
Children1.vue
this.$bus.$emit('foo')
Children2.vue
this.$bus.$on('foo', this.handle)
parent或parent 或parent或root
- 通过共同祖辈
$parent
或者$root
搭建通信侨联
兄弟组件
this.$parent.on('add',this.add)
另一个兄弟组件
this.$parent.emit('add')
attrs 与 listeners
- 适用场景:祖先传递数据给子孙
- 设置批量向下传属性
$attrs
和$listeners
- 包含了父级作用域中不作为
prop
被识别 (且获取) 的特性绑定 ( class 和 style 除外)。 - 可以通过
v-bind="$attrs"
传⼊内部组件
// child:并未在props中声明foo
<p>{{$attrs.foo}}</p>
// parent
<HelloWorld foo="foo"/>
// 给Grandson隔代传值,communication/index.vue
<Child2 msg="lalala" @some-event="onSomeEvent"></Child2>
// Child2做展开
<Grandson v-bind="$attrs" v-on="$listeners"></Grandson>
// Grandson使⽤
<div @click="$emit('some-event', 'msg from grandson')">
{{msg}}
</div>
provide 与 inject
- 在祖先组件定义
provide
属性,返回传递的值 - 在后代组件通过
inject
接收组件传递过来的值
祖先组件
provide(){
return {
foo:'foo'
}
}
后代组件
inject:['foo'] // 获取到祖先组件传递过来的值
Vuex和Pinia
vuex
- 适用场景: 复杂关系的组件数据传递
Vuex
作用相当于一个用来存储共享变量的容器state
用来存放共享变量的地方getter
,可以增加一个getter
派生状态,(相当于store
中的计算属性),用来获得共享变量的值mutations
用来存放修改state
的方法。actions
也是用来存放修改state的方法,不过action
是在mutations
的基础上进行。常用来做一些异步操作
Pinia
和Vuex相比,Pinia有什么优势?
- mutations不再存在,只有state,gettes,actions。
- 更友好的TypeScript支持。
- 不再有modules的嵌套结构,每个store都是独立的,互不影响。
- 没有命名空间模块。
- 无需动态添加 Store,默认情况下它们都是动态的。
- 不再需要注入、导入函数、调用函数。
- 支持插件来扩展自身功能。
- 支持服务端渲染(SSR)。
Vue中的$nextTick有什么作用
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM
当数据发生变化,Vue
将开启一个异步更新队列,视图需要等队列中所有数据变化完成之后,再统一进行更新
如果想要在修改数据后立刻得到更新后的DOM
结构,可以使用Vue.nextTick()
父组件
<Children ref="foo" />
this.$refs.foo // 获取子组件实例,通过子组件实例我们就能拿到对应的数据
watch 与 computed 的区别是什么
都是观察数据变化的(相同)
区别:
- 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
- computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
- watch 支持异步,computed 不支持;
- watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
- watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
- computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)
watch 的 参数:
- deep:深度监听
- immediate :组件加载立即触发回调函数执行
computed 缓存原理:
conputed本质是一个惰性的观察者;当计算数据存在于 data 或者 props里时会被警告;
vue 初次运行会对 computed 属性做初始化处理(initComputed),初始化的时候会对每一个 computed 属性用 watcher 包装起来 ,这里面会生成一个 dirty 属性值为 true;然后执行 defineComputed 函数来计算,计算之后会将 dirty 值变为 false,这里会根据 dirty 值来判断是否需要重新计算;如果属性依赖的数据发生变化,computed 的 watcher 会把 dirty 变为 true,这样就会重新计算 computed 属性的值。
slot
slot
可以分来以下三种:
- 默认插槽
- 具名插槽
- 作用域插槽
vue中key的理解
1.key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速 2.如果不使用 key,Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。 3.如果绑定数组的索引index,则起不到优化diff算法的作用,因为一旦数组内元素进行增删,后续节点的绑定的key也会发生变化,导致diff进行多余的更新操作。
Vue 实现双向数据绑定原理是什么?
- Vue2.x 采用数据劫持结合发布订阅模式(PubSub 模式)的方式,通过 Object.defineProperty 来劫持各个属性的 setter、getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
-
当把一个普通 Javascript 对象传给 Vue 实例来作为它的 data 选项时,Vue 将遍历它的属性,用 Object.defineProperty 将它们转为 getter/setter。用户看不到 getter/setter,但是在内部它们让 Vue 追踪依赖,在属性被访问和修改时通知变化。
-
Vue 的数据双向绑定整合了 Observer,Compile 和 Watcher 三者,通过 Observer 来监听自己的 model 的数据变化,通过 Compile 来解析编译模板指令,最终利用 Watcher 搭起 Observer 和 Compile 之间的通信桥梁,达到数据变化->视图更新,视图交互变化(例如 input 操作)->数据 model 变更的双向绑定效果。
- Vue3.x 放弃了 Object.defineProperty ,使用 ES6 原生的 Proxy,来解决以前使用 Object.defineProperty 所存在的一些问题。
v-model 双向绑定的原理是什么
全网最详细的v-model讲解 - 掘金 (juejin.cn)
keep-alive缓存
keep-alive
是vue
中的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM
keep-alive
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们
修饰符
vue
中修饰符分为以下五种:
- 表单修饰符
- 事件修饰符
- 鼠标按键修饰符
- 键值修饰符
- v-bind修饰符
自定义指令
面试官:你有写过自定义指令吗?自定义指令的应用场景有哪些? · Issue #21 · febobo/web-interview · GitHub
转载自:https://juejin.cn/post/7254014646779576378