标签:关系 计算 required component 布尔 graph lang 常用 live
目录
首发日期:2019-01-26
【官方的话】组件系统是 Vue 的另一个重要概念,因为它是一种抽象,允许我们使用小型、独立和通常可复用的组件构建大型应用。仔细想想,几乎任意类型的应用界面都可以抽象为一个组件树:
小菜鸟的话:定义组件就好像定义了一堆“带名字”的模板,比如说可能会有叫做“顶部菜单栏”的组件,我们可以多次复用这个“顶部菜单栏”而省去了大量重复的代码。
<body>
<div id="app">
<my-template></my-template><!-- 利用组件名来使用定义的“模板” -->
<my-template></my-template>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-template',{ // 第一个参数是组件名,第二个参数是模板的内容
template: '<div><span>我的模板</span></div>'
})
var vm = new Vue({
el: '#app'
})
</script>
代码效果:
组件注册就是“定义模板”,只有注册了的组件,Vue才能够了解怎么渲染。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<ol>
<!-- 使用组件 -->
<todo-item></todo-item>
<todo-item></todo-item>
<todo-item></todo-item>
</ol>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
//全局定义组件,第一个参数是组件名,template的值是组件的内容
Vue.component('todo-item', {
template: '<li>这是个待办项</li>'
})
// 实例化是必须的,要把使用组件的区域交给vue管理
var app = new Vue({
el: '#app',
})
</script>
</html>
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。
在这些情况下,你可以通过一个普通的 JavaScript 对象来定义组件:
<body>
<div id="app">
<my-component></my-component><!-- 利用组件名来使用组件 -->
<my-component></my-component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var ComponentA = { // 定义一个组件
template: '<div><span>我的模板</span></div>'
}
var vm = new Vue({
el: '#app',
components: { // 然后在实例中声明要使用这个组件,key是在这个实例中的组件名,value是组件
'my-component': ComponentA
}
})
</script>
上面的全局注册说了允许在组件中使用其他组件,但注意局部注册的组件要声明使用其他组件才能够嵌套其他组件。例如,如果你希望 ComponentA 在 ComponentB 中可用,则你需要这样写:
<body>
<div id="app">
<my-component></my-component>
<my-component-b></my-component-b>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var ComponentA = { // 1.定义一个组件
template: '<div><span>我的模板</span></div>'
}
var ComponentB = { // 2.定义一个组件
components: { //3.声明使用A组件
'my-component': ComponentA
},
template: '<my-component></my-component>' // 4.需要在组件的template中写上另一个组件
}
var vm = new Vue({
el: '#app',
components: {
'my-component': ComponentA,
'my-component-b': ComponentB
}
})
</script>
组件名可以使用类my-component-name(kebab-case (短横线分隔命名))或MyComponentName的格式(PascalCase 首字母大写命名法),使用组件的时候可以<my-component-name>
或<MyComponentName>
,但在有些时候首字母大写命名法定义组件的是不行的,所以通常推荐使用<my-component-name>
【当你使用首字母大写命名法来定义组件的时候,不能直接在body中直接写组件名,而要求写在template中,如下例】。
<body>
<div id="app">
<my-component></my-component>
<my-component-demo></my-component-demo>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component',{
template: '<div><span>我的模板A</span></div>'
})
Vue.component('my-component-demo',{ // 这个是用来测试第二种命名法定义组件的,首字母大写时要写在字符串模板中才能显示(不然显示不了)
template: '<MyComponentB></MyComponentB>'
})
Vue.component('MyComponentB',{
template: '<div><span>我的模板B</span></div>'
})
var vm = new Vue({
el: '#app'
})
</script>
每个组件必须只有一个根元素!!
所以下面是不合法的:
如果你确实要有多个元素,那么要有一个根元素包裹它们:
组件也是一个实例,所以组件也可以定义我们之前在根实例中定义的内容:data,methods,created,components等等。
但一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
在一些html元素中只允许某些元素的存在,例如tbody元素中要求有tr,而不可以有其他的元素(有的话会被提到外面)。下面是一个元素被提到外面的例子【而ul并没有太严格,所以我们在前面的todo-list的例子中能够演示成功】
下图可以看的出来div被提到table外面了:
这是为什么呢?目前来说,我们在页面中其实是先经过html渲染再经过vue渲染的(后面项目话后是整体渲染成功再展示的),当html渲染时,它就发现了tr里面有一个“非法元素”,所以它就把我们自定义的组件提到了table外面。
解决方案:
使用tr元素,元素里面有属性is,is的值是我们要使用的组件名
<body>
<div id="app">
<table>
<tr is='mtr'></tr>
</table>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('mtr', {
template: `<tr><td>123</td></tr>` // 这个成功的检测要使用检测元素来看
});
var app = new Vue({
el: '#app'
})
</script>
但不会在一下情况中出错:
<script type="text/x-template">
在上面定义的组件中使用的数据都是固定的数据,通常我们都希望模板能根据我们传入的数据来显示。
(子组件的意思是当前组件的直接子组件,在目前的单个html文件为例时,你可以仅认为是当前页面的非嵌套组件。后面讲到多个组件的合作时由于多个组件之间的嵌套,就形成了组件的父子、祖孙、兄弟关系)
要给子组件传递数据主要有两个步骤
演示代码如下:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<div id="app">
<ol>
<!-- 2.给数据赋值 -->
<todo-item todo="第一个值"></todo-item>
<todo-item todo="第二个值"></todo-item>
<todo-item :todo="msg"></todo-item>
<!-- 传入的数据可以是父组件实例中定义的数据。注意要用:来绑定,不然不会识别成实例数据,而是一个字符串 -->
</ol>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('todo-item', {
props: ['todo'], // 1.使用props来定义这个组件支持传的参数
template: '<li>{{ todo }}</li>' // 3.这里演示一下在组件中使用这个传入的参数
})
var app = new Vue({
el: '#app',
data: {
msg: 'hello world'
}
})
</script>
</html>
代码效果:很明显的,我们的值成功传给子组件了。
$emit()
可以有多个参数,第一个参数是触发的事件的名称,后面的参数都是随着这个事件向外抛出的参数。演示代码如下:
<body>
<div id="app" >
<my-component-c v-on:change-color="doSomething"></my-component-c>
<!-- 2.使用组件的时候监听一下事件,这里将会调用父组件的函数来处理事件 -->
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('my-component-c', {
template: `
<button @click="$emit('change-color','hello')">改变颜色</button>
` // 1.定义组件,在组件内定义一个能触发click事件的按钮,onclick事件发生时将会调用emit来向外抛出事件【这里触发的事件不能使用驼峰命名法。推荐使用短横线命名法】
// 【第一个参数是自定义事件名称,第二个和后续的参数可以用于向组件抛出值,这些值会作为事件响应函数的参数】
})
var app = new Vue({
el: '#app',
data: {
msg: 'hello world'
},
methods: {
// 3.将会调用下面的函数来处理事件
doSomething: function(value){ // 这里的value是从子组件中抛出的。
console.log(value) // 打印一下
}
}
})
</script>
【小tips:上面有多重字符串的使用,普通的双引号和单引号已经不足以嵌套使用了,在外层可以使用反引号` ` `【esc下面那个键】来包裹,它也可以达到字符串包裹的效果,特别的是它支持多行字符串。】
祖孙组件传数据、兄弟组件传数据都属于非父子组件之间的传值。
使用bus传输数据的步骤:
Vue.prototype.bus = new Vue()
this.bus.$emit(‘change‘,当前组件的数据)
this.bus.$on(‘change‘,一个用于赋值的函数)
下面的代码是点击某个组件发生数据变化时,另一个组件的数据也发生变化:
<body>
<div id="app">
<child-a></child-a>
<child-a></child-a>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 给Vue的原型添加一个bus,bus是一个Vue实例
Vue.prototype.bus = new Vue()
Vue.component('child-a', {
data: function() {
return {
content: 0
}
},
methods: {
// 2.定义一个函数,能够在数据发生变化时调用emit(这里定义一个点击事件的响应函数)
handleClick: function() {
this.content+=1
this.bus.$emit('change',this.content)
}
},
mounted: function(){
var this_= this // 这里要有一个这样的赋值,下面不能直接使用this,因为在函数中this指向的已经不是当前对象了,而用_this保存的才是当前对象
// 3. 在组件中对bus中触发的事件进行监听。(当emit触发事件时会调用)
this.bus.$on('change',function(msg){ // 4.函数中的参数是emit触发事件带的参数。
this_.content = msg // 5.修改当前组件中的数据
})
},
template: `<div @click='handleClick'>{{ content }}</div>`
});
var app = new Vue({
el: '#app'
})
</script>
【有个建议,建议写属性名的时候都使用kebab-case (短横线分隔命名) 命名,因为这个的兼容效果最好】
HTML 中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。如果你在props中使用了驼峰命名法,那你在定义属性的时候需要使用kebab-case (短横线分隔命名) 命名才能正确传输数据【因为短横线后面的字符可以识别成大写,从而能够匹配到】。
如果在属性中也使用驼峰命名法命名属性的时候会报这样的错:Prop "mycontent" is passed to component
, but the declared prop name is "myContent". Note that HTML attributes are case-insensitive and camelCased props need to use their kebab-case equivalents when using in-DOM templates. You should probably use "my-content" instead of "myContent"
<body>
<div id="app">
<!--下面这里会报错 -->
<child myContent='hello-1'></child>
<!-- 下面会正常 -->
<child my-content='hello-2'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child', {
props:['myContent'],
template: `<div>{{myContent}}</div>`
});
var app = new Vue({
el: '#app'
})
</script>
同样的,如果在组件的template属性中使用驼峰命名法的属性,那么这个限制就不存在了。
有时候需要使用第三方的组件的时候,所以会需要传输数据给这个组件来渲染。但如何限制传入的数据的类型呢?
格式:
props: {
// 数据名:数据类型
title: String,
likes: Number,
...
}
如果传入的类型不对,那么会报Invalid prop: type check failed for prop "xxx". Expected String with value "xxx", got Number with value xxx.
的错误。
如果允许多种值,可以定义一个数组:
props: {
content: [String,Number]
}
我们也可以给props中的数据设置默认值,如果使用default设置值,那么没有传某个数据时默认使用这个数据。
props: {
content: {
type:[String,Number],
default:'我的默认值'
}
}
如果使用default给数组或对象类型的数据赋默认值,那么要定义成一个函数。
如果要求某个数据必须传给子组件,那么可以为它设置required。
格式:
props: {
content: {
type: String,
required: true
}
}
如果没传,会报Missing required prop: "xxx"
的错。
如果想要更精确的校验,可以使用validator,里面是一个函数,函数的第一个参数就是传入的值,当函数内返回true时,这个值才会校验通过。
以下的代码是要求传入的字符串长度大于6位的校验:
Vue.component('child', {
props: {
content: {
type: String,
validator: function(value) {
return (value.length > 6)
}
}
}
如果验证不通过,会报Invalid prop: custom validator check failed for prop "xxx"
的错。
用下面的代码来说一个问题:
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button>按钮</button>
`
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
上面的代码你会发现点击了按钮却没有调用函数。
而下面的按钮按了会打出child。
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button @click='childFunction'>按钮</button>
`,
methods: {
childFunction: function() {
console.log('child')
}
}
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
下面的代码是使用了emit来达到同样效果的代码:
<html lang="en">
<head>
<meta charset="utf-8">
<title>demo</title>
</head>
<body>
<div id="app">
<child @click='myFunction'></child>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
template:`
<button @click="$emit('click')">按钮</button>
`
});
var vm = new Vue({
el: '#app',
methods: {
myFunction: function() {
console.log('haha')
}
}
})
</script>
</html>
<template>
元素当做不可见的包裹元素,让成组的元素能够统一被渲染出来。<template>
元素当做不可见的包裹元素<template v-if="ok">
<h1>Title</h1>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</template>
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
插槽也可以分发多份数据。使用指定的插槽名就可以获取指定的插槽数据。
如果没有数据传过来,那么插槽可以定义一个默认的数据用来显示。
<body>
<div id="app">
<child-a>
<template slot-scope='props'> <!--2. slot-scope的属性值是可以随意的,代表作用域插槽的名字 -->
<h3>{{props.index}}</h3> <!--3. props.xxx 是传过来的值的名字,值需要绑定到slot中 -->
</template>
</child-a>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.prototype.bus = new Vue()
Vue.component('child-a', {
data: function() {
return {
list: [2,4,6,8,10]
}
},
// 1. 绑定属性,名为index,那么作用域插槽就可以获取名为index的数据
template: `<div>
<slot v-for='item of list' :index=item></slot>
</div>`
});
var app = new Vue({
el: '#app'
})
</script>
<component :is=‘组件名‘></component>
如果改变了is属性的值,那么渲染的组件就会发生变化。下面是上图的演示代码:
<body>
<div id="app">
<button @click='changeLogin'>切换登录方式</button>
<component :is='loginType'></component>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('userInfoLogin',{
template:`
<div>
<div>用户名登陆:<input type="text"></div>
<div>请输入密码:<input type="password"></div>
</div>
`
});
Vue.component('phoneLogin',{
template:`
<div>
<div>请输入手机号:<input type="text"></div>
<div>请输入验证码:<input type="text"></div>
</div>
`
})
var vm = new Vue({
el: '#app',
data: {
loginType:'userInfoLogin'
},
methods: {
changeLogin: function() {
this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin';
}
}
})
</script>
下面以上面的动态组件切换为例:
如果给上面的登录组件都加上一个created用来显示渲染次数的话,我们就可以看到是不是每次切换都会重新渲染。
如果加了keep-alive之后不再重复输出,那么就说明组件被缓存了。
<body>
<div id="app">
<button @click='changeLogin'>切换登录方式</button><!-- 2.多次点击切换,查看组件渲染情况 -->
<keep-alive>
<component :is='loginType'></component>
<!-- 4.注意在keep-alive包裹之前和之后的控制台输出情况 -->
</keep-alive>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('userInfoLogin',{
template:`
<div>
<div>用户名登陆:<input type="text"></div>
<div>请输入密码:<input type="password"></div>
</div>
`,
created: function(){
console.log('userInfoLogin')// 1.用来说明渲染了userInfoLogin组件,多次渲染则多次输出
}
});
Vue.component('phoneLogin',{
template:`
<div>
<div>请输入手机号:<input type="text"></div>
<div>请输入验证码:<input type="text"></div>
</div>
`,
created: function(){
console.log('phoneLogin') // 2.用来说明渲染了phoneLogin组件,多次渲染则多次输出
}
})
var vm = new Vue({
el: '#app',
data: {
loginType:'userInfoLogin'
},
methods: {
changeLogin: function() {
this.loginType = this.loginType === 'userInfoLogin'?'phoneLogin':'userInfoLogin';
}
}
})
</script>
1.在元素中使用ref属性给元素起一个有标识意义的名字。
2.this.\(refs可以获取当前组件实例的所有使用了ref的元素,`this.\)refs.名字`代表指定的元素。
3.然后你可以进行dom操作了。
<body>
<div id="app">
<div ref='demo'>1</div> <!-- 1. 设置ref -->
<button @click='changeColor'>+1</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el: '#app',
methods: {
changeColor:function() {
console.log(this.$refs.demo.innerText) // 获取ref对象,有了dom对象你就可以进行dom操作了。
this.$refs.demo.style = 'color:red'
this.$refs.demo.innerText+=this.$refs.demo.innerText
}
}
})
</script>
refs也可以用在组件上,可以获取组件的数据(但这时候的ref获取的不是一个dom对象,而是一个vue实例对象,所以不能获取innerText这类dom属性)。
<body>
<div id="app">
<child ref='demo'></child>
<button @click='changeColor'>+1</button>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
Vue.component('child',{
data: function(){
return {
msg:'hello world!'
}
},
template:'<div>hello</div>'
})
var app = new Vue({
el: '#app',
methods: {
changeColor:function() {
console.log(this.$refs.demo.msg) // 获取组件实例的数据。
}
}
})
</script>
如果说前面讲的都是基础,必须要掌握的话,那么动画效果是锦上添花,可有可无(对于我这些不追求动画效果就不显得重要了),所以这里就不讲了,这里仅仅是为了显示vue能有实现动画效果的功能。
有兴趣的可以看一下官网:
vue官网动画效果直通车
标签:关系 计算 required component 布尔 graph lang 常用 live
原文地址:https://www.cnblogs.com/progor/p/10324712.html