Vue基础

对着 Vue 官网一行一行扒下来,知识点总结包含大部分 Vue 的基础使用内容。

数据与方法

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。

circle-exclamation

也就是说如果你添加一个新的 property,那么后续对于该 property 所作的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。

circle-exclamation

为什么 data 需要是函数?

组件复用时所有组件实例都会共享 data,如果 data 是对象的话,就会造成一个组件修改 data 以后会影响到其他所有组件,所以需要将 data 写成函数,每次用到就调用一次函数获得新的数据。

当我们使用 new Vue() 的方式的时候,无论我们将 data 设置为对象还是函数都是可以的,因为 new Vue() 的方式是生成一个根组件,该组件不会复用,也就不存在共享 data 的情况了。

模板

模板这块没有特别需要说明的,只有一处需要注意一下:

有些 HTML 元素,诸如 <ul><ol><table><select>,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li><tr><option>,只能出现在其它某些特定的元素内部。

这会导致我们使用这些有约束条件的元素时遇到一些问题。例如:

<table>
  <tr is="blog-post-row"></tr>
</table>

这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is attribute 给了我们一个变通的办法:

需要注意的是如果我们从以下来源使用模板的话,这条限制是不存在

  • 字符串 (例如:template: '...')

  • 单文件组件 (.vue)

  • <script type="text/x-template">

生命周期

circle-info

每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子的函数,这给了用户在不同阶段添加自己的代码的机会。

Vue 的全部生命周期钩子(非 SSR):

  • beforeCreate

    在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

  • created

    在实例创建完成后被立即调用。在这一步,实例已完成以下的配置:数据观测 (data observer),property 和方法的运算,watch/event 事件回调。然而,挂载阶段还没开始,$el property 目前尚不可用。

  • beforeMount

    在挂载开始之前被调用:相关的 render 函数首次被调用。

  • mounted

    实例被挂载后调用,这时 el 被新创建的 vm.$el 替换了。如果根实例挂载到了一个文档内的元素上,当 mounted 被调用时 vm.$el 也在文档内。

    注意: mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTickarrow-up-right

  • beforeUpdate

    数据更新时调用,发生在虚拟 DOM 打补丁之前。这里适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器。

  • updated

    由于数据更改导致的虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。

    当这个钩子被调用时,组件 DOM 已经更新,所以你现在可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态。如果要相应状态改变,通常最好使用计算属性arrow-up-rightwatcherarrow-up-right 取而代之。

    注意: updated 不会保证所有的子组件也都一起被重绘。如果你希望等到整个视图都重绘完毕,可以在 updated 里使用 vm.$nextTickarrow-up-right

  • activated

    被 keep-alive 缓存的组件激活时调用。

  • deactivated

    被 keep-alive 缓存的组件停用时调用。

  • beforeDestroy

    实例销毁之前调用。在这一步,实例仍然完全可用。

  • destroyed

    实例销毁后调用。该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。

  • errorCaptured

    当捕获一个来自子孙组件的错误时被调用。此钩子会收到三个参数:错误对象、发生错误的组件实例以及一个包含错误来源信息的字符串。此钩子可以返回 false 以阻止该错误继续向上传播。

circle-info

需要注意:如果使用 Vue 的服务端渲染框架比如:Nestjs,那么生命周期中只有:beforeCreate、created 和 errorCaptured 有效。因为服务端渲染只负责第一屏,渲染完成后,后续的 DOM 挂载、数据变更、销毁等,均由浏览器接管。

circle-exclamation

下图是一个 Vue 实例的生命周期图谱:

关于Vue生命周期的总结

beforeCreate 钩子函数调用的时候,是获取不到 props 或者 data 中的数据的,因为这些数据的初始化都在 initState 中。

然后会执行 created 钩子函数,在这一步的时候已经可以访问到之前不能访问到的数据,但是这时候组件还没被挂载,所以是看不到的。

接下来会先执行 beforeMount 钩子函数,开始创建 VDOM,最后执行 mounted 钩子,并将 VDOM 渲染为真实 DOM 并且渲染数据。mounted 不会保证所有的子组件也都一起被挂载。

接下来是数据更新时会调用的钩子函数 beforeUpdateupdated,这两个钩子函数分别在数据更新前和更新后会调用。

