baixiaoji / blog

writting & thinking
3 stars 0 forks source link

少年,好好看Vue文档! #1

Open baixiaoji opened 6 years ago

baixiaoji commented 6 years ago

本质

Vue 文档在起先就说的很明白了,学习 Vue 就是熟悉实例化前所传的选项对象中的各个属性值。

实例篇

实例篇中介绍了属性对象中最重要的两类属性:data 和 生命周期。

着重介绍了选项对象中 data 数据具有响应原理,Vue 给开发者提供了一些 hook 方便开发者在 Vue 的某些阶段做自己想做的事情的生命周期。

同时 Vue 还提示我们之后遇见以 $ 开头的属性和方法是 Vue 暴露给用户使用的,还有建议我们不要在选项对象中使用剪头函数,毕竟不清楚上下文的话,this 指向的并不一定是 Vue实例噢。

计算属性和侦听器

当你快速的阅读了一遍文档的时候,计算属性、方法和侦听器实现出来的效果是差不多的,那为什么 Vue 会出现这样的区分呢?想必是有用的,计算属性可以根据已有的数据去计算对应的值,但重要的一点是计算属性是有缓存的机制,只要依赖源没有发生变化,计算属性值是走第一遍的缓存,注意计算属性的依赖必须是 data 中的响应数据源。

那侦听器呢?有这样的需求:某个数据改变的时候,会发生一些异步请求,那推荐使用侦听器。在 API 文档中,有一些惊人的使用方法:

 watch: {
    a: function (val, oldVal) {
      console.log('new: %s, old: %s', val, oldVal)
    },
    // string method name
    b: 'someMethod',
    // deep watcher
    c: {
      handler: function (val, oldVal) { /* ... */ },
      deep: true
    },
    // the callback will be called immediately after the start of the observation
    d: {
      handler: function (val, oldVal) { /* ... */ },
      immediate: true
    },
    e: [
      function handle1 (val, oldVal) { /* ... */ },
      function handle2 (val, oldVal) { /* ... */ }
    ],
    // watch vm.e.f's value: {g: 5}
    'e.f': function (val, oldVal) { /* ... */ }
  }

是不是很少机会去看 API 文档呢?

列表渲染

这部分文档中提到了v-for的用法,当然我们的第一印象仅仅停留在数组对象上,结合in 使用;文档中同样说明了对象也是可以使用v-for但结合of使用。

数组拥有变异的方法,使用这些方法可让视图发生更新,如:push/pop/shift/unshift/splice/sort/reverse。可开发者想根据索引去改变对应的数组元素是不行的,但 Vue.$set方法可以帮你解决这部分的烦恼,这里引出了使用 Vue 的暴露出来的方法可以将数据增加响应式的能力。

为什么要有 prop 值?prop 是父组件和子组件通信的媒介,如果没有规定好对应的 prop 值,这样子组件复用性就有待考虑了。

事件处理

如果想让 Vue 能够间接操作 DOM 则可以给函数传入 $event 参数(注:请注意场景)。并且 Vue 给事件提供了许多的修饰符(.stop / .prevent / .capture / .self / .once / .passive )。

着重讲讲 .passive,首先讲讲我们原生 addEventlistener 的第二个参数,我们仅仅只知道第二个参数是控制事件在捕获或是冒泡阶段触发,但是 passive 同样也可以是第二个参数,其作用出不会调用 event.preventDefault() ,但是在 Vue 中这个修饰符是包含了 event.preventDefault(),能够提升移动端的性能。

修饰符不只是事件的,还有你能想到的其他东西。

表单输入绑定

原本的双向绑定v-model 指令,现在成了语法糖,主要是 value 和 input 事件的简写而已。惊喜的是在 API 文档中,发现了 model 属性,可以修改v-model对应的 prop 值和 event 事件。

当然如果你并不希望去监听表单的 input 事件,你可以使用 .lazy 修饰符,这样 v-model监听的事件由 input 改为了 change 事件:

<!-- 在“change”时而非“input”时更新 -->
<input v-model.lazy="msg" >

给表单还提供了 .number 和 .trim 这两个修饰符。

正因为 v-model 成为了语法糖,这样可以让自定义组件也可以痛快的使用 v-model了。

