Vue
不允许在已经创建的实例上动态添加新的响应式属性
若想实现数据与视图同步更新,可采取下面三种解决方案:
Vue.set()
Vue.set( target, propertyName/index, value )
参数
{Object | Array} target
{string | number} propertyName/index
{any} value
返回值:设置的值
通过Vue.set
向响应式对象中添加一个property
,并确保这个新 property
同样是响应式的,且触发视图更新
关于Vue.set
源码(省略了很多与本节不相关的代码)
源码位置:src\core\observer\index.js
function set (target: Array
这里无非再次调用defineReactive
方法,实现新增属性的响应式
关于defineReactive
方法,内部还是通过Object.defineProperty
实现属性拦截
大致代码如下:
function defineReactive(obj, key, val) { Object.defineProperty(obj, key, { get() { console.log(`get ${key}:${val}`); return val }, set(newVal) { if (newVal !== val) { console.log(`set ${key}:${newVal}`); val = newVal } } }) }
Object.assign()
直接使用Object.assign()
添加到对象的新属性不会触发更新
应创建一个新的对象,合并原对象和混入对象的属性
this.someObject = Object.assign({},this.someObject,{newProperty1:1,newProperty2:2 ...})
$forceUpdate
如果你发现你自己需要在 Vue
中做一次强制更新,99.9% 的情况,是你在某个地方做错了事
$forceUpdate
迫使 Vue
实例重新渲染
PS:仅仅影响实例本身和插入插槽内容的子组件,而不是所有子组件。
小结
如果为对象添加少量的新属性,可以直接采用Vue.set()
如果需要为新对象添加大量的新属性,则通过Object.assign()
创建新对象
如果你实在不知道怎么操作时,可采取$forceUpdate()
进行强制刷新 (不建议)
PS:vue3
是用过proxy
实现数据响应式的,直接动态添加新属性仍可以实现数据响应式
整理vue
中8种常规的通信方案
props传递数据
props
属性,定义接收父组件传递过来的参数Children.vue
props:{
// 字符串形式
name:String // 接收的类型参数
// 对象形式
age:{
type:Number, // 接收的类型为数值
defaule:18, // 默认值为18
require:true // age属性必须传递
}
}
Father.vue
组件
$emit 触发自定义事件
$emit触发
自定义事件,$emit
第二个参数为传递的数值Chilren.vue
this.$emit('add', good)
Father.vue
ref
ref
ref
来获取数据父组件
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 或 $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
{{$attrs.foo}}
// parent
// 给Grandson隔代传值,communication/index.vue
// Child2做展开
// Grandson使⽤
{{msg}}
provide 与 inject
provide
属性,返回传递的值inject
接收组件传递过来的值祖先组件
provide(){
return {
foo:'foo'
}
}
后代组件
inject:['foo'] // 获取到祖先组件传递过来的值
vuex
适用场景: 复杂关系的组件数据传递
Vuex作用相当于一个用来存储共享变量的容器
state
用来存放共享变量的地方
getter
,可以增加一个getter
派生状态,(相当于store
中的计算属性),用来获得共享变量的值
mutations
用来存放修改state
的方法。
actions
也是用来存放修改state的方法,不过action
是在mutations
的基础上进行。常用来做一些异步操作
小结
props
与 $emit
进行传递,也可选择ref
$bus
,其次可以选择$parent
进行传递attrs
与listeners
或者 Provide
与 Inject
vuex
存放共享的变量响应式系统(Reactivity System):Vue 3 引入了 Composition API,这是一种新的响应式系统。Composition API 提供了更灵活和强大的组件状态和逻辑管理方式,使代码组织和重用更加方便。Composition API 使用函数而不是对象,可以提高摇树优化(Tree Shaking)并减小打包体积。
更小的包体积:Vue 3 通过更好的 Tree Shaking 和更高效的运行时代码生成,相较于 Vue 2,打包体积更小。Vue 3 的响应式系统也经过优化,性能更好。
性能改进:Vue 3 采用了更快、更高效的渲染机制,得益于新的编译器。虚拟 DOM 的差异化算法经过优化,减少不必要的更新,提升渲染性能。
**作用域插槽替代为
**:在 Vue 3 中,作用域插槽的概念被更直观、更简化的
语法所取代,使得在组件组合中定义和使用插槽更加容易。
引入 Teleport 组件:Vue 3 引入了 Teleport 组件,可以在 DOM 树中的不同位置渲染内容,用于创建模态框、工具提示和其他覆盖层效果。
片段(Fragments):Vue 3 引入了一个名为片段(Fragment)的内置组件,允许将多个元素进行分组,而无需添加额外的包装元素。
更好的 TypeScript 支持:Vue 3 默认提供了更好的 TypeScript 支持,具有增强的类型推断和与 TypeScript 工具更好的集成。
简化的 API:Vue 3 对许多 API 进行了简化和优化,使得学习和使用框架更加容易。新的 API 提供了更好的一致性,并与 JavaScript 标准更加对齐。
虽然 Vue 3 引入了这些变化,但它保持与 Vue 2 API 的向后兼容性,允许现有的 Vue 2 项目逐步升级。Vue 3 提供了一个迁移构建版本,与大多数 Vue 2 代码兼容,从而使开发者的过渡更加平滑。
总体而言,Vue 3 在性能、包体积和开发者体验方面带来了显著的改进,同时引入了 Composition API 作为管理组件状态和逻辑的更强大工具。
key
的工作原理如下:
key
值,则 Vue 认为它们是相同的节点,会尝试复用已存在的真实 DOM 节点。key
值,Vue 会将其视为不同的节点,并进行适当的更新、移动或删除操作。使用 key
可以提供更准确的节点识别和跟踪,避免出现一些常见的问题,比如在列表中重新排序时导致的元素闪烁、输入框内容丢失等。
key
必须是唯一且稳定的,最好使用具有唯一标识的值,例如使用数据的唯一 ID。同时,不推荐使用随机数作为 key
,因为在每次更新时都会生成新的 key
,导致所有节点都重新渲染,无法复用已有的节点,降低性能。
修饰符是什么
在程序世界里,修饰符是用于限定类型以及类型成员的声明的一种符号
在Vue
中,修饰符处理了许多DOM
事件的细节,让我们不再需要花大量的时间去处理这些烦恼的事情,而能有更多的精力专注于程序的逻辑处理
vue
中修饰符分为以下五种:
修饰符的作用
表单修饰符
在我们填写表单的时候用得最多的是input
标签,指令用得最多的是v-model
关于表单的修饰符有如下:
lazy
在我们填完信息,光标离开标签的时候,才会将值赋予给value
,也就是在change
事件之后再进行信息同步
{{value}}
trim
自动过滤用户输入的首空格字符,而中间的空格不会过滤
number
自动将用户的输入值转为数值类型,但如果这个值无法被parseFloat
解析,则会返回原来的值
事件修饰符
事件修饰符是对事件捕获以及目标进行了处理,有如下修饰符:
stop
阻止了事件冒泡,相当于调用了event.stopPropagation
方法
prevent
阻止了事件的默认行为,相当于调用了event.preventDefault
方法
self
只当在 event.target
是当前元素自身时触发处理函数
使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用
v-on:click.prevent.self
会阻止所有的点击,而v-on:click.self.prevent
只会阻止对元素自身的点击
once
绑定了事件以后只能触发一次,第二次就不会触发
capture
使事件触发从包含这个元素的顶层开始往下触发
obj1
obj2
obj3
obj4
// 输出结构: 1 2 4 3
passive
在移动端,当我们在监听元素滚动事件的时候,会一直触发onscroll
事件会让我们的网页变卡,因此我们使用这个修饰符的时候,相当于给onscroll
事件整了一个.lazy
修饰符
...
不要把
.passive
和.prevent
一起使用,因为.prevent
将会被忽略,同时浏览器可能会向你展示一个警告。
passive
会告诉浏览器你不想阻止事件的默认行为
native
让组件变成像html
内置标签那样监听根元素的原生事件,否则组件上使用 v-on
只会监听自定义事件
使用.native修饰符来操作普通HTML标签是会令事件失效的
鼠标按钮修饰符
鼠标按钮修饰符针对的就是左键、右键、中键点击,有如下:
键盘修饰符
键盘修饰符是用来修饰键盘事件(onkeyup
,onkeydown
)的,有如下:
keyCode
存在很多,但vue
为我们提供了别名,分为以下两种:
// 只有按键为keyCode的时候才触发
还可以通过以下方式自定义一些全局的键盘码别名
Vue.config.keyCodes.f2 = 113
v-bind修饰符
v-bind修饰符主要是为属性进行操作,用来分别有如下:
sync
能对props
进行一个双向绑定
//父组件
//子组件
this.$emit('update:myMessage',params);
以上这种方法相当于以下的简写
//父亲组件
func(e){
this.bar = e;
}
//子组件js
func2(){
this.$emit('update:myMessage',params);
}
使用sync
需要注意以下两点:
使用sync
的时候,子组件传递的事件名格式必须为update:value
,其中value
必须与子组件中props
中声明的名称完全一致
注意带有 .sync
修饰符的 v-bind
不能和表达式一起使用
将 v-bind.sync
用在一个字面量的对象上,例如 v-bind.sync=”{ title: doc.title }”
,是无法正常工作的
props
设置自定义标签属性,避免暴露数据,防止污染HTML结构
camel
将命名变为驼峰命名法,如将 view-Box
属性名转换为 viewBox
应用场景
根据每一个修饰符的功能,我们可以得到以下修饰符的应用场景:
$route:$route 是一个当前路由信息的对象,包括当前 URL 路径、查询参数、路径参数等信息。$route 对象是只读的,不可以直接修改其属性值,而需要通过路由跳转来更新。
$router:$router 是 Vue Router 的实例对象,包括了许多用于导航控制和路由操作的 API,例如 push、replace、go、forward 等方法。$router 可以用来动态地改变 URL,从而实现页面间的无刷新跳转。
因此,$route 和 $router 在功能上有所不同,$route 主要用于获取当前路由信息,$router 则是用于进行路由操作,例如跳转到指定的路由、前进、后退等。通常来说,$route 和 $router 是紧密关联的,并且常常一起使用。
是什么
Tree shaking
是一种通过清除多余代码方式来优化项目打包体积的技术,专业术语叫 Dead code elimination
简单来讲,就是在保持代码运行结果不变的前提下,去除无用的代码
如果把代码打包比作制作蛋糕,传统的方式是把鸡蛋(带壳)全部丢进去搅拌,然后放入烤箱,最后把(没有用的)蛋壳全部挑选并剔除出去
而treeshaking
则是一开始就把有用的蛋白蛋黄(import)放入搅拌,最后直接作出蛋糕
也就是说 ,tree shaking
其实是找出使用的代码
在Vue2
中,无论我们使用什么功能,它们最终都会出现在生产代码中。主要原因是Vue
实例在项目中是单例的,捆绑程序无法检测到该对象的哪些属性在代码中被使用到
import Vue from 'vue' Vue.nextTick(() => {})
而Vue3
源码引入tree shaking
特性,将全局 API 进行分块。如果您不使用其某些功能,它们将不会包含在您的基础包中
import { nextTick, observable } from 'vue'
nextTick(() => {})
如何做
Tree shaking
是基于ES6
模板语法(import
与export
),主要是借助ES6
模块的静态编译思想,在编译时就能确定模块的依赖关系,以及输入和输出的变量
Tree shaking
无非就是做了两件事:
ES6 Module
判断哪些模块已经加载作用
通过Tree shaking
,Vue3
给我们带来的好处是:
组件化方式不同:React 是基于组件实现的,组件包含了状态和行为,所有组件共享一个状态树。Vue 也是基于组件实现的,但是每个组件都有自己的状态,并且可以很容易地将数据和行为绑定在一起。
数据驱动方式不同:React 使用单向数据流来管理数据,即从父组件到子组件的传递,所以 React 中组件之间的数据交互相对更加复杂。Vue 则使用双向数据绑定来管理数据,使得组件之间的数据交互更加简洁。
模板语法不同:React 使用 JSX 语法,将 HTML 和 JavaScript 结合在一起,使得编写组件更加直观和灵活。Vue 则使用模板语法,并且支持模板内的表达式和指令,使得编写组件具有更高的可读性和可维护性。
生命周期不同:React 组件的生命周期分为三个阶段:初始化、更新和卸载。Vue 组件的生命周期分为八个阶段:创建、挂载、更新、销毁等。
状态管理方式不同:React 使用 Redux 或者 MobX 来管理应用程序的状态。Vue 则提供了自己的状态管理库 Vuex,可以更方便地管理组件之间的共享状态。
性能优化方式不同:React 使用虚拟 DOM 技术来实现高效的渲染性能,可以减少每次渲染时需要操作真实 DOM 的次数。Vue 则使用模板编译和响应式系统来实现高效的渲染性能,并且还提供了一些优化技术,例如懒加载和缓存等。
iconfont
等字体文件来代替。diff
算法是一种通过同层的树节点进行比较的高效算法
其有两个特点:
diff
算法的在很多场景下都有应用,在 vue
中,作用于虚拟 dom
渲染成真实 dom
的新旧 VNode
节点比较
diff
整体策略为:深度优先,同层比较
原理:
watcher
就会调用patch
给真实的DOM
打补丁isSameVnode
进行判断,相同则调用patchVnode
方法patchVnode
做了以下操作:
dom
,称为el
el
文本节点设置为Vnode
的文本节点oldVnode
有子节点而VNode
没有,则删除el
子节点oldVnode
没有子节点而VNode
有,则将VNode
的子节点真实化后添加到el
updateChildren
函数比较子节点updateChildren
主要做了以下操作:
VNode
的头尾指针patchVnode
进行patch
重复流程、调用createElem
创建一个新节点,从哈希表寻找 key
一致的VNode
节点再分情况操作方法一 config.globalProperties
vue2.x
挂载全局是使用 Vue.prototype.$xxxx=xxx
的形式来挂载,然后通过 this.$xxx
来获取挂载到全局的变量或者方法。
这在 Vue 3
中,就等同于 config.globalProperties
。这些 property
将被复制到应用中作为实例化组件的一部分。
// 之前 (Vue 2.x)
Vue.prototype.$http = () => {}
// 之后 (Vue 3.x)
const app = createApp({})
app.config.globalProperties.$http = () => {}
方法二 Provide / Inject
vue3新的 provide/inject
功能可以穿透多层组件,实现数据从父组件传递到子组件。
可以将全局变量放在根组件的 provide
中,这样所有的组件都能使用到这个变量。
如果需要变量是响应式的,就需要在 provide
的时候使用 ref
或者 reactive
包装变量。
在平常开发中,有部分组件没有必要多次初始化,这时,我们需要将组件进行持久化,使组件的状态维持不变,在下一次展示时,也不会进行重新初始化组件。
也就是说,keepalive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染,也就是所谓的组件缓存。
是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。
包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 相似, 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。
include和exclude指定是否缓存某些组件
include 包含的意思。值为字符串或正则表达式或数组。只有组件的名称与include的值相同的才会被缓存,即指定哪些被缓存,可以指定多个被缓存。这里以字符串为例,指定多个组件缓存,语法是用逗号隔开。如下:
// 指定home组件和about组件被缓存
exclude相当于include的反义词,就是除了的意思,指定哪些组件不被缓存,用法和include类似,如下:
// 除了home组件和about组件别的都缓存,本例中就是只缓存detail组件
使用keep-alive的钩子函数执行顺序问题
首先使用了keep-alive的组件以后,组件上就会自动加上了activated
钩子和deactivated
钩子。
activated
当组件被激活(使用)的时候触发 可以简单理解为进入这个页面的时候触发deactivated
当组件不被使用(inactive状态)的时候触发 可以简单理解为离开这个页面的时候触发假设我们只缓存home组件,我们先看一下代码,再在钩子中打印出对应的顺序。就知道钩子执行的顺序了,自己动手印象深刻
备选项
进入组件打印结果如下:
我是created钩子
我是mounted钩子
我是activated钩子
离开组件打印结果如下:
我是deactivated钩子
得出结论:
初始进入和离开 created ---> mounted ---> activated --> deactivated
后续进入和离开 activated --> deactivated
keep-alive的应用场景举例
服务端渲染(SSR)原理和客户端(CSR)渲染区别
一、服务端渲染(SSR)是什么
服务端渲染简单来说就是:
用户使用的浏览器浏览的都是一些没有复杂逻辑的、简单的页面,这些页面都是在后端将 html 拼接好的,然后返回给前端完整的 html 文件,浏览器拿到这个 html 文件之后就可以直接解析展示了,这也就是所谓的服务器端渲染。
将组件或页面通过服务器生成HTML字符串,在发送到游览器,最后将静态标记 “混合”为客户端上完全交互的应用程序。
二、客户端渲染(CSR)是什么
客户端渲染简单来说就是:
随着前端页面的复杂性提高,前端就不仅仅是普通的页面展示了,可能是添加更多功能的组件,复杂性更大,另外,此时 ajax 的兴起,使得页面就开始崇拜前后端分离的开发模式,即后端不提供完整的 html 页面,而是提供一些 api 使得前端可以获取 json 数据,然后前端拿到 json 数据之后再在前端进行 html 页面的拼接,然后展示在浏览器上,这就是所谓的客户端渲染
三、SSR的优势
服务器端渲染的优势就是容易 SEO,首屏加载快,因为客户端接收到的是完整的 HTML 页面。
利于SEO:不同爬虫工作原理类似,只会爬取源码,不会执行网站的任何脚本,使用了React或者其它MVVM框架之后,页面大多数DOM元素都是在客户端根据js动态生成,可供爬虫抓取分析的内容大大减少(如图一)。另外,浏览器爬虫不会等待我们的数据完成之后再去抓取我们的页面数据。服务端渲染返回给客户端的是已经获取了异步数据并执行JavaScript脚本的最终HTML,网络爬中就可以抓取到完整页面的信息。
首屏加载快:首页是通过node加载的HTML字符串,用户直接可以看到完整HTML,所以更快。
三、CSR的优势
节省后端资源,局部刷新页面,多端渲染,前后端分离。
1、节省后端资源。
即服务器端完成html模板的解析,如果请求较多,会对服务器造成一定的访问压力。而如果使用前端渲染,就是把这些解析的压力分摊了前端。
2、便于前后端分离。
前端专注于前端UI,后端专注于api开发,且前端有更多的选择性,而不需要遵循后端特定的模板.
3、用户体验更好。
比如,我们将网站做成SPA或者部分内容做成SPA,这样,尤其是移动端,可以使体验更接近于原生app。
对比图
四、渲染过程
服务端渲染过程
客户端渲染过程
五、SSR的局限性
渲染过程由后端完成,会造成服务器压力较大、开发环境受限,服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,不利于前后端分离,开发效率低。
1、服务端压力较大
本来是通过客户端完成渲染,现在统一到服务端node服务去做。尤其是高并发访问的情况,会大量占用服务端CPU资源;
渲染过程在后端完成,那么肯定会耗费后端资源。费流量,即使局部页面的变化也需要重新发送整个页面。不利于前后端分离,开发效率低。
2、开发环境受限
在服务端渲染中,只会执行到componentDidMount之前的生命周期钩子,因此项目引用的第三方的库也不可用其它生命周期钩子,这对引用库的选择产生了很大的限制;
六、CSR的局限性
首屏加载时间会比较慢、性能差,白屏,对于SEO无能为力、
1、前端响应较慢。
客户端渲染、由于页面显示过程要进行JS文件拉取和React代码执行,前端还需要进行拼接字符串的过程,需要耗费额外的时间,不如服务器渲染的速度快。
2、不利于SEO
对于SEO(Search Engine Optimazition,即搜索引擎优化),完全无能为力,因为搜索引擎爬虫只认识html结构的内容,而不能识别JS代码内容。
七,区别及适用场景
CSR和SSR最大的区别在于:
前者的页面渲染是JS负责进行的,而后者是服务器端直接返回HTML让浏览器直接渲染。
CSR和SSR重要的区别在于:
究竟是谁来完成html文件的完整拼接
适用场景
不谈业务场景而盲目选择使用何种渲染方式都是耍流氓。
1、比如企业级网站,
主要功能是展示而没有复杂的交互,并且需要良好的SEO,则这时我们就需要使用服务器端渲染;
2、类似后台管理页面,
交互性比较强,不需要seo的考虑,那么就可以使用客户端渲染。
3、具体使用何种渲染方法并不是绝对的
比如现在一些网站采用了首屏服务器端渲染,即对于用户最开始打开的那个页面采用的是服务器端渲染,这样就保证了渲染速度,而其他的页面采用客户端渲染,这样就完成了前后端分离。