vue组件

目录

2. 局部注册

3. dom模板和字符串模板

  • 当使用dom模板时候要注意有些元素内包含的元素会受到限制

  • HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case

  • 当使用字符串模板时不会受到以上两点限制

4.父子组件的关系

prop 向下传递,事件向上传递。

父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。

例子

//子组件内容
Vue.component('child', {
  // 声明 props
  props: ['message'],
  // 就像 data 一样,prop 也可以在模板中使用
  // 同样也可以在 vm 实例中通过 this.message 来使用
  template: '<span>{{ message }}</span>'
})

//父组件调用子组件
<child message="hello!"></child>

//结果
hello!

以上使用字面量语法,当需要向子组件传递动态内容可以使用,使用动态prop

//父组件中
<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

//v-bind 的缩写语法:
<child :my-message="parentMsg"></child>
//如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind (即用 v-bind 而不是 v-bind:prop-name)。例如,已知一个 todo 对象:
todo: {
  text: 'Learn Vue',
  isComplete: false
}

//然后:
<todo-item v-bind="todo"></todo-item>
//将等价于:
<todo-item
  v-bind:text="todo.text"
  v-bind:is-complete="todo.isComplete"
></todo-item>

5. 字面量语法vs动态语法

不能使用字面量语法传递数值

<!-- 传递了一个字符串 "1" -->
<comp some-prop="1"></comp>

<!-- 传递真正的数值 -->
<comp v-bind:some-prop="1"></comp>

6. 在子组件中修改父组件数据

prop是单向绑定的,父组件属性变化时候将传导给子组件,但是反过来不会,不能在子组件中改变prop

但是我们忍不住想修改它

正确的应对方式是:

//定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'],
data: function () {
  return { counter: this.initialCounter }
}
//定义一个计算属性,处理 prop 的值并返回:
props: ['size'],
computed: {
  normalizedSize: function () {
    return this.size.trim().toLowerCase()
  }
}

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

6. prop验证

7. 非prop特性

所谓非 prop 特性,就是指它可以直接传入组件,而不需要定义相应的 prop。

尽管为组件定义明确的 prop 是推荐的传参方式,组件的作者却并不总能预见到组件被使用的场景。所以,组件可以接收任意传入的特性,这些特性都会被添加到组件的根元素上。

//在父组件上添加 data-3d-date-picker="true"
<bs-date-input data-3d-date-picker="true"></bs-date-input>

//属性 data-3d-date-picker="true" 会被自动添加到bs-date-input的根元素上。

替换或合并现有特性

当根元素和父组件都设置了同一个属性怎么处理?

//假设这是 bs-date-input 的模板:
<input type="date" class="form-control">

//为了给该日期选择器插件增加一个特殊的主题,我们可能需要增加一个特殊的 class,比如:
<bs-date-input
  data-3d-date-picker="true"
  class="date-picker-theme-dark"
></bs-date-input>

在这个例子当中,我们定义了两个不同的 class 值:

  • form-control,来自组件自身的模板
  • date-picker-theme-dark,来自父组件

对于多数特性来说,传递给组件的值会覆盖组件本身设定的值。即例如传递 type="large" 将会覆盖 type="date" 且有可能破坏该组件!所幸我们对待 class 和 style 特性会更聪明一些,这两个特性的值都会做合并 (merge) 操作,让最终生成的值为:form-control date-picker-theme-dark。

8. 子组件向父组件通信,自定义事件

当子组件想和父组件通信时候,可以使用自定义事件

//父组件
<div >
  <p>{{ total }}</p>
  <!-- v-on:increment 给子组件一个可以调用increment事件 -->
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>
Vue.component('button-counter', {
  template: '<button v-on:click="incrementCounter">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    incrementCounter: function () {
      this.counter += 1
      //点击子组件中的时间后,调用父组件中绑定的事件
      this.$emit('increment')
    }
  },
})

new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    //子组件和父组件通信后,调用自身的方法,来改变父组件中的属性
    incrementTotal: function () {
      this.total += 1
    }
  }
})

9. 双向通信,.sync修饰符

//如下代码
<comp :foo.sync="bar"></comp>

//会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>

//当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)

10. 非父子组件间通信

可以使用状态管理模式

11. 使用插槽分发内容

作用域,父组件模板的内容在父组件作用域内编译;子组件模板的内容在子组件作用域内编译。

一个常见错误是试图在父组件模板内将一个指令绑定到子组件的属性/方法:

<!-- 无效 -->
<child-component v-show="someChildProperty"></child-component>
  • 单个插槽
//假定 my-component 组件有如下模板:
<div>
  <h2>我是子组件的标题</h2>
  <slot>
    只有在没有要分发的内容时才会显示。
  </slot>
</div>

//父组件模板:
<div>
  <h1>我是父组件的标题</h1>
  <my-component>
    <p>这是一些初始内容</p>
    <p>这是更多的初始内容</p>
  </my-component>
</div>

