一、组件的使用
1.全局注册
1)必须先注册再实例化
2)命名最佳是小写,中间有一个横杠:my-component
3)Vue.component({
})
4)显然,组件只能在Vue作用域内有效,必须写在<div id="app"> ...</div>
里。
但是,如果你有两个vm,全局注册可以在这两个vm都起作用。
2.局部注册
var app2=new Vue({
el:"#app2",
components :{
'my-component2':{
template:'<p>我是component2</p>'
}
}
})
那么
<my-component2>
只能是在app2的作用域,也就是
'#app2'
下起作用。
3.组件在dom中放置的位置有讲究
<ul>
、<ol>
、<table>
、<select>
这样的元素里允许包含的元素有限制,而另一些像 <option>
这样的元素只能出现在某些特定元素的内部。
比如在
<table> <my-row>...</my-row> </table>
比如这样写组件是不行的,正确的做法你需要把<table></table>
也包括到你的组件中去。
如果你硬要实现上面的这种写法,vue也提供了一个办法:
自定义组件 <my-row>
会被当作无效的内容,因此会导致错误的渲染结果。变通的方案是使用特殊的 is
特性:
<table> <tr is="my-row"></tr> </table>
4.组件的data属性
错误写法
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: {
message: 'hello'
}
})
data必须是函数
Vue.component('my-component', {
template: '<span>{{ message }}</span>',
data: function(){
return {
message:'hello'
}
}
})
5.组件的嵌套使用
1) 组件内定义的数据只能在组件内使用,所以子组件想使用父组件的数据,就需要通过传参数的方式。2)参数传递的顺序是 从上到下(从父组件向下传递到子组件)
另一个是事件的传递。我们大家都了解事件冒泡,事件是从子节点传递到父节点,一直传递到顶部的。
Vue组件也是这个原理,子组件一有点风吹草动(事件),父组件,父父组件就会一层一层的马上知道。
简而言之,参数往下传,事件往上传。
二、Props
1.定义
组件实例的作用域是孤立的。子组件不能直接引用父组件的数据。需要借助props才能下发到子组件中
而子组件要 显式 地用props选项 声明它语气的数据
Vue.component('child', {
// 声明 props,像函数传参的参数一样
props: ['message'],
// 同样也可以在 vm 实例中通过 this.message 来使用
template: '<span>{{ message }}</span>'
})
然后我们可以这样向它传入一个普通字符串:
<child message="hello!"></child>
2.驼峰命名和短横线命名
HTML 特性是不区分大小写的。所以,当使用的不是字符串模板时,camelCase (驼峰式命名) 的 prop 需要转换为相对应的 kebab-case (短横线分隔式命名):Vue.component('child', { // 在 JavaScript 中使用 camelCase props: ['myMessage'], template: '<span>{{ myMessage }}</span>' })
<!-- 在 HTML 中使用 kebab-case --> <child my-message="hello!"></child>
3.动态参数
我们上面传的是一个字符串,如果我想传入一个变量名呢
<div id="app">
<label for="input1">输入</label>
<input v-model="parentMsg" type="text" id="input1"/>
<my-cpn v-bind:message="parentMsg"></my-cpn>
</div>
<script>
Vue.component('my-cpn',{
props:['message'],//像函数的参数一样
template:'<span>{{message}}</span>'
});
new Vue({
el:'#app',
data:{
parentMsg:''
}
});
用v-bind:message="变量名"
我们可以用 v-bind
来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件:父变子变,即父组件的parentMsg的值改变,被绑定到parentMsg的子组件的message属性也会发生相应的变化。
如果想把一个对象的所有属性传给子组件:
如果你想把一个对象的所有属性作为 prop 进行传递,可以使用不带任何参数的 v-bind
(即用 v-bind
而不是 v-bind:prop-name
)。
注意v-bind:my-message="parentMsg"
,这样就把一个动态,响应式的对象传给了子组件。结合前面学过的知识,我们进一步认识到v-bind
有把死的变活的功效。
4.字面量语法 vs 动态语法
我们再通过一个例子来加深对v-bind
的理解:
<!-- 这样传进去的是静态的字符串1,而不是数字1 -->
<comp some-prop="1"></comp>
可是我需要传进去的是数字,要实现这个目的,可以这样写:
<!-- 这个就传进去了数字,因为v-bind把后面变成了js的表达式 -->
<comp v-bind:some-prop="1"></comp>
5.单向数据流
Prop 是单向绑定的:父变子变,但是反过来不会。这是为了防止子组件修改了父组件的状态,避免应用的数据流变得难以理解。
另外,父变则子变。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
在两种情况下,我们想修改 prop 中数据:
prop为初始值,子组件把它赋给另一个值当局部变量用;
Prop 作为原始数据传入,由子组件处理成其它数据输出。
对这两种情况,正确的应对方式是:
定义一个局部变量,并用 prop 的值初始化它:
props: ['initialCounter'], data: function () { return { counter: this.initialCounter } }
定义一个计算属性,处理 prop 的值并返回:
props: ['size'], computed: { normalizedSize: function () { return this.size.trim().toLowerCase() } }
注意在 JavaScript 中对象和数组是引用类型,指向同一个内存空间,如果 prop 是一个对象或数组,在子组件内部改变它会影响父组件的状态。
总结:不要直接在子组件改prop的值
<div id="app"> <my-cpn v-bind="parentMsg"></my-cpn> </div> <script src="vue.js"></script> <script> Vue.component('my-cpn',{ props:['name','ages'],//像函数的参数一样 template:'<a v-on:click="showname" href="#">{{name}}{{ages}}</a>', data:function () { return { myname:this.name, myage:this.ages } }, methods:{ showname:function () { alert(this.myname); } } }); new Vue({ el:'#app', data:{ parentMsg:{ name:'jenny', ages:'18' } } });
6.参数验证
我们可以为组件的prop指定验证规则。
要指定验证规则,需要用对象的形式来定义prop
Vue.component('example',{
//1.基础类型检测,null-》允许任何类型
propA:Number,
//2.可能是多种类型
propB: [String,Number],
//3.不能为空,而且为字符串
propC:{
type:String,
required:true
}
//4.为数值,且有默认值
porpD:{
type:Number,
default:100
}
//5.对象,默认值需要用闭包返
type:Objec
default:function(){
return { message : 'hello'}
//自定义验证函数
propF:{
validator:function(value){ return vaule>10
}
})
type可以是下面几种类型
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
三、非Prop特性
所谓非 prop 特性,就是指它可以直接传入组件,而不需要定义相应的 prop。
不能预见组件所有的prop属性,而当没有预设prop属性时,但是在组件标签内设置了新的属性,此时会自动把新属性写入props里
替换、合并现有的特性
如,下面的my-component的模板:
<input type="data" class="form-control"/>
而为了给上面这个组件添加一个特殊的主题,我们还需要添加一个特殊的class,比如
<my-component class="date-picker-theme-dark"></my-component>
在例子里,我们定义了两个class
按常理来说,传递给my-component的值会覆盖my-component本身设定的值,即“form-control”常理会被覆盖掉
但是vue设定了对待class和style特性的值,会被合并,让最终生成值为: form-control date-picker-theme-dark
四、自定义事件
1.使用v-on绑定自定义事件
使用$on(eventName)监听事件
使用$emit(eventName,optionalPayload)触发事件
on:事件名字
然后$emit(事件名字)代表触发了这个事件
<div id="message-event-example" class="demo">
<p v-for="msg in messages">{{ msg }}</p>
<button-message v-on:message="handleMessage"></button-message>
</div>
代表监听message这个事件,一旦message这个事件被触发,就执行handleMessage
Vue.component('button-message', { template: `<div> <input type="text" v-model="message" /> <button v-on:click="handleSendMessage">Send</button> </div>`, data: function () { return { message: 'test message' } }, methods: { handleSendMessage: function () { this.$emit('message', { message: this.message }) } } }) new Vue({ el: '#message-event-example', data: { messages: [] }, methods: { handleMessage: function (payload) { this.messages.push(payload.message) } } })
上面语句代表在那个位置,触发名字是message的事件
所以最后的流程就是:这个button被click的时候,执行handleSendMessage(子组件)事件,handleSendMessage事件执行过程中内部触发message(自定义)事件,并把参数传递进去,而message事件被触发时,会执行handleMessage(父组件)时间,并接收参数
2.给组件绑定原生事件
上面的click事件是绑定在模板(template)上的,如果想直接在组件的根元素上监听一个原生事件,加native后缀即可
<my-component v-on:click.native="doTheThing"></my-component>
3. .sync修饰符
在一些情况下,我们可能会需要对一个 prop 进行“双向绑定”。事实上,这正是 Vue 1.x 中的 .sync
修饰符所提供的功能。当一个子组件改变了一个带 .sync
的 prop 的值时,这个变化也会同步到父组件中所绑定的值。这很方便,但也会导致问题,因为它破坏了单向数据流。由于子组件改变 prop 的代码和普通的状态改动代码毫无区别,当光看子组件的代码时,你完全不知道它何时悄悄地改变了父组件的状态。这在 debug 复杂结构的应用时会带来很高的维护成本。
上面所说的正是我们在 2.0 中移除 .sync
的理由。但是在 2.0 发布之后的实际应用中,我们发现 .sync
还是有其适用之处,比如在开发可复用的组件库时。我们需要做的只是让子组件改变父组件状态的代码更容易被区分。
从 2.3.0 起我们重新引入了 .sync
修饰符,但是这次它只是作为一个编译时的语法糖存在。它会被扩展为一个自动更新父组件属性的 v-on
监听器。
<comp :foo.sync="bar"></comp>
会被扩展为:
<comp :foo="bar" @update:foo="val => bar = val"></comp>
当子组件需要更新 foo 的值时,它需要显式地触发一个更新事件:
this.$emit('update:foo', newValue)
当使用一个对象一次性设置多个属性的时候,这个 .sync 修饰符也可以和 v-bind 一起使用:
<comp v-bind.sync="{ foo: 1, bar: 2 }"></comp>
这个例子会为 foo 和 bar 同时添加用于更新的 v-on 监听器。
4.表单类型组件的自定义事件
<currency-input v-model="price"></currency-input>
Vue.component('currency-input', {
template: '
<span>
<input ref="input" v-bind:value="value" v-on:input="updateValue($event.target.value)"/>
</span>
',
props: ['value'],
methods: {
// 不是直接更新值,而是使用此方法来对输入值进行格式化和位数限制
updateValue: function (value) {
var formattedValue = value
// 删除两侧的空格符
.trim()
// 保留 2 位小数
.slice(
0,
value.indexOf('.') === -1
? value.length
: value.indexOf('.') + 3
)
// 如果值尚不合规,则手动覆盖为合规的值
if (formattedValue !== value) {
this.$refs.input.value = formattedValue
}
// 通过 input 事件带出数值
this.$emit('input', Number(formattedValue))
}
}
})
5.自定义组件的v-model
默认情况下,一个组件的 v-model
会使用 value
prop 和 input
事件。但是诸如单选框、复选框之类的输入类型可能把 value
用作了别的目的。model
选项可以避免这样的冲突:
Vue.component('my-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean,
// 这样就允许拿 `value` 这个 prop 做其它事了
value: String
},
// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代码等价于:
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
注意你仍然需要显式声明 checked 这个 prop。
6.非父子组件之间的通信
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:var bus = new Vue()
// 触发组件 A 中的事件
bus.$emit('id-selected', 1)
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
})
在复杂的情况下,我们应该考虑使用专门的状态管理模式。
五、使用插槽分发内容
概念
在使用组件时,我们常常要像这样组合它们:
<app>
<app-header></app-header>
<app-footer></app-footer>
</app>
为了让组件可以组合,我们需要一种方式来混合父组件的内容与子组件自己的模板。这个过程被称为内容分发 (即 Angular 用户熟知的“transclusion”)。
即父组件使用子组件时,想把某些内容传递进去子组件(因为默认使用子组件时,子组件内容不可有其他内容,如果有,会被丢弃,而为了实现父使用子,但是子内又有东西时,就可使用插槽机制)
1.编译的作用域
<parent-component>
<child-component>
{{ message }}
</child-component>
</parent-component>
写在child-component中间的这个{{message}}
,到底算是绑定child-component的还是parent-component的?
答案是:parent-component的。
规则就是:
所有在父组件模板里的东西都将视为父组件的作用域,所有在子组件的东西都将视为子组件的作用域;
最常见的一个错误就是:
<child-component v-show="someChildProperty"></child-component>
你以为someChildProperty是子组件的数据属性,其实它绑定了父组件的数据属性;所以你只能这样写:
Vue.component('child-component', {
// this does work, because we are in the right scope
template: '<div v-show="someChildProperty">Child</div>',
data: function () {
return {
someChildProperty: true
}
}
})
写在子组件的模板里,someChildProperty就可以和子组件里的数据绑定了
讲完这个概念,我们来看“内容分发”,也是这个原则;
2.单插槽
把一个东西通过slot丢到另一个东西里面去;
除非子组件模板包含至少一个 <slot>
插口(有一个以上的slot插口),否则父组件的内容将会被丢弃。
当子组件模板只有一个没有属性的插槽时,父组件传入的整个内容片段将插入到插槽所在的 DOM 位置,并替换掉插槽标签本身。
<div id="example">
<parent-com>
</parent-com>
</div>
<template id='parent-com'>
<div>
<h1>I'm the parent title</h1>
<child-com>
<p>
我是在父组件的内容,写在子组件的标签内,将会自动传入子组件模板中的slot
</p>
</child-com>
</div>
</template>
<template id='child-com'>
<div>
<h2>I'm the child title</h2>
<slot>
没有slot注入时,这段文字才会被显示。
</slot>
</div>
</template>
Vue.component('parent-com', {
template: '#parent-com'
});
Vue.component('child-com', {
template: '#child-com'
});
new Vue({
el: '#example'
});
我们在写parent-com的时候,写到了
<child-com></child-com>
,本来child-com标签之间是不应该写内容的,因为内容是内部模板渲染出来的。但是如果你写了,
这些html内容将会当做参数传入child-com内部定义有slot的地方。
3.命名插槽
上面的例子,只要你把内容写在child-com标签之间,这些内容就会自动在child-com内部找到slot的位置并注入;
如果我有多个内容要分别注入不同的slot怎么办?
给slot起个名字不就可以了;
这个时候,子组件模板:
<template id='child-com'>
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
父组件传入的时候这样写:
<template id='parent-com'>
<div>
<h1>I'm the parent title</h1>
<child-com>
<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>
</child-com>
</div>
</template>
<slot>
元素可以用一个特殊的特性 name
来进一步配置如何分发内容。多个插槽可以有不同的名字。具名插槽将匹配内容片段中有对应 slot
特性的元素。
仍然可以有一个匿名插槽,它是默认插槽,作为找不到匹配的内容片段的备用插槽。如果没有默认插槽,这些找不到匹配的内容片段将被抛弃。
4.默认插槽的内容
有的时候为插槽提供默认的内容是很有用的。例如,一个 <submit-button>
组件可能希望这个按钮的默认内容是“Submit”,但是同时允许用户覆写为“Save”、“Upload”或别的内容。
你可以在 <slot>
标签内部指定默认的内容来做到这一点。
<button type="submit">
<slot>Submit</slot>
</button>
如果父组件为这个插槽提供了内容,则默认的内容会被替换掉。
5.编译作用域
当你想在插槽内使用数据时,例如:
<navigation-link url="/profile">
Logged in as {{ user.name }}
</navigation-link>
该插槽可以访问跟这个模板的其它地方相同的实例属性 (也就是说“作用域”是相同的)。但这个插槽不能访问 <navigation-link>
的作用域。例如尝试访问 url
是不会工作的。牢记一条准则:
父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
6.作用域插槽
作用域插槽是一种特殊类型的插槽,用作一个 (能被传递数据的) 可重用模板,来代替已经渲染好的元素。
在子组件中,只需将数据传递到插槽,就像你将 prop 传递给组件一样,即子组件把数据上传到父组件
<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>
即在父组件中使用子组件的数据
上面<template>之间的内容,会被完整插入子组件的slot标签中,替换掉slot,而且还能读取slot原来的prop对象传递过来的props.text的内容
作用域插槽更典型的用例是在列表组件中,允许使用者自定义如何渲染列表的每一项:
有的时候你希望提供的组件带有一个可从子组件获取数据的可复用的插槽。例如一个简单的 <todo-list>
组件的模板可能包含了如下代码:
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
但是在我们应用的某些部分,我们希望每个独立的待办项渲染出和 todo.text
不太一样的东西。这也是作用域插槽的用武之地。
为了让这个特性成为可能,你需要做的全部事情就是将待办项内容包裹在一个 <slot>
元素上,然后将所有和其上下文相关的数据传递给这个插槽:在这个例子中,这个数据是 todo
对象:
<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- 我们为每个 todo 准备了一个插槽,-->
<!-- 将 `todo` 对象作为一个插槽的 prop 传入,即在子组件里,把todo当做一个对象,往上传给父组件,即下面父组件接收到的slotProps。-->
<slot v-bind:todo="todo">
<!-- 回退的内容 -->
{{ todo.text }}
</slot>
</li>
</ul>
现在当我们使用
<todo-list>
组件的时候,我们可以选择为待办项定义一个不一样的
<template>
作为替代方案,并且可以通过
slot-scope
特性从子组件获取数据:
<todo-list v-bind:todos="todos">
<!-- 将 `slotProps` 定义为插槽作用域的名字 -->
<template slot-scope="slotProps">//slot-scope从子组件传过来的todo对象,并命名为slotProps
<!-- 为待办项自定义一个模板,-->
<!-- 通过 `slotProps` 定制每个待办项。-->
<span v-if="slotProps.todo.isComplete">✓</span>
{{ slotProps.todo.text }}//这样就可以显示每一个子组件自己的内容了
</template>
</todo-list>
在 2.5.0+,slot-scope
不再限制在 <template>
元素上使用,而可以用在插槽内的任何元素或组件上。
7.解构slot-scope
如果一个 JavaScript 表达式在一个函数定义的 参数位置有效,那么这个表达式实际上就可以被slot-scope
接受。也就是说你可以在支持的环境下 (
单文件组件或
现代浏览器),在这些表达式中使用
ES2015 解构语法。例如:
<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
这会使作用域插槽变得更干净一些。
六、动态组件和异步组件
1.动态组件
var vm = new Vue({
el: '#example',
data: {
currentView: 'home'
},
components: {
home: { /* ... */ },
posts: { /* ... */ },
archive: { /* ... */ }
}
})
利用v-bind:is属性绑定不同的组件:
<component v-bind:is="currentView">
<!-- vm.currentView改变的时候,组件就会切换 -->
</component>
当在这些组件之间切换的时候,你有时会想保持这些组件的状态,以避免反复重渲染导致的性能问题。例如我们来展开说一说这个多标签界面:
你会注意到,如果你选择了一篇文章,切换到 Archive 标签,然后再切换回 Posts,是不会继续展示你之前选择的文章的。这是因为你每次切换新标签的时候,Vue 都创建了一个新的 currentTabComponent
实例。
重新创建动态组件的行为通常是非常有用的,但是在这个案例中,我们更希望那些标签的组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive>
元素将其动态组件包裹起来。
<!-- 失活的组件将会被缓存!--> <keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
2.异步组件
没看懂
https://cn.vuejs.org/v2/guide/components-dynamic-async.html