组件基础

组件上分为:全局和局部组件两种形式,全局组件可以在任意组件中都可以使用,但是局部组件只可以在注册该组件的组件上使用。

该节提到了一个细节:组件内部的 data 属性应该是一个函数。如若不是函数,则同样引用了该组件的地方都会直接修改了该组件对应的 data 数据,那么对数据源的操作就不清楚是哪里触发的,并且还会修改其他地方的数据展示问题。

上面有提到过父组件和子组件通信使用定义好的 prop 值,那问题子组件如何和父组件通信呢?

只要看过了文档,我们清楚 Vue 提供了 Vue.$emit 这样的一个方法,可以让父组件监听子组件的自定义事件。

该方法的第一个参数是「自定义事件名」(建议使用 kebab-case 命名规范,至于为什么请往下继续阅读),第二个的参数则是对应的 payload ,如果想要在父组件上直接访问的对应的 payload 的值,则在对应的自定义事件的表达式中可以使用 \$event (是不是很眼熟,在事件处理中同样出现过)来获取子组件中的参数。

如果描述不清楚,还可以看看文档中的例子

Vue 给开发还提供了一个内置组件( component 组件 ),is 属性结合这个组件就可以「升级」为动态组件咯。

组件注册

该节出现一个问题:如何命名组件?

官方提到的只有两种:

  1. kebab-case ( 短横线分隔命名 )
  2. PascalCase (驼峰式命名)

那问题来了,这两种都是可以的吗?答案是肯定的。但是回想到 prop 、$emit 中其实也存在这这样的命名问题,不如不想多动脑子的话,可以统一使用 kebab-case 的命名规范( 可以直接阅读下移部分 )。

但如果要抠细节的话,在组件命名层面,使用 PascalCase 规范,如:

Vue.component('MyComponentName', { /* ... */ })

你在引用这个自定义元素时两种命名法都可以使用。也就是说 都是可接受的。

注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

但在自定义事件层面并且在运行时( 用来创建 Vue 实例、渲染并处理虚拟 DOM 等的代码。基本上就是除去编译器的其它一切 ),如果使用 PascalCase 规范,表现形式是不能触发对应的事件,但是实际上事件触发了,但是那时候的在HTML 上监听的自定义事件已经转为了小写格式(谁让 HTML 对大小写不敏感呢?),当然 Vue 在控制台同样也给了开发者对应的提示,那 prop 问题其实也相同,在下一节中说咯。(注:请注意发生这些事的前提)

如果项目结合了 webpack 打包,可以利用其提供的方法动态加载组件。

Prop

命名问题

同样在运行时的情况下,由于 HTML 大小写不敏感,浏览器会将所有的大写字符解释为小写字符。意味着当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。

为了防止组件的开发者能够防止其他开发者的「恶作剧」,可以对 prop 进行类型的要求,这样就减少了不该有的报错问题。

如何传入一个对象

其实当初从 React 转到 Vue 上的时候嫌弃 Vue 的传值要一个一个定义的去传(这样才规范嘛),但是真的没有 React 一次性传入对象的所有数据的那种快感,后来发现仅仅是因为我并没有好好看文档,Vue 同样提供了这样的功能。

如果你想一次性将一个对象上的所有属性都传入组件可以使用v-bind = obj这样的语法。如果觉得抽象可以看见下面的例子:

post: {
  id: 1,
  title: 'My Journey with Vue'
}

下面的模板:

<blog-post v-bind="post"></blog-post>

等价于:

<blog-post
  v-bind:id="post.id"
  v-bind:title="post.title"
></blog-post>

Vue 希望数据是单向流动的,子组件内部不能修改父组件来的prop,防止数据流修改的原因不一致问题。那我想使用父组件原始的值,但是又能子组件同时有一个状态值去维护自身的状态,文档提供了两种解决方案:

  1. 将 prop 作为子组件中的 data 中的一个数据的初始值来使用,但后续的 prop 的改变并不会改变到子组件;
  2. 如果需要依赖该 prop 可以将子组件中增加一个计算属性去控制,后续 prop 的改变会同时改变子组件的计算属性。

如果描述不清楚,可以回到文档在深深的阅读一遍。

