[关闭]
@FarmerZ 2016-08-31T05:21:34.000000Z 字数 13092 阅读 530

vue.js 2.0 文档翻译

组件

什么是组件

组件是vue最有威力的特性之一。他帮助你扩展基础的html元素,并且包含可重用的代码。甚至可以说组件是你自定义的元素,vue的编译器可用来让他执行。在某些情况下他就是html元素,只不过拥有一个特殊的is属性。

使用组件

注册

从我们先前学过的一些知识中,我们知道可以用下面的方法创建一个vue实例。

  1. new Vue({
  2. el: '#some-element',
  3. //options..
  4. })

想要创建一个全局的组件,我们可以使用Vue.component(tagName,options)方法。eg

  1. Vue.component('my-component',{
  2. //options
  3. })

注意,vue不强制实行w3c 规则来创建自定义标签的名字,(都小写、包含连字符),但是根据这一标准来创建的话是一个好的实践。

注册完组件,就可以用它创建一个模版,并当做自定义元素放在页面中。
,确保在实例化一个vue之前,一个组件已经被注册了。下面有个完整的例子。

html

  1. <div id='example'>
  2. <my-component></my-componeng>
  3. </div>

js

  1. //register
  2. Vue.component('my-component',{
  3. template:'<div>A custom component!</div>'
  4. })
  5. //create a root instance
  6. new Vue({
  7. el:'#example'
  8. })

渲染结果

  1. <div id='example'>
  2. <div>A custom component!</div>
  3. </div>

本地注册

你不必把每个组件都注册为全局性的,你可以使用component这个实例选项在其他的组件或者实例中注册一个本地组件,该组件仅仅在其父级下是可用的。

  1. var child = {
  2. template : '<div>a custom component</div>'
  3. };
  4. new Vue({
  5. //..
  6. components:{
  7. 'my-component': child
  8. }
  9. })

这种封装特性也适用于其他可注册的vue属性,如指令。

组件选项说明

大多数可用在vue构造函数中的选项也可用在组件中,但是有两个比较特殊的例外————data和el,这两个必须用函数。如果你使用这样的写法
js

  1. Vue.componeng('my-component',{
  2. template:'<span>{{message}}</span>',
  3. data:{
  4. message:'hello'
  5. }
  6. })

vue将会停止并且在控制台发出警告,告诉你'data'在组件实例中必须使用一个函数来返回。这很容易理解,如果我们试下上面的组件
html

  1. <div id='example'>
  2. <simple-counter></simple-counter>
  3. <simple-counter></simple-counter>
  4. <simple-counter></simple-counter>
  5. </div>

js

  1. var data = {counter: 0};
  2. Vue.component('simple-counter', {
  3. template:'button v-on:click="counter += 1">{{counter}}</button>',
  4. //data是一个技术上的函数,所以vue不会有什么抱怨,但是我们返回了同一个对象,这个对象被每一个组件所引用。
  5. data: function(){
  6. return data;
  7. }
  8. })
  9. new Vue({
  10. el: '#example-2'
  11. })

因此,三个组件实例共享一个data对象,当其中一个增加是,其他两个也跟着增加。额,这不是我们想要的,我们可以做下面的更改来达到我们独立增加计数的目的。
js

  1. data:function(){
  2. return:{counter:0}
  3. }

经过上面的说做更改,事情会变的是我们想要的。
el选项也要求是个函数,道理相同。

模版解析说明

只要你使用字符串模版(,内联模版,或者vue组件,),你就不收html父级元素的限制。但是,如果你使用el选项来使用现成的元素当做模版。

在实际中,这些限制会导致意外结果。尽管在简单的情况下他可能可以工作,但是你不能依赖自定义组件在浏览器验证之前的展开结果。

另一个结果是你不能用自定义标签()包含ul,select,table以及其他首先得元素。自定义的标签会被提升,最终渲染或出错。

对于自定义元素应当使用is属性
html

  1. <table>
  2. <tr is='my-component'><tr>
  3. </table>

不能使用在table内,这时应该是用,table可使用多个tbody