另外还有 keep-alive 独有的生命周期,分别为 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

最后就是销毁组件的钩子函数 beforeDestroydestroyed。前者适合移除事件、定时器等等,否则可能会引起内存泄露的问题。然后进行一系列的销毁操作。

计算属性和侦听器

计算属性的使用场景

circle-info

当绑定在模板中的数据需要依赖其他属性值进行计算,或者需要经过一些复杂计算、特殊处理才能渲染到页面上的时候,我们可以使用计算属性:computed

计算属性的特性

circle-info

计算属性可以让你的绑定值依赖其他值的变化来进行动态计算,并且会根据依赖值进行缓存,只有当依赖值变化才会返回新的计算内容。

计算属性和侦听器的区别

circle-info

computed 是计算属性,依赖其他属性计算值,并且 computed 的值有缓存,只有当计算值变化才会返回内容。

watch 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作。

所以一般来说需要依赖别的属性来动态获得值的时候可以使用 computed,对于监听到值的变化需要做一些复杂业务逻辑的情况可以使用 watch

计算属性和侦听器还支持对象形式的写法:

circle-check

计算属性和方法的区别

有的时候,计算属性和方法(methods)都可以达到相同的效果,那么它们之间的区别是什么呢?

circle-info

计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要依赖值还没有发生改变,多次访问计算属性会立即返回之前的计算结果,而不必再次执行函数。

而 相比之下,每当触发重新渲染时,调用方法将总会再次执行函数,这是有性能损耗的。

Class 与 Style 绑定

自动添加前缀

v-bind:style 使用需要添加浏览器引擎前缀的 CSS property 时,如 transform,Vue.js 会自动侦测并添加相应的前缀。

条件渲染

v-show 与 v-if 区别

v-show 只是在 display: nonedisplay: block 之间切换。无论初始条件是什么都会被渲染出来,后面只需要切换 CSS,DOM 还是一直保留着的。所以总的来说 v-show 在初始渲染时有更高的开销,但是切换开销很小,更适合于频繁切换的场景。

v-if 的话就得说到 Vue 底层的编译了。当属性初始为 false 时,组件就不会被渲染,直到条件为 true,并且切换条件时会触发销毁/挂载组件,所以总的来说在切换时开销更高,更适合不经常切换的场景。

并且基于 v-if 的这种惰性渲染机制,可以在必要的时候才去渲染组件,减少整个页面的初始渲染开销。

key 管理可复用的元素

Vuekey 这个东西官网上说的官方解释有些抽象,推荐大家看:Vue2.0 v-for 中 :key 到底有什么用?arrow-up-right

简单来说:

  • 如果这个元素没有加上 key,那么在 Vue 下一次更新视图的时候,它就会尽可能地将该元素进行复用,而不是重新创造一个新的元素将其替换。典型的场景是:表单登录,用户可以选择微信号或者手机号登录,当用户输入账号后忽然想切换登录方式,代码里虽然是使用的 v-if 进行了微信号和手机号两个 input 之间的切换,但是之前填写的登录账号信息依然会保留下来,这个就是 Vue 对 Input 标签进行了复用。

  • 如果这个元素上加了唯一的 key ,就等于说给这个元素及其子元素 / 子组件加上了一个唯一的 id 标识,那么 Vue 在下一次更新视图的时候,就能够识别出哪些元素是被修改了,哪些元素是被删除掉了,从而更有针对性地选择复用还是重建元素,而不是直接一股脑地能复用就复用。

官网解释:

key 的特殊 attribute 主要用在 Vue 的虚拟 DOM 算法,在新旧 nodes 对比时辨识 VNodes。如果不使用 key,Vue 会使用一种 最大限度减少动态元素 并且 尽可能的尝试就地修改/复用相同类型元素 的算法。而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

最常见的用例是结合 v-for

它也可以用于强制替换元素/组件而不是重复使用它。当你遇到如下场景时它可能会很有用:

  • 完整地触发组件的生命周期钩子

  • 触发过渡

例如:

text 发生改变时,<span> 总是会被替换而不是被修改,因此会触发过渡。

列表渲染

