全局组件
-
- 创建组件构造器对象
-
- 注册组件
-
- 使用组件
<div id="app"> <!--3.使用组件--> <my-cpn></my-cpn> <div> <div> <!--3.使用组件--> <my-cpn></my-cpn> </div> </div> </div> <div id="app2"> <!--3.使用组件--> <my-cpn></my-cpn> </div> <!--在此处使用组件无效,因为每在Vue实例挂载的范围内,脱离了Vue的管理--> <my-cpn></my-cpn> <script src="../js/vue.js"></script> <script> // 1.创建组件构造器对象 const cpnC = Vue.extend({ template: ` <div> <h2>我是标题</h2> <p>我是内容, 哈哈哈哈</p> <p>我是内容, 呵呵呵呵</p> </div>` }) // 2.注册全局组件,因此在被挂载的#app和#app2中都可以使用 Vue.component('my-cpn', cpnC) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) const app2 = new Vue({ el: '#app2' }) </script>
局部组件
<div id="app">
<!--3.使用组件-->
<my-cpn></my-cpn>
<div>
<div>
<!--3.使用组件-->
<my-cpn></my-cpn>
</div>
</div>
</div>
<div id="app2">
<!--在此处使用组件无效-->
<my-cpn></my-cpn>
</div>
<!--在此处使用组件无效-->
<my-cpn></my-cpn>
<script src="../js/vue.js"></script>
<script>
// 1.创建组件构造器对象
const cpnC = Vue.extend({
template: `
<div>
<h2>我是标题</h2>
<p>我是内容, 哈哈哈哈</p>
<p>我是内容, 呵呵呵呵</p>
</div>`
})
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
// 2.注册局部组件,因此只能在被该Vue实例挂载下的dom使用
myCpn: cpnC
}
})
const app2 = new Vue({
el: '#app2'
})
</script>
父组件与子组件
- 父子组件错误用法:以子标签的形式在Vue实例中使用
- 因为当子组件注册到父组件的components时,Vue会编译好父组件的模块
- 该模板的内容已经决定了父组件将要渲染的HTML(相当于父组件中已经有了子组件中的内容了)
<child-cpn></child-cpn>是只能在父组件中被识别的- 类似这种用法,
<child-cpn></child-cpn>是会被浏览器忽略的
- 正确用法
<div id="app">
<cpn2></cpn2>
<!--<cpn1></cpn1>-->
</div>
<script src="../js/vue.js"></script>
<script>
// 1.创建第一个组件构造器(子组件)
const cpnC1 = Vue.extend({
template: `
<div>
<h2>我是标题1</h2>
<p>我是内容, 哈哈哈哈</p>
</div>
`
})
// 2.创建第二个组件构造器(父组件)
const cpnC2 = Vue.extend({
template: `
<div>
<h2>我是标题2</h2>
<p>我是内容, 呵呵呵呵</p>
<cpn1></cpn1>
</div>
`,
components: {
cpn1: cpnC1
}
})
// root组件
const app = new Vue({
el: '#app',
data: {
message: '你好啊'
},
components: {
cpn2: cpnC2
}
})
</script>
-
语法糖
主要是省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替
<div id="app"> <cpn1></cpn1> <cpn2></cpn2> </div> <script src="../js/vue.js"></script> <script> // 1.全局组件注册的语法糖 // 1.创建组件构造器 // const cpn1 = Vue.extend() // 2.注册组件 Vue.component('cpn1', { template: ` <div> <h2>我是标题1</h2> <p>我是内容, 哈哈哈哈</p> </div> ` }) // 2.注册局部组件的语法糖 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { 'cpn2': { template: ` <div> <h2>我是标题2</h2> <p>我是内容, 呵呵呵</p> </div> ` } } }) </script> -
组件模板的分离写法
使用
<template>标签<div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div> <!-- 1.script标签, 注意:类型必须是text/x-template--> <!--<script type="text/x-template" id="cpn">--> <!--<div>--> <!--<h2>我是标题</h2>--> <!--<p>我是内容,哈哈哈</p>--> <!--</div>--> <!--</script> --> <!--2.template标签--> <template id="cpn"> <div> <h2>我是标题</h2> <p>我是内容,呵呵呵</p> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('cpn', { template: '#cpn' }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script> -
数据存放问题
组件是一个单独功能模块的封装,有属性自己的数据data(还有methods等属性,原型就是Vue实例),不能直接访问父组件中的data
<div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div> <!--1.script标签, 注意:类型必须是text/x-template--> <!--<script type="text/x-template" id="cpn">--> <!--<div>--> <!--<h2>我是标题</h2>--> <!--<p>我是内容,哈哈哈</p>--> <!--</div>--> <!--</script>--> <!--2.template标签--> <template id="cpn"> <div> <h2>{{title}}</h2> <p>我是内容,呵呵呵</p> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册一个全局组件 Vue.component('cpn', { template: '#cpn', data() { return { title: 'abc' } } }) const app = new Vue({ el: '#app', data: { message: '你好啊', // title: '我是标题' } }) </script> -
组件中的
data为什么是函数,且返回一个对象,对象内部保存着数据如果不是函数,则会导致不同的组件操作数据时会导致其他组件的数据一起变动,而函数返回的数据属于该组件自己,和其他组件无关
<!--组件实例对象--> <div id="app"> <cpn></cpn> <cpn></cpn> <cpn></cpn> </div> <template id="cpn"> <div> <h2>当前计数: {{counter}}</h2> <button @click="increment">+</button> <button @click="decrement">-</button> </div> </template> <script src="../js/vue.js"></script> <script> // 1.注册组件 const obj = { counter: 0 } Vue.component('cpn', { template: '#cpn', // 使用这样的data,则每个组件有独立的数据,每个组件实例都会接受到一个新的对象,对应一块新的内存地址 data() { return { counter: 0 } }, // 使用这样的data,每个组件共享数据,因为obj会传回一个地址,故每个组件实例接受到的是一个地址 // data() { // return obj // }, methods: { increment() { this.counter++ }, decrement() { this.counter-- } } }) const app = new Vue({ el: '#app', data: { message: '你好啊' } }) </script> -
父组件向子组件传递数据
通过props向子组件传递数据
- 方式一:字符串数组,数组中的字符串就是传递时的名称
- 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等
<div id="app"> <!--<cpn v-bind:cmovies="movies"></cpn>--> <!--<cpn cmovies="movies" cmessage="message"></cpn>--> <cpn :cmessage="message" :cmovies="movies"></cpn> </div> <template id="cpn"> <div> <ul> <li v-for="item in cmovies">{{item}}</li> </ul> <h2>{{cmessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> // 父传子: props const cpn = { template: '#cpn', // props: ['cmovies', 'cmessage'], props: { // 1.类型限制 // cmovies: Array, // cmessage: String, // 2.提供一些默认值, 以及必传值 cmessage: { type: String, default: 'aaaaaaaa', required: true //如果使用子组件每传递值给cmessage时,会报错 }, // 类型是对象或者数组时, 默认值必须是一个函数 cmovies: { type: Array, default() { return [] }//默认值时数组或者对象时 必须用函数返回,否则会报错 }, cmessage: { // 自定义验证函数 validtor: function (value){ return ['你好啊', '你好吗'].indexOf(value) !== -1 } }, }, data() { return {} }, methods: { } } const app = new Vue({ el: '#app', data: { message: '你好啊', movies: ['海王', '海贼王', '海尔兄弟'] }, components: { cpn } }) </script> -
props中的驼峰标识&template下有多个标签的话,需要有一个根节点,否则会报错v-bind:class不能正确识别驼峰标识,因此传入props时,下面需要用 c-info替换cInfo,child-my-message替换childMyMessage
<div id="app"> <!-- 下面不能使用驼峰标识符 --> <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn> </div> <template id="cpn"> <div><!-- 模板里面有多个属性的话,需要用一个dom元素包裹起来,否则会报错 --> <h2>{{cInfo}}</h2> <h2>{{childMyMessage}}</h2> </div> </template> <script src="../js/vue.js"></script> <script> const cpn = { template: '#cpn', props: { cInfo: { type: Object, default() { return {} }, required:true // 如果用了上面这个属性则一定要调用,否则会报错 }, childMyMessage: { type: String, default: '' } } } const app = new Vue({ el: '#app', data: { info: { name: 'CodingKid', age: 18, height: 1.88 }, message: 'Hello, Vue.js!' }, components: { cpn } }) </script> -
子组件向父组件传递自定义事件
在发送事件同时,可以传递参数到父组件
<!--父组件模板--> <div id="app"> <!-- 接受子组件发射的事件,传递过来的参数会默认传递给cpnClick,就如同$event,如果在这里使用$event,传递过来就是item,而不是事件本身 --> <!-- 在这里不能按照驼峰的方式写,但在脚手架里面可以 --> <cpn @item-click="cpnClick"></cpn> </div> <!--子组件模板--> <template id="cpn"> <div> <button v-for="item in categories" @click="btnClick(item)"> {{item.name}} </button> </div> </template> <script src="../js/vue.js"></script> <script> // 1.子组件 const cpn = { template: '#cpn', data() { return { categories: [ {id: 'aaa', name: '热门推荐'}, {id: 'bbb', name: '手机数码'}, {id: 'ccc', name: '家用家电'}, {id: 'ddd', name: '电脑办公'}, ] }//必须返回一个对象 }, methods: { btnClick(item) { // 发射事件: 自定义事件 this.$emit('item-click', item)//(发射事件的名称,传递的参数) } } } // 2.父组件 const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn }, methods: { cpnClick(item) { console.log('cpnClick', item); } } }) </script> -
父子组件通信案例
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<!--<input type="text" v-model="dnumber1">-->
<input type="text" :value="dnumber1" @input="num1Input">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<!--<input type="text" v-model="dnumber2">-->
<input type="text" :value="dnumber2" @input="num2Input">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
methods: {
num1Input(event) {
// 1.将input中的value赋值到dnumber中
this.dnumber1 = event.target.value;//这条语句不能缺少,
// 2.为了让父组件可以修改值, 发出一个事件
this.$emit('num1change', this.dnumber1)
// 3.同时修饰dnumber2的值
this.dnumber2 = this.dnumber1 * 100;
this.$emit('num2change', this.dnumber2);
},
num2Input(event) {
this.dnumber2 = event.target.value;
this.$emit('num2change', this.dnumber2)
// 同时修饰dnumber1的值
this.dnumber1 = this.dnumber2 / 100;
this.$emit('num1change', this.dnumber1);
}
}
}
}
})
</script>
- 利用
watch属性v-model实现
<div id="app">
<cpn :number1="num1"
:number2="num2"
@num1change="num1change"
@num2change="num2change"/>
</div>
<template id="cpn">
<div>
<h2>props:{{number1}}</h2>
<h2>data:{{dnumber1}}</h2>
<input type="text" v-model="dnumber1">
<h2>props:{{number2}}</h2>
<h2>data:{{dnumber2}}</h2>
<input type="text" v-model="dnumber2">
</div>
</template>
<script src="../js/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
num1: 1,
num2: 0
},
methods: {
num1change(value) {
this.num1 = parseFloat(value)
},
num2change(value) {
this.num2 = parseFloat(value)
}
},
components: {
cpn: {
template: '#cpn',
props: {
number1: Number,
number2: Number,
name: ''
},
data() {
return {
dnumber1: this.number1,
dnumber2: this.number2
}
},
watch: {
dnumber1(newValue) {
this.dnumber2 = newValue * 100;
this.$emit('num1change', newValue);
},
dnumber2(newValue) {
this.number1 = newValue / 100;
this.$emit('num2change', newValue);
}
}
}
}
})
</script>
-
父组件访问子组件:
$children,$refs<div id="app"> <cpn></cpn> <cpn></cpn> <cpn ref="aaa"></cpn> <button @click="btnClick">按钮</button> </div> <template id="cpn"> <div>我是子组件</div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, methods: { btnClick() { // 1.$children把所有使用的组件全拿过来组成一个数组 // console.log(this.$children);是一个数组 // for (let c of this.$children) {遍历每一个组件 // console.log(c.name);使用属性 // c.showMessage();使用方法 // } // console.log(this.$children[3].name); // 2.$refs => 对象类型, 默认是一个空的对象 console.log(this.$refs.aaa.name);//组件里面需要有ref属性和这里引用的属性匹配 } }, components: { cpn: { template: '#cpn', data() { return { name: '我是子组件的name' } }, methods: { showMessage() { console.log('showMessage'); } } }, } }) </script> -
子组件访问父组件:
$parent,$root- 尽管在Vue开发中,我们允许通过$parent来访问父组件,但是在真实开发中尽量不要这样做
- 子组件应该尽量避免直接访问父组件的数据,因为这样耦合度太高了,而组件化开发就是降低耦合度
- 如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题
- 另外,更不好做的是通过$parent直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,不知道是哪个子组件修改的,很不利于我的调试和维护
<div id="app"> <cpn></cpn> </div> <template id="cpn"> <div> <h2>我是cpn组件</h2> <ccpn></ccpn> </div> </template> <template id="ccpn"> <div> <h2>我是子组件</h2> <button @click="btnClick">按钮</button> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn', data() { return { name: '我是cpn组件的name' } }, components: { ccpn: { template: '#ccpn', methods: { btnClick() { // 1.访问父组件$parent // console.log(this.$parent); // console.log(this.$parent.name); // 2.访问根组件$root console.log(this.$root); console.log(this.$root.message); } } } } } } }) </script>
插槽的使用(已废弃的语法)
-
插槽的基本使用
- 插槽的基本使用
<slot></slot> - 插槽的默认值
<slot>默认值</slot> - 如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
<div id="app"> <cpn></cpn> <div>-----------------------------</div><!--- 分割线 --> <cpn><span>哈哈哈</span></cpn> <div>-----------------------------</div> <cpn> <i>呵呵呵</i> <div>我是div元素</div> <p>我是p元素</p> </cpn> </div> <template id="cpn"> <div> <h2>我是组件</h2> <slot><button>按钮</button></slot> <!--<button>按钮</button>--> </div> </template> <script src="../js/vue.js"></script> <script> const app = new Vue({ el: '#app', data: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }) </script> - 插槽的基本使用
-
具名插槽的使用
<div id="app"> <cpn> <span slot="center">标题</span> <button slot="left">返回</button> </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: { message: '你好啊' }, components: { cpn: { template: '#cpn' } } }) </script> -
插槽的用法,其中涉及到编译作用域
案例
<div id="app"> <cpn></cpn> <cpn> <!--目的是获取子组件中的pLanguages--> <div slot-scope="slot"><!--slot只是一个名字--> <!--<span v-for="item in slot.data"> - {{item}}</span>--> <span>{{slot.data.join(' - ')}}</span> </div> </cpn> <cpn> <!--目的是获取子组件中的pLanguages--> <div slot-scope="slot"> <!--<span v-for="item in slot.data">{{item}} * </span>--> <span>{{slot.data.join(' * ')}}</span> </div> </cpn> <!--<cpn></cpn>--> </div> <template id="cpn"> <div> <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: { message: '你好啊' }, components: { cpn: { template: '#cpn', data() { return { pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift'] } } } } }) </script> -
关于插槽的新语法
写在最后
其中代码参考codewhy老师的vue.js的教学,以上内容均为个人笔记
码字不易,你懂的