html

  1. <table>
  2. <tbody v-for='item items'>
  3. <tr>Even row</tr>
  4. <tr>Odd row</tr>
  5. </tbody>
  6. </table>

再次提醒,这些限制不能够应用在string模板中。

props


使用props传递数据

每个组件都有自己独立的数据域,这意味着在自己的模版里不能直接访问父级数据。数据可以使用props向下传递给子模版。

一个prop是一个属性,用来传递父级膜组件的信息。一个子组件需要使用props选项显示地声明想要接收的数据。(因为父级是向下传的)。

js

  1. Vue.component('child', {
  2. //声明props
  3. props:['message'],
  4. template:'<span>{{{message}}</span>'
  5. })

然后我们可以传递数据了
html

  1. <child message='hello'></child>

驼峰vs烤串

HTML的属性是类型迟钝型,所以使用非字符串、驼峰命名时需要使用他们的相应的横杠连接名字。

动态props

类似于绑定一个平常的属性到表达式,我们也可以使用v-bind命令来动态绑定父级的数据到props上。当父级的数据更新时,子级的数据也会更新。

html

  1. <div>
  2. <input v-bind='parentMsg'>
  3. <br>
  4. <child v-bind:my-message='parentMsg'></child>
  5. </div>

当然我们经常使用简写模式

  1. <child :my-message="parentMsg"></child>

文本 vs 动态

一个常见的错误是给文本数据传递数字类型数据。
html

  1. <!--这个传递一个纯文本‘1’-->
  2. <comp some-prop='1'></comp>

如果我们想要传递一个真是的数字,我们需要使用v-bind,这样传递的数据会被重新赋值为js的一个表达式,也就可以让数字存在。

html

  1. <!--这个传递的是一个数字-->
  2. <comp v-bind:some-prop='1'></comp>

单向数据传输

props传递的数据在父级和子级之间是单方面的,父级可以更改相应的数据,而子级不能。这会防止子级无意更改父级数据,某些方面来说,这会使数据流更坚实。

这意味着你最好不要去更改props,如果尝试那样,控制台会发出警告。如果你需要更改,你可以使用计算属性,或者定义出事属性data。

注意,如果prop是一个对象或数组,时按引用传递。在子组件内修改它**会**影响父组件的状态,不管是使用哪种类型绑定。

eg
html

  1. <div id='parent-object-mutation-demo'>
  2. <p>Parent:{{message.text}}</p>
  3. <p>Child:<my-component v-bind:parent-message='message'></my-component></p>
  4. </div>

js

  1. Vue.compoment('my-component', {
  2. template:'<input v-model="message-text">',
  3. props:['parentMessage'],
  4. data: function(){
  5. return {message:this.partentMessage}
  6. }
  7. })
  8. new Vue({
  9. el: '#parent-object-mutation-demo',
  10. data:{
  11. message:{
  12. text:'hello'
  13. }
  14. }
  15. })

要想解决这个问题,你可以克隆这个对象。
js

  1. data:function(){
  2. return {
  3. message:JSON.parse(JSON.stringify(this.parentMessage))
  4. }
  5. }

或者使用babel的es2015,这个更简单
js

  1. data(){
  2. return{
  3. message:{...this.parentMessage}
  4. }
  5. }

props 验证

porp可以被定义接受那些数据。如果定义的验证没有通过,控制台就会抛出警示。在一个需要被其他应用的组件中,这是非常有用的。

props选项在验证方面,我们使用对象替代了文字数组。

eg
js

  1. Vue.component('example', {
  2. props:{
  3. //基本的类型核对(‘null’标识所有类型都可以通过)
  4. propA:Number,
  5. //多种可行类型
  6. propB:[String, Number],
  7. //只要求string字符串
  8. propC:{
  9. type:String,
  10. required: true
  11. },
  12. //一个带有默认值的数值类型
  13. propD:{
  14. type:NUmber,
  15. default: 100
  16. },
  17. //对象/数组,默认返回类型应该是一个工厂函数
  18. propE:{
  19. type: Object,
  20. default: function(){
  21. return {message: 'hello'}
  22. }
  23. },
  24. //自定义验证函数
  25. propF:{
  26. validator: function(value){
  27. return value > 10
  28. }
  29. }
  30. }
  31. })

