3.1、什么是组件
3.1.1、传统方式开发的应用
一个网页通常包括三部分:结构(HTML)、样式(CSS)、交互(JavaScript)
3.1.2、组件化方式开发的应用
使用组件化方式开发解决了以上的两个问题:
① 每一个组件都有独立的js,独立的css,这些独立的js和css只供当前组件使用,不存在纵横交错。更加便于维护。
② 代码复用性增强。组件不仅让js css复用了,HTML代码片段也复用了(因为要使用组件直接引入组件即可)。
3.1.3、什么是组件?
① 组件:实现应用中局部功能的代码和资源的集合。凡是采用组件方式开发的应用都可以称为组件化应用。
② 模块:一个大的js文件按照模块化拆分规则进行拆分,生成多个js文件,每一个js文件叫做模块。凡是采用模块方式开发的应用都可以称为模块化应用。
③ 任何一个组件中都可以包含这些资源:HTML CSS JS 图片 声音 视频等。从这个角度也可以说明组件是可以包括模块的。
3.1.4、组件的划分粒度很重要
粒度太粗会影响复用性。为了让复用性更强,Vue的组件也支持父子组件嵌套使用。
子组件由父组件来管理,父组件由父组件的父组件管理。在Vue中根组件就是vm。因此每一个组件也是一个Vue实例。
3.2、组件的创建,注册,使用
3.2.1、组件的创建、注册、局部使用
第一步:创建组件
Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})
区别有哪些?
1. 创建Vue组件的时候,配置项中不能使用el配置项。
因为组件具有通用性,不特定为某个容器服务,它为所有容器服务
2. 配置项中的data不能使用直接对象的形式,必须使用function
以保证数据之间不会相互影响
3、使用template配置项来配置模板语句:HTML结构
第二步:注册组件
局部注册:
在配置项当中使用components,语法格式:
components : {
组件的名字 : 组件对象
}
全局注册:
第三步:使用组件
1、直接在页面中写<组件的名字></组件的名字>
2、也可以使用单标签<组件的名字 />
这种方式一般在脚手架中使用,否则会有元素不渲染的问题
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 3. 使用组件 -->
<userlist></userlist>
</div>
<script>
// 1.创建组件(结构HTML 交互JS 样式CSS)
const myComponent = Vue.extend({
template: `
<ul>
<li v-for="(user,index) of users" :key="user.id">
{{index}},{{user.name}}
</li>
</ul>
`,
data() {
return {
users: [
{ id: "001", name: "jack" },
{ id: "002", name: "lucy" },
{ id: "003", name: "james" },
],
};
},
});
// Vue实例
const vm = new Vue({
el: "#app",
data: {
msg: "第一个组件",
},
// 2. 注册组件(局部注册)
components: {
// userlist是组件的名字。myComponent只是一个变量名。
userlist: myComponent,
},
});
</script>
</body>
3.2.2、为什么组件中data数据要使用函数形式
面试题:为什么组件中data数据要使用函数形式
<script>
// 数据只有一份,数据会互相影响
let dataobj = {
counter: 1,
};
let a = dataobj;
let b = dataobj;
function datafun() {
return {
counter: 1,
};
}
// 只要运行一次函数,就会创建一个全新的数据,互不影响
let x = datafun();
let y = datafun();
</script>
3.2.3、创建组件对象的简写方式
创建组件对象也有简写形式:Vue.extend() 可以省略。直接写:{}
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 3. 使用组件 -->
<userlogin></userlogin>
</div>
<script>
// 1. 创建组件
/* const userLoginComponent = Vue.extend({
template : `
<div>
<h3>用户登录</h3>
<form @submit.prevent="login">
账号:<input type="text" v-model="username"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
<button>登录</button>
</form>
</div>
`,
data(){
return {
username : '',
password : ''
}
},
methods: {
login(){
alert(this.username + "," + this.password)
}
},
}) */
// 底层会在局部或全局注册组件时,自动调用Vue.extend()
const userLoginComponent = {
template: `
<div>
<h3>用户登录</h3>
<form @submit.prevent="login">
账号:<input type="text" v-model="username"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
<button>登录</button>
</form>
</div>
`,
data() {
return {
username: "",
password: "",
};
},
methods: {
login() {
alert(this.username + "," + this.password);
},
},
};
// Vue实例
const vm = new Vue({
el: "#app",
data: {
msg: "第二个用户登录组件",
},
// 2. 注册组件(局部注册)
components: {
userlogin: userLoginComponent,
},
});
</script>
</body>
3.2.4、组件的全局注册
<body>
<!--
组件的使用分为三步:
第一步:创建组件
Vue.extend({该配置项和new Vue的配置项几乎相同,略有差别})。
第二步:注册组件
局部注册:
在配置项当中使用components,语法格式:
components : {
组件的名字 : 组件对象
}
全局注册:
Vue.component('组件的名字', 组件对象)
第三步:使用组件
-->
<div id="app">
<h1>{{msg}}</h1>
<!-- 3. 使用组件 -->
<userlogin></userlogin>
</div>
<hr />
<div id="app2">
<userlogin></userlogin>
</div>
<script>
const userLoginComponent = {
template: `
<div>
<h3>用户登录</h3>
<form @submit.prevent="login">
账号:<input type="text" v-model="username"> <br><br>
密码:<input type="password" v-model="password"> <br><br>
<button>登录</button>
</form>
</div>
`,
data() {
return {
username: "",
password: "",
};
},
methods: {
login() {
alert(this.username + "," + this.password);
},
},
};
// 全局注册
Vue.component("userlogin", userLoginComponent);
// 第2个vue实例
const vm2 = new Vue({
el: "#app2",
});
// Vue实例
const vm = new Vue({
el: "#app",
data: {
msg: "全局注册组件",
},
// 注册组件(局部注册)
// components: {
// userlogin : userLoginComponent
// },
});
</script>
</body>
3.2.5、组件的命名细节
注册组件细节:
1. 在Vue当中是可以使用自闭合标签的,如果组件需要多次使用,前提必须在脚手架环境中使用。
2. 在创建组件的时候Vue.extend()可以省略,但是底层实际上还是会调用的,在注册组件的时候会调用。
3. 组件的名字
(1):全部小写
(2):首字母大写,后面都是小写
(3):kebab-case命名法(串式命名法。例如:user-login)
(4):CamelCase命名法(驼峰式命名法。例如:UserLogin),
但是这种方式只允许在脚手架环境中使用。
(5)不要使用HTML内置的标签名作为组件的名字。例如:header,main,footer
(6)在创建组件的时候,通过配置项配置一个name,这个name不是组件的名字, 是设置Vue开发者工具中显示的组件的名字。
<body>
<div id="app">
<h1>{{msg}}</h1>
<!-- 3. 使用组件 -->
<hello-world></hello-world>
<hello-world />
<!-- 使用多个的时候,会报错 -->
<!-- <hello-world />
<hello-world /> -->
</div>
<script>
// 1、创建组件
// const hello = {
// template: `<h1> helloworld </h1>`,
// };
// 2、全局注册组件
// Vue.component("hello-world", hello);
// 注册的时候,同时创建组件
Vue.component("hello-world", {
name: "hw",
template: `<h1> HelloWorld </h1>`,
});
// Vue实例
const vm = new Vue({
el: "#app",
data: {
msg: "组件注册注意点",
},
});
</script>
</body>
3.3、组件的嵌套
哪里要使用,就到哪里去注册,去使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>组件嵌套</title>
<script src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<app></app>
</div>
<script>
//4创建child组件
const child = {
template: `
<h3>child组件</h3>
`,
};
//3创建girl组件
const girl = {
template: `
<h2>girl组件</h2>
`,
};
//2 创建son组件
const son = {
template: `
<div>
<h2>son组件</h2>
<child />
</div>
`,
components: {
child,
},
};
//1、创建app组件,并注册son组件和girl组件
const app = {
template: `
<div>
<h1>app组件</h1>
<girl />
<son />
</div>
`,
components: {
girl,
son,
},
};
// 创建vm,并注册app组件
const vm = new Vue({
el: "#root",
// 1.3 使用app组件
//1.2、 注册app组件
components: {
app,
},
});
</script>
</body>
</html>
3.4、VueComponent & Vue
3.4.1、 this
new Vue({})配置项中的this就是:Vue实例(vm)
Vue.extend({})配置项中的this就是:VueComponent实例(vc)
打开vm和vc你会发现,它们拥有大量相同的属性。例如:生命周期钩子、methods、watch等。
<body>
<div id="app">
<h1>{{msg}}</h1>
<user></user>
</div>
<script>
// 创建组件
const user = Vue.extend({
template: `
<div>
<h1>user组件</h1>
</div>
`,
mounted() {
// user是什么呢????是一个全新的构造函数 VueComponent构造函数。
// this是VueComponent实例
console.log('vc', this)
},
});
// vm
const vm = new Vue({
el: "#app",
data: {
msg: "vm与vc",
},
components: {
user,
},
mounted() {
// this是Vue实例
console.log("vm", this);
},
});
</script>
</body>
3.4.2 vm === vc ???
只能说差不多一样,不是完全相等。
例如:
vm上有el,vc上没有。
另外data也是不一样的。vc的data必须是一个函数。
只能这么说:vm上有的vc上不一定有,vc上有的vm上一定有。
3.4.3 通过vc可以访问Vue原型对象上的属性
通过vc可以访问Vue原型对象上的属性:
为什么要这么设计?代码复用。Vue原型对象上有很多方法,例如:$mount(),对于组件VueComponent来说就不需要再额外提供了,直接使用vc调用$mount(),代码得到了复用。
Vue框架是如何实现以上机制的呢?
VueComponent.prototype.__proto__ = Vue.prototype
1、回顾原型对象
<script>
// prototype __proto__
// 构造函数(函数本身又是一种类型,代表Vip类型)
function Vip() {}
// Vip类型/Vip构造函数,有一个 prototype 属性。
// 这个prototype属性可以称为:显式的原型属性。
// 通过这个显式的原型属性可以获取:原型对象
// 获取Vip的原型对象
let x = Vip.prototype;
// 通过Vip可以创建实例
let a = new Vip();
// 对于实例来说,都有一个隐式的原型属性: __proto__
// 注意:显式的(建议程序员使用的)。隐式的(不建议程序员使用的。)
// 这种方式也可以获取到Vip的原型对象
let y = a.__proto__;
// 原型对象只有一个,其实原型对象都是共享的。
console.log(x === y); // true
// 作用:
// 在给“Vip的原型对象”扩展属性
Vip.prototype.counter = 1000;
// 通过a实例可以访问这个扩展的counter属性吗?可以访问。为什么?原理是啥?
// 访问原理:首先去a实例上找counter属性,如果a实例上没有counter属性的话,
//会沿着__proto__这个原型对象去找。
// 下面代码看起来表面上是a上有一个counter属性,实际上不是a实例上的属性,是a实例对应的原型对象上的属性counter。
console.log(a.counter);
//console.log(a.__proto__.counter)
</script>
2、底层实现
VueComponent.prototype.__proto__ = Vue.prototype
<body>
<div id="app">
<h1>{{msg}}</h1>
<user></user>
</div>
<script>
// 创建组件
const user = Vue.extend({
template: `
<div>
<h1>user组件</h1>
</div>
`,
mounted() {
// this是VueComponent实例
// user是什么呢????是一个全新的构造函数 VueComponent构造函数。
// 为什么要这样设计?为了代码复用。
// 底层实现原理:
// VueComponent.prototype.__proto__ = Vue.prototype
console.log("vc.counter", this.counter);
},
});
// vm
const vm = new Vue({
el: "#app",
data: {
msg: "vm与vc",
},
components: {
user,
},
mounted() {
// this是Vue实例
console.log("vm", this);
},
});
// 这个不是给Vue扩展counter属性。
// 这个是给“Vue的原型对象”扩展一个counter属性。
Vue.prototype.counter = 1000;
console.log("vm.counter", vm.counter);
// 本质上是这样的:
console.log("vm.counter", vm.__proto__.counter);
console.log("user.prototype.__proto__ === Vue.prototype", user.prototype.__proto__ === Vue.prototype);
</script>
</body>