vue注意事项(混矿石)

183 阅读7分钟

1.组件注册:

组件名:

短横线分割命名(kebab-case):注册时若使用此命名方式则在引用组件时也必须使用此方式。

大驼峰命名(PascalCase):注册时若使用此命名方式则引用时这两种方式均可使用,但直接在 DOM (即非字符串的模板) 中使用时只有 kebab-case 是有效的。

全局注册: Vue.component('name',{ ... })

它们在注册之后可以用在任何新创建的 Vue 根实例 (new Vue) 的模板中。在所有子组件中也是如此。

缺点:全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。

局部注册:

首先,通过一个普通的js对象来定义组件:

var ComponentA = { /* ... */ }
var ComponentB = { /* ... */ }

其次,在components选项中定义你想要使用的组件:

new Vue({
  el: '#app'
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

对于components对象中的每个属性来说,其属性名就是自定义元素的名字,其属性值就是这个组件的选项对象。

注意局部注册的组件在其子组件中不可用。如果你希望ComponentAComponentB中可用,则你需要这样写:

var ComponentA = { /* ... */ }

var ComponentB = {
  components: {
    'component-a': ComponentA
  },
  // ...
}

如果你通过 Babel 和 webpack 使用 ES2015 模块,那么代码看起来更像:

import ComponentA from './ComponentA.vue'

export default {
  components: {
    ComponentA
  },
  // ...
}

基础组件的自动化全局注册: 如果你使用了 webpack (或在内部使用了 webpack 的Vue CLI 3+),那么就可以使用require.context只全局注册这些非常通用的基础组件。注意: 全局注册的行为必须在根 Vue 实例 (通过new Vue) 创建之前发生。

2.Prop:

大小写问题:

当你使用 DOM 中的模板时,camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名。

静态&&动态:

<blog-post title="My journey with Vue"></blog-post>
<blog-post v-bind:title="post.title"></blog-post>

传入一个对象的所有属性:

如果你想要将一个对象的所有属性都作为 prop 传入,你可以使用不带参数的v-bind(取代v-bind:prop-name)。下面的两种写法是等价的:

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

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

单向数据流:

所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。

额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你应该在一个子组件内部改变 prop。

这里有两种常见的试图改变一个 prop 的情形:

// 这个 prop 用来传递一个初始值;这个子组件接下来希望将其作为一个本地的 prop 数据来使用。
// 在这种情况下,最好定义一个本地的 data 属性并将这个 prop 用作其初始值:
props: ['initialCounter'],
data: function () {
 return {
    counter: this.initialCounter
  }
}

// 这个 prop 以一种原始的值传入且需要进行转换。在这种情况下,
// 最好使用这个 prop 的值来定义一个计算属性:
props: ['size'],
computed: {
  normalizedSize: function () {
 return this.size.trim().toLowerCase()
  }
}

注意:在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。

prop验证:

为了定制 prop 的验证方式,你可以为props中的值提供一个带有验证需求的对象,而不是一个字符串数组。

注意:那些 prop 会在一个组件实例创建之前进行验证,所以实例的属性 (如datacomputed等) 在defaultvalidator函数中是不可用的。

替换、合并已有特性:

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

禁用特性继承:

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

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input
        v-bind="$attrs"
        v-bind:value="value"
        v-on:input="$emit('input', $event.target.value)"
      >
    </label>
  `
})

<base-input
  v-model="username"
  class="username-input"
  placeholder="Enter your username"
></base-input>

3.自定义事件:

事件名:

v-on 事件监听器在 DOM 模板中会被自动转换为全小写 (因为 HTML 是大小写不敏感的),所以 v-on:myEvent 将会变成 v-on:myevent——导致 myEvent 不可能被监听到。

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

自定义组件的v-model:

一个组件上的v-model默认会利用名为value的 prop 和名为input的事件,但是像单选框、复选框等类型的输入控件可能会将value特性用于不同的目的model选项可以用来避免这样的冲突:

Vue.component('base-checkbox', {
  model: {
    prop: 'checked',
    event: 'change'
  },
  props: {
    checked: Boolean
  },
  template: `
    <input
      type="checkbox"
      v-bind:checked="checked"
      v-on:change="$emit('change', $event.target.checked)"
    >
  `
})

将原生事件绑定到组件:

<base-input v-on:focus.native="onFocus"></base-input>

当base-input被重构为一个非input类型的组件时,事件监听会默认的失败。Vue 提供了一个$listeners属性,它是一个对象,里面包含了作用在这个组件上的所有监听器。

有了这个$listeners属性,你就可以配合v-on="$listeners"将所有的事件监听器指向这个组件的某个特定的子元素。对于类似<input>的你希望它也可以配合v-model工作的组件来说,为这些监听器创建一个类似下述inputListeners的计算属性通常是非常有用的:

Vue.component('base-input', {
  inheritAttrs: false,
  props: ['label', 'value'],
  computed: {
    inputListeners: function () {
      var vm = this
      // `Object.assign` 将所有的对象合并为一个新对象
      return Object.assign({},
        // 我们从父级添加所有的监听器
        this.$listeners,
        // 然后我们添加自定义监听器,
        // 或覆写一些监听器的行为
        {
          // 这里确保组件配合 `v-model` 的工作
          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修饰符:

把title作为prop传进子组件,添加v-on监听器:

<text-document v-bind:title.sync="doc.title"></text-document>

在子组件中:

this.$emit('update:title', newTitle)。

当我们用一个对象同时设置多个 prop 的时候,也可以将这个.sync修饰符和v-bind配合使用:

<text-document v-bind.sync="doc"></text-document>

这样会把doc对象中的每一个属性 (如title) 都作为一个独立的 prop 传进去,然后各自添加用于更新的v-on监听器。

4.插槽:

Vue 实现了一套内容分发的 API,将元素作为承载分发内容的出口。

具名插槽:

base-layout组件的模板:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

向具名插槽提供内容:

<base-layout>
  <template slot="header">
    <h1>Here might be a page title</h1>
  </template>

  <p>A paragraph for the main content.</p>
  <p>And another one.</p>

  <template slot="footer">
    <p>Here's some contact info</p>
  </template>
</base-layout>

等价于

<base-layout>
    <h1 slot="header">Here might be a page title</h1>
    <p>A paragraph for the main content.</p>
    <p>And another one.</p>
    <p slot="footer">Here's some contact info</p>
</base-layout>

默认插槽:

它会作为所有未匹配到插槽的内容的统一出口。

插槽的默认内容:

在标签的内部制定默认内容。

注意: 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。

作用域插槽:

可从子组件获取数据的可复用的插槽。

todo-list组件模板:

<ul>
  <li
    v-for="todo in todos"
    v-bind:key="todo.id"
  >
    <!-- 我们为每个 todo 准备了一个插槽,-->
    <!-- 将 `todo` 对象作为一个插槽的 prop 传入。-->
    <slot v-bind:todo="todo">
      <!-- 回退的内容 -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

通过slot-scope特性从子组件取出数据:

<todo-list v-bind:todos="todos">
  <!-- 将 `slotProps` 定义为插槽作用域的名字 -->
  <template slot-scope="slotProps">
    <!-- 为待办项自定义一个模板,-->
    <!-- 通过 `slotProps` 定制每个待办项。-->
    <span v-if="slotProps.todo.isComplete"></span>
    {{ slotProps.todo.text }}
  </template>
</todo-list>

解构:

<todo-list v-bind:todos="todos">
  <template slot-scope="{ todo }">
    <span v-if="todo.isComplete">✓</span>
    {{ todo.text }}
  </template>
</todo-list>

5.动态组件&&异步组件:

动态组件:

在一个多标签的界面中使用is特性来切换不同的组件:

<component v-bind:is="currentTabComponent"></component>

当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题,可以用一个<keep-alive>元素将其动态组件包裹起来。

<keep-alive>
  <component v-bind:is="currentTabComponent"></component>
</keep-alive>

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

异步组件:

在大型应用中,我们可能需要将应用分割成小一些的代码块,并且只在需要的时候才从服务器加载一个模块。为了简化,Vue 允许你以一个工厂函数的方式定义你的组件,这个工厂函数会异步解析你的组件定义。Vue 只有在这个组件需要被渲染的时候才会被触发,且会把结果缓存起来供未来重渲染。

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

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

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

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

6.处理边界的情况:(有几个点没看懂)

访问根实例: this.$root

访问父级组件实例: this.$parent

访问子组件实例或元素: this.$refs

<base-input ref="usernameInput"></base-input>

this.$refs.usernameInput

注:$refs只会在组件渲染完成之后生效,并且它们不是响应式的。

依赖注入:provideinject

provide:允许我们指定我们想要提供给后代组件的数据/方法。

provide: function () {
  return {
    getMap: this.getMap
  }
}

inject:在任何后代组件里,我们都可以使用inject选项来接收指定的我们想要添加在这个实例上的属性:

inject: ['getMap']

注意:

依赖注入还是有负面影响的。它将你的应用目前的组件组织方式耦合了起来,使重构变得更加困难。同时所提供的属性是非响应式的。这是出于设计的考虑,因为使用它们来创建一个中心化规模化的数据跟 href="cn.vuejs.org/v2/guide/co…访问根实例">使用$root做这件事都是不够好的。如果你想要共享的这个属性是你的应用特有的,而不是通用化的,或者如果你想在祖先组件中更新所提供的数据,那么这意味着你可能需要换用一个像Vuex这样真正的状态管理方案了。

强制更新: $forceUpdate

href="cn.vuejs.org/v2/guide/co…通过-v-once-创建低开销的静态组件">通过 v-once 创建低开销的静态组件:

大量静态内容。在这种情况下,你可以在根元素上添加v-once特性以确保这些内容只计算一次然后缓存起来。如下:

Vue.component('terms-of-service', {
  template: `
    <div v-once>
      <h1>Terms of Service</h1>
      ... a lot of static content ...
    </div>
  `
})