组件

Vue.component("item7", {
        template: "<div v-on:click='add()'>{{counter}}</div>",
        data:function(){
            return {counter:0}
        },
        methods:{
            add:function(){
                this.counter+=1;
                this.$emit("increment");
            }
        }
    });
new Vue({
el:"#app",
data:{
  total:0
},
methods:{
incrementTotal:{
  return this.total++;
}

}
});
<item7 v-on:increment="incrementTotal"></item7>

渲染后的DOM绑定click事件add().点击组件的局部变量counter+1;this.$emit("increment")触发increment事件,increment事件对应的incrementTotal

什么是组件

组件是Vue.js最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以是原生HTML元素的形式,以js特性展开。

使用组件

注册

之前说过,我们可以通过以下方式创建一个Vue实例:

new Vue({

  el:"#app",

  //选项

});

要注册一个全局组件,你可以使用Vue.component(tagName,options).

Vue.component("todo-item",{

  //选项

});

对于自定义标签名,Vue.js不强制要求遵循W3C规则(小写,并且包含一个短杠),尽管遵循这个规则比较好

组件在注册只有,便可以在父实例的模块中以自定义元素<todo-item></todo-item>的形式使用。要确保在初始化

根实例之前注册组件:

/ 注册
Vue.component('my-component', {
  template: '<div>A custom component!</div>'
})
// 创建根实例
new Vue({
  el: '#example'
})
<div id="example">
  <my-component></my-component>
</div>

渲染为:

<div id="example">
  <div>A custom component!</div>
</div>
A custom component!

局部注册

不必在全局注册每一个组件,通过使用组件实例选项注册,可以使组件仅在另一个实例/组件的作用域可用

demo:

var child={

  template:'<div>this is a custom component!</div>'

};

new Vue({

  el:"#app",

  component:{
    <!--my-component 只能在父模板中使用-->
    "my-component":child
    }

});

DOM模板解析说明

当使用DOM作为模板时,(例如,将el 选项挂载到一个已存在的元素上),你会收到HTML的一些限制,因为Vue只有在浏览器解析和标准化HTML后才能获取模板内容。尤其像这些元素<ul> <ol> <tabel>  <select>限制了能被它包裹的元素,<option>只能出现在其他元素的内部。

在自定义组件中使用这些受限制的元素时会导致一些问题,例如:

<table>

  <my-row></my-row>

</table>

上面的自定义组件被认为是无效的内容,因此在渲染的时候会导致错误。变通的方案是使用特殊的 is 属性:

<table>

  <row is="my-row"></row>

</table>

应当注意,如果您使用来自以下来源之一的字符串模板,这些限制将不适用:

  • <script type="text/x-template">
  • javascript 内联模板字符串
  • .vue组件

因此,有必要的话请使用字符串模板

data必须是函数

通过Vue构造器传入的各种选项大多数都可以在组件里用。data是一个例外。它必须是一个函数。实际上,如果你这样做:

Vue.component('my-component', {
  template: '<span>{{ message }}</span>',
  data: {
    message: 'hello'
  }
})

那么Vue会停止,并在控制台发出警告,告诉你咋组件中data必须是一个函数。理解这种规则的存在意义很有帮助,让我们假设用如下的方式来绕开Vue的警告:

var data={counter:0};
Vue.component("todo-item", { template: "<li v-on:click='counter+=1'>this is my first global template::::{{ counter }}</li>", data: function () { return data; } });
<ul>
    <todo-item></todo-item>
    <todo-item></todo-item>
</ul>

以为这两个组件共享同一个data,因此counter值改变会影响所有的组件!我们可以通过为每一个组件返回全新的data 对象来解决这个问题:

Vue.component("todo-item", {
    template: "<li  v-on:click='counter+=1'>this is my first global template::::{{ counter }}</li>",
    data: function () {
        return {counter:0};
    }
});

构成组件

组件意味着协同工作,通常父子组件会是这样的关系:组件A在它的模板中使用了组件B。它们之间必然需要相互通信:父组件要给子组件传递数据,子组件需要将内部发生的事情告知父组件。然而你,在良好定义的接口中尽可能将父子组件解耦是很重要的。这保证了没有个组件可以在相对隔离的环境中书写和理解,也大幅提高了组价的可维护性和可重用性。

