Vue.js 教程 (8) : 组件系统
发布在Vue.js 中文入门2014年3月24日view:38677
在文章任何区域双击击即可给文章添加【评注】!浮到评注点上可以查看详情。

扩展 Vue

在讨论组件系统之前,我们首先得了解一下如何扩展 Vue 类。方法和 Backbone 非常相似:用 Vue.extend 方法即可:

var MyView = Vue.extend({
   template: '...',
   // 其他选项 ...
})
// 用这个函数创建多个实例
var view1 = new MyView(),
    view2 = new MyView()

大部分创建 Vue 实例时候的选项都可以作为扩展时的选项,除了 el,因为多个实例不能共享同一个 DOM 元素。扩展后的 VM 类称之为组件,因为它们所创建出来的每一个实例都会具有相同的行为,某种程度上,和 Web Components 里的 Custom Element (自定义元素)非常相似。

注册和使用组件

那么,能否和嵌套 HTML 元素那样嵌套 Vue 的组件呢?当然是可以的。不过首先,要先给我们刚创建的组件注册一个 id:

Vue.component('my-view', MyView)

为了方便,我们也可以不需要事先 Vue.extend,而是直接传入一个选项对象:

Vue.component('my-view', {
    template: '...',
    // ...
})

然后就可以在任意组件的模板里使用了:

<div>
    <div v-component="my-view"></div>
</div>

如果你喜欢 Custom Elements,甚至可以这样:

<div>
    <my-view></my-view>
</div>

需要注意的是,要作为 Custom Elements 使用,组件的 ID 中一定要带有一个短横 -。这是 W3C Custom Elements 标准草案中的一个规定。

组件封装

在创建自定义的指令、过滤器和组件的时候,我们并不总是希望把它们注册在 Vue.js 的全局作用域上,这样可能会产生命名冲突,不利于这些功能的复用。这种时候,我们可以把它们作为私有属性封装在一个组件内部:

var MyComponent = Vue.extend({
    directives: {
        // 这里的指令只能在 MyComponent 的实例和其子VM中使用!
        'private-directive': function () {
            // ...
        }
    },
    filters: {
        // ...
    },
    components: {
        // ...
    },
    effects: {
        // ...
    },
    partials: {
        // ...
    }
})

扩展后的组件类也具有和 Vue 一样的注册方法,且可以链式调用:

MyComponent
    .directive('my-directive', {})
    .filter('my-filter', {})
    .component('sub-component', {})
    //...

继承数据

子 VM 可以在模板中直接使用所有父级 VM 上存在的数据属性:

<div id="parent">
    <div id="child" v-component="Child"></div>
</div>
Vue.component('Child', {
    template: '{{parentName}}'
})

new Vue({
    el: '#parent',
    data: {
        parentName: 'Dad'
    }
})

结果会输出 Dad.

在使用组件的时候,我们有时候需要传入父VM上的一个数据对象,从而可以在子 VM 内部对其进行操作。我们可以用 v-with 来传入一个父VM上的对象,使得 v-component 创建子 VM 时,使用这个对象作为其 data 选项:

<div id="demo-1">
    <p v-component="user-profile" v-with="user"></p>
</div>
// 先注册一个组件
Vue.component('user-profile', {
    template: '{{name}}<br>{{email}}'
})
// user 对象将会被作为 `data` 提供给 user-profile 组件。
var parent = new Vue({
    el: '#demo-1',
    data: {
        user: {
            name: 'Foo Bar',
            email: 'foo@bar.com'
        }
    }
})

效果:http://jsfiddle.net/yyx990803/rxbWP/

同时,v-with 也可以用来实现将父级 vm 上的单独属性继承到子 vm 上:

<div id="demo-1">
    <p v-component="user-profile" v-with="name: user.name, email: user.email"></p>
</div>

此例中,父 vm 上的 user.nameuser.email 这两个属性将会继承到 user-profile 组件中,在组件本身的方法中可以用 this.namethis.email 来操作这两个属性。

v-repeat 中使用组件

之前我们使用 v-repeat 遍历数组的时候,Vue.js 会为数组中的每一个对象建立一个子 VM。我们可以将 v-componentv-repeat 组合使用,让每一个子 VM 都成为指定组件的实例:

<ul id="demo-2">
    <!-- 这里使用了上面注册的 user-profile 组件 -->
    <li v-repeat="users" v-component="user-profile"></li>
</ul>
var parent2 = new Vue({
    el: '#demo-2',
    data: {
        users: [
            {
                name: 'Chuck Norris',
                email: 'chuck@norris.com'
            },
            {
                name: 'Bruce Lee',
                email: 'bruce@lee.com'
            }
        ]
    }
})

效果:http://jsfiddle.net/yyx990803/9RMKR/

事件通信