非 Prop 特性

上面谈论到 Prop 都是事先定义好的,想必会出现开发者传入其他并没定义的 Prop 值,那么我们将其称之为非 Prop 特性。

显式定义的 prop 适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的特性,而这些特性会被添加到这个组件的根元素上。( 第二遍强调 )

这样很容易出现一个问题:或许父组件中将一个 type = date 的 input 因传入 type = text 而替换了子组件对应的属性。但庆幸的是:class 和 style 特性会稍微智能一些,即两边的值会被合并起来。

还有一个问题是:而这一类特性会自动继承在子组件的「根元素」身上。

那我能不能去控制其位置呢?答案是可以的,这样我们就要使用了选项对象中的 inheritAttrs将其置为 false。而实例提供了一个属性 $attrs 可以获取到了对应的非 Prop 特性对象,然后可以使用上面如何传入一个对象的方法去绑定到你想要的地方。

自定义事件

还是要提一下上面提及的事情:

同样在运行时的前提下:自定义事件名的书写规范,推荐使用 kebab-case 的事件名,因为 @myEvent HTML 对解释为 @myevent 导致对应的事件无法监听 实例 (看控制台的信息)

在 Vue 2.2.0+ 在选项对象中新增了一个 model 属性,可以去控制 v-model 语法糖的事件名(event)和 prop 值。

我们知道非 Prop 特性是绑定到子组件的根元素上,那我们在父组件中的定义的原生事件同样自动绑定到了子组件的根元素上,可是往往根元素是一个 wrapper 的作用,我们还是想将付组件定义的原生事件绑定到对应的子元素上。

Vue 提供了 $listener 这个实例属性,它是一个对象,里面包含了作用在这个组件上的所有监听器.

你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。 文档有一个好例子,飞机票

如果子组件想要去修改了一个对应父组件属性值,往往要 Prop 和 event 的结合,但 Vue 给了我们偷懒的部分机会,提供了 .sync 修饰符,该修饰符同时会监听一个名为 update:PropName 的自定义事件,只要在子组件中触发这样的一个事件,就可以去修改对应绑定的值了。

插槽

插槽分为默认以及具名插槽,一般使用在组件的封装和让用户自定义上。

其中最为最为特殊的作用域插槽:这是比较高级的组件用户,在父组件中去获取子组件中的数据,但神奇的是只要在父组件中去命名一个对象就好了,当然如果子组件中的对象可以解构,那么在父组件也是可以的(笔者最近在抽组件的时候用到这个,但还是有困惑的地方为什么绑定的数据并不是响应的)。

处理边界情况

Vue 提供了一些实例属性如 $root $parent $refs 让开发者可以获取到 DOM 元素或组件实例。同样提供了on once off这样的实用得监听 API。

提到的依赖与注入,是在选项对象上有两个属性 provideinject,只要在父组件中提供了对应的provide的值,在其下的子组件只要使用inject便可获取到对应的值(这样很神奇,尤其在跨越多个子组件去共享付组件状态或函数的情况,但有问题是提供的属性并不是响应式的(好奇,如果是一个函数去返回对应的属性那这样会是响应式的吗?))。

如果想强制更新,则使用 $forceUpdate ,但是官方说了 Vue是不会要求开发者去强制更新的,如果用到99%的情况下是开发者做错了什么(说不定你就是 1% 呢)。

混入

混入模式是比较好的一种功能,是对一些公用函数或是数据的共享,但是有一个问题如果这个数据或是函数都自生拥有,则是自身的同名函数或是属性比重较大 (混入有全局和局部)。

当然你可以去修改对应的合规则,使用 Vue.config.optionMergeStrategies

起因:因为有次看到同事代码,发现有一点比较困惑去想去文档找找理论依据,结果奇怪的是搜索能够搜索到对应的目录,但是却不能到对应的链接地址。唯独去看英文文档的时候,我发现我 Vue 文档的目录好像和他不一样,之后发现我居然看过时的文档看了两遍,突然感受到了世界深深的恶意,于是鼓起勇气去整理了一部分的内容出来,同样硬逼自己输出点什么东西。

注:本文是基于 Vue 2.x 文档,例子来源文档。