过滤器的作用,如何实现一个过滤器
依据过滤器的名称,过滤器是用来过滤数据的,在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/lifecyclefunction 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-componentinit (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.tsimport { 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.tsimport { defineStore } from 'pinia'// 第一个参数是应用程序中 store 的惟一 idexport const useUsersStore = defineStore('users', { // 其它配置项})
创立store
很简略,调用pinia
中的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>// 重置storeconst 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.prototypeexport 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适宜频繁切换。