type可以是一下的本地构造函数
* String
* Number
* Boolean
* Function
* Object
* Array

另外,type也可以是用户自定义的构造函数并且使用instanceof检测。

如果prop验证失败了,Vue将拒绝在子组件上设置此值,如果使用的是开发版本则会抛出一条警告。


父子组件通信

父链

子组件拥有能够访问父组件的途径——this.访root。每个父组件都有一个数组——this.$children,其包含着所有的子组件。

!这些特性可以被当做逃生舱来应对极端情况。他们不是用来访问和修改大量组件的好方法,如果滥用会使你的组件难以理解。

我们应该优先明确的使用props来明确地传递数据。如果数据真的需要被分享并且很多组件可以修改,可以使用父组件来管理状态,控制所有。改变父组件的状态,自定义的事件能够被发送,然后父组件选择监听或者传递更改方法给子组件。

为了能够在复杂的应用中更好的管理状态,vuex是个官方推荐的第三方库。

自定义事件。

你可以使用一个类似于Node.js的事件发射器————事件集中管理器,允许组件之间通信,不用考虑他们身处组件树的那个地方。因为Vue是事件发射器的实例,你可以使用一个空Vue来实现这个目的。

js

  1. vue bus = new Vue();
  1. //在组建A的方法
  2. bus.$emit('id-selected',1)
  1. //在组件B中的钩子(引发事件)
  2. bus.$on('id-selected', function(id){
  3. //...
  4. })

正如你所见,事件系统做的是:

上述例子看起来十分的简单,但是当查看组件B的代码会发现,很难对‘id-selected’事件定位。这就是为什么我们推荐显式的使用v-on来在父级和子级之间进行通信。

另一个例子:

html

  1. <div id="counter-event-example">
  2. <p>{{total}}</p>
  3. <button-counter v-on:increment="incrementTotal"></button-counter>
  4. <button-counter v-on:increment="incrementTotal"></button-counter>
  5. </div>

js

  1. Vue.component('button-counter', {
  2. template:'<button v-on:click="increment">{{counter}}</button>',
  3. data: function(){
  4. return{
  5. counter: 0
  6. }
  7. },
  8. methods:{
  9. increment: function(){
  10. this.counter += 1;
  11. this.$emit('increment');
  12. }
  13. }
  14. });
  15. new Vue({
  16. el:'#counter-event-example',
  17. data:{
  18. tota: 0
  19. },
  20. methods:{
  21. incrementTotal: function(){
  22. this.total += 1;
  23. }
  24. }
  25. })

在这例子中,需要注意到子组件相对于外面发生的动作是松散解耦的,他所做的只是发出自己动作的信息,有可能这些信息会触发父级的一些动作。

对组件绑定本地事件。

有时候你也许需要监听一个组件的根元素的本地事件。在这种情况下,你可以在v-on后面使用.native修饰符。例子:
html

  1. <my-component v-on:click.native='doTheThing'></my-component>

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

该策略(自定义事件)也可以用在创建自定义表单输入上,协同v-model工作。
注意:
html

  1. <input v-model='something'>

语法糖
html

  1. <input v-bind:value='something' v-on:input='something=$event.target.value'>

当使用组件,可简化为
html

  1. <input v-bind:value='something' v-on:input="something=arguments[0]">

所以,一个组件需要使用v-model,它必须:

让我们在实际中看一下:
html

  1. <div id="v-model-example">
  2. <p>{{message}}</p>
  3. <my-input label="message" v-model='message'></my-input>
  4. </div>

