单页应用又称 SPA(Single Page Application)指的是使用单个 HTML 完成多个页面切换和功能的应用。这些应用只有一个 html 文件作为入口,一开始只需加载一次 js,css 等相关资源。使用 js 完成页面的布局和渲染。页面展示和功能室根据路由完成的。单页应用跳转,就是切换相关组件,仅刷新局部资源。
优点:
具有桌面应用的即时性、网站的可移植性和可访问性
用户体验好、快,内容的改变不需要重新加载整个页面
良好的前后端分离,分工更明确
缺点:
不利于搜索引擎的抓取
首次渲染速度相对较慢
如何给SPA做SEO
基于Vue的SPA实现SEO的三种方式:
SPA和MPA的区别
组成 :一个主页面和多个页面片段 | 多个主页面
刷新方式:局部刷新 | 整页刷新
url模式: 哈希模式 | 历史模式
SEO搜索引擎优化 : 难实现,可使用SSR方式改善 | 容易实现
数据传递: 容易 | 通过url、cookie、localStorage等传递
页面切换: 速度快,用户体验良好 | 切换加载资源,速度慢,用户体验差
维护成本:相对容易 | 相对复杂
MVC
MVC 全名是 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范.MVC是包括view视图层、controller控制层、model数据层。各部分之间的通信都是单向的。
View 传送指令到 ControllerController 完成业务逻辑后,要求 Model 改变状态Model 将新的数据发送到 View,用户得到反馈
一句话描述就是 Controller 负责将 Model 的数据用 View 显示出来.
v-if 在编译过程中会被转化成三元表达式,条件不满足时不渲染此节点。
v-show 会被编译成指令,条件不满足时控制样式将对应节点隐藏 (display:none)
使用场景
v-if 适用于在运行时很少改变条件,不需要频繁切换条件的场景
v-show 适用于需要非常频繁切换条件的场景
控制手段:v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。v-if显示隐藏是将dom元素整个添加或删除
编译过程:v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件;v-show只是简单的基于css切换。v-show 由false变为true的时候不会触发组件的生命周期,v-if由false变为true的时候,触发组件的beforeCreate、create、beforeMount、mounted钩子,由true变为false的时候触发组件的beforeDestory、destoryed方法
编译条件:v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。只有渲染条件为假时,并不做操作,直到为真才渲染
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果.
详见vue3组件通信方式
2.x中生命周期钩子放在跟methods同级属性下
3.x中需要先导入钩子,然后在setup方法中注册钩子回调,并且钩子命名也跟React保持一样了
3.x移除了2.x中的beforeCreate和created钩子,通过setup方法代替
beforeCreate :在实例初始化之后,数据观测(data observer) 和 event/watcher 事件配置之前被调用。在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问
created :实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算, watch/event 事件回调。这里没有$el,如果非要想与 Dom 进行交互,可以通过 vm.$nextTick 来访问 Dom
beforeMount :在挂载开始之前被调用:相关的 render 函数首次被调用。
mounted :在挂载完成后发生,在当前阶段,真实的 Dom 挂载完毕,数据完成双向绑定,可以访问到 Dom 节点
beforeUpdate :数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁(patch)之前。可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程
updated :发生在更新完成之后,当前阶段组件 Dom 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新,该钩子在服务器端渲染期间不被调用。
beforeDestroy: 实例销毁之前调用。在这一步,实例仍然完全可用。我们可以在这时进行善后收尾工作,比如清除计时器。
destroyed :Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。
activated : keep-alive 专属,组件被激活时调用
deactivated :keep-alive 专属,组件被销毁时调用
异步请求在哪一步发起?
可以在钩子函数 created、beforeMount、mounted 中进行异步请求,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。
如果异步请求不需要依赖 Dom 推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
数据总是从父组件传到子组件,子组件没有权利修改父组件传过来的数据,只能请求父组件对原始数据进行修改。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
注意:在子组件直接用 v-model 绑定父组件传过来的 prop 这样是不规范的写法 开发环境会报警告
如果实在要改变父组件的 prop 值 可以再 data 里面定义一个变量 并用 prop 的值初始化它 之后用$emit 通知父组件去修改
1、computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容,它可以设置 getter 和 setter。
2、watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。
3、计算属性一般用在模板渲染中,某个值是依赖了其它的响应式对象甚至是计算属性计算而来;而侦听属性适用于观测某个值的变化去完成一段复杂的业务逻辑
v-for 和 v-if 不要在同一个标签中使用,因为解析时先解析 v-for 再解析 v-if。如果遇到需要同时使用时可以考虑写成计算属性的方式。
Object.defineProperty
定义:Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。通过defineProperty 两个属性,get及set实现响应式。
proxy
Proxy的监听是针对一个对象的,那么对这个对象的所有操作会进入监听操作,这就完全可以代理所有属性了
vue-router路由钩子函数
官方
我们经常需要把某种模式匹配到的所有路由,全都映射到同个组件。例如,我们有一个 Post 组件,对于所有文章ID 各不相同的文章,都要使用这个组件来渲染。那么,我们可以在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) 来达到这个效果
const Post = {
template: "Post",
};
const router = new VueRouter({
routes: [
// 动态路径参数 以冒号开头
{ path: "/post/get_post/:postId", component: Post },
],
});
问题:vue-router 组件复用导致路由参数失效怎么办?
法一:
1、定义数据获取方法
首先定义一个获取文章的方法,根据文章ID从后台获取对应的文章信息。
methods: {
getPost(postId) {
this.$http.get(`/post/get_post/${postId}`).then((response) => {
if(response.data.code === 0){
this.post = response.data.post;
}
});
}
}
2、监听路由
接着是在路由切换的时候判断目标组件是否是Post.vue组件,这里可以根据定义的路由名称name实现,如果是,我们就可以从路由信息中获取目标文章ID来更新组件内容。
watch: {
'$route' (to, from) {
if(to.name === 'post'){
this.getPost(to.params.postId);
}
}
}
3、组件初始化
需要注意的是,当组件首次被挂载到组件树上时,对路由的监听是无效的,这时我们需要在生命周期钩子mounted对组件进行初始化工作:
mounted() {
this.getPost(this.$route.params.postId);
}
法二:
在 router-view 上加上一个唯一的 key,来保证路由切换时都会重新渲染触发钩子
1.通过 watch 监听路由参数再发请求
watch: { //通过watch来监听路由变化
"$route": function(){
this.getData(this.$route.params.xxx);
}
}
2.用 :key 来阻止“复用”
<router-view :key="$route.fullPath" />
vuex 是专门为 vue 提供的全局状态管理系统,用于多个组件中数据共享、数据缓存等。(无法持久化、内部核心原理是通过创造一个全局实例 new Vue)
Vuex是实现组件全局状态(数据)管理的一种机制,可以方便实现组件数据之间的共享;Vuex集中管理共享的数据,易于开发和后期维护;能够高效的实现组件之间的数据共享,提高开发效率;存储在Vuex的数据是响应式的,能够实时保持页面和数据的同步;
主要包括以下几个模块:
如:在vuex中保存导航栏的折叠状态。当刷新页面时,并不会保留上次的折叠状态
需要做 vuex 数据持久化 一般使用本地存储的方案来保存数据 可以自己设计存储方案 也可以使用第三方插件。推荐使用 vuex-persist 插件,它就是为 Vuex 持久化存储而生的一个插件。不需要你手动存取 storage ,而是直接将状态保存至 cookie 或者 localStorage 中
(react也有相应的数据持久化插件,react-persist)
**模块:**由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块。
**命名空间:**默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。
SSR 也就是服务端渲染,也就是将 Vue 在客户端把标签渲染成 HTML 的工作放在服务端完成,然后再把 html 直接返回给客户端。
优点:
SSR 有着更好的 SEO、并且首屏加载速度更快
缺点:
1.开发条件会受到限制,服务器端渲染只支持 beforeCreate 和 created 两个钩子,当我们需要一些外2部扩展库时需要特殊处理,服务端渲染应用程序也需要处于 Node.js 的运行环境。
2.服务器会有更大的负载需求
1.工厂模式 - 传入参数即可创建实例
虚拟 DOM 根据参数的不同返回基础标签的 Vnode 和组件 Vnode
2.单例模式 - 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册方法 install 判断如果系统存在实例就直接返回掉
3.发布-订阅模式 (vue 事件机制)
4.观察者模式 (响应式数据原理)
5.装饰模式: (@装饰器的用法)
6.策略模式 策略模式指对象有某个行为,但是在不同的场景中,该行为有不同的实现方案-比如选项的合并策略
在日常的开发中,我们经常会遇到在不同的组件中经常会需要用到一些相同或者相似的代码,这些代码的功能相对独立,可以通过 Vue 的 mixin 功能抽离公共的业务逻辑,原理类似“对象的继承”,当组件初始化时会调用 mergeOptions 方法进行合并,采用策略模式针对不同的属性进行合并。当组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”。
1.使用原理
2.应用
vue中的nextTick主要用于处理数据动态变化后,DOM还未及时更新的问题,用nextTick可以获取数据更新后最新dom的变化。
3.场景:
1)点击修改显示input组件时,dom还没更新就操作就会报错。
2)第三方插件,在vue生成的某些dom动态发生变化时重新应用该插件。
3) 视图更新之后,基于新的视图进行操作。
keep-alive 是 Vue 内置的一个组件,可以实现组件缓存,当组件切换时不会对当前组件进行卸载。
如:多个静态tab页的切换
扩展补充:LRU 算法是什么?
LRU 的核心思想是如果数据最近被访问过,那么将来被访问的几率也更高,所以我们将命中缓存的组件 key 重新插入到 this.keys 的尾部,这样一来,this.keys 中越往头部的数据即将来被访问几率越低,所以当缓存数量达到最大值时,我们就删除将来被访问几率最低的数据,即 this.keys 中第一个缓存的组件。
了解 Vue 响应式原理的同学都知道在两种情况下修改数据 Vue 是不会触发视图更新的
1.在实例创建之后添加新的属性到实例上(给响应式对象新增属性)
2.直接更改数组下标来修改数组的值
Vue.set 或者说是$set 原理如下
因为响应式数据 我们给对象和数组本身都增加了__ob__属性,代表的是 Observer 实例。当给对象新增不存在的属性 首先会把新的属性进行响应式跟踪 然后会触发对象__ob__的 dep 收集到的 watcher 去更新,当修改数组索引时我们调用数组本身的 splice 方法去更新数组
参考链接
参考链接
指令本质上是装饰器,是 vue 对 HTML 元素的扩展,给 HTML 元素增加自定义功能。vue 编译 DOM 时,会找到指令对象,执行指令的相关方法。
自定义指令有五个生命周期(也叫钩子函数),分别是 bind、inserted、update、componentUpdated、unbind
原理
1.在生成 ast 语法树时,遇到指令会给当前元素添加 directives 属性
2.通过 genDirectives 生成指令代码
3.在 patch 前将指令的钩子提取到 cbs 中,在 patch 过程中调用对应的钩子
4.当执行指令对应钩子函数时,调用对应指令定义的方法
自定义指令分为全局自定义指令和局部自定义指令
全局自定义指令 :用Vue.directive来注册。
比如一个input自动聚焦的例子:在main.js中写入以下内容:
import Vue from 'vue'
import Directives from './JS/directives'
Vue.use(Directives)
// 注册一个全局自定义指令 `v-focus`
app.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
vue3
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.directive('focus', {
mounted(el) {
el.focus();
}
})
app.mount('#app')
局部自定义指令 :通过在组件内设置directives属性
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus()
}
}
}
vue3
<script>
export default{
setup() {},
directives: {
// 指令名
focus: {
// 生命周期
mounted(el) {
// 处理DOM的逻辑
el.focus();
},
}
}
}
</script>
<template>
<input v-focus />
</template>
使用的时候要在自定义的指令前面加上v-,然后你可以在模板中任何元素上使用新的 v-focus
<input v-focus></input>
vue3钩子函数
// 指令是具有一组生命周期的钩子:
created // 在绑定元素的 attribute 或事件监听器被应用之前调用
beforeMount // 在绑定元素的父组件挂载之前调用
mounted // 绑定元素的父组件被挂载时调用
beforeUpdate // 在包含组件的 VNode 更新之前调用
updated // 在包含组件的 VNode 及其子组件的 VNode 更新之后调用
beforeUnmount // 在绑定元素的父组件卸载之前调用
unmounted // 卸载绑定元素的父组件时调用
指令钩子函数会被传入以下参数:钩子函数的参数
el:指令所绑定的元素,可以用来直接操作 DOM。
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
vnode:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。如以下例子:
事件修饰符
.stop 阻止事件继续传播
.prevent 阻止标签默认行为
.capture 使用事件捕获模式,即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理
.self 只当在 event.target 是当前元素自身时触发处理函数
.once 事件将只会触发一次
.passive 告诉浏览器你不想阻止事件的默认行为
v-model 的修饰符
.lazy 通过这个修饰符,转变为在 change 事件再同步
.number 自动将用户的输入值转化为数值类型
.trim 自动过滤用户输入的首尾空格
键盘事件的修饰符
.enter
.tab
.delete (捕获“删除”和“退格”键)
.esc
.space
.up
.down
.left
.right
系统修饰键
.ctrl
.alt
.shift
.meta
鼠标按钮修饰符
.left
.right
.middle
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)
第二步是对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器)
第三步是 使用 element ASTs 生成 render 函数代码字符串(代码生成器)
vue3.2
● 解绑自定义事件event.$off
● 清除定时器
● 解绑自定义的DOM事件,如window、scroll等
export default new VueRouter({
routes:[
{
path:"/"
component:()=>import(
/* webpackChunkName:"navigator" */
"./../components/Navigator"
)
}
]
})
● 异步渲染(以及合并data修改),以提高渲染性能
● $nextTick在DOM更新完之后,触发回调
● 合理使用v-show和v-if
● 合理使用computed
● v-for时加key,以及避免和v-if同时使用
● 自定义事件、DOM事件及时销毁
● 合理使用异步组件
● 合理使用keep-alive
● data层级不要太深
● 使用vue-loader在开发环境做模版编译(预编译)
● webpack层面的优化
● 前端通用的性能优化,如图片懒加载
● 使用SSR
vue2.x版本中,当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级;
vue3.x版本中,当 v-if 与 v-for 一起使用时,v-if 具有比 v-for 更高的优先级。
官网明确指出:避免 v-if 和 v-for 一起使用,永远不要在一个元素上同时使用 v-if 和 v-for。
可以先对数据在计算数据中进行过滤,然后再进行遍历渲染;
操作和实现起来都没有什么问题,页面也会正常展示。但是会带来不必要的性能消耗;
Server-Side Rendering 我们称其为SSR,意为服务端渲染指由服务侧完成页面的 HTML 结构拼接的页面处理技术,发送到浏览器,然后为其绑定状态与事件,成为完全可交互页面的过程;
解决了以下两个问题:
seo:搜索引擎优先爬取页面HTML结构,使用ssr时,服务端已经生成了和业务想关联的HTML,有利于seo
首屏呈现渲染:用户无需等待页面所有js加载完成就可以看到页面视图(压力来到了服务器,所以需要权衡哪些用服务端渲染,哪些交给客户端)
缺点: