Better

Ethan的博客,欢迎访问交流

Vue基础

对Vue文档的缩减与总结,小习惯,没点输出感觉白看了,但这么一总结下来,印象就深刻了,而且细节不容易被忽视。

Vue对象

学习一个对象无非就是掌握对象的属性和方法。

el属性:通过指定元素来指定Vue作用作用域,也可以通过Vue实例的$mount挂载到指定元素

data函数:返回组件数据

  • 当一个 Vue 实例被创建时,它向 Vue 的响应式系统中加入了其 data 对象中能找到的所有的属性
  • 只有当实例被创建时 data 中存在的属性才是响应式的。如果你知道你会在晚些时候需要一个属性,但是一开始它为空或不存在,那么你仅需要设置一些初始值。
  • 唯一的例外是使用 Object.freeze(),这会阻止修改现有的属性,也意味着响应系统无法再追踪变化。
  • 为什么data必须是函数呢?因为这样每个实例可以维护一份被返回对象的独立的拷贝

methods方法属性:组件函数

computed计算属性:计算属性是基于它们的依赖进行缓存的。计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter。

watch 侦听属性

  • 注意不要滥用watch,通常更好的做法是使用计算属性而不是命令式的 watch 回调。
  • 虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

钩子函数

  • created
  • mounted
  • updated
  • destoryed

组件声明周期的整个流程

初始化实践和生命周期 --> 调用beforeCreate --> 初始化注入和响应式 --> 调用created --> 判断是有el

  1. 无 --> 当vm.$mount(el)调用时

--> 继续判断是否有template

  1. 有,解析模版进入render函数
  2. 无,解析el的outerHTML作为模版

--> 调用breforeMount函数 --> 创建vm.$el并替换el内容 --> 调用mounted函数

如果数据有改动 --> 调用beforeUpdate --> 虚拟DOM re-render 且 patch --> updated

如果调用vm.$destroy --> 调用beforeDestroy --> 资源释放(watchers、子组件、事件监听) --> 调用destroyed

Vue 实例还暴露了一些有用的实例属性与方法。它们都有前缀 $,以便与用户定义的属性区分开来。

  • $data
  • $el
  • $watch

vue实例化对象可以直接访问 data 下数据和 methods 下方法

注意,在 Vue 中,生命周期钩子、组件methods、computed也好,其 this 上下文指向调用它的 Vue 实例,因此不要在选项属性或回调上使用箭头函数,否则函数中 this 将不正确

HTML增强

Vue 提供了指令系统,而且内置了许多常用的指令来增强HTML功能,方便我们的开发。

首先了解一下指令

  • 指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
  • 指令能够接收参数,在指令名称之后以冒号表示。
  • 修饰符 (Modifiers) 是以半角句号 . 指明的特殊后缀,用于指出一个指令应该以特殊方式绑定。

Vue提供的HTML增强有那些呢?

插值功能

  • 文本:Mustache 语法 {{}},通过使用 v-once 指令,你也能执行一次性地插值,当数据改变时,插值处的内容不会更新。
  • v-bind:Mustache 语法不能作用在 HTML 特性上,比如给 HTML 属性动态绑定值,遇到这种情况应该使用 v-bind 指令
  • 原始 HTML:v-html

Vue对于常用的指令提供了缩写:v-bind 可以直接缺省,v-on 使用 @ 代替

你的站点上动态渲染的任意 HTML 可能会非常危险,因为它很容易导致 XSS 攻击。请只对可信内容使用 HTML 插值,绝不要对用户提供的内容使用插值。

在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。

对于所有的数据绑定,Vue.js 都提供了完全的 JavaScript 表达式支持。

为方便开发者操作DOM样式,Vue 专门对 Class 和 Style 进行了增强

  • 表达式结果的类型除了字符串之外,还可以是对象或数组。
  • 我们可以绑定一个返回对象的计算属性。这是一个常用且强大的模式
  • 数组语法可以将多个样式对象应用到同一个元素上
  • v-bind:style 的对象语法十分直观——看着非常像 CSS,但其实是一个 JavaScript 对象。CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来) 来命名
  • 当 v-bind:style 使用需要添加浏览器引擎前缀的 CSS 属性时,自动侦测并添加相应的前缀。
  • 从 2.3.0 起你可以为 style 绑定中的属性提供一个包含多个值的数组,常用于提供多个带前缀的值

条件渲染

  • v-if、v-else、v-else-if
  • v-show:v-show 的元素始终会被渲染并保留在 DOM 中。v-show 只是简单地切换元素的 CSS 属性 display。
  • 用 key 管理可复用的元素
    • Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。
    • 那如果需求是不复用他们呢?只需要添加 key 属性即可

v-if 与 v-show 区别

  • v-if 是真正的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
  • v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做——直到条件第一次变为真时,才会开始渲染条件块。
  • v-show 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。
  • v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。

列表渲染

  • 在 v-for 块中,我们拥有对父作用域属性的完全访问权限。v-for 还支持一个可选的第二个参数为当前项的索引。
  • 遍历对象 v-for="(value, key, index) in object"
  • v-for 也可以取整数。在这种情况下,它将重复多次模板。
  • key 优化
    • 当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用“就地复用”策略。如果数据项的顺序被改变,Vue 将不会移动 DOM 元素来匹配数据项的顺序, 而是简单复用此处每个元素,并且确保它在特定索引下显示已被渲染过的每个元素。
    • 为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。理想的 key 值是每项都有的且唯一的 id。
    • 建议尽可能在使用 v-for 时提供 key,除非遍历输出的 DOM 内容非常简单,或者是刻意依赖默认行为以获取性能上的提升。

注意,v-show 不支持 <template> 元素,也不支持 v-else。

表单绑定

  • v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值而总是将 Vue 实例的数据作为数据来源。
  • 修饰符
    • .lazy:转变为使用 change 事件进行同步
    • .number:自动将用户的输入值转为数值类型,这通常很有用,因为即使在 type="number" 时,HTML 输入元素的值也总会返回字符串。
    • .trim:自动过滤用户输入的首尾空白字符

响应式数据

先聊聊数组的变异方法和非编译方法

常用的变异方法

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

常用的非变异方法

  • filter()
  • concat()
  • slice()

变异方法 (mutation method),顾名思义,会改变被这些方法调用的原始数组。非变异 (non-mutating method) 方法不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组。

你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的、启发式的方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

注意事项

  • Vue 不能检测以下变动的数组
    • 当你利用索引直接设置一个项时,解决办法如下
      • Vue.set(vm.items, indexOfItem, newValue)
      • vm.items.splice(indexOfItem, 1, newValue)
      • vm.$set 实例方法,该方法是全局方法,Vue.set的一个别名
    • 当你直接修改数组的长度属性时
      • vm.items.splice(newLength)
  • Vue 不能检测对象属性的添加或删除
    • 对于已经创建的实例,Vue 不能动态添加根级别的响应式属性。
    • 可以使用 Vue.set(object, key, value) 方法向嵌套对象添加响应式属性。
    • 你可能需要为已有对象赋予多个新属性,比如使用 Object.assign()。在这种情况下,你应该用两个对象的属性创建一个新的对象。
      vm.userProfile = Object.assign({}, vm.userProfile, {
          age: 27,
          favoriteColor: 'Vue Green'
      })
      

事件处理

Vue中事件处理直接使用v-on或on绑定时间名称即可。在这里记载几个特别指出

函数调用,在这里让我觉得惊讶之处就是和react有些不一样,它即可绑定绑定方法名称,也可以调用方法,这里Vue应该是用闭包进行了处理,从而达到传参的目的。

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

事件修饰符

  • .stop:阻止冒泡
  • .prevent:阻止默认行为
  • .capture:事件捕获方式
  • .self:当前元素自身时触发处理函数
  • .once:事件将只会触发一次
  • .passive:提升移动端的性能。

按键修饰符

  • .enter
  • .tab
  • .delete (捕获“删除”和“退格”键)
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right

系统修饰键

  • .ctrl
  • .alt
  • .shift
  • .meta

鼠标按钮修饰符

  • .left
  • .right
  • .middle

config.keyCodes 对象自定义按键修饰符别名

自定义事件

事件名:跟组件和 prop 不同,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。

跟组件和 prop 不同,事件名不会被用作一个 JavaScript 变量名或属性名,所以就没有理由使用 camelCase 或 PascalCase 了。并且 v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

因此,我们推荐你始终使用 kebab-case 的事件名。

比较特别的情况,比如组件A封装input元素,但是input不是组件A的根元素,根元素是Label,那么作用在组件A上的事件,比如focus等,就作用到了Label元素上,这样就会失效,这里我们需要this.$listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。有了这个 $listeners 属性,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。DEMO如下:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      return Object.assign({},
        this.$listeners,
        {
          input: function (event) {
            vm.$emit('input', event.target.value)
          }
        }
      )
    }
  },
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on="inputListeners"
      >
    </label>
  `
})

.sync 修饰符

在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。

因此使用事件的方式来通信,对于需要双向绑定的,推荐以update:my-prop-name的模式。同时为了方便起见,我们为这种模式提供一个缩写,即 .sync 修饰符。

当我们用一个对象同时设置多个 prop 的时候,也可以将这个 .sync 修饰符和 v-bind 配合使用,直接传递对象,无需指明属性,此时对象中的每一个属性 (如 title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。示例如下

this.$emit('update:title', newTitle)
// 可以理解成原生方式
<text-document
  v-bind:title="doc.title"
  v-on:update:title="doc.title = $event"
></text-document>
// 上述需要两行代码,太麻烦了,简写如下
<text-document v-bind:title.sync="doc.title"></text-document>
// 如果我需要传递一个对象呢?总不能将每个属性都列出来把,此时无需指明属性且传递对象
<text-document v-bind.sync="doc"></text-document>

组件化

组件基础

介绍完Vue对象后,组件化开发其实也就不难理解了。

组件化的目的:扩展 HTML 元素,封装可重用的代码。

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

组件注册

  • 全局组件:Vue.component(name,option)
  • 局部注册:Vue对象选项,components属性,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

组件的命名

  • 使用 kebab-case,更通用的做法
  • 使用 PascalCase,当使用 PascalCase (驼峰式命名) 定义一个组件时,你在引用这个自定义元素时两种命名法都可以使用。注意,尽管如此,直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

对于一些基础的全局组件,我们有没有办法自动化呢,我们可以通过webpack提供的 require.context 自动化全局注册这些通用的基础组件。

创建组件参数

  • props 父子数据传递
  • template 指定 html 模板

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

组件之间数据传递

  • 通过 Prop 向子组件传递数据
    • camelCase vs. kebab-case:HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名)
    • 动态 Prop:v-bind 来动态地将 prop 绑定到父组件的数据
    • 如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind
  • 通过事件向父级组件发送消息
    // 子组件
    this.$emit((eventName, optionalPayload)
    // 父组件引用子组件
    <son-component @eventName="handle"></son-component>
    

Prop 验证

  • 要指定验证规则,需要用对象的形式来定义 prop,而不能用字符串数组
  • type/required/default/validator
  • DEMO
    {
      props:[{
          propA: Number,
          propB: [String, Number],
          propC: {
              type: String,
              required: true
          },
          propF: {
              validator: function (value) {
                  // 这个值必须匹配下列字符串中的一个
                  return ['success', 'warning', 'danger'].indexOf(value) !== -1
              }
          }
      }]
    }
    

注意 prop 会在组件实例创建之前进行校验,所以在 default 或 validator 函数里,诸如 data、computed 或 methods 等实例属性还无法使用。

单项数据流

  • 当父组件的属性变化时,将传导给子组件,但是反过来不会。
  • 在两种情况下,我们很容易忍不住想去修改 prop 中数据
    • Prop 作为初始值传入后,子组件想把它当作局部数据来用;
    • Prop 作为原始数据传入,由子组件处理成其它数据输出。
  • 对这两种情况,正确的应对方式是
    • 定义一个局部变量,并用 prop 的值初始化它
    • 定义一个计算属性,处理 prop 的值并返回

注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。

自定义组件如何支持v-model功能

  1. 建立正确的认知,v-model实质是什么
  2. 为了让它正常工作,这个组件内的 <input> 必须:
    • 将其 value 特性绑定到一个名叫 value 的 prop 上
    • 在其 input 事件被触发时,将新的值通过自定义的 input 事件抛出
  3. DEMO
    <input v-model="searchText">
    // 等同于
    <input
    v-bind:value="searchText"
    v-on:input="searchText = $event.target.value"
    >
    Vue.component('custom-input', {
    props: ['value'],
    template: `
     <input
       v-bind:value="value"
       v-on:input="$emit('input', $event.target.value)"
     >
    `
    })
    

DOM 模板解析注意事项

  • <ul>、<ol>、<table>、<select> 这样的元素里允许包含的元素有限制,而另一些像 <option> 这样的元素只能出现在某些特定元素的内部。在自定义组件中使用这些受限制的元素时会导致一些问题,变通的方案是使用特殊的 is 特性
  • 如果使用来自以下来源之一的字符串模板,则没有这些限制:
    • <script type="text/x-template">
    • JavaScript 内联模板字符串
    • .vue 组件
  • 因此,请尽可能使用字符串模板。
  • DEMO
    // 这个自定义组件 <blog-post-row> 会被作为无效的内容提升到外部,并导致最终渲染结果出错。
    <table>
    <blog-post-row></blog-post-row>
    </table>
    // =>
    <table>
    <tr is="blog-post-row"></tr>
    </table>
    

动态组件

动态组件:通过 Vue 的 <component> 元素加一个特殊的 is 特性来实现:

<!-- 组件会在 `currentTabComponent` 改变时改变 -->
<component v-bind:is="currentTabComponent"></component>

currentTabComponent 可以包括已注册组件的名字,或一个组件的选项对象

keep-alive:把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。

<keep-alive>
<!-- 组件会在 `currentTabComponent` 改变时改变 -->
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

注意这个 <keep-alive> 要求被切换到的组件都有自己的名字,不论是通过组件的 name 选项还是局部/全局注册。

替换/合并已有的特性

对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type="text" 就会替换掉 type="date" 并把它破坏!庆幸的是,class 和 style 特性会稍微智能一些,即两边的值会被合并起来

禁用特性继承:如果你不希望组件的根元素继承特性,你可以设置在组件的选项中设置 inheritAttrs: false

异步组件

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

一个推荐的做法是将异步组件和 webpack 的 code-splitting 功能一起配合使用:

Vue.component('async-webpack-example', function (resolve) {
  // 这个特殊的 `require` 语法将会告诉 webpack
  // 自动将你的构建代码切割成多个包,这些包
  // 会通过 Ajax 请求加载
  require(['./my-async-component'], resolve)
})

你也可以在工厂函数中返回一个 Promise,所以把 webpack 2 和 ES2015 语法加在一起,我们可以写成这样:

Vue.component(
  'async-webpack-example',
  // 这个 `import` 函数会返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

当使用局部注册当时候,你也可以直接提供一个返回 Promise 的函数:

new Vue({
  // ...
  components: {
    'my-component': () => import('./my-async-component')
  }
})

处理加载状态

const AsyncComponent = () => ({
  // 需要加载的组件 (应该是一个 `Promise` 对象)
  component: import('./MyComponent.vue'),
  // 异步组件加载时使用的组件
  loading: LoadingComponent,
  // 加载失败时使用的组件
  error: ErrorComponent,
  // 展示加载时组件的延时时间。默认值是 200 (毫秒)
  delay: 200,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000
})

编写可复用组件

提高组件的通用性

  • Prop 允许外部环境传递数据给组件;
  • 事件允许从组件内触发外部环境的副作用;
  • 插槽允许外部环境将额外的内容组合在组件中。

子组件引用

  • 尽管有 prop 和事件,但是有时仍然需要在 JavaScript 中直接访问子组件。为此可以使用 ref 为子组件指定一个引用 ID。
  • $refs 只在组件渲染完成后才填充,并且它是非响应式的。它仅仅是一个直接操作子组件的应急方案——应当避免在模板或计算属性中使用 $refs。

插槽系统

如果我们的组件包含子元素,那么如何控制这些元素的显示呢。通过插槽系统可以轻松解决。

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发。Vue 使用特殊的 <slot> 元素作为原始内容的插槽。

单个插槽

  • 除非子组件模板包含至少一个 <slot> 插口,否则父组件的内容将会被丢弃。当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。
  • 最初在 <slot> 标签中的任何内容都被视为备用内容。备用内容在子组件的作用域内编译,并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

具名插槽

  • <slot> 元素可以用一个特殊的特性 name 来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot 特性的元素。
  • 在向具名插槽提供内容的时候,我们可以在一个父组件的元素上使用 slot 特性
  • 仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。
  • DEMO
    <div class="container">
    <header>
      <slot name="header"></slot>
    </header>
    </div>
    <template slot="header">
    <h1>Here might be a page title</h1>
    </template>
    

作用域插槽

在使用 Element.UI 中发现作用于插槽被广泛使用,组件内部定义好自己默认逻辑,但还有一种情形,用户可能需要进行自定义,我们就可以使用作用域插槽,在组件的模版中定义<slot>显示默认值,在使用组件时可以内嵌自己的逻辑,而且可以传参。

作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。

如何传参和获取呢

  • 在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样
  • 父级中添加slot-scope属性,slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 prop 对象

作用域插槽更典型的用例是在列表组件中,允许使用者自定义如何渲染列表的每一项

示例

// 组件模版
<ul>
    <li v-for="todo in todos" v-bind:key="todo.id">
        <slot v-bind:todo="todo">
            <!-- 回退的内容 -->
            {{ todo.text }}
        </slot>
    </li>
</ul>
// 组件使用
<todo-list :todos="todos">
  <template slot-scope="slotProps">
    <span v-if="slotProps.todo.isComplete">✓</span>
    {{ slotProps.todo.text }}
  </template>
</todo-list>

边界情况

一些需要对 Vue 的规则做一些小调整的特殊情况。

访问元素 & 组件

访问根实例

在每个 new Vue 实例的子组件中,其根实例可以通过 $root 属性进行访问。通过 this.$root 可以访问数据,方法,计算属性。

对于 demo 或非常小型的有少量组件的应用来说这是很方便的。不过这个模式扩展到中大型应用来说就不然了。因此在绝大多数情况下,我们强烈推荐使用 Vuex 来管理应用的状态。

访问父级组件实例

$parent 属性可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。

访问子组件实例或子元素

尽管存在 prop 和事件,有的时候你仍可能需要在 JavaScript 里直接访问一个子组件。为了达到这个目的,你可以通过 ref 特性为这个子组件赋予一个 ID 引用。

<base-input ref="usernameInput"></base-input>
this.$refs.usernameInput

当 ref 和 v-for 一起使用的时候,你得到的引用将会是一个包含了对应数据源的这些子组件的数组。

依赖注入

上述通过 $parent 访问父组件属性存在如下几个问题:

  1. 父组件对于子组件的依赖无感知,即不知道这个属性是否被子组件需要着,这样会导致我们代码不容易维护
  2. 如果是更深层次的子组件呢,难道我们通过 this.$parent.$parent 一直往上寻找么,这显然是不合适的。

对于上面的问题,Vue提供了两个新的实例选项:provide 和 inject。

provide 选项允许我们指定我们想要提供给后代组件的数据/方法。然后在任何后代组件里,我们都可以使用 inject 选项来接收指定的我们想要添加在这个实例上的属性。DEMO如下:

// 父元素
provide: function () {
  return {
    getMap: this.getMap
  }
}
// 子组件
inject: ['getMap']

实际上,你可以把依赖注入看作一部分“大范围有效的 prop”,除了:

  • 祖先组件不需要知道哪些后代组件使用它提供的属性
  • 后代组件不需要知道被注入的属性来自哪里

如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像 Vuex 这样真正的状态管理方案了。

事件侦听器

之前我们知道 $emit 的用法,它可以被 v-on 侦听,但是 Vue 实例同时在其事件接口中提供了其它的方法。我们可以:

  • 通过 $on(eventName, eventHandler) 侦听一个事件
  • 通过 $once(eventName, eventHandler) 一次性侦听一个事件
  • 通过 $off(eventName, eventHandler) 停止侦听一个事件

循环引用

递归组件

组件是可以在它们自己的模板中调用自身的。不过它们需要通过 name 选项来做这件事。这不难理解,自己调用自己,你肯定得知道自己是个啥。

循环引用

组件A中引用组件B,组件B中引用A,你可能直观的觉得这会有问题,但是我们有办法可以解决

比如<tree-folder><tree-folder-contents,如果通过Vue.component全局注册组件的时候,就不会有问题,但是我们通常不会那么做,而是通过 ES6 的动态 import 实现。

我们需要给定一个点,这里对另一个的依赖改为异步加载,问题就可以解决。示例如下

// tree-folder code
<template>
    <p>
        <span>{{ folder.name }}</span>
        <tree-folder-contents :children="folder.children" />
    </p>
</template>

<script>
export default {
    props: ['folder'],
    components: {
        TreeFolderContents: () => import('./tree-folder-contents.vue')
    }
}
</script>
// tree-folder-contents
<template>
    <ul>
        <li v-for="child in children" :key="child.name">
            <tree-folder v-if="child.children" :folder="child" />
            <span v-else>{{ child.name }}</span>
        </li>
    </ul>
</template>
<script>
import TreeFolder from './tree-folder.vue'
export default {
    props: ['children'],
    components: { TreeFolder }
}
</script>

模板定义的替代品

内联模板

当 inline-template 这个特殊的特性出现在一个子组件上时,这个组件将会使用其里面的内容作为模板,而不是将其作为被分发的内容。

<component inline-template>
  <!-- 在使用时指定模版 --> 
  ...
</component>

不推荐,inline-template 会让你模板的作用域变得更加难以理解。所以作为最佳实践,请在组件内优先选择 template 选项或 .vue 文件里的一个 <template> 元素来定义模板。

X-Templates

另一个定义模板的方式是在一个 <script> 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。

<script type="text/x-template" id="hello-world-template">
  <p>Hello hello hello</p>
</script>
Vue.component('hello-world', {
  template: '#hello-world-template'
})

可以用于模板特别大的 demo 或极小型的应用,但是其它情况下请避免使用,因为这会将模板和该组件的其它定义分离开。

vscode环境搭建

默认.vue文件在vscode中是没有语法高亮,安装vetur扩展即可。

Vscode 格式化vue Template代码段

  1. 安装 vetur
  2. 在User Setting中增加设置:
    "vetur.format.defaultFormatter.html": "js-beautify-html"
    

经验证上述代码无效,折腾了好久,找到一个可行方案

  1. 安装扩展beautify
  2. 配置
    "beautify.language": {
     "js": {
         "type": [
             "javascript",
             "json"
         ],
         "filename": [
             ".jshintrc",
             ".jsbeautify"
         ]
     },
     "css": [
         "css",
         "scss"
     ],
     "html": [
         "htm",
         "html",
         "vue" //在这里加上vue
     ]
    }
    

vscode 安装 vetur 之后强制双引号和自动添加分号,个人并不喜欢

  • 安装 prettier
  • 设置
    "prettier.singleQuote": true,
    "prettier.semi": false
    

来源

主要是官方文档的缩减与总结



留言