js

  1. Vue.component('my-input', {
  2. template: "\<div class='form-group'>\
  3. <label v-bind:for='randomId'>{{label}}:</label>\
  4. <input v-bind:id='randomId' v-bind:value='value' v-on:input='onInput'>\
  5. </div>\",
  6. props:['value','label'],
  7. data: function(){
  8. return:{
  9. randomId:'input-'+Math.random()
  10. }
  11. },
  12. methods:{
  13. onInput: function(){
  14. this.$emit('input',event.target.value)
  15. }
  16. },
  17. })
  18. new Vue({
  19. el:'#v-model-example',
  20. data:{
  21. message:'hello'
  22. }
  23. })

这个实例不仅能够用来连接组件内部的输入内容,也可以容易的集成你自己发明的输入类型。如下例子:
html

  1. <voice-recognizer v-model="question"></voice-recognizer>
  2. <webcam-gesture-reader v-model="gueture"></webcam-gesture-reader>
  3. <webcam-retinal-scanner v-model='retinalImage'></webcam-retinal-scanner>

子组件引用

如果我们不考虑props和event的存在,我仍然想要在javascript中访问子组件的话,可以给子组件上使用ref指定一个reference ID。例子:
html

  1. <div id="parent">
  2. <user-profile ref="profile"></user-profile>
  3. </div>

js

  1. var parent = new Vue({el:'#parent'});
  2. //访问子组件的接口
  3. var child = parent.$refs.profile;

当ref和v-for一块使用,你将得到一个数组或者一个包含所有组件的对象。这些组件是原始组件的镜像。


内容分发————Slots

使用组件的时候,我们经常想用下面的方式组合他们。
js

  1. <app>
  2. <app-header></app-header>
  3. <app-footer></app-footer>
  4. </app>

有两点需要注意:

1 组件不知道他的挂载点会有什么内容,挂载点的内容是由的父组件决定的。
2 组件很可能有它自己的模版。

为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个处理称为内容分发(或 “transclusion”,如果你熟悉 Angular)。Vue.js 实现了一个内容分发 API,参照了当前 Web 组件规范草稿,使用特殊的 元素作为原始内容的插槽。

编译作用域

在深入api之前,先让我们弄清内容的编译作用域。假定有如下模版:

js

  1. <child-component>
  2. {{message}}
  3. </child-component>

message是父级的数据还是子级的数据呢?答案是父级。一个简单的经验是:

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

一个常见的错误是在父组件模版内讲一个指令绑定到子组件的属性/方法。
html

  1. //无效
  2. <child-template v-show='someChildProperty'></child-template>

假设someChildProperty是子组件的一个属性,那么上面的例子不会有效。父组件不清楚子组件的状态。

如果你需要在根节点绑定子组件数据,你应该在子组件的模版里这样绑定。
js

  1. Vue.component('child-component', {
  2. //这个是有效的,因为我们在子组件的模版里添加绑定
  3. template: '<div v-show="somechildProperty">Child</div>',
  4. data:function(){
  5. return {
  6. somechildProperty: true
  7. }
  8. }
  9. })

类似的,分发内容是在父组件作用域内。

单个Slot

父组件的内容将被抛弃,除非子组件模板包含 。如果子组件模板只有一个没有特性的 slot,父组件的整个内容将插到 slot 所在的地方并替换它。

标签的内容视为回退内容。回退内容在子组件的作用域内编译,当宿主元素为空并且没有内容供插入时显示这个回退内容。

假设我们有一叫做my-component的组件,模版具体如下:
html

  1. <div>
  2. <h2>i'm the child title</h2>
  3. <slot>
  4. This will only be displayed if there is no content to be distributed.
  5. </slot>
  6. </div>

添加一父组件
html

  1. <div>
  2. <h1>I'm the parent title</h1>
  3. <my-component>
  4. <p>This is some original content</p>
  5. <p>This is some more original content</p>
  6. </my-component>
  7. </div>

最终的渲染结果是:
html

  1. <div>
  2. <h1>I'm the parent title</h1>
  3. <div>
  4. <h2>I'm the child title</h2>
  5. <p>This is some original content</p>
  6. <p>This is some more original content</p>
  7. </div>
  8. </div>

命名的slot

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

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

假如,我们有一个multi-insertion组件,他的模版为:
html

  1. <div class="container">
  2. <header>
  3. <slot name="header"></slot>
  4. </header>
  5. <main>
  6. <slot></slot>
  7. </main>
  8. <footer>
  9. <slot name="footer"></slot>
  10. </footer>
  11. </div>

父模版
html

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

渲染结果是
html

  1. <div class="container">
  2. <header>
  3. <h1>Here might be a page title</h1>
  4. </header>
  5. <main>
  6. <p>A paragraph for the main content.</p>
  7. <p>And another one.</p>
  8. </main>
  9. <footer>
  10. <p>Here's some contact info</p>
  11. </footer>
  12. </div>

内容分发是用来组合组件内容的一个非常好的机制。

动态组件

多个组件可以使用同一个挂载点,然后动态地在他们之间切换。使用保留的元素,动态地绑定到is特性。

js

  1. new Vue({
  2. el: 'body',
  3. data:{
  4. currentView:'home'
  5. },
  6. components:{
  7. home:{},
  8. posts:{},
  9. archive:{}
  10. }
  11. })

html

  1. <component v-bind:is='currentView'>
  2. //当vm.currentView变化时,模版也会变化
  3. </component>

如果你喜欢,你也可以直接绑定组件对象。
js

  1. var Home = {
  2. template:'<p>Welcome home!</p>'
  3. };
  4. new Vue.({
  5. el:'body',
  6. data:{
  7. currentView: Home
  8. }
  9. })

keep-alive

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

  1. <keep-alive>
  2. <component :is='currentView'>
  3. <!--inactive 状态的组件将保留在内存中-->
  4. </component>
  5. </keep-alive>

杂项

组件和v-for

你可以直接在定义组件上使用v-for,跟其他元素一样。
html

  1. <my-component v-for='intem of items'></my-component>

然而,它不会之间传递数据给组件,因为组件是有着自己的独立的数据域。为了达到传递数据的目的,我们需要使用props:
html

  1. <my-component
  2. v-for="item in items"
  3. v-bind:item='item'
  4. v-bind:index='index'>
  5. </my-component>

不自动把 item 注入组件的原因是这会导致组件跟当前v-for紧密耦合。显式声明数据来自哪里可以让组件复用在其它地方。

这有一个完整的todu列表例子:
html

  1. <div id='todo-list-example'>
  2. <input
  3. v-model='newTodoText'
  4. v-on:keyup.enter='addNewTodo'
  5. placeholder='Add a todo'
  6. >
  7. <ul>
  8. <li
  9. is='todo-item'
  10. v-for='(todo,index) in todo'
  11. v-bind:title='todo'
  12. v-on:remove='todos.splice(index,1)'
  13. ></lic>
  14. </ul>
  15. </div>

js

  1. Vue.component('todo-item', {
  2. template:'\
  3. <li>\
  4. {{title}}\
  5. <button v-on:click=$emit(\'remove\')"X</button>\
  6. <\li>\
  7. ',
  8. props:['title']
  9. });
  10. new Vue.({
  11. el:'#todo-list-example',
  12. data:{
  13. newTodoText:'',
  14. todos:[
  15. 'Do the dishes',
  16. 'Take out the trash',
  17. 'Now the lawn'
  18. ]},
  19. methods:{
  20. addNewTodo: function(){
  21. this.todos.push(this.newTodoText);
  22. this.newTodoText='';
  23. }
  24. }
  25. })

编写可重用的组件

当编写一个组件,一个好的做法是时刻考虑到这个组件的再后面代码的可重用性,如果是使用一次就不用了,那么可以采用紧密的写法。如果需要重用那么应该给其一个干净的接口,并且尽量不要只适合特殊情况。

组建的接口API包含三个部分:props、events、slots。

使用专用的v-bind 和v-on的简写形式可以清晰简短地传达出你的想法。

html

  1. <my-component
  2. :foo='baz'
  3. :bar="quz"
  4. @event-a='doThis'
  5. @event-b='dothat'
  6. >
  7. <img slot='icon' src='...'>
  8. <p slot='main-text'>hello</p>
  9. </my-component>