嵌套的 VM 实例之间可以通过 Vue.js 的事件API来进行通信。每一个 VM 实例都具有以下方法:$on, $once, $off, $emit, $dispatch$broadcast。其中前三个应该作用很明显,后面的三个里面,$emit 是在 VM 自身上触发事件,$dispatch 会触发一个向上层 VM 冒泡的事件,而 $broadcast 则是向所有自身的子 VM 广播事件。

一个简单的例子:

var Child = Vue.extend({
    created: function () {
        this.$dispatch('child-created', this)
    }
})

var parent = new Vue({
    template: '<div v-component="child"></div>',
    components: {
        child: Child
    },
    created: function () {
        this.$on('child-created', function (child) {
            console.log('new child created: ')
            console.log(child)
        })
    }
})

直接获取子VM

虽然有方便的事件系统,有时候我们也会需要直接获得子 VM 对其进行操作。想要实现这一点需要在组件上使用 v-ref 指令来注册一个 reference id。当一个组件有 v-ref="id" 存在的时候,就可以用 parent.$[id] 来指向它。

<div id="parent">
    <div v-component="user-profile" v-ref="profile"></div>
</div>
var parent = new Vue({ el: '#parent' })
// 直接获取子VM
var child = parent.$.profile

如果 v-ref 是和 v-repeat 的组件一起使用,那么 parent.$[id] 会指向一个包含了所有 repeat VM 的数组。

<content> 内容插入点

假如我们的组件在实例化之前,其元素内部已经有内容存在,这些内容被称为原始内容。我们可以在组件的模板中用 <content> 元素来指定在哪里插入这些原始内容。Vue.js 的内容插入点实现参照了目前的 Web Components 规范草案。

假如我们有一个组件的模板是这样的:

<h1>This is my component!</h1>
<content>This will only be displayed if no content is inserted</content>

而组件外部的页面结构是这样的:

<div v-component="my-component">
    <p>This is some original content</p>
    <p>This is some more original content</p>
</div>

那么最后渲染出来的内容会是这样的:

<div>
    <h1>This is my component!</h1>
    <p>This is some original content</p>
    <p>This is some more original content</p>
</div>

同时,你也可以配合 select 属性使用多个 <content> 插入点。select 属性的值可以是任意的 CSS 选择器。原始内容中符合选择器的元素将会被插入到对应的 <content> 插入点。

假如有个组件的模板是这样的:

<content select="p:nth-child(3)"></content>
<content select="p:nth-child(2)"></content>
<content select="p:nth-child(1)"></content>

外部模板是这样的:

<div v-component="multi-insertion-component">
    <p>One</p>
    <p>Two</p>
    <p>Three</p>
</div>

最后的结果:

<div>
    <p>Three</p>
    <p>Two</p>
    <p>One</p>
</div>

组件实例

最后,这里是几个通过 Vue.js 实现的组件实例:

评论
发表评论
3年前
赞了此文章!
3年前
赞了此文章!
4年前
赞了此文章!
5年前
赞了此文章!
5年前
赞了此文章!
5年前
赞了此文章!
5年前
添加了一枚【评注】:还有data
5年前
添加了一枚【评注】:
5年前
添加了一枚【评注】:考虑过这钟注释带来的体验问题否
5年前

@rwu823 你正好赶上新版本发布了,引用了 0.11 版。新版本里面 child component 默认是 isolated scope,不继承 parent data,除非加上 inherit: ture 选项:http://jsfiddle.net/yyx990803/rxbWP/3/

5年前

實作後發現 parent 的 data 是傳不進去 child 的 http://jsfiddle.net/rxbWP/2/

5年前

@尤小右 抱歉,可以直接users[0], 我测试的时候users.length===0

5年前

@尤小右

这里如果希望w-with users[0] 这样的数据该怎么弄了? 我这儿有个怪异的需求,数组的中每个数据对象对应固定的不同样式

6年前

@尤小右 我后来就这么实现的。谢

6年前

@叉烧仔 可以在选项里加入一个 created 回调,在组件被创建的时候会调用它。在里面 this.a = 1 这样设置初始数据就可以了。http://vuejs.org/api/instantiation-options.html#created

6年前

如果定义 component 时 extend({data: obj}),创建出的多个 component 就会共享同一个 data = obj; 有没有办法使每个 data 都是新的实例。

6年前

@RuPyCer 一样的,也是根据模板中的嵌套关系自动建立父子关系。

6年前

在Angular中,Controller之间会按照DOM的父子关系形成对应的父子关系,这样在子Controller中可以直接访问父Controller中的数据,在Vue中支持这种实现么?还是必须靠components属性在代码中控制父子关系?

WRITTEN BY
尤小右
不会搞艺术的程序员不是好设计师
TA的新浪微博
PUBLISHED IN
Vue.js 中文入门

Vue.js 是一个轻巧、高性能、可组件化的MVVM库,同时拥有非常容易上手的API,让编写动态的UI界面变得轻松简单。本专栏是Vue.js的中文版入门教程。

我的收藏