过滤器的作用,如何实现一个过滤器

依据过滤器的名称,过滤器是用来过滤数据的,在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中的模板编译原理,次要过程:

  1. 将模板转换成 ast 树, ast 用对象来形容实在的JS语法(将实在DOM转换成虚构DOM)
  2. 优化树
  3. 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 包裹动静组件时,会缓存不流动的组件实例。

次要流程

  1. 判断组件 name ,不在 include 或者在 exclude 中,间接返回 vnode,阐明该组件不被缓存。
  2. 获取组件实例 key ,如果有获取实例的 key,否则从新生成。
  3. key生成规定,cid +"∶∶"+ tag ,仅靠cid是不够的,因为雷同的构造函数能够注册为不同的本地组件。
  4. 如果缓存对象内存在,则间接从缓存对象中获取组件实例给 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函数:

  1. 会在 keep-alive 组件外部去写本人的内容,所以能够去获取默认 slot 的内容,而后依据这个去获取组件
  2. keep-alive 只对第一个组件无效,所以获取第一个子组件。
  3. 和 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数组最初,以便革除最不罕用组件。

实现步骤:

  1. 获取 keep-alive 下第一个子组件的实例对象,通过他去获取这个组件的组件名
  2. 通过以后组件名去匹配原来 include 和 exclude,判断以后组件是否须要缓存,不须要缓存,间接返回以后组件的实例vNode
  3. 须要缓存,判断他以后是否在缓存数组外面:
  4. 存在,则将他原来地位上的 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的:PiniaVue 的存储库,它容许您跨组件/页面共享状态。
  • 实际上,pinia就是Vuex的升级版,官网也说过,为了尊重原作者,所以取名pinia,而没有取名Vuex,所以大家能够间接将pinia比作为Vue3Vuex

2. 为什么要应用pinia?

  • Vue2Vue3都反对,这让咱们同时应用Vue2Vue3的小伙伴都能很快上手。
  • pinia中只有stategetteraction,摈弃了Vuex中的MutationVuexmutation始终都不太受小伙伴们的待见,pinia间接摈弃它了,这无疑缩小了咱们工作量。
  • piniaaction反对同步和异步,Vuex不反对
  • 良好的Typescript反对,毕竟咱们Vue3都举荐应用TS来编写,这个时候应用pinia就十分适合了
  • 无需再创立各个模块嵌套了,Vuex中如果数据过多,咱们通常分模块来进行治理,稍显麻烦,而pinia中每个store都是独立的,相互不影响。
  • 体积十分小,只有1KB左右。
  • pinia反对插件来扩大本身性能。
  • 反对服务端渲染

3. pinna应用

pinna文档(opens new window)

  1. 筹备工作

咱们这里搭建一个最新的Vue3 + TS + Vite我的项目

npm create [email protected] my-vite-app --template vue-ts
  1. 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替换为新的对象,可能这种场景用得比拟少

  1. getters属性
  2. gettersdefineStore参数配置项外面的另一个属性
  3. 能够把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>
  1. actions属性
  2. 后面咱们提到的stategetters属性都次要是数据层面的,并没有具体的业务逻辑代码,它们两个就和咱们组件代码中的data数据和computed计算属性一样。
  3. 那么,如果咱们有业务代码的话,最好就是卸载actions属性外面,该属性就和咱们组件代码中的methods类似,用来搁置一些解决业务逻辑的办法。
  4. 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适宜频繁切换。