异步组件

在大型应用中,我们需要把我们的应用分成小的部分。当需要一个组件时,我们可以向服务器发送请求并加载。为了容易些,Vue允许你以工厂函数的形式定义你的组件,以便动态的加载他们。Vue只有在一个组件需要渲染时候才会触发这个工厂函数,并且会缓存函数的执行结果,一方后面再次使用。例如:
js

  1. Vue.component('async-example', function(resolve, reject){
  2. setTimeout(function(){
  3. resolve({
  4. template:'<div>I am async!</dvi>'
  5. })
  6. },1000)
  7. })

工厂函数接受一个resolve回调函数,它会在你向服务器请求(检索)时返回并执行。你也可以调用reject(reosn)函数来说明,当加载失败是的一些情况。至于setTimeout函数仅仅是模拟说明。怎样调用你的组件,完全取决于你。一个建议是集中使用动态组件,通过webpack's code-splitting feature

js

  1. Vue.component('async-webpack-example', function(resolve){
  2. //这是一个特殊的require语法,他会通知webpack自动分离你的代码到bundle中,在需要时通过ajax请求进行加载。
  3. require(['./my-async-component'], resolve)
  4. })

组件命名习惯

当注册一个组件时,你可以使用驼峰习惯命名也可以使用烤串习惯,vue没有限制。

js

  1. //定义一个组件时
  2. component:{
  3. //定义使用驼峰习惯
  4. 'kabab-cased-component':{},
  5. 'cameCasedComponent':{},
  6. "TitleCasedCommponent":{}
  7. }

在HTML转化模版,你必须使用相应的烤串形式。
html

  1. <!--在HTML中只能使用烤串模式-->
  2. <kebab-cased-component></kebab-cased-component>
  3. <camel-cased-component></camel-cased-component>
  4. <title-cased-component></title-cased-component>

然而使用文字模版时,我们不用必须使用烤串模式,你可以使用喜欢换的任何模式,因为这些可以在HTML中自动转化为烤串模式。

html

  1. <!-- use whatever you want in string templates! -->
  2. <my-component></my-component>
  3. <myComponent></myComponent>
  4. <MyComponent></MyComponent>

如果你的组件不包含slot元素,你甚至可以使用在名字后面使用直接关闭。
html

  1. <my-component/>

递归组件

组件可以在自己的模板中递归地调用自己,前提是必须有一个名字选项。
js

  1. name:'stack-overflow',
  2. template:'<div><stack-overflow></stack-overflow></div>'

像上面的组件执行的结果会得到一个'max stack size exceeded'错误,所以,必须有一个调用条件(例如使用v-if,并且最终有一个false结果来结束递归)。当你使用Vue.component注册一个全剧组件。全局id自动设置为组建的name选项。

内联模版

当inline-template属性被用在一个子组件上,组件会把他们包括的元素当做模板的一部分。这允许更灵活的模版编写。
html

  1. <my-component inline-component>
  2. <p>这个将被编译成组件模版的一部分</p>
  3. <p>是当前模版而不是父模版</p>
  4. </my-component>

但是,inline-template会使编译数据域显得不那么容易理解。更好的做法是,在模版内部使用template选项或者在一个.vue文件里使用template元素。

X-Template

另一个做法是在标签内部定义type为'text/x-template',然后通过id引用模版。例子:
html

  1. <script type="text/x-template" id="hello-world-template">
  2. <p>hello world</p>
  3. </script>

在大的模板中,或者非常小的应用中示例时很有用,但是其他的会被忽视,因为他把模版从组件中分开了。

使用v-once的静态小模版

vue渲染纯html元素的速度是非常快的。但是有时候你的组件包含了很多静态内容(图片等),在这种情况下,你可以使它们只被渲染一次便被缓存起来,方式通过在在外层的标签使用v-once属性。例子
js

  1. Vue.component("term-of-serveic", {
  2. template:'\
  3. <div v-once>\
  4. <h1>Terms of Service</h1>\
  5. ...其他静态资源...\
  6. </div>\',
  7. })
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注