//渲染结果:
<div>
  <h1>我是父组件的标题</h1>
  <div>
    <h2>我是子组件的标题</h2>
    <p>这是一些初始内容</p>
    <p>这是更多的初始内容</p>
  </div>
</div>
  • 具名插槽
//例如,假定我们有一个 app-layout 组件,它的模板为:
<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

//父组件模板:
<app-layout>
  <h1 slot="header">这里可能是一个页面标题</h1>

  <p>主要内容的一个段落。</p>
  <p>另一个主要段落。</p>

  <p slot="footer">这里有一些联系信息</p>
</app-layout>

//渲染结果为:
<div class="container">
  <header>
    <h1>这里可能是一个页面标题</h1>
  </header>
  <main>
    <p>主要内容的一个段落。</p>
    <p>另一个主要段落。</p>
  </main>
  <footer>
    <p>这里有一些联系信息</p>
  </footer>
</div>
  • 作用域插槽

实质上是通过子组件中的数据传递到父组件,更改父组件中已经渲染好的视图

//在子组件中,将数据传递到插槽
<div class="child">
  <slot text="hello from child"></slot>
</div>

//在父级中,具有特殊特性 slot-scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。slot-scope 的值将被用作一个临时变量名,此变量接收从子组件传递过来的 prop 对象:
<div class="parent">
  <child>
    <template slot-scope="props">
      <span>hello from parent</span>
      <span>{{ props.text }}</span>
    </template>
  </child>
</div>

//如果我们渲染上述模板,得到的输出会是:
<div class="parent">
  <div class="child">
    <span>hello from parent</span>
    <span>hello from child</span>
  </div>
</div>

应用

//作用域插槽更典型的用例是在列表组件中,允许使用者自定义如何渲染列表的每一项:
<my-awesome-list :items="items">
  <!-- 作用域插槽也可以是具名的 -->
  <li
    slot="item"
    slot-scope="props"
    class="my-fancy-item">
    {{ props.text }}
  </li>
</my-awesome-list>

//列表组件的模板:
<ul>
  <slot name="item"
    v-for="item in items"
    :text="item.text">
    <!-- 这里写入备用内容 -->
  </slot>
</ul>

12. 动态组件和keep-alive

var vm = new Vue({
  el: '#example',
  data: {
    currentView: 'home'
  },
  components: {
    home: { /* ... */ },
    posts: { /* ... */ },
    archive: { /* ... */ }
  }
})

<component v-bind:is="currentView">
  <!-- 组件在 vm.currentview 变化时改变! -->
</component>
//如果把切换出去的组件保留在内存中,可以保留它的状态或避免重新渲染。为此可以添加一个 keep-alive 指令参数:
<keep-alive>
  <component :is="currentView">
    <!-- 非活动组件将被缓存! -->
  </component>
</keep-alive>

13. 内联模板

如果子组件有 inline-template 特性,组件将把它的内容当作它的模板,而不是把它当作分发内容。这让模板编写起来更灵活。

<my-component inline-template>
  <div>
    <p>这些将作为组件自身的模板。</p>
    <p>而非父组件透传进来的内容。</p>
  </div>
</my-component>

但是 inline-template 让模板的作用域难以理解。使用 template 选项在组件内定义模板或者在 .vue 文件中使用 template 元素才是最佳实践

element ui中table自定义内容

<el-table :data="props.row.strategies" >
    //使用inline-template
    <el-table-column inline-template label="可访问用户组" width="180">
        <div>
            <el-tag v-for="user in row.user_group" :key="user.id">{{ user.name }}</el-tag>
        </div>
    </el-table-column>
    
    //使用slot-scop,官方推荐
    <el-table-column label="操作">
        <template slot-scope="strategyScope">
            <div>
                <el-tooltip content="编辑" placement="top" effect="light">
                    <el-button size="common" class="edit" type="text" @click="editStrategy(strategyScope.row)"><i class="el-icon-ui-edit"></i></el-button>
                </el-tooltip>
                <el-tooltip content="删除" placement="top" effect="light">
                    <el-button size="common" class="apply" type="text" @click="deleteStrategy(strategyScope.row)">
                        <i class="el-icon-delete2"></i></el-button>
                </el-tooltip>
            </div>
        </template>
    </el-table-column>
</el-table>

14. X-Template

另一种定义模板的方式是在 JavaScript 标签里使用 text/x-template 类型,并且指定一个 id。例如:

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

这在有很多大模板的演示应用或者特别小的应用中可能有用,其它场合应该避免使用,因为这将模板和组件的其它定义分离了。

15. 对低开销的静态组件使用 v-once

尽管在 Vue 中渲染 HTML 很快,不过当组件中包含大量静态内容时,可以考虑使用 v-once 将渲染结果缓存起来,就像这样:

Vue.component('terms-of-service', {
  template: '\
    <div v-once>\
      <h1>Terms of Service</h1>\
      ...很多静态内容...\
    </div>\
  '
})