这里的 v-for 指令就涉及到上面所说的 key 的问题。

假设有一个列表,列表有三个子组件,自组件没有加唯一 key 值,每个子组件里面有一个「有状态的」孙子组件。

现在用户点击删除按钮,删除掉第二项,会出现以下结果:Vue 识别自组件 2 的名称被修改为 3 但是其他的东西都会进行复用,包括自组件,然后删除原来的 3 组件及其自组件。这就出现了问题。

circle-exclamation

数组的更新监测

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:

  • push()

  • pop()

  • shift()

  • unshift()

  • splice()

  • sort()

  • reverse()

circle-exclamation

不要将 v-forv-if 一同使用

triangle-exclamation

当 Vue 处理指令时,v-forv-if 具有更高的优先级,所以这个模板:

将会经过如下运算:

因此哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。

通过将其更换为在如下的一个计算属性上遍历:

一般我们在两种常见的情况下会倾向于这样做:

  • 为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表。

  • 为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ulol)。

triangle-exclamation
circle-check

事件

事件处理这块可能是 Vuer 用的最多的东西之一,大部分内容大家可能也了解,就没什么好说的了,就大概总结一下吧。

事件的写法

在 Vue 中有下面几种事件的写法:

  • 内联写法:

  • 方法名写法,这种写法,默认会传入一个 DOM 的原生事件对象作为事件处理函数的第一个参数

  • 内联语句中直接调用:

    有时也需要在内联语句处理器中访问原始的 DOM 事件。可以用特殊变量 $event 把它传入方法:

circle-exclamation

事件修饰符

这块不是面试的重点,大家大概看一下文档了解就好:事件处理——事件修饰符arrow-up-right。只有一种需要特殊说明一下:.sync 修饰符

.sync 修饰符也是语法糖的一种,在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以变更父组件,且在父组件和子组件都没有明显的变更来源。这个时候,我们可以使用 .sync 语法糖来模拟双向绑定,并且 .sync 也能够在语意上让子组件更改父组件的状态代码更容易被区分。

等价于:

当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:

.sync 修饰符经常会用在弹框的显隐功能上:

双向绑定 v-model

v-model 的本质——语法糖

你可以用 v-model 指令在表单 <input><textarea><select> 元素上创建双向数据绑定。其实,v-model 本质上只是 v-bind:value v-on:input 的语法糖而已:

使用 v-model

等价于:

v-model 语法糖在内部为不同的输入元素使用不同的 property 并抛出不同的事件:

  • text 和 textarea 元素使用 value property 和 input 事件;

  • checkbox 和 radio 使用 checked property 和 change 事件;

  • select 字段将 value 作为 prop 并将 change 作为事件。

v-model 修饰符

这块内容面试不太可能问到,不过作为平时开发的小技巧可以学习一下。

.lazy

在默认情况下,v-model 在每次 input 事件触发后将输入框的值与数据进行同步 (除了上述arrow-up-right输入法组合文字时)。你可以添加 lazy 修饰符,从而转为在 change 事件_之后_进行同步:

.number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符:

这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。如果这个值无法被 parseFloat() 解析,则会返回原始的值。

.trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符:

自定义组件使用 v-model

自定义组件使用 v-model 的方式,和原生 DOM 上绑定的 v-model 方式一样,只需要组件内部对外暴露 value prop ,并且组件内部向外发射 input 事件即可。

circle-info

注意:一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value attribute 用于不同的目的。

等价于

组件内部

自定义组件

circle-info

组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 datacomputedwatchmethods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。

全局注册

直接使用 Vue.component 进行全局注册,然后再创建 Vue 的根实例,需要注意的是:全局注册必须在根 Vue 实例创建之前。

局部注册

局部注册没有什么好说的,大家基本都明白。

单个根元素

circle-info

每个组件必须只有唯一一个根元素

Props

静态 Props 和动态 Props

静态 props

动态 props

多个 Props 的传入方式

我们可以对一个组件传入多个 props,如果组件的 props 太多,可以将 props 组合成一个对象 prop 传入。

也可以传入一个对象的所有 property:

circle-info

如果你想要将一个对象的所有 property 都作为 prop 传入,你可以使用不带参数的 v-bind (取代 v-bind:prop-name)。例如,对于一个给定的对象 post