在Vue.js中,父子组件的关系可以总结为 props down,events up.父组件通过props向下传递数据给子组件,子组件通过events给父组件发送信息。看看它们是怎么工作的

Prop

使用Prop传递数据

组件实例的作用域是孤立的。这意味着不能(也不应该)在子组件么模板内引用父组件的数据。要让子组件使用父组件的数据,我们需要通过子组件的props选项。

子组件要显式地用props选项声明它期待获得的数据:

 Vue.component("item4", {
        props: ['number'],
        template: "<div v-on:click='++number'>{{number}}</div>"
    });
<item4 number=0></item4> //点击元素 number加1

camelCase vs.kebab-case

HTML特性是不区分大小写的。所以,当使用的不是字符串模板,camlcased(驼峰式)命名的prop需要扎UN哈UN为对应的kebab-case(短横线隔开式)命名:

Vue.component("item5", {
        props: ['myMessage'],
        template: "<div>{{myMessage}}</div>" });
<item5 my-message="hello"></item5>

如果你使用字符串模板,则没有这些限制。

动态props

在模板中,要动态地绑定父组件的数据到子组件的props,与绑定任何普通的HTML特性相类似,就是用v-bind。每当父组件的数据变化时,改变化也会传递给子组件:

 Vue.component("item6", {
        props: ['number'],
        template: "<div v-on:click='++number'>{{number}}</div>"
    });
 <input type="text" v-model="counter">
  <item6 v-bind:number="counter"></item6>

字面量语法vs动态语法

初学者常犯的一个错误是使用字面量语法传递数值:

<!--传递一个字符串“1”-->

<comp some-prop="1"></comp>

因为它是一个字面prop,它的值是字符产“1”而不是number。如果想传递一个实际的number,需要使用v-bind,从而让它的值被当做javascript

表达式计算:

<!--传递实际的number-->

<comp v-bind:some-prop="1"></comp>

单向数据流

prop是单向绑定的:当父组件的属性变化时,将传递给子组件,但是不会反过来。这是为了防止子组件无意修改了父组件的状态---这会让应用的数据流难以理解。

另外,每次父组件更新时,子组件的所有prop都会更新为最新值。这意味着你不应该在子组件内部改变prop。如果你这样做了。Vue会在控制台给出警告

为什么我们会有修改prop中数据的冲动呢?通常是这两种原因:

1 prop作为初始值传入后,子组件想要把它当做局部数据来用

2 prop作为初始值传入,由子组件处理成其他数据输出。

对这两种原因,正确的对应方式是:

1 定义一个局部变量,并用prop的值初始化它:

props:['initData'],

data:function(){

  return counter=this.initData
}

2 定义一个计算属性,处理prop的值并返回

props:['size'],

computed:{

  normalizedSize:function(){

    return this.size.trim.toLowerCase()
  }

}

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

demo

Vue.component("item6", {
    props: ['number'],
    template: "<div v-on:click='++number.value'>{{number.value}}</div>"
});
<input type="text" v-model="counterObj.value">
<item6 v-bind:number="counterObj"></item6>
传递的参数是一个对象,子组件会影响到父组件,如果是一个数组会有同样的情况发生

Prop验证

我们可以为组件的props指定验证规格。如果传入的数据不符合规则。Vue会发出警告。当组件给其他人使用时,这很有用。
要指定验证规格,需要用对象的形式,而不是用字符串数组:
Vue.component('example', {
  props: {
    // 基础类型检测 (`null` 意思是任何类型都可以)
    propA: Number,
    // 多种类型
    propB: [String, Number],
    // 必传且是字符串
    propC: {
      type: String,
      required: true
    },
    // 数字,有默认值
    propD: {
      type: Number,
      default: 100
    },
    // 数组/对象的默认值应当由一个工厂函数返回
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 自定义验证函数
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

type 可以是下面原生构造器:

  • String
  • Number
  • Boolean
  • Function
  • Object
  • Array

type 也可以是一个自定义构造器函数,使用instanceof 检测。

当prop验证失败,Vue会在抛出警告

自定义事件

我们知道,父组件是使用props传递数据给子组件,但如果子组件要把数据传递回去,应该怎么做?那就是自定义事件

使用v-on绑定自定义事件

每个Vue实例都实现了事件接口,即:

使用$on(eventName)监听事件

使用$emit(eventName)触发事件

Vue是事件系统分离自浏览器的EventTarget API.尽管它们的运行类似,但是$on 和$emit不是addEventListener 和dispatchEvent的别名。

另外,父组件可以在使用子组的地方直接用v-on来监听子组件触发的事件。

不能用$on 侦听子组件抛出的事件,而必须在模板里直接用v-on绑定,就像下面额例子:

下面是一个例子:

Vue.component("item7", {
        template: "<div v-on:click='add()'>{{counter}}</div>",
        data:function(){
            return {counter:0}
        },
        methods:{
            add:function(){
                this.counter+=1;
                this.$emit("increment");
            }
        }
    });
new Vue({
  el:"#app",
  data:{
    total:0
  },
  methods:{
  incrementTotal:function(){
      return this.total++;
  }
}
});
<div>{{total}}</div>
<item7 v-on:increment="incrementTotal"></item7>
<item7 v-on:increment="incrementTotal"></item7>

渲染后的html,绑定了点击事件,add,对局部变量counter+1.同时触发this.$emit("increment"),increment事件,increment事件对应的方法是incrementTotal方法,对父元素的total变量进行操作

在本例中,子组件已经和它外部完全解耦。它所做的只是报告自己的内部事情,至于父组件是否关心则与它无关。

给组件绑定原生事件

有时候,你可能想在某个组件的根元素上监听一个原生事件,可以使用.native修饰v-on。例如:

<my-component v-on:click.native="doTheTing"></my-component>

使用自定义事件的表单输入组件

自定义事件可以用来创建自定义的表单输入组件,使用v-model来进行双向数据绑定,看看这个:

<input type="text" v-model="something">

这不过是一下示例的语法糖:

<input type='text' v-bind:value="somthing" v-on:input="something=$event.target.value">

$event.target 获取触发事件的元素

所以在组件中使用时,它相当于下面的简写:

<input type='text' v-model="something">

所以要让组件的v-model生效,它必须:

  • 接受一个 value 属性
  • 在有新的vlaue时触发input事件

demo:

<currency-input label="Price"  v-model="price"></currency-input>
<currency-input  label="Shipping"  v-model="shipping"></currency-input>
<currency-input  label="Handling" v-model="handling"></currency-input>
<currency-input  label="Discount" v-model="discount"></currency-input>
  <p>Total: ${{ total }}</p>
Vue.component('currency-input', {
  template: '
    <div>
      <label v-if="label">{{ label }}</label>
      $
      <input
        ref="input"
        v-bind:value="value"
        v-on:input="updateValue($event.target.value)"
        v-on:focus="selectAll"
        v-on:blur="formatValue"
      >
    </div>
  ',
  props: {
    value: {
      type: Number,
      default: 0
    },
    label: {
      type: String,
      default: ''
    }
  },
  mounted: function () {
    this.formatValue()
  },
  methods: {
    updateValue: function (value) {
      var result = currencyValidator.parse(value, this.value)
      if (result.warning) {
        this.$refs.input.value = result.value
      }
      this.$emit('input', result.value)
    },
    formatValue: function () {
      this.$refs.input.value = currencyValidator.format(this.value)
    },
    selectAll: function (event) {
      // Workaround for Safari bug
      // http://stackoverflow.com/questions/1269722/selecting-text-on-focus-using-jquery-not-working-in-safari-and-chrome
      setTimeout(function () {
          event.target.select()
      }, 0)
    }
  }
})

new Vue({
  el: '#app',
  data: {
    price: 0,
    shipping: 0,
    handling: 0,
    discount: 0
  },
  computed: {
    total: function () {
      return ((
        this.price * 100 + 
        this.shipping * 100 + 
        this.handling * 100 - 
        this.discount * 100
      ) / 100).toFixed(2)
    }
  }
})

事件接口不仅仅可以用来连接组件内部的表单输入,也很容易集成你自己创造的输入类型。想象一下:

<voice-recognizer v-model="question"></voice-recognizer>
<webcam-gesture-reader v-model="gesture"></webcam-gesture-reader>
<webcam-retinal-scanner v-model="retinalImage"></webcam-retinal-scanner>

非父子组件通信

有时候两个组件也需要通信(非父子关系),在简单的场景下,可以使用一个空的Vue实例作为中央事件总线:

var bus=new Vue();

//触发组件A中的事件

bus.$emit('id-selected',1);

//在组件B创建的钩子中监听事件

bus.$on('id-selected',function(id){

//....

});

在复杂的情况下,我们应该考虑使用专门的状态管理模式。

使用slot分发内容

在使用组件时,我们通常要像这样组合它们:

<app>

  <app-header></app-header>

  <app-footer></app-footer>

</app>

注意两点:

  1. <app>组件不知道它的挂载点有什么内容。挂载点的内容是由<app>的父组件决定的
  2. <app>组件很可能有它自己的模板

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发(或‘transclusion’如果你熟悉Angular)。

Vue.js实现了一个内容分发API,参照了当前web组件规范草案,使用特殊的<slot>元素作为原始内容的插槽。

编译作用域

在深入内容分发API之前,我们先明确内容在哪个作用域里编译。假定模板为:

<child-component>{{message}}</child-component>

message应该绑定到父组件的数据,还是绑定到子组件的数据?答案是父组件,组件作用域简单地说是:

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

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

<!--无效-->

<child-component v-shoe="someChildProperty"></child-component>

假定someChildProperty是子组件的属性,上栗不会如逾期那样工作。父组件模板不应该知道子组件的状态。

如果要绑定作用域内的指令到一个组件的根节点,你应当在组件自己的模板上做:

Vue.component("child-component",{

  template:'<div v-show="someChildProperty"></div>',

  data:function(){

    return someChildProperty:true
  }

});

类似地,分发内容是在副作用域内编译。

单个slot

除非自组件模板包含至少一个<slot>插口,否则父组件的内容将会被丢弃。当子组件模板只有一个属性的slot时,父组件整个内容片断将插入到slot坐在的DOM位置,并替换掉slot标签本身。

最初在<slot>标签中的任何内容都被视为备用内容,备用内容在子组件的作用域内编译。并且只有在宿主元素为空,且没有要插入的内容时才显示备用内容。

假定 my-component组件有下面的模板:

<div>

  <h2>我是子组件的标题</h2>

  <slot>只有在没有要分发的内容时才会显示</slot>

</div>

父组件模板:

<div>

  <h2>我是父组件的标题</h2>

  <my-component>

    <p>这是一些初始内容</p>
    <p>这是一些更多的初始内容</p>

  </my-component>

</div>

渲染后:

<div>
    <h2>我是父组件的标题</h2>
        <div>
            <h2>我是子组件的标题</h2>
             <p>这是一些初始内容</p>
           <p>这是一些更多的初始内容</p>
        </div>
</div>

如果子组件没有slot插口

渲染后:

<div>
    <h2>我是父组件的标题</h2>
        <div>
            <h2>我是子组件的标题</h2>
             <p>这是一些初始内容</p>
           <p>这是一些更多的初始内容</p>
        </div>
</div>

如果父组件使用子组件内部没有初始内容,渲染后:

<div>
    <h2>我是父组件的标题</h2>
        <div>
            <h2>我是子组件的标题</h2>
             只用在没有分发的内容时才会显示
        </div>
</div>

具名slot

<slot>元素可以用一个特殊的属性 name 来配置如何分发内容。多个slot可以有不同的名字。具名slot将匹配内容片断中有对应 slot 特性的元素。

仍然可以有个匿名slot,它是默认slot,作为找不到匹配的内容片断的裴勇插槽。如果没有默认的slot,这些找不到匹配的内容片断将会被抛弃。

例如,假定我们有一个 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>

渲染结果为:(slot---header 找到对应的header 插口,slot-layout找到中对应的footer插口,没有标明slot的内容,匹配到没有具名的slot插口)

<div class="container">
  <header>
    <h1>这里可能是一个页面标题</h1>
  </header>
  <main>
    <p>主要内容的一个段落。</p>
    <p>另一个主要段落。</p>
  </main>
  <footer>
    <p>这里有一些联系信息</p>
  </footer>
</div>

在组合组件时,内容分发 API 是非常有用的机制。

作用域插槽

作用域插槽是一种特殊类型的插槽,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。

在子组件中,只需将数据传递到插槽,就像你将prop传递给组件一样:

Vue.component('slot-component3',{
        props:['items'],
        template:'<ul>' +
        '<slot  name="item" v-for="item in items" :text="item.labels"></slot>' +
        '</ul>'
    });
data:{

  items:[

  {labels: "name1"},
  {labels: "name2"},
  {labels: "name3"},
  {labels: "name4"},
  {labels: "name5"},
  {labels: "name6"}
  ]

}
<slot-component3 :items="items">
    <template slot="item" scope="props">
        <li class="my-fancy-item">{{ props.text}}</li>
    </template>
</slot-component3>

渲染后:

<ul>
    <li class="my-fancy-item">name1</li>
    <li class="my-fancy-item">name2</li>
    <li class="my-fancy-item">name3</li>
    <li class="my-fancy-item">name4</li>
    <li class="my-fancy-item">name5</li>
    <li class="my-fancy-item">name6</li>
</ul>

上面的demo中子元素通过props接受父组件传递的参数,在父组件中使用 slot-component3 组件,通过v-bind:items给子组件传递参数,在具有特殊属性scope的template上指定slot插口为name=‘item’,指定scope,scope的值对应一个临时变量名,此变量接受从子组件传递的prop对象,在子组件中绑定的是属性text,所以在

取的是props.text,也可以绑定到别的属性上,只要前后对应即可(   演示出错点   1 template拼写错误---报错 2 子组件没有写props参-----报错items未定义)

动态组件

通过使用保留的<component>元素,动态的绑定到它的 is 特性,我们可以让多个组件使用同一个挂载点,并动态切换

data:{

  home:'home',

  foot:'foot',

  component:{

    home:{

      template:'<div>this is a home component</div>'
    },
    foot:{

      template:'<div>this is a foot component</div>'
    }
  }

}
<component v-bind:is="home"></component>
<component v-bind:is="foot"></component>

(   演示出错点  :

1 template的值没有标签元素包裹,直接写一个字符串---组件不能使用----笑哭

2 使用html保留字符 footer ----不能使用组件  ====改为foot

你也可以直接绑定到组件对象上:

var component1={
        template:'<div>this is a component1</div>'
    };
new Vue("component1",{

  template:component1
});

或者

data:{

  component1:'component1',
  component:{

    component1:component1
  }
}

keep-alive

如果把切换出去的组件保留在内存中,可以保留它的状态或避免重复渲染。为此可以添加一个keep-alive指令参数:

<keep-alive>

  <component :is='currentView'> 

    <!--非活动组件将被缓存!-->
  </component>

</keep-alive>

杂项

编写可复用的组件

在编写组件时,记住是否要复用组件有好处。一次性组件跟其他组件紧密耦合没关系,但是可复用组件应当定义一个清晰的公开接口。

  1. Vue组件的API来自三部分 ---props events 和slots
  2. Props允许外部环境传递数据给组件
  3. Events允许组件触发外部环境的副作用
  4. Slots 允许外部环境将额外的内容组合在组件中

使用v-bind和v-on的简写语法,模板的缩进清晰且简洁:

<my-component
  :foo="baz"
  :bar="qux"
  @event-a="doThis"
  @event-b="doThat"
>
  <img slot="icon" src="...">
  <p slot="main-text">Hello!</p>
</my-component>

子组件索引 ref

尽管有props和events,但是有时任然需要在JavaScript中直接访问子组件,为此可以使用 ref 为子组件指定一个索引ID.例如:

<div id="parent">
  <user-profile ref="profile"></user-profile>
</div>
var parent = new Vue({ el: '#parent' })
// 访问子组件
var child = parent.$refs.profile

 当ref 和v-for 一起使用时,ref是一个数组或者对象,包含相应的子组件。

$refs 只在组件渲染完成后才填充,并且它是非响应式的,它仅仅作为一个直接访问子组件的应急方案----应当避免在模板或计算属性中输赢$refs.

异步组件

在大型应用中,我们可能需要将应用拆分为多个小模块,按需从服务器下载。为了让事情更简单。Vue.js允许将组件定义为一个工厂函数,动态的解析组件的定义。Vue.js

只在组件需要渲染时触发工厂函数,并且将结果缓存起来,用于后面的再次渲染。

Vue.component("my-async-example",function(resovle,reject){

  setTimeout(function(){

    template:'<div>this is a anync example</div>'
  },1000);

});

工厂函数接收一个resolve回调,在收到从服务器下载的组件定义时调用。也可以调用reject指示加载失败。这里setTimeout只是为了演示。怎么获取组价完全由你决定。

推荐配合使用:Webpack的代码分割功能:

Vue.component("async-webpack-example",function(resolve){

  //这是特殊的require 语法告诉 webpack 自动将编译后的代码分割成不同的块

  // 这些块将通过Ajax 请求自动下载  
  require([../my-async-component''],resolve);

});

你可以使用Webpack 2+ES2015 的语法返回一个Promise resolve函数:

Vue.component('async-webpack-example',()=>System.import('../my-async-component'));

如果你是 Browserify 用户,可能就无法使用异步组件了,它的作者已经表明 Browserify 是不支持异步加载的。Browserify 社区发现 一些解决方法,可能有助于已存在的复杂应用。对于其他场景,我们推荐简单实用 Webpack 构建,一流的异步支持

组件命名约定

当注册组件(或者 props)时,可以使用 kebab-case ,camelCase ,或 TitleCase 。Vue 不关心这个。

// 在组件定义中
components: {
// 使用 kebab-case 形式注册
'kebab-cased-component': { /* ... */ },
// register using camelCase
'camelCasedComponent': { /* ... */ },
// register using TitleCase
'TitleCasedComponent': { /* ... */ }
}

在 HTML 模版中,请使用 kebab-case 形式:

<!-- 在HTML模版中始终使用 kebab-case -->
<kebab-cased-component></kebab-cased-component>
<camel-cased-component></camel-cased-component>
<title-cased-component></title-cased-component>

当使用字符串模式时,可以不受 HTML 的 case-insensitive 限制。这意味实际上在模版中,你可以使用 camelCase 、 TitleCase 或者 kebab-case 来引用:

<!-- 在字符串模版中可以用任何你喜欢的方式! -->
<my-component></my-component>
<myComponent></myComponent>
<MyComponent></MyComponent>

如果组件未经slot元素传递内容,你甚至可以在组件名后使用/使其自闭和:

<my-component/>

当然,这只在字符串模板中有效。因为自闭的自定义元素是无效的HTML,浏览器原生的解析器也无法识别它。

递归组件

组件在它的模板内可以递归地调用自己,不过,只有它有name选项时才可以:

name:'unique-name-of -my-component'

当你利用Vue.component全局注册一个组件,全局的ID作为组件的name 选项,被自动设置。

Vue.component('unique-name-of my-component',{//...});

如果你不谨慎,递归组件可能导致死循环:

name:'stack-overflow',

template:'<div><stack-overflow></stack-overflow></div>'

上面的组件会导致一个错误‘max stack size exceeded’,所以要确保递归调用有终止条件,(比如递归调用时使用v-if并让他最终返回false)。

组件间的循环引用Circular References Between Component

假设你正在构建一个文件目录树,像在finder或我那件资源管理器中,你可能有一个tree-folder组件:

<p>

  <span></span>

  <tree-folder-contents :children='folder.children'/>

</p>

然后 一个tree-folder-contents组件:

<ul>
<li v-for="child in children">
<tree-folder v-if="child.children" :folder="child"/>
<span v-else>{{ child.name }}</span>
</li>
</ul>

为了解释为什么会报错,简单的将上面两个组件称为 A 和 B ,模块系统看到它需要 A ,但是首先 A 需要 B ,但是 B 需要 A, 而 A 需要 B,陷入了一个无限循环,因此不知道到底应该先解决哪个。要解决这个问题,我们需要在其中一个组件中(比如 A )告诉模块化管理系统,“A 虽然需要 B ,但是不需要优先导入 B”
在我们的例子中,我们选择在tree-folder 组件中来告诉模块化管理系统循环引用的组件间的处理优先级,我们知道引起矛盾的子组件是tree-folder-contents,所以我们在beforeCreate 生命周期钩子中去注册它:

beforeCreate: function () {
this.$options.components.TreeFolderContents = require('./tree-folder-contents.vue')
}

问题解决了。

内联模板

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

<my-component inline-template>
<div>
<p>These are compiled as the component's own template.</p>
<p>Not parent's transclusion content.</p>
</div>
</my-component>

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

X-Templates

另一种定义模版的方式是在 JavaScript 标签里使用 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'
})

这在有很多模版或者小的应用中有用,否则应该避免使用,因为它将模版和组件的其他定义隔离了

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

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

Vue.component('my-once-component',{

  template:'<div v-once>this is my  once render component</div>'

});
原文地址:https://www.cnblogs.com/xiaofenguo/p/6555740.html