一、组件化
1.1 简单认识组件化
1.2 全局组件与局部组件
全局组件:可以在多个Vue实例中使用。
局部组件:只能在单个Vue实例中使用。
在项目开发中,一般只设置一个vue实例,一般采用局部组件的方式。
<body>
<div id="app">
<mycpn></mycpn>
</div>
<div id="app2">
<mycpn></mycpn>
</div>
<script src="../../vue学习/js/vue.js"></script>
<script>
//1.创建组件构造器
const cpnC = Vue.extend({
template: `
<div>
<h2>你好啊</h2>
<h1> ahhha</h1>
</div>
`
})
// // 2.注册全局组件:
// Vue.component('mycpn',cpnC)
const app = new Vue({
el: '#app',
data: {
message: '你好'
},
// 2.注册局部组件
components: {
mycpn: cpnC
}
})
const app2 = new Vue({
el: '#app2',
data: {
message: '你好'
}
})
</script>
</body>
1.3 父组件与子组件
父组件中使用子组件,父组件中要注册子组件。
<body>
<div id="app">
<cpn1></cpn1>
</div>
<script src="../../vue学习/js/vue.js"></script>
<script>
const cpn2 = Vue.extend({
template: `
<div>
<h1>你好,我是标题二</h1>
</div>
`
})
const cpn1 = Vue.extend({
template: `
<div>
<h1>你好,我是标题一</h1>
<cpn2><cpn2>
</div>
`,
//注意要在父组件中注册子组件
components: {
cpn2:cpn2
}
})
const app = new Vue({
el: '#app',
components: {
cpn1: cpn1,
cpn2: cpn2
}
})
</script>
</body>
在运行的时候,会找到对应的组件,将模板内容编译好,编译成render函数,直接渲染Html,因此子组件与vue实例没有任何关系,如果想用子组件,需要在vue实例中注册。
语法糖的写法。省去了Vue.extend(),但是实际上还是调用了Vue.extend()
Vue.component('mycpn',{
template: `
<div>
<h2>你好啊</h2>
<h1> ahhha</h1>
</div>
`
})
const app = new Vue({
el: '#app',
components: {
'mycpn': {
template: `
<div>
<h2>你好啊</h2>
<h1> ahhha</h1>
</div>
`
}
}
})
1.4 组件的模板分离
js与html混在一起,显得很乱,因此要使用模板分离:有以下两种方式:
法1:使用script标签,注意类型要设置成 text/x-template
法2:使用template标签
<body>
<!-- 写法一:使用script标签,注意类型要设置成 text/x-template -->
<!-- <script type="text/x-template" id="tmp">
<div>
<h1>hhahaha</h1>
<h2>你好</h2>
</div>
</script> -->
<!-- 写法2:使用template标签 -->
<template id="tmp">
<div>
<h1>hhahaha</h1>
<h2>你好</h2>
</div>
</template>
<div id="app">
<cpn1></cpn1>
</div>
<script src="../../vue学习/js/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
components: {
"cpn1": {template: '#tmp'}
}
})
</script>
</body>
1.5 组件能够访问vue中的数据吗。
不能。如果数据都在vue实例中,相当于多个vue组件共享了数据,但其实我们希望,相同的组件是分离的(各自运行自己的,互不影响).因此,组件有自己的数据。并且组件的数据不是一个对象,而是一个函数,并且该函数返回一个对象。
为什么这样设计呢。可以用以下代码来理解。
如果data是一个对象,vue在获取数据的时候,相当于返回data,其中一个组件改变了data中的值,因为data是共享的,所以data中的值相对于其他的组件就会改变。
因为改成了函数的方式,并且返回一个大括号形式的对象。
<script>
function fn() {
return {
name: 18,
age: 20
}
}
var obj1 = fn();
var obj2 = fn();
var obj3 = fn();
obj1.age = 'lklkl'
console.log(obj1);
console.log(obj2);
console.log(obj3);
// 上述三个obj返回三个不同的地址。因此,三个obj互不影响。
const person = {
name:18,
age:20
}
function fnn(){
return person;//返回的其实是地址
}
var obj4 = fnn();
var obj5 = fnn();
var obj6 = fnn();
obj4.age = 'lklkl'
console.log(obj4);
console.log(obj5);
console.log(obj6);
// 上述obj返回的是相同的地址,因此三个obj是相互影响的。一个改变,其余三个也跟着改变,并且person也受影响。
// 组件的数据写法:
Vue.extend("cpn1",{
template:`...`,
data(){
return {
title: "title"
}
}
})
</script>
1.6 父子组件的通信
1.6.1 父传子:
<body>
<template id="cpn">
<div>
<h1>{{cmessage}}</h1>
<h2>{{cmovies}}</h2>
<ul>
<li v-for="item in cmovies">{{item}}</li>
</ul>
</div>
</template>
<div id="app">
<cpn1 :cmovies="movies" :c-message="message"></cpn1>
</div>
<script src="../../vue学习/js/vue.js"></script>
<script>
const cpn1 = Vue.component("cpn1",{
template: '#cpn',
// 写法1:数组写法:
//props: ["cMovies","cmessage"],
// 写法2:对象写法:类型限制
// props: {
// cmovies: Array, //[String, Number] 可以写多个类型 Person,也可以自定义类型
// cMessage: String
// },
// 写法3:对象写法:默认值
// props: {
// cmovies: {
// type: Array,
// default(){return ["你好啊"]}, //数组和对象,此处必须是函数
// required:false
// },
// cMessage: {
// type: String,
// default: "你好啊",
// required:true //当为true时,表示该参数必须传入,不传入的话会报错
// }
// },
// 写法4:自己写验证:还不太懂,初次尝试
props: {
cmovies: {
validator(value){
if(value.length >= 1) //如果小于1,就会报错。
return "success";
}
},
cMessage: {
type: String,
default: "你好啊",
required:true //当为true时,表示该参数必须传入,不传入的话会报错
}
},
data(){
return {}
}
})
const app = new Vue({
el: '#app',
data: {
movies: ['海贼王','花束般的恋爱','奇迹笨小孩', '你好旧时光'],
message: "电影系列"
},
components: {
cpn1
}
})
</script>
</body>
一些小的注意点:
(1)template中,必须有一个明确的根元素
(2)v:bind绑定时,不支持驼峰命名法,比如定义的元素叫cMessage,因此使用v:bind绑定时,需要做一个转换::c-message。
1.6.2 子传父
首先在模板中绑定子组件事件,同时在子组件中的方法中写调用的方法,然后用$emit来向父组件发射点击事件m,同时在父组件模板中写监听事件@m=“f”,然后在父组件methods中写调用的方法f。
<body>
<template id="cpn">
<div>
<!--步骤1:子组件写监听-->
<button v-for="item in btns" @click="btnclick(item)">{{item.name}}</button>
</div>
</template>
<div id="app">
<!-- 不写括号时,默认是子组件传过来的参数。但是vue实例中,不写括号时,默认时event参数,注意区分 -->
<!--步骤3:父组件监听-->
<cpn @btnclickfather="btnclickfather"></cpn>
</div>
<script src="../../vue学习/js/vue.js"></script>
<script>
const cpn = Vue.component("cpn",{
template: '#cpn',
data(){
return {
btns: [
{id:'aaa',name: "电器"},
{id:'bbb',name: "手机"},
{id:'ccc',name: "服饰"},
{id:'ddd',name: "化妆品"},
]
}
},
methods: {
//步骤2:写监听函数
btnclick(item){
this.$emit("btnclickfather", item);
}
}
})
const app = new Vue({
el: '#app',
components: {
cpn
},
methods: {
//步骤4:父组件写监听函数
btnclickfather(value) {
console.log(value);
}
}
})
</script>
</body>
案例练习:在子组件中双向绑定从父组件中传递来的数据
<body>
<!-- 实现功能:子组件中数据cnum1和cnum2是从父组件传递过来的。在子组件中有一个input输入框,想要和数据实现双向绑定。 -->
<!-- 但是vue中不支持直接和props中数据做双向绑定。因为毕竟数据是父组件的。 -->
<!-- 因此,在子组件的data中,定义dnum1和dnunm2,使其等于prop中的cnum1和cnum2 -->
<!-- 因此子组件中,input可以和dnum1和dnum2实现双向绑定。-->
<!-- 法1:为了实现额外的功能,可以利用v-model的原理,绑定value和事件@input,在@input中做一些别的事 -->
<!-- 如何使父组件可以绑定呢:在子组件中绑定input事件函数,当数据发生改变时,在该事件中利用emit事件向父组件发射监听事件-->
<!-- 然后在父组件中,实现该事件的监听,动态改变num1和num2的值。 -->
<!-- 额外功能,num2=num1*100 -->
<!-- 法2: 利用wathch,监听数据的变化,然后在watch中,做一些其他的事-->
<div id="app">
<cpn :cnum1="num1" :cnum2="num2" @num1change="num1change" @num2change="num2change"></cpn>
</div>
<template id="cpn">
<div>
<h1>cnum1:{{cnum1}}</h1>
<h1>dnum1:{{dnum1}}</h1>
<!-- 法1:使用组件的双向绑定原理来实现:利用v:bind value和@input -->
<input type="text" :value="dnum1" @input="num1change">
<input type="text" name="" id="" :value="dnum2" @input="num2change">
<!-- 法2:使用model和watch实现 -->
<!-- <input type="text" v-model="dnum1">
<input type="text" name="" id="" v-model="dnum2"> -->
<h2>cnum2:{{cnum2}}</h2>
<h2>dnum2:{{dnum2}}</h2>
</div>
</template>
<script src="../../vue学习/js/vue.js"></script>
<script>
const cpn = {
template: '#cpn',
props: {
cnum1: {
type: Number,
default: -1
},
cnum2: {
type: Number,
default: -2
}
},
data(){
return {
dnum1: this.cnum1,
dnum2: this.cnum2
}
},
methods: {
num1change(event){
this.dnum1 = parseInt(event.target.value);
this.dnum2 = parseInt(this.dnum1)*100;
this.$emit('num1change',parseInt(this.dnum1));
},
num2change(event){
this.dnum2 = parseInt(event.target.value);
this.dnum1 = parseInt(this.dnum1) / 100;
this.$emit('num2change',parseInt(this.dnum2));
}
},
watch: {
dnum1(){
this.dnum2 = parseInt(this.dnum1)*100;
this.$emit('num1change',parseInt(this.dnum1));
},
dnum2(){
this.$emit('num2change',parseInt(this.dnum2));
}
}
}
const app = new Vue({
el: '#app',
data: {
num1: 0,
num2: 1
},
components: {
cpn
},
methods: {
num1change(value){
this.num1 = value;
this.num2 = value * 100;
},
num2change(value){
this.num2 = value;
}
}
})
</script>
</body>
1.6.3 父访问子组件的属性。
(1)使用$children,获得的是一个数组。
this.$children[0].name
(2)使用$refs,获得的是一个对象.
要先在子组件中,加一个ref属性。然后就可以通过ref属性获得子组件
//设置ref属性
<cpn ref='aaa'></cpn>
//获取组件
this.$ref.aaa
1.6.4 子组件访问父组件属性
使用$parent访问父组件,只用$root访问vue实例
二、slot插槽
2.1 插槽的基本使用
(1)slot的基本使用:<slot></slot>
(2)插槽的默认值:<slot>button</slot>
(3)如果有多个值同时放入到组建中进行替换时,一起作为替换元素。
(4)具名插槽:有多个插槽时,要给插槽起个名字(加个属性name),替换时,在元素前加个属性slot='name'。(新的版本使用的是v-slot,自己去了解)
2.2 变量的编译作用域
变量会在自己的作用域中寻找变量
2.3 作用域插槽的使用
父组件替换插槽的标签,但是内容由子组件来提供。
实现内容:在子组件的的插槽中,按照父组件的意愿显示子组件的数据。
<body>
<div id="app">
<cpn></cpn>
<cpn>
<template slot-scope="slot">
<div>
<ul>
<li v-for="item in slot.data">{{item}}</li>
</ul>
<ul>
<li>{{slot.data.join(' - ')}}</li>
</ul>
</div>
</template>
</cpn>
</div>
<template id="cpn">
<slot :data="movies">
<ul>
<li v-for="item in movies">{{item}}</li>
</ul>
</slot>
</template>
<script src="../../vue学习/js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
components: {
"cpn": {
template: '#cpn',
data(){
return {
movies: ['美人鱼', '送你一朵小红花', '穿过寒冬拥抱你', '奇迹笨小孩']
}
}
}
}
})
</script>
三、模块化:
3.1 什么是模块化
由于多人在编写代码时,会存在变量名重复的问题。因此引入了模块化,一个js文件就是一个模块。
(1)匿名函数解决变量名冲突的问题。但是不能在另一个文件中引用变量
为了不能引用变量的问题,使用模块作为出口。返回一个对象。
(2)CommonJS的导入和导出
(3)es6的模块化的思想
①使用exports导出:
②使用import导入
案例: 练习commonJS和es6的导入和导出
CommonJS导出
const sum = function(a,b) {
return a + b;
}
const mult = function(a, b) {
return a*b;
}
module.exports = {
sum,
mult
}
es6导出
export const name = 'why'
export const age = 18
导入:
const {sum, mult} = require('./fn.js');
console.log(sum(3,4));
console.log(mult(3,4));
import {name, age } from './info'
console.log(name)
console.log(age)