下面的模板:

等价于:

Props 的接收方式

组件内部接收 props 时我们可以写成数组形式:

也可以写成对象形式,如果写成对象形式的话,就需要 Props 的类型判断:

props 的类型检查

type 可以是下列原生构造函数中的一个:

  • String

  • Number

  • Boolean

  • Array

  • Object

  • Date

  • Function

  • Symbol

额外的,type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认。例如,给定下列现成的构造函数:

你可以使用:

来验证 author prop 的值是否是通过 new Person 创建的。

Props 和 html-attribute

circle-info

注意:Vue 的组件中是不限制 prop 的传入数量的,如果外部传入的 prop ,组件内部没有接收,那么就会变成 kebab-case (短横线分隔命名) 命名 的方式作为自定义属性添加到组件的根元素上。 Vue 会选择性地对重复的 attribute 进行替换或者合并,比如,如果是重复的 input type ,那么则会替换,如果是 class、style 等,则会合并。对于绝大多数 attribute 来说,从外部提供给组件的值会替换掉组件内部设置好的值, classstyle attribute 会稍微智能一些,即两边的值会被合并起来,从而得到最终的值。

如果你希望组件的根元素继承 attribute,你可以在组件的选项中设置 inheritAttrs: false。例如:

circle-exclamation

vm.$attrs

