人面对复杂问题的处理方式:
组件化也是类似的思想:
组件化是Vue.js
中的重要思想:
组件的使用分成三个步骤:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<my-cpn>my-cpn>
<my-cpn>my-cpn>
div>
<script src="../js/vue.js">script>
<script>
// 1、创建组件构造器
const cpnC = Vue.extend({
template: `
我是标题
我是内容,哈哈哈哈
我是内容,呵呵呵呵
`,
});
// 2、注册组件
Vue.component('my-cpn', cpnC);
const app = new Vue({
el: '#app',
data: {
}
})
script>
body>
html>
Vue.extend()
:
Vue.extend()
创建的是一个组件构造器。template
代表我们自定义组件的模板。该模板就是在使用到组件的地方,要显示的HTML
代码。Vue2.x
的文档中几乎已经看不到了,它会直接使用下面我们会讲到的语法糖,但是在很多资料还是会提到这种方式,而且这种方式是学习后面方式的基础。Vue.component()
:
Vue.component()
是将刚才的组件构造器注册为一个组件,并且给它起一个组件的标签名称。1、
注册组件的标签名,2、
组件构造器组件必须挂在在某个Vue
实例下,否则它不会生效
Vue
根实例 (new Vue
) 的模板中。Vue
实例中才能使用,并不能在其他未进行注册的Vue
实例中使用。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<my-cpn>my-cpn>
<cpn>cpn>
div>
<hr>
<div id="app2">
<my-cpn>my-cpn>
<cpn>cpn>
div>
<script src="../js/vue.js">script>
<script>
// 1、创建组件构造器
const cpnC = Vue.extend({
template: `
我是标题
我是内容,哈哈哈哈
我是内容,呵呵呵呵
`,
});
// 2、注册组件(全局组件,意味着可以在多个 Vue 实例下面使用)
Vue.component('my-cpn', cpnC);
const app = new Vue({
el: '#app',
data: {
},
// 局部组件
components: {
cpn: cpnC
}
})
const app2 = new Vue({
el: '#app2',
data: {
}
})
script>
body>
html>
在上面的代码中,我们创建了两个Vue
实例对象app
和app2
,同时我们使用注册了全局组件my-cpn
以及在app
内注册了局部组件cpn
。我们在app
和app2
中使用上面的两个组件,可以看到局部组件cpn
并没有在app2
中生效,符合我们的预期。
在前面我们看到了组件树:
我们来通过代码看这种层级关系:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn2>cpn2>
div>
<script src="../js/vue.js">script>
<script>
// 1、创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
我是标题1
我是内容,哈哈哈哈
`,
});
// 2、创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
我是标题2
我是内容,呵呵呵呵
`,
components: {
cpn1: cpnC1,
},
});
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn2: cpnC2,
}
})
script>
body>
html>
通过语法糖的方式,我们可以跳过Vue.extend()
方法,直接通过Vue.component()
方法实现组件的注册。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn1>cpn1>
<cpn2>cpn2>
div>
<script src="../js/vue.js">script>
<script>
// 1、全局组件注册语法糖
// 1.1 创建组件构造器
// const cpn1 = Vue.extend({
// template: `
//
// 我是标题
// 我是内容,哈哈哈哈
// 我是内容,呵呵呵呵
// `,
// });
// 1.2 注册组件
// Vue.component('cpn1', cpn1);
Vue.component('cpn1', {
template: `
我是标题
我是内容,哈哈哈哈
我是内容,呵呵呵呵
`,
});
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn2: {
template: `
我是标题
我是内容,哈哈哈哈
我是内容,呵呵呵呵
`,
}
}
})
script>
body>
html>
在IDE
中,写组件的template
时,由于其是一个字符串,所以没有代码提示,写起来非常不方便。并且template
的代码直接耦合在Vue
的代码中,非常的凌乱。为此,我们可以将组件的模板抽离出来,有两种抽离的方式。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn1>cpn1>
<hr>
<cpn2>cpn2>
div>
<script type="text/x-template" id="cpn">
<div>
<h2>我是标题</h2>
<p>我是内容,哈哈哈哈</p>
</div>
script>
<template id="cpn2">
<div>
<h2>我是标题h2>
<p>我是内容,哈哈哈哈p>
div>
template>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn1', {
template: '#cpn',
});
Vue.component('cpn2', {
template: '#cpn2',
});
const app = new Vue({
el: '#app',
data: {
},
})
script>
body>
html>
通常,我们采用的是方式二。可以看到,组件模板抽离后的代码看起来非常简洁。
组件是一个单独功能模块的封装:
这个模块有自己的 HTML 模板,也应该有属于自己的数据data
组件中的数据是保存在哪里呢?顶层的Vue
实例中吗?
我们可以通过下面的代码测试,组件中能不能直接访问Vue
实例中的data
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>cpn>
div>
<template id="a">
<div>
<h2>{
{ message}}h2>
div>
template>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn', {
template: '#a',
});
const app = new Vue({
el: '#app',
data: {
message: 'Hello'
},
})
script>
body>
html>
我们发现并不能访问,而且即使可以访问,如果将所有的数据都放在Vue
实例中,Vue
实例会变的非常臃肿。
结论:Vue 组件应该有自己保存数据地方。
组件自己的数据存放在哪里呢?
data
属性(也可以有methods
属性)data
属性必须是一个函数
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>cpn>
div>
<template id="a">
<div>
<h2>{
{ title }}h2>
<p>我是内容,哈哈哈哈p>
div>
template>
<script src="../js/vue.js">script>
<script>
Vue.component('cpn', {
template: '#a',
data() {
return {
title: 'Hello'
}
}
});
const app = new Vue({
el: '#app',
data: {
},
})
script>
body>
html>
子组件是不能引用父组件或者Vue
实例的数据的。
但是,在开发中,往往一些数据确实需要从上层产地到下层:
如果进行父子组件间的通信呢?官方提到
Vue
实例同样是组件,所以Vue
实例与子组件通信和父组件与子组件通信的过程是一样的。
在子组件中,使用选项props
来声明需要从父组件接收到的数据。
props
的值有两种方式:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn :cmovies="movies" :cmessage="message">cpn>
<cpn cmovies="movies" cmessage="message">cpn>
div>
<template id="a">
<div>
{
{cmessage}}
<ul>
<li v-for="movie in cmovies">{
{movie}}li>
ul>
div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = {
template: '#a',
props: ['cmovies', 'cmessage'],
}
const app = new Vue({
el: '#app',
data: {
message: 'Hello',
movies: ['a', 'b', 'c', 'd']
},
components: {
cpn
}
})
script>
body>
html>
我们说过,处理数组之外,我们也可以使用对象,当需要对props
进行类型等验证时,就需要对象写法了。
验证都支持如下数据类型:
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn :cmovies="movies" >cpn>
div>
<template id="a">
<div>
{
{cmessage}}
<ul>
<li v-for="movie in cmovies">{
{movie}}li>
ul>
div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = {
template: '#a',
// props: ['cmovies', 'cmessage'],
props: {
// 1、类型检查
// cmovies: Array,
// cmessage: String
// 2、提供一些默认值
cmessage: {
type: String,
default: 'aaaa',
required: false
},
cmovies: {
type: Array,
}
}
}
const app = new Vue({
el: '#app',
data: {
message: 'Hello',
movies: ['a', 'b', 'c', 'd']
},
components: {
cpn
}
})
script>
body>
html>
props
用于父组件向子组件传递数据,还有一种比较常见的是子组件传递数据或事件到父组件中。
我们应该如何处理呢?这个时候,我们需要使用自定义事件来完成。
什么时候需要自定义事件呢?
v-on
不仅仅可以用于监听DOM
事件,也可以用于组件间的自定义事件。自定义事件的流程︰
$emit('事件名', [参数])
来触发事件。v-on
来监听子组件事件。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn v-on:itemclick="cpnClick">cpn>
div>
<template id="a">
<div>
<button v-for="item in categories" @click="btnClick(item)">
{
{item.name}}
button>
div>
template>
<script src="../js/vue.js">script>
<script>
const cpn = {
template: '#a',
data() {
return {
categories: [
{
id: 1, name: '热门推荐'},
{
id: 2, name: '手机数码'},
{
id: 3, name: '家用家电'},
{
id: 4, name: '电脑办公'},
]
}
},
methods: {
btnClick(item) {
// 产生事件:自定义事件
this.$emit('itemclick', item);
}
}
}
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn
},
methods: {
cpnClick(item) {
console.log(item)
}
}
})
script>
body>
html>
上面代码的含义如下:
itemclick
事件,该事件产生时,执行cpnClick
方法btnClick
方法btnClick
方法内部,产生itemclick
事件有时候,我们需要父组件直接访问子组件,子组件直接访问父组件,或者是子组件访问根组件。
$children
或$refs
$parent
this.$children
是一个数组类型,它包含所有子组件对象
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>cpn>
<button @click="btnClick">点击button>
div>
<template id="cpn">
<div>
<h2>我是子组件h2>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
methods: {
btnClick() {
console.log(this.$children);
this.$children[0].showMessage();
}
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage() {
console.log("showMessage")
}
}
}
},
})
script>
body>
html>
可以看到,我们通过this.$children
成功访问了cpn
组件,并且调用了cpn
所拥有的methods
。当然也可以访问cpn
的其他属性。
通过this.$children
存在这样一个问题,便是我们要访问一个组件时,可能并不知道其索引,需要进行遍历查找。
而this.$refs
的作用相当于给组件一个id
,这样我们便能根据这个id
直接找到对应的组件了。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn ref="aaa">cpn>
<button @click="btnClick">点击button>
div>
<template id="cpn">
<div>
<h2>我是子组件h2>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
methods: {
btnClick() {
console.log(this.$refs);
console.log(this.$refs.aaa);
this.$refs.aaa.showMessage();
}
},
components: {
cpn: {
template: '#cpn',
methods: {
showMessage() {
console.log("showMessage")
}
}
}
},
})
script>
body>
html>
this.$parent
获得父组件this.$root
获得根组件
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>我是子组件h2>
<button @click="btnClick">点击button>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
methods: {
showMessage() {
console.log("showMessage")
}
},
components: {
cpn: {
template: '#cpn',
methods: {
btnClick() {
console.log(this.$parent);
this.$parent.showMessage();
// 访问根组件
console.log(this.$root);
}
}
}
},
})
script>
body>
html>
slot
翻译为插槽:
组件的插槽︰
例子∶移动网站中的导航栏。
nav-bar
组件。一旦有了这个组件,我们就可以在多个页面中复用了。如何去封装这类的组件呢?
如何封装合适呢?抽取共性,保留不同。
这就是为什么我们要学习组件中的插槽slot
的原因。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn><button>按钮button>cpn>
<cpn><div>哈哈哈div>cpn>
<cpn><a href="">百度a>cpn>
<cpn>cpn>
div>
<template id="cpn">
<div>
<h2>我是组件h2>
<div>我是组件,哈哈哈div>
<slot>slot>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpn',
}
}
})
script>
body>
html>
在上面的代码中,我们在组件中使用
创建插槽,当我们使用该组件时,组件标签内的内容会自动替换掉
。
当子组件的功能复杂时,子组件的插槽可能并非是一个。
如何使用具名插槽呢?
slot
元素一个name
属性即可
我们来给出一个案例︰
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>
<span slot="center">标题span>
cpn>
div>
<template id="cpn">
<div>
<slot name="left"><span>左边span>slot>
<slot name="center"><span>中间span>slot>
<slot name="right"><span>右边span>slot>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpn',
}
}
})
script>
body>
html>
在上面的代码中,我们在组件中创建了三个具名插槽,分别为left
、center
、right
.
我们使用该组件时,通过slot="center"
指定要将内容插入到center
的插槽。
在了解作用域插槽之前,我们需要西安了解一个概念:编译作用域。
官方对于编译的作用域解析比较简单,我们自己来通过一个例子来理解这个概念:
我们来考虑下面的代码是否最终是可以渲染出来的︰
中,我们使用了isShow
属性。isShow
属性包含在组件中,也包含在Vue
实例中。 答案︰最终可以渲染出来,也就是使用的是Vue
实例的属性。
为什么呢?
的时候,整个组件的使用过程是相当于在父组件中出现的。isShow
使用的是Vue
实例中的属性,而不是子组件的属性。
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn v-show="isShow">cpn>
div>
<template id="cpn">
<div>
<h2>我是组件h2>
<div>我是组件,哈哈哈div>
<button v-show="isShow">按钮button>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
isShow: true
},
components: {
cpn: {
template: '#cpn',
data() {
return {
isShow: false
}
}
}
}
})
script>
body>
html>
作用域插槽是slot
—个比较难理解的点,而且官方文档说的又有点不清晰。
这里,我们用一句话对其做一个总结,然后我们在后续的案例中来体会︰
我们先提一个需求︰
pLanguages: ['JavaScript', 'Python', 'Swift', 'Go','C++']
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div id="app">
<cpn>cpn>
<cpn>
<template slot-scope="slot">
<span v-for="item in slot.data">{
{item}} - span>
template>
cpn>
div>
<template id="cpn">
<div>
<h2>我是组件h2>
<slot :data="pLanguages">
<ul>
<li v-for="item in pLanguages">
{
{item}}
li>
ul>
slot>
div>
template>
<script src="../js/vue.js">script>
<script>
const app = new Vue({
el: '#app',
data: {
},
components: {
cpn: {
template: '#cpn',
data() {
return {
pLanguages: ['Python', 'Java', 'Go', 'C++']
}
}
}
}
})
script>
body>
html>