共计 19520 个字符,预计需要花费 49 分钟才能阅读完成。
过滤器的作用,如何实现一个过滤器
依据过滤器的名称,过滤器是用来过滤数据的,在 Vue 中应用 filters
来过滤数据,filters
不会批改数据,而是过滤数据,扭转用户看到的输入(计算属性 computed
,办法 methods
都是通过批改数据来解决数据格式的输入显示)。
应用场景:
- 须要格式化数据的状况,比方须要解决工夫、价格等数据格式的输入 / 显示。
- 比方后端返回一个 年月日的日期字符串 ,前端须要展现为 多少天前 的数据格式,此时就能够用
fliters
过滤器来解决数据。
过滤器是一个函数,它会把表达式中的值始终当作函数的第一个参数。过滤器用在 插值表达式 {{}}
和 v-bind
表达式 中,而后放在操作符“|
”前面进行批示。
例如,在显示金额,给商品价格增加单位:
<li> 商品价格:{{item.price | filterPrice}}</li>
filters: {filterPrice (price) {return price ? ('¥' + price) : '--'
}
}
路由的 hash 和 history 模式的区别
Vue-Router 有两种模式:hash 模式 和history 模式。默认的路由模式是 hash 模式。
1. hash 模式
简介: hash 模式是开发中默认的模式,它的 URL 带着一个 #
特点:hash 值会呈现在 URL 外面,然而不会呈现在 HTTP 申请中,对后端齐全没有影响。所以扭转 hash 值,不会从新加载页面。这种模式的浏览器反对度很好,低版本的 IE 浏览器也反对这种模式。hash 路由被称为是前端路由,曾经成为 SPA(单页面利用)的标配。
原理: hash 模式的次要原理就是 onhashchange() 事件:
window.onhashchange = function(event){console.log(event.oldURL, event.newURL);
let hash = location.hash.slice(1);
}
应用 onhashchange()事件的益处就是,在页面的 hash 值发生变化时,无需向后端发动申请,window 就能够监听事件的扭转,并按规定加载相应的代码。除此之外,hash 值变动对应的 URL 都会被浏览器记录下来,这样浏览器就能实现页面的后退和后退。尽管是没有申请后端服务器,然而页面的 hash 值和对应的 URL 关联起来了。
2. history 模式
简介: history 模式的 URL 中没有 #,它应用的是传统的路由散发模式,即用户在输出一个 URL 时,服务器会接管这个申请,并解析这个 URL,而后做出相应的逻辑解决。特点: 相比 hash 模式更加难看。然而,history 模式须要后盾配置反对。如果后盾没有正确配置,拜访时会返回 404。API: history api 能够分为两大部分,切换历史状态和批改历史状态:
- 批改历史状态:包含了 HTML5 History Interface 中新增的
pushState()
和replaceState()
办法,这两个办法利用于浏览器的历史记录栈,提供了对历史记录进行批改的性能。只是当他们进行批改时,尽管批改了 url,但浏览器不会立刻向后端发送申请。如果要做到扭转 url 但又不刷新页面的成果,就须要前端用上这两个 API。 - 切换历史状态: 包含
forward()
、back()
、go()
三个办法,对应浏览器的后退,后退,跳转操作。
尽管 history 模式抛弃了俊俏的 #。然而,它也有本人的毛病,就是在刷新页面的时候,如果没有相应的路由或资源,就会刷出 404 来。
如果想要切换到 history 模式,就要进行以下配置(后端也要进行配置):
const router = new VueRouter({
mode: 'history',
routes: [...]
})
3. 两种模式比照
调用 history.pushState() 相比于间接批改 hash,存在以下劣势:
- pushState() 设置的新 URL 能够是与以后 URL 同源的任意 URL;而 hash 只可批改 # 前面的局部,因而只能设置与以后 URL 同文档的 URL;
- pushState() 设置的新 URL 能够与以后 URL 截然不同,这样也会把记录增加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录增加到栈中;
- pushState() 通过 stateObject 参数能够增加任意类型的数据到记录中;而 hash 只可增加短字符串;
- pushState() 可额定设置 title 属性供后续应用。
- hash 模式下,仅 hash 符号之前的 url 会被蕴含在申请中,后端如果没有做到对路由的全笼罩,也不会返回 404 谬误;history 模式下,前端的 url 必须和理论向后端发动申请的 url 统一,如果没有对用的路由解决,将返回 404 谬误。
hash 模式和 history 模式都有各自的劣势和缺点,还是要依据理论状况选择性的应用。
个别在哪个生命周期申请异步数据
咱们能够在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 曾经创立,能够将服务端端返回的数据进行赋值。
举荐在 created 钩子函数中调用异步申请,因为在 created 钩子函数中调用异步申请有以下长处:
- 能更快获取到服务端数据,缩小页面加载工夫,用户体验更好;
- SSR 不反对 beforeMount、mounted 钩子函数,放在 created 中有助于一致性。
vue 中应用了哪些设计模式
1. 工厂模式 – 传入参数即可创立实例
虚构 DOM 依据参数的不同返回根底标签的 Vnode 和组件 Vnode
2. 单例模式 – 整个程序有且仅有一个实例
vuex 和 vue-router 的插件注册办法 install 判断如果零碎存在实例就间接返回掉
3. 公布 - 订阅模式 (vue 事件机制)
4. 观察者模式 (响应式数据原理)
5. 装璜模式: (@装璜器的用法)
6. 策略模式 策略模式指对象有某个行为, 然而在不同的场景中, 该行为有不同的实现计划 - 比方选项的合并策略
… 其余模式欢送补充
常见的事件修饰符及其作用
.stop
:等同于 JavaScript 中的event.stopPropagation()
,避免事件冒泡;.prevent
:等同于 JavaScript 中的event.preventDefault()
,避免执行预设的行为(如果事件可勾销,则勾销该事件,而不进行事件的进一步流传);.capture
:与事件冒泡的方向相同,事件捕捉由外到内;.self
:只会触发本人范畴内的事件,不蕴含子元素;.once
:只会触发一次。
Computed 和 Methods 的区别
能够将同一函数定义为一个 method 或者一个计算属性。对于最终的后果,两种形式是雷同的
不同点:
- computed: 计算属性是基于它们的依赖进行缓存的,只有在它的相干依赖产生扭转时才会从新求值;
- method 调用总会执行该函数。
Vue 模板编译原理
Vue 的编译过程就是将 template 转化为 render 函数的过程 分为以下三步
第一步是将 模板字符串 转换成 element ASTs(解析器)第二步是对 AST 进行动态节点标记,次要用来做虚构 DOM 的渲染优化(优化器)第三步是 应用 element ASTs 生成 render 函数代码字符串(代码生成器)
形容下 Vue 自定义指令
在 Vue2.0 中,代码复用和形象的次要模式是组件。然而,有的状况下,你依然须要对一般 DOM 元素进行底层操作,这时候就会用到自定义指令。
个别须要对 DOM 元素进行底层操作时应用,尽量只用来操作 DOM 展现,不批改外部的值。当应用自定义指令间接批改 value 值时绑定 v -model 的值也不会同步更新;如必须批改能够在自定义指令中应用 keydown 事件,在 vue 组件中应用 change 事件,回调中批改 vue 数据;
(1)自定义指令根本内容
- 全局定义:
Vue.directive("focus",{})
- 部分定义:
directives:{focus:{}}
-
钩子函数:指令定义对象提供钩子函数
o bind:只调用一次,指令第一次绑定到元素时调用。在这里能够进行一次性的初始化设置。
o inSerted:被绑定元素插入父节点时调用(仅保障父节点存在,但不肯定已被插入文档中)。
o update:所在组件的 VNode 更新时调用,然而可能产生在其子 VNode 更新之前调用。指令的值可能产生了扭转,也可能没有。然而能够通过比拟更新前后的值来疏忽不必要的模板更新。
o ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全副更新后调用。
o unbind:只调用一次,指令与元素解绑时调用。
-
钩子函数参数
o el:绑定元素o bing:指令外围对象,形容指令全副信息属性
o name
o value
o oldValue
o expression
o arg
o modifers
o vnode 虚构节点
o oldVnode:上一个虚构节点(更新钩子函数中才有用)
(2)应用场景
- 一般 DOM 元素进行底层操作的时候,能够应用自定义指令
- 自定义指令是用来操作 DOM 的。只管 Vue 推崇数据驱动视图的理念,但并非所有状况都适宜数据驱动。自定义指令就是一种无效的补充和扩大,不仅可用于定义任何的 DOM 操作,并且是可复用的。
(3)应用案例
高级利用:
- 鼠标聚焦
- 下拉菜单
- 绝对工夫转换
- 滚动动画
高级利用:
- 自定义指令实现图片懒加载
- 自定义指令集成第三方插件
如何从实在 DOM 到虚构 DOM
波及到 Vue 中的模板编译原理,次要过程:
- 将模板转换成
ast
树,ast
用对象来形容实在的 JS 语法(将实在 DOM 转换成虚构 DOM) - 优化树
- 将
ast
树生成代码
参考:前端 vue 面试题具体解答
Computed 和 Watch 的区别
对于 Computed:
- 它反对缓存,只有依赖的数据产生了变动,才会从新计算
- 不反对异步,当 Computed 中有异步操作时,无奈监听数据的变动
- computed 的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 申明过,或者父组件传递过去的 props 中的数据进行计算的。
- 如果一个属性是由其余属性计算而来的,这个属性依赖其余的属性,个别会应用 computed
- 如果 computed 属性的属性值是函数,那么默认应用 get 办法,函数的返回值就是属性的属性值;在 computed 中,属性有一个 get 办法和一个 set 办法,当数据发生变化时,会调用 set 办法。
对于 Watch:
- 它不反对缓存,数据变动时,它就会触发相应的操作
- 反对异步监听
- 监听的函数接管两个参数,第一个参数是最新的值,第二个是变动之前的值
- 当一个属性发生变化时,就须要执行相应的操作
-
监听数据必须是 data 中申明的或者父组件传递过去的 props 中的数据,当发生变化时,会触发其余操作,函数有两个的参数:
- immediate:组件加载立刻触发回调函数
- deep:深度监听,发现数据外部的变动,在简单数据类型中应用,例如数组中的对象发生变化。须要留神的是,deep 无奈监听到数组和对象外部的变动。
当想要执行异步或者低廉的操作以响应一直的变动时,就须要应用 watch。
总结:
- computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值产生扭转,下一次获取 computed 的值时才会从新计算 computed 的值。
- watch 侦听器 : 更多的是 察看 的作用,无缓存性,相似于某些数据的监听回调,每当监听的数据变动时都会执行回调进行后续操作。
使用场景:
- 当须要进行数值计算, 并且依赖于其它数据时,应该应用 computed,因为能够利用 computed 的缓存个性,防止每次获取值时都要从新计算。
- 当须要在数据变动时执行异步或开销较大的操作时,应该应用 watch,应用 watch 选项容许执行异步操作 (拜访一个 API),限度执行该操作的频率,并在失去最终后果前,设置中间状态。这些都是计算属性无奈做到的。
子组件能够间接扭转父组件的数据吗?
子组件不能够间接扭转父组件的数据。这样做次要是为了保护父子组件的单向数据流。每次父级组件产生更新时,子组件中所有的 prop 都将会刷新为最新的值。如果这样做了,Vue 会在浏览器的控制台中收回正告。
Vue 提倡单向数据流,即父级 props 的更新会流向子组件,然而反过来则不行。这是为了避免意外的扭转父组件状态,使得利用的数据流变得难以了解,导致数据流凌乱。如果毁坏了单向数据流,当利用简单时,debug 的老本会十分高。
只能通过 $emit
派发一个自定义事件,父组件接管到后,由父组件批改。
对 keep-alive 的了解,它是如何实现的,具体缓存的是什么?
如果须要在组件切换的时候,保留一些组件的状态避免屡次渲染,就能够应用 keep-alive 组件包裹须要保留的组件。
(1)keep-alive
keep-alive 有以下三个属性:
- include 字符串或正则表达式,只有名称匹配的组件会被匹配;
- exclude 字符串或正则表达式,任何名称匹配的组件都不会被缓存;
- max 数字,最多能够缓存多少组件实例。
留神:keep-alive 包裹动静组件时,会缓存不流动的组件实例。
次要流程
- 判断组件 name,不在 include 或者在 exclude 中,间接返回 vnode,阐明该组件不被缓存。
- 获取组件实例 key,如果有获取实例的 key,否则从新生成。
- key 生成规定,cid +”∶∶”+ tag,仅靠 cid 是不够的,因为雷同的构造函数能够注册为不同的本地组件。
- 如果缓存对象内存在,则间接从缓存对象中获取组件实例给 vnode,不存在则增加到缓存对象中。5. 最大缓存数量,当缓存组件数量超过 max 值时,革除 keys 数组内第一个组件。
(2)keep-alive 的实现
const patternTypes: Array<Function> = [String, RegExp, Array] // 接管:字符串,正则,数组
export default {
name: 'keep-alive',
abstract: true, // 形象组件,是一个形象组件:它本身不会渲染一个 DOM 元素,也不会呈现在父组件链中。props: {
include: patternTypes, // 匹配的组件,缓存
exclude: patternTypes, // 不去匹配的组件,不缓存
max: [String, Number], // 缓存组件的最大实例数量, 因为缓存的是组件实例(vnode),数量过多的时候,会占用过多的内存,能够用 max 指定下限
},
created() {
// 用于初始化缓存虚构 DOM 数组和 vnode 的 key
this.cache = Object.create(null)
this.keys = []},
destroyed() {
// 销毁缓存 cache 的组件实例
for (const key in this.cache) {pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {// prune 削减精简[v.]
// 去监控 include 和 exclude 的扭转,依据最新的 include 和 exclude 的内容,来实时削减缓存的组件的内容
this.$watch('include', (val) => {pruneCache(this, (name) => matches(val, name))
})
this.$watch('exclude', (val) => {pruneCache(this, (name) => !matches(val, name))
})
},
}
render 函数:
- 会在 keep-alive 组件外部去写本人的内容,所以能够去获取默认 slot 的内容,而后依据这个去获取组件
- keep-alive 只对第一个组件无效,所以获取第一个子组件。
- 和 keep-alive 搭配应用的个别有:动静组件 和 router-view
render () {
//
function getFirstComponentChild (children: ?Array<VNode>): ?VNode {if (Array.isArray(children)) {for (let i = 0; i < children.length; i++) {const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {return c}
}
}
}
const slot = this.$slots.default // 获取默认插槽
const vnode: VNode = getFirstComponentChild(slot)// 获取第一个子组件
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions // 组件参数
if (componentOptions) { // 是否有组件参数
// check pattern
const name: ?string = getComponentName(componentOptions) // 获取组件名
const {include, exclude} = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
// 如果不匹配以后组件的名字和 include 以及 exclude
// 那么间接返回组件的实例
return vnode
}
const {cache, keys} = this
// 获取这个组件的 key
const key: ?string = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// LRU 缓存策略执行
vnode.componentInstance = cache[key].componentInstance // 组件首次渲染的时候 componentInstance 为 undefined
// make current key freshest
remove(keys, key)
keys.push(key)
// 依据 LRU 缓存策略执行,将 key 从原来的地位移除,而后将这个 key 值放到最初面
} else {
// 在缓存列表外面没有的话,则退出,同时判断以后退出之后,是否超过了 max 所设定的范畴,如果是,则去除
// 应用工夫距离最长的一个
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 将组件的 keepAlive 属性设置为 true
vnode.data.keepAlive = true // 作用:判断是否要执行组件的 created、mounted 生命周期函数
}
return vnode || (slot && slot[0])
}
keep-alive 具体是通过 cache 数组缓存所有组件的 vnode 实例。当 cache 内原有组件被应用时会将该组件 key 从 keys 数组中删除,而后 push 到 keys 数组最初,以便革除最不罕用组件。
实现步骤:
- 获取 keep-alive 下第一个子组件的实例对象,通过他去获取这个组件的组件名
- 通过以后组件名去匹配原来 include 和 exclude,判断以后组件是否须要缓存,不须要缓存,间接返回以后组件的实例 vNode
- 须要缓存,判断他以后是否在缓存数组外面:
- 存在,则将他原来地位上的 key 给移除,同时将这个组件的 key 放到数组最初面(LRU)
- 不存在,将组件 key 放入数组,而后判断以后 key 数组是否超过 max 所设置的范畴,超过,那么削减未应用工夫最长的一个组件的 key
- 最初将这个组件的 keepAlive 设置为 true
(3)keep-alive 自身的创立过程和 patch 过程
缓存渲染的时候,会依据 vnode.componentInstance(首次渲染 vnode.componentInstance 为 undefined)和 keepAlive 属性判断不会执行组件的 created、mounted 等钩子函数,而是对缓存的组件执行 patch 过程∶ 间接把缓存的 DOM 对象直接插入到指标元素中,实现了数据更新的状况下的渲染过程。
首次渲染
- 组件的首次渲染∶判断组件的 abstract 属性,才往父组件外面挂载 DOM
// core/instance/lifecycle
function initLifecycle (vm: Component) {
const options = vm.$options
// locate first non-abstract parent
let parent = options.parent
if (parent && !options.abstract) { // 判断组件的 abstract 属性,才往父组件外面挂载 DOM
while (parent.$options.abstract && parent.$parent) {parent = parent.$parent}
parent.$children.push(vm)
}
vm.$parent = parent
vm.$root = parent ? parent.$root : vm
vm.$children = []
vm.$refs = {}
vm._watcher = null
vm._inactive = null
vm._directInactive = false
vm._isMounted = false
vm._isDestroyed = false
vm._isBeingDestroyed = false
}
- 判断以后 keepAlive 和 componentInstance 是否存在来判断是否要执行组件 prepatch 还是执行创立 componentlnstance
// core/vdom/create-component
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) { // componentInstance 在首次是 undefined!!!
// kept-alive components, treat as a patch
const mountedNode: any = vnode // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode) // prepatch 函数执行的是组件更新的过程
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch 操作就不会在执行组件的 mounted 和 created 生命周期函数,而是间接将 DOM 插入
(4)LRU(least recently used)缓存策略
LRU 缓存策略∶ 从内存中找出最久未应用的数据并置换新的数据。
LRU(Least rencently used)算法依据数据的历史拜访记录来进行淘汰数据,其核心思想是 “ 如果数据最近被拜访过,那么未来被拜访的几率也更高 ”。最常见的实现是应用一个链表保留缓存数据,具体算法实现如下∶
- 新数据插入到链表头部
- 每当缓存命中(即缓存数据被拜访),则将数据移到链表头部
- 链表满的时候,将链表尾部的数据抛弃。
Vue data 中某一个属性的值产生扭转后,视图会立刻同步执行从新渲染吗?
不会立刻同步执行从新渲染。Vue 实现响应式并不是数据发生变化之后 DOM 立刻变动,而是按肯定的策略进行 DOM 的更新。Vue 在更新 DOM 时是异步执行的。只有侦听到数据变动,Vue 将开启一个队列,并缓冲在同一事件循环中产生的所有数据变更。
如果同一个 watcher 被屡次触发,只会被推入到队列中一次。这种在缓冲时去除反复数据对于防止不必要的计算和 DOM 操作是十分重要的。而后,在下一个的事件循环 tick 中,Vue 刷新队列并执行理论(已去重的)工作。
Vue 中 key 的作用
vue 中 key 值的作用能够分为两种状况来思考:
- 第一种状况是 v-if 中应用 key。因为 Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。因而当应用 v-if 来实现元素切换的时候,如果切换前后含有雷同类型的元素,那么这个元素就会被复用。如果是雷同的 input 元素,那么切换前后用户的输出不会被革除掉,这样是不合乎需要的。因而能够通过应用 key 来惟一的标识一个元素,这个状况下,应用 key 的元素不会被复用。这个时候 key 的作用是用来标识一个独立的元素。
- 第二种状况是 v-for 中应用 key。用 v-for 更新已渲染过的元素列表时,它默认应用“就地复用”的策略。如果数据项的程序产生了扭转,Vue 不会挪动 DOM 元素来匹配数据项的程序,而是简略复用此处的每个元素。因而通过为每个列表项提供一个 key 值,来以便 Vue 跟踪元素的身份,从而高效的实现复用。这个时候 key 的作用是为了高效的更新渲染虚构 DOM。
key 是为 Vue 中 vnode 的惟一标记,通过这个 key,diff 操作能够更精确、更疾速
- 更精确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 比照中能够防止就地复用的状况。所以会更加精确。
- 更疾速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历形式更快
Vuex 有哪几种属性?
有五种,别离是 State、Getter、Mutation、Action、Module
- state => 根本数据(数据源寄存地)
- getters => 从根本数据派生进去的数据
- mutations => 提交更改数据的办法,同步
- actions => 像一个装璜器,包裹 mutations,使之能够异步。
- modules => 模块化 Vuex
用过 pinia 吗?有什么长处?
1. pinia 是什么?
- 在
Vue3
中,能够应用传统的Vuex
来实现状态治理,也能够应用最新的pinia
来实现状态治理,咱们来看看官网如何解释pinia
的:Pinia
是Vue
的存储库,它容许您跨组件 / 页面共享状态。- 实际上,
pinia
就是Vuex
的升级版,官网也说过,为了尊重原作者,所以取名pinia
,而没有取名Vuex
,所以大家能够间接将pinia
比作为Vue3
的Vuex
2. 为什么要应用 pinia?
Vue2
和Vue3
都反对,这让咱们同时应用Vue2
和Vue3
的小伙伴都能很快上手。pinia
中只有state
、getter
、action
,摈弃了Vuex
中的Mutation
,Vuex
中mutation
始终都不太受小伙伴们的待见,pinia
间接摈弃它了,这无疑缩小了咱们工作量。pinia
中action
反对同步和异步,Vuex
不反对- 良好的
Typescript
反对,毕竟咱们Vue3
都举荐应用TS
来编写,这个时候应用pinia
就十分适合了 - 无需再创立各个模块嵌套了,
Vuex
中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而pinia
中每个store
都是独立的,相互不影响。 - 体积十分小,只有
1KB
左右。 pinia
反对插件来扩大本身性能。- 反对服务端渲染
3. pinna 应用
pinna 文档(opens new window)
- 筹备工作
咱们这里搭建一个最新的 Vue3 + TS + Vite
我的项目
npm create [email protected] my-vite-app --template vue-ts
pinia
根底应用
yarn add pinia
// main.ts
import {createApp} from "vue";
import App from "./App.vue";
import {createPinia} from "pinia";
const pinia = createPinia();
const app = createApp(App);
app.use(pinia);
app.mount("#app");
2.1 创立store
//sbinsrc/store/user.ts
import {defineStore} from 'pinia'
// 第一个参数是应用程序中 store 的惟一 id
export const useUsersStore = defineStore('users', {// 其它配置项})
创立 store
很简略,调用 p inia
中的 defineStore
函数即可,该函数接管两个参数:
name
:一个字符串,必传项,该store
的惟一id
。options
:一个对象,store
的配置项,比方配置store
内的数据,批改数据的办法等等。
咱们能够定义任意数量的 store
,因为咱们其实一个store
就是一个函数,这也是 pinia
的益处之一,让咱们的代码扁平化了,这和 Vue3
的实现思维是一样的
2.2 应用store
<!-- src/App.vue -->
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
console.log(store);
</script>
2.3 增加state
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
});
2.4 读取 state
数据
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p> 姓名:{{name}}</p>
<p> 年龄:{{age}}</p>
<p> 性别:{{sex}}</p>
</template>
<script setup lang="ts">
import {ref} from "vue";
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
const name = ref<string>(store.name);
const age = ref<number>(store.age);
const sex = ref<string>(store.sex);
</script>
上段代码中咱们间接通过 store.age
等形式获取到了 store
存储的值,然而大家有没有发现,这样比拟繁琐,咱们其实能够用解构的形式来获取值,使得代码更简洁一点
import {useUsersStore, storeToRefs} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store); // storeToRefs 获取的值是响应式的
2.5 批改 state
数据
<template>
<img alt="Vue logo" src="./assets/logo.png" />
<p> 姓名:{{name}}</p>
<p> 年龄:{{age}}</p>
<p> 性别:{{sex}}</p>
<button @click="changeName"> 更改姓名 </button>
</template>
<script setup lang="ts">
import child from './child.vue';
import {useUsersStore, storeToRefs} from "../src/store/user";
const store = useUsersStore();
const {name, age, sex} = storeToRefs(store);
const changeName = () => {
store.name = "张三";
console.log(store);
};
</script>
2.6 重置state
- 有时候咱们批改了
state
数据,想要将它还原,这个时候该怎么做呢?就比方用户填写了一部分表单,忽然想重置为最初始的状态。 - 此时,咱们间接调用
store
的$reset()
办法即可,持续应用咱们的例子,增加一个重置按钮
<button @click="reset"> 重置 store</button>
// 重置 store
const reset = () => {store.$reset();
};
当咱们点击重置按钮时,store
中的数据会变为初始状态,页面也会更新
2.7 批量更改 state
数据
如果咱们一次性须要批改很多条数据的话,有更加简便的办法,应用 store
的$patch
办法,批改 app.vue
代码,增加一个批量更改数据的办法
<button @click="patchStore"> 批量批改数据 </button>
// 批量批改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
- 有教训的小伙伴可能发现了,咱们采纳这种批量更改的形式仿佛代价有一点大,如果咱们
state
中有些字段无需更改,然而依照上段代码的写法,咱们必须要将 state 中的所有字段例举出了。 - 为了解决该问题,
pinia
提供的$patch
办法还能够接管一个回调函数,它的用法有点像咱们的数组循环回调函数了。
store.$patch((state) => {state.items.push({ name: 'shoes', quantity: 1})
state.hasChanged = true
})
2.8 间接替换整个state
pinia
提供了办法让咱们间接替换整个 state
对象,应用 store
的$state
办法
store.$state = {counter: 666, name: '张三'}
上段代码会将咱们提前申明的 state
替换为新的对象,可能这种场景用得比拟少
getters
属性getters
是defineStore
参数配置项外面的另一个属性- 能够把
getter
设想成Vue
中的计算属性,它的作用就是返回一个新的后果,既然它和Vue
中的计算属性相似,那么它必定也是会被缓存的,就和computed
一样
3.1 增加getter
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 10,
sex: "男",
};
},
getters: {getAddAge: (state) => {return state.age + 100;},
},
})
上段代码中咱们在配置项参数中增加了 getter
属性,该属性对象中定义了一个 getAddAge
办法,该办法会默认接管一个 state
参数,也就是 state
对象,而后该办法返回的是一个新的数据
3.2 应用getter
<template>
<p> 新年龄:{{store.getAddAge}}</p>
<button @click="patchStore"> 批量批改数据 </button>
</template>
<script setup lang="ts">
import {useUsersStore} from "../src/store/user";
const store = useUsersStore();
// 批量批改数据
const patchStore = () => {
store.$patch({
name: "张三",
age: 100,
sex: "女",
});
};
</script>
上段代码中咱们间接在标签上应用了 store.gettAddAge
办法,这样能够保障响应式,其实咱们 state
中的 name
等属性也能够以此种形式间接在标签上应用,也能够放弃响应式
3.3 getter
中调用其它getter
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return state.age + 100;},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
});
3.3 getter
传参
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return (num: number) => state.age + num;
},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
});
<p> 新年龄:{{store.getAddAge(1100) }}</p>
actions
属性- 后面咱们提到的
state
和getter
s 属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的data
数据和computed
计算属性一样。 - 那么,如果咱们有业务代码的话,最好就是卸载
actions
属性外面,该属性就和咱们组件代码中的methods
类似,用来搁置一些解决业务逻辑的办法。 actions
属性值同样是一个对象,该对象外面也是存储的各种各样的办法,包含同步办法和异步办法
4.1 增加actions
export const useUsersStore = defineStore("users", {state: () => {
return {
name: "test",
age: 20,
sex: "男",
};
},
getters: {getAddAge: (state) => {return (num: number) => state.age + num;
},
getNameAndAge(): string {return this.name + this.getAddAge; // 调用其它 getter},
},
actions: {
// 在理论场景中,该办法能够是任何逻辑,比方发送申请、存储 token 等等。大家把 actions 办法当作一个一般的办法即可,非凡之处在于该办法外部的 this 指向的是以后 store
saveName(name: string) {this.name = name;},
},
});
4.2 应用actions
应用 actions
中的办法也非常简单,比方咱们在 App.vue
中想要调用该办法
const saveName = () => {store.saveName("poetries");
};
总结
pinia
的知识点很少,如果你有 Vuex 根底,那么学起来更是大海捞针
pinia 无非就是以下 3 个大点:
state
getters
actions
Vue 是如何收集依赖的?
在初始化 Vue 的每个组件时,会对组件的 data 进行初始化,就会将由一般对象变成响应式对象,在这个过程中便会进行依赖收集的相干逻辑,如下所示∶
function defieneReactive (obj, key, val){const dep = new Dep();
...
Object.defineProperty(obj, key, {
...
get: function reactiveGetter () {if(Dep.target){dep.depend();
...
}
return val
}
...
})
}
以上只保留了要害代码,次要就是 const dep = new Dep()
实例化一个 Dep 的实例,而后在 get 函数中通过 dep.depend()
进行依赖收集。(1)Dep Dep 是整个依赖收集的外围,其要害代码如下:
class Dep {
static target;
subs;
constructor () {
...
this.subs = [];}
addSub (sub) {this.subs.push(sub)
}
removeSub (sub) {remove(this.sub, sub)
}
depend () {if(Dep.target){Dep.target.addDep(this)
}
}
notify () {const subs = this.subds.slice();
for(let i = 0;i < subs.length; i++){subs[i].update()}
}
}
Dep 是一个 class,其中有一个关 键的动态属性 static,它指向了一个全局惟一 Watcher,保障了同一时间全局只有一个 watcher 被计算,另一个属性 subs 则是一个 Watcher 的数组,所以 Dep 实际上就是对 Watcher 的治理,再看看 Watcher 的相干代码∶
(2)Watcher
class Watcher {
getter;
...
constructor (vm, expression){
...
this.getter = expression;
this.get();}
get () {pushTarget(this);
value = this.getter.call(vm, vm)
...
return value
}
addDep (dep){
...
dep.addSub(this)
}
...
}
function pushTarget (_target) {Dep.target = _target}
Watcher 是一个 class,它定义了一些办法,其中和依赖收集相干的次要有 get、addDep 等。
(3)过程
在实例化 Vue 时,依赖收集的相干过程如下∶
初 始 化 状 态 initState,这 中 间 便 会 通 过 defineReactive 将数据变成响应式对象,其中的 getter 局部便是用来依赖收集的。
初始化最终会走 mount 过程,其中会实例化 Watcher,进入 Watcher 中,便会执行 this.get() 办法,
updateComponent = () => {vm._update(vm._render())
}
new Watcher(vm, updateComponent)
get 办法中的 pushTarget 实际上就是把 Dep.target 赋值为以后的 watcher。
this.getter.call(vm,vm),这里的 getter 会执行 vm._render() 办法,在这个过程中便会触发数据对象的 getter。那么每个对象值的 getter 都持有一个 dep,在触发 getter 的时候会调用 dep.depend() 办法,也就会执行 Dep.target.addDep(this)。方才 Dep.target 曾经被赋值为 watcher,于是便会执行 addDep 办法,而后走到 dep.addSub() 办法,便将以后的 watcher 订阅到这个数据持有的 dep 的 subs 中,这个目标是为后续数据变动时候能告诉到哪些 subs 做筹备。所以在 vm._render() 过程中,会触发所有数据的 getter,这样便曾经实现了一个依赖收集的过程。
Vue 为什么没有相似于 React 中 shouldComponentUpdate 的生命周期?
考点: Vue 的变动侦测原理
前置常识: 依赖收集、虚构 DOM、响应式零碎
根本原因是 Vue 与 React 的变动侦测形式有所不同
React 是 pull 的形式侦测变动, 当 React 晓得发生变化后, 会应用 Virtual Dom Diff 进行差别检测, 然而很多组件实际上是必定不会发生变化的, 这个时候须要用 shouldComponentUpdate 进行手动操作来缩小 diff, 从而进步程序整体的性能.
Vue 是 pull+push 的形式侦测变动的, 在一开始就晓得那个组件产生了变动, 因而在 push 的阶段并不需要手动管制 diff, 而组件外部采纳的 diff 形式实际上是能够引入相似于 shouldComponentUpdate 相干生命周期的, 然而通常正当大小的组件不会有适量的 diff, 手动优化的价值无限, 因而目前 Vue 并没有思考引入 shouldComponentUpdate 这种手动优化的生命周期.
Vue 中封装的数组办法有哪些,其如何实现页面更新
在 Vue 中,对响应式解决利用的是 Object.defineProperty 对数据进行拦挡,而这个办法并不能监听到数组外部变动,数组长度变动,数组的截取变动等,所以须要对这些操作进行 hack,让 Vue 能监听到其中的变动。那 Vue 是如何实现让这些数组办法实现元素的实时更新的呢,上面是 Vue 中对这些办法的封装:
// 缓存数组原型
const arrayProto = Array.prototype;
// 实现 arrayMethods.__proto__ === Array.prototype
export const arrayMethods = Object.create(arrayProto);
// 须要进行性能拓展的办法
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
/** * Intercept mutating methods and emit events */
methodsToPatch.forEach(function(method) {
// 缓存原生数组办法
const original = arrayProto[method];
def(arrayMethods, method, function mutator(...args) {
// 执行并缓存原生数组性能
const result = original.apply(this, args);
// 响应式解决
const ob = this.__ob__;
let inserted;
switch (method) {
// push、unshift 会新增索引,所以要手动 observer
case "push":
case "unshift":
inserted = args;
break;
// splice 办法,如果传入了第三个参数,也会有索引退出,也要手动 observer。case "splice":
inserted = args.slice(2);
break;
}
//
if (inserted) ob.observeArray(inserted);// 获取插入的值,并设置响应式监听
// notify change
ob.dep.notify();// 告诉依赖更新
// 返回原生数组办法的执行后果
return result;
});
});
简略来说就是,重写了数组中的那些原生办法,首先获取到这个数组的__ob__,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 持续对新的值察看变动(也就是通过 target__proto__ == arrayMethods
来扭转了数组实例的型),而后手动调用 notify,告诉渲染 watcher,执行 update。
v-if 和 v -show 的区别
- 伎俩:v-if 是动静的向 DOM 树内增加或者删除 DOM 元素;v-show 是通过设置 DOM 元素的 display 款式属性管制显隐;
- 编译过程:v-if 切换有一个部分编译 / 卸载的过程,切换过程中适合地销毁和重建外部的事件监听和子组件;v-show 只是简略的基于 css 切换;
- 编译条件:v-if 是惰性的,如果初始条件为假,则什么也不做;只有在条件第一次变为真时才开始部分编译; v-show 是在任何条件下,无论首次条件是否为真,都被编译,而后被缓存,而且 DOM 元素保留;
- 性能耗费:v-if 有更高的切换耗费;v-show 有更高的初始渲染耗费;
- 应用场景:v-if 适宜经营条件不大可能扭转;v-show 适宜频繁切换。