inheritAttrs: false 的情况很适合配合 vm.$attrs 来使用。vm.$attrs 包含了父作用域中不作为 prop 被识别 (且获取) 的 attribute 绑定 (classstyle 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (classstyle 除外),并且可以通过 v-bind="$attrs" 传入内部组件,这在创建高级别的组件时非常有用。有了 inheritAttrs: false$attrs,你就可以手动决定这些 attribute 会被赋予哪个元素。这个类似于 React 中的 <MyComponent {...this.props} />

组件通讯

组件通信一般分为以下几种情况:

  • 父子组件通信

  • 兄弟组件通信

  • 跨多层级组件通信

  • 任意组件

对于以上每种情况都有多种方式去实现,接下来就来学习下如何实现。

父子通信

props 和 $emit

父组件通过 props 传递数据给子组件,子组件通过 emit 发送事件传递数据给父组件,这两种方式是最常用的父子通信实现办法。

这种父子通信方式也就是典型的单向数据流,父组件通过 props 传递数据,子组件不能直接修改 props, 而是必须通过发送事件的方式告知父组件修改数据。

v-model 和 .sync

另外这两种方式还可以使用语法糖 v-model 来直接实现,因为 v-model 默认会解析成名为 valueprop 和名为 input 的事件。这种语法糖的方式是典型的双向绑定,常用于 UI 控件上,但是究其根本,还是通过事件的方法让父组件修改数据。

.sync 属性是个语法糖,可以很简单的实现子组件与父组件通信

$parent 和 $children 和 $ref

当然我们还可以通过访问 $parent 或者 $children 对象来访问组件实例中的方法和数据,或者使用 this.$refs[compnentRefName] 的方式来拿到具名子元素实例。

另外如果你使用 Vue 2.3 及以上版本的话还可以使用 $listeners.sync 这两个属性。

circle-exclamation

$listeners

$listeners 属性会将父组件中的 (不含 .native 修饰器的) v-on 事件监听器传递给子组件,子组件可以通过访问 $listeners 来自定义监听器。

vm.$listener

简单点讲它是一个对象,里面包含了作用在这个组件上所有的监听器(监听事件),可以通过 v-on="$listeners" 将事件监听指向这个组件内的子元素(包括内部的子组件)。

上例中,父组件在child1组件中设置两个监听事件test1test2,分别在child1组件和child1组件内部的child2组件中执行。还设置了三个属性p-child1p-child2p-child-attrs。其中p-child1p-child2被对应的组件的prop识别。所以: p-child1组件中$attrs{ "p-child2": "pChild2的值", "p-child-attrs": 1231 }; p-child2组件中$attrs{ "p-child-attrs": 1231 },效果如下图:

我们再点击child2组件中的按钮,触发事件,控制台可以打印出:

兄弟组件通信

对于这种情况可以通过查找父组件中的子组件实现,也就是 this.$parent.$children,在 $children 中可以通过组件 name 查询到需要的组件实例,然后进行通信。

跨多层次组件通信

provide / inject

对于这种情况可以使用 Vue 2.2 新增的 API provide / inject,虽然文档中不推荐直接使用在业务中,但是如果用得好的话还是很有用的。

provide 选项允许我们指定我们想要 提供 给后代组件的数据 / 方法,然后在后代组件中使用 inject 注入,就可以访问组件组件提供的 provide 出来的数据或者调用 provide 出来的方法。 假设有父组件 A,然后有一个跨多层级的子组件 B:

任意组件

这种方式可以通过 Vuex 或者 Event Bus 解决,另外如果你不怕麻烦的话,可以使用这种方式解决上述所有的通信情况

Event Bus

Event Bus 的通信机制其实非常简单:独立与主 Vue 实例再创建一个单独的 Vue 实例,利用 vm.$on vm.$off vm.$once 等事件监听 / 解绑的 api ,专门就做事件处理的事情。因为他式独立的 Vue 实例,所以并不需要知道谁监听了事件,谁触发了事件。非常的方便。

动态组件

有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里。上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is attribute 来实现,也就是动态组件:

在上述示例中,currentTabComponent 可以包括

  • 已注册组件的名字,或

  • 一个组件的选项对象

circle-info

动态组件也可以使用 keep-alive 进行缓存

异步组件

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块,这个就是异步组件加载。

异步组件的全局注册

异步组件的局部注册

异步组建的局部注册唯一的区别就是 components 的写法

递归组件

有的时候,本来想秀下操作,然而由于 水平菜鸡 业务复杂,稍有不慎我们就很容易写出死循环的递归组件:

类似上述的组件将会导致“max stack size exceeded”错误,所以请确保递归调用是条件性的 (例如使用一个最终会得到 falsev-if)。

组件的循环引用

组件间的循环引入的场景之一就是—— tree 组件。导致死循环引用的原因,其实是因为两个 vue 文件中的循环 import,webpack 打包这种文件的时候,就会不知所措,于是只能给你抛出一个错误。所以这种场景往往出现在局部注册的组件下,而 Vue.component() 进行的全局组件注册由于不存在循环引用,所以没有这个问题。

对于这个问题的解决方案有两种:

  1. 你可以在 beforeCreate 钩子函数里进行引入:

  2. 你可以在注册局部组件的时候,使用动态组件的注册方式:

两种解决问题的核心思想,都是延迟引入,从而规避掉 webpack 打包时可能出现的懵逼情况。

keep-alive

keep-alive 多出来两个钩子函数 activateddeactivated 。用 keep-alive 包裹的组件在切换时不会进行销毁,而是缓存到内存中并执行 deactivated 钩子函数,命中缓存渲染后会执行 actived 钩子函数。

对于这个知识点,一般也就问问它是做什么用的,也不太会问别的东西,毕竟没有什么值得深挖的。只要能答上来这个是做组件缓存的基本就没问题。想要详细了解的,这里给个传送门吧:keep-alivearrow-up-right

分发插槽

当我们需要在自定义组件中再嵌套自定义组件,就会用到 “分发插槽” 的功能,这个就类似于 React 中的语法 <MyComponent>{...this.children}</MyComponent>

默认插槽

Vue 组件支持一个默认默认插槽,默认插槽有得话只能有一个:

你也可以为插槽提供一个后备用内容,当父组件没有任何东西插入的时候,后备内容就会默认显示,像这样:

具名插槽

具名插槽可以有很多,不过名称不能重复,具名插槽和默认插槽可以同时存在:

作用域插槽

当使用插槽的时候,父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。也就是说,子元素没有办法访问到父模板中的内容,那么这个时候可以使用作用域插槽来将父模板里的内容传入:

如果是默认插槽的 prop 可以使用简写:

circle-exclamation
circle-exclamation

可以以为结构的方式写插槽

动态插槽

动态指令参数也可以用在 v-slot 上,来定义动态的插槽名

具名插槽缩写

加入作用域插槽

混入

我们写一些相似度很高的组件时,经常会发现很多能够提取出来公共使用的东西部分,Vue 对于这部分内容,提供了一个混入的工具:mixin。mixin 对象,其实也就是普通的 vue 对象而已。

全局混入 Mixin

局部混入 Mixins

circle-exclamation

自定义混入方式

这个我从来没有用过,也没有被问到过,感觉不重要也不常用,需要的话自取文档:自定义选项合并策略arrow-up-right

混入和 extend

Vue 里还有一个 extend 的 api,那么这个和 mixin 有什么区别呢?个人感觉这两个东西的目的都是一样的:都是作为混入和扩展的一种方法,只不过实现层面不同:mixin 是在组件层面混入,或者可以理解为对实例进行扩展;而 extend 是先扩展了构造函数也就是累类,然后根据这个扩展完成的类,在再进行实例化。看代码:

自定义指令

这块内容不多,但是面试的时候可能会被提一句:你有没有写过自定义指令?所以我建议大家直接看官网:自定义指令arrow-up-right,官网上介绍的很详细,关于自定义指令内容大部分是使用方式、传参细节等,没有太多的坑在里面。

这里提一下我认为有可能被深入问到的问题:

circle-info

你觉得指令和组件有什么区别?它们各自的应用场景是什么?

circle-check

渲染函数和 JSX

从这块内容开始,说实话,Vue 的画风逐渐变态,已经开始在我这种白嫖党最不喜欢的 “底层” 、“原理” 这种关键词之间疯狂试谈。

里面的内容比较多,这里就不写了,我自觉不可能比官网写的好,大家还是把这块的官网内容看一下:渲染函数 & JSXarrow-up-right。如果不太愿意看的朋友,就看我下面的总结大概:

  1. 首先呢,我们现在开发 vue 的基本操作就是 .vue 文件了,我们在里面写的很爽,但是在最终 Vue 的 compile 编译阶段,会将我们的组件编译成如下的渲染函数的形式:

    转换为:

    是不是看不懂?是不是看不懂?看不懂就对了,因为这个本来就不是给人看的,是编译成这种格式给机器运行的。

  2. 拿到了编译完成的渲染函数并且执行后,vue 就能得到一个叫做 VNode虚拟 DOM,这个虚拟 DOM其实就是 用普通 js 对象的格式,对于即将渲染成的 DOM 文档格式的一种抽象描述。类似于下面这样:

  3. 有了这个 VNode Tree 的虚拟文档描述,Vue 就可以 将虚拟 DOM 渲染成真实的 DOM 节点,然后一次插入。

至于为什么会有这个东西呢?

  1. 首先,我们写的很爽的 template 这种模板语言格式,浏览器是不认识的,更不会执行,这个只是一种对于开发者更友好的语法糖,将真正的底层全部封装,简化最大程度简化写法,让程序员聚焦业务。而 render 函数本质上是浏览器能够识别并执行的 javascript ,浏览器拿到就很开心。

  2. 写过 React 的朋友就知道,Vue 和 React 的差别之一,就是在于: Vue 里面没办法大量地编写 javascript ,而 React 却可以。Vue 的人性化语法极大地迎合了开发者,但是这也造成了自由度没有 React 高,毕竟 React 全部 js,就连 jsx 其实也是普通的 string 。那这种情况下,在某些 SB需求 更为复杂、需要高度自由的场景下,语法糖就不适用了,比如说 table 组件。那怎么兼得鱼和熊掌?vue 的 render 函数就提供了一个解决方案,可能看上去没有那么好看,也不好理解,但是它确实能解决你的需求。

至于最后的 JSX

这也不是 Vue 官方推荐的东西,只不过考虑到这样的语法写起来确实难受,官方提供了一个可供选择的方案而已。

插件

内容不多,推荐看下官网:插件arrow-up-right

  1. Vue.use(plugin) 注册插件

  2. 插件需要暴露一个 install 方法,接收 Vue 构造器和 options 选项作为参数:

  3. 插件里面可以扩展 Vue 原型、扩展全局指令、扩展全局 mixin 等。

过滤器

内容不多,推荐看下官网:过滤器arrow-up-right

  1. 过滤器的挂载:

  2. 过滤器的使用:

  3. 过滤器的链式使用:

参考文档

最后更新于