vue组件化开发:调试工具、组件通信、插槽
程序员文章站
2022-06-03 23:21:53
...
Vue调试工具
调试工具安装
官方文档:https://github.com/vuejs/vue-devtools
- 克隆仓库
- 安装依赖包
- 构建
- 打开Chrome扩展页面,选中开发者模式
- 加载已解压的扩展,选择:shells / chrome
vue组件化开发
组件化开发核心思想:把不同的功能封装到不同的组件中,然后组件通过组合的方式形成完整意义上的应用。
组件注册
注意事项
-
组件参数的
data值必须是函数
,return一个对象。原因:使用函数会形成一个闭包的环境,这样就保证每一个组件都拥有一份独立的数据。
注意:
new Vue实例
的时候,data是一个对象。var vm = new Vue({ el: '#app', data: {} });
-
组件模板必须是
单个根元素
,即只能有一个父元素多个子元素:div>div 这样写会报错:template: `<div></div> <div></div>`
-
组件模板的内容可以是模板字符串,提高代码的可读性! template:
模板内容
-
组件命名方式
如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板template中用驼峰的方式使用组件,
例如:代码示例中
<HelloWorld></HelloWorld>
但是在普通的标签模板(父组件里面)中,必须使用字母小写且短横线的方式使用组件
所有的
html标签
都是小写形式!例如:代码示例中<hello-world></hello-world>
- 短横线方式
Vue.component('button-counter', {})
- 驼峰方式
Vue.component('HelloWorld', {})
代码示例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<button-counter></button-counter>
<hello-world></hello-world>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
组件注册注意事项
如果使用驼峰式命名组件,那么在使用组件的时候,
只能在字符串模板template中用驼峰的方式使用组件,但是
在普通的标签模板(父组件里面)中,必须使用字母小写且短横线的方式使用组件
所有的html标签都是小写形式!
-->
<script type="text/javascript">
// 注册全局子组件 HelloWorld
Vue.component('HelloWorld', {
data: function () {
return {
msg: 'HelloWorld1'
}
},
template: '<div>{{msg}}</div>'
});
// 注册全局子组件 button-counter
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: `
<div>
<button @click="handle">点击了{{count}}次</button>
<button>测试123</button>
<HelloWorld></HelloWorld>
</div>
`,
methods: {
handle: function () {
this.count += 2;
}
}
});
// 注册父组件
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
全局组件注册语法
Vue.component('组件名称', {
data: 组件数据,
template: 组件模板内容
})
完整代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<!--
组件是可以重用的:赋值多个组件标签即可!
因为子组件data是一个函数返回的是一个对象,每个组件都拥有独立的数据
-->
<span>点击了{{count}}次</span>
<button-counter></button-counter>
<button-counter></button-counter>
<button-counter></button-counter>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
功能:计算点击了多少次
-->
<script type="text/javascript">
// 注册子组件
Vue.component('button-counter', {
data: function () {
return {
count: 0
}
},
template: '<button @click="handle">点击了{{count}}次</button>',
methods: {
handle: function () {
// 每次点击增加2个数
// count必须通过this调用
this.count += 2;
}
}
})
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
局部组件注册
代码示例如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>局部组件用法</title>
</head>
<body>
<div id="app">
<hello-world></hello-world>
<hello-tom></hello-tom>
<hello-jerry></hello-jerry>
<test-com></test-com>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
局部组件注册
局部组件只能在注册他的父组件中使用
下面代码,会报错:
template: '<div>Test<hello-world></hello-world></div>'
子组件:显示的内容由子组件data的return值决定
-->
<script type="text/javascript">
// 注册全局子组件 test-com
Vue.component('test-com', {
template: '<div>Test<hello-world></hello-world></div>'
});
// 注册局部子组件 HelloWorld
var HelloWorld = {
data: function () {
return {
msg: 'HelloWorld'
}
},
template: '<div>{{msg}}</div>'
};
// 注册局部子组件 HelloTom
var HelloTom = {
data: function () {
return {
msg: 'HelloTom'
}
},
template: '<div>{{msg}}</div>'
};
// 注册局部子组件 HelloJerry
var HelloJerry = {
data: function () {
return {
msg: 'HelloJerry'
}
},
template: '<div>{{msg}}</div>'
};
// 注册父组件
// 在父组件中使用components属性,注册局部组件
var vm = new Vue({
el: '#app',
data: {
},
// 左边:组件的名称
// 右边:组件的内容,可以抽取到一个对象当中,对象里面包含的信息:data、template、methods等
components: {
'hello-world': HelloWorld,
'hello-tom': HelloTom,
'hello-jerry': HelloJerry
}
});
</script>
</body>
</html>
组件间数据交互
父组件向子组件传值
- 父组件通过属性方式向子组件传值
- 子组件通过props接收
- 通过子组件template,渲染到页面上
父组件通过属性的方式传值给子组件,两种形式
形式1:间接传值
在父组件data对象中首先把内容传递给 ptitle;然后再将ptitle传递给title,
子组件通过props接收数据,最后 渲染页面
在父组件data:{ ptitle: '动态绑定属性1' }
<menu-item :title='ptitle'></menu-item>
props: ['title', 'content'],
template: '<div>{{title}}</div>'
形式2:直接传值
直接将内容传递给 content,子组件通过props接收数据,最后渲染页面
<menu-item content='hello'></menu-item>
props接收数据 props: ['content']
template: '<div>{{content}}</div>'
基本代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<menu-item title='来自父组件的值'></menu-item>
<menu-item :title='ptitle' content='hello'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
// 定义全局子组件
Vue.component('menu-item', {
props: ['title', 'content'],
data: function () {
return {
msg: '子组件本身的数据'
}
},
template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
});
// 定义父组件
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
ptitle: '动态绑定属性1'
}
});
</script>
</body>
</html>
父组件向子组件传值-props属性名规则:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>props属性名规则</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<menu-item :menu-title='ptitle'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
父组件向子组件传值-props属性名规则:
1.在props中使用驼峰形式,模板中需要使用短横线的形式
因为DOM元素的组件,不区分大小写!
父组件向子组件传值: <menu-item :menu-title='ptitle'></menu-item> 必须使用短横线的形式
子组件负责接收: props: ['menuTitle']
2.字符串形式的模板中没有这个限制
template: '<div>{{menuTitle}}<third-com testTile="hello"></third-com></div>'
-->
<script type="text/javascript">
Vue.component('third-com', {
props: ['testTile'],
template: '<div>{{testTile}}</div>'
});
Vue.component('menu-item', {
props: ['menuTitle'],
template: '<div>{{menuTitle}}<third-com testTile="hello"></third-com></div>'
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
ptitle: '动态绑定属性'
}
});
</script>
</body>
</html>
父组件向子组件传值-props属性值类型
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{pmsg}}</div>
<menu-item :pstr='pstr' :pnum='12' pboo='true' :parr='parr' :pobj='pobj'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
父组件向子组件传值-props属性值类型
父组件通过属性方式向子组件传值
子组件通过props接收
通过子组件template,渲染到页面上
加冒号,写什么类型就是什么类型
不加冒号,统一是字符串
-->
<script type="text/javascript">
Vue.component('menu-item', {
props: ['pstr', 'pnum', 'pboo', 'parr', 'pobj'],
template: `
<div>
<div>{{pstr}}</div>
<div>{{12 + pnum}}</div>
<div>{{typeof pboo}}</div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<span>{{pobj.name}}</span>
<span>{{pobj.age}}</span>
</div>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
pstr: 'hello',
parr: ['apple', 'orange', 'banana'],
pobj: {
name: 'lisi',
age: 12
}
}
});
</script>
</body>
</html>
子组件向父组件传值
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<menu-item :parr='parr' @enlarge-text='handle'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
子组件向父组件传值-基本用法
props传递数据原则:单向数据流
功能:点击按钮增大字体!
-->
<script type="text/javascript">
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<button @click='$emit("enlarge-text")'>扩大父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple', 'orange', 'banana'],
fontSize: 10
},
methods: {
handle: function () {
// 扩大字体大小
this.fontSize += 5;
}
}
});
</script>
</body>
</html>
子组件向父组件传值-携带参数
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
<menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
子组件向父组件传值-携带参数
父组件用$event接收,然后通过事件将参数传递给函数handle使用
handle:function(val){} ; val接收传递的参数
-->
<script type="text/javascript">
Vue.component('menu-item', {
props: ['parr'],
template: `
<div>
<ul>
<li :key='index' v-for='(item,index) in parr'>{{item}}</li>
</ul>
<button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
<button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
pmsg: '父组件中内容',
parr: ['apple', 'orange', 'banana'],
fontSize: 10
},
methods: {
handle: function (val) {
// 扩大字体大小
this.fontSize += val;
}
}
});
</script>
</body>
</html>
兄弟之间通信
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<div>父组件</div>
<div>
<button @click='handle'>销毁事件</button>
</div>
<test-tom></test-tom>
<test-jerry></test-jerry>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
兄弟组件之间数据传递:
定义一个事件中心:
1. 在兄弟1的methods方法中,向外触发事件且传递参数。
然后在兄弟2的mounted方法中监听事件,接收参数。
-->
<script type="text/javascript">
// 提供事件中心管理组件之间的通信:产生Vue实例
var hub = new Vue();
// 定义子组件:test-tom 加2
Vue.component('test-tom', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>TOM:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
hub.$emit('jerry-event', 2);
}
},
mounted: function () {
// 在第二个test-jerry子组件的methods方法中:向外触发事件,携带参数1
// 在第一个test-tom子组件的mounted方法中:监听事件
// 接收参数val = 1
hub.$on('tom-event', (val) => {
this.num += val;
});
}
});
// 定义子组件:test-jerry
Vue.component('test-jerry', {
data: function () {
return {
num: 0
}
},
template: `
<div>
<div>JERRY:{{num}}</div>
<div>
<button @click='handle'>点击</button>
</div>
</div>
`,
methods: {
handle: function () {
// 触发兄弟组件的事件
hub.$emit('tom-event', 1);
}
},
mounted: function () {
// 在第一个test-tom子组件的methods方法中:向外触发事件,携带参数2
// 在第二个test-jerry子组件的mounted方法中:监听事件
// 接收参数val = 2
hub.$on('jerry-event', (val) => {
this.num += val;
});
}
});
// 父组件
var vm = new Vue({
el: '#app',
data: {
},
methods: {
handle: function () {
hub.$off('tom-event');
hub.$off('jerry-event');
}
}
});
</script>
</body>
</html>
插槽的用法
插槽基本用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<alert-box>有bug发生</alert-box>
<alert-box>有一个警告</alert-box>
<alert-box></alert-box>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
组件插槽:父组件向子组件传递内容
1.插槽的位置:在子组件template模板中
2.插槽的内容在父组件标签的中间,父组件标签中的内容会传递给 <slot></slot>
如果父组件标签中间没有内容 <alert-box></alert-box>
slot 标签可以提供默认内容:<slot>默认内容</slot>
-->
<script type="text/javascript">
Vue.component('alert-box', {
template: `
<div>
<strong>ERROR:</strong>
<slot>默认内容</slot>
</div>`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
具名插槽用法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<base-layout>
<p slot='header'>标题信息</p>
<p>主要内容1</p>
<p>主要内容2</p>
<p slot='footer'>底部信息信息</p>
</base-layout>
<base-layout>
<template slot='header'>
<p>标题信息1</p>
<p>标题信息2</p>
</template>
<p>主要内容1</p>
<p>主要内容2</p>
<p slot='footer'>底部信息信息11111111111</p>
<template slot='footer'>
<p>底部信息信息1</p>
<p>底部信息信息2</p>
</template>
</base-layout>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
具名插槽
插槽中填充多个标签:可以用template包裹
html标签只能使用一个具名插槽
-->
<script type="text/javascript">
Vue.component('base-layout', {
template: `
<div>
<header>
<slot name='header'></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name='footer'></slot>
</footer>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
作用域插槽用法
- 应用场景:父组件对子组件的内容进行加工处理,从而呈现不同的样式
- 语法规范:
1.父组件调用子组件的时候,给子组件传递插槽;这个插槽称为作用域插槽
使用作用域插槽:子组件可以向父组件的插槽中传递内容 :自定义属性=item
父组件中作用域插槽的内容以template开头和结尾。 <template></template>
父组件同时要声明从子组件接收的数据,放在那里 props ! slot-scope="props"
父组件同时还要告诉子组件模板的信息:接收到props怎么展示
例如:以h1标签的形式展示 <h1>{{props.item}}</h1>
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style type="text/css">
.current {
color: orange;
}
</style>
<body>
<div id="app">
<fruit-list :list='list'>
<template slot-scope='slotProps'>
<strong v-if='slotProps.info.id==3' class="current">{{slotProps.info.name}}</strong>
<span v-else>{{slotProps.info.name}}</span>
</template>
</fruit-list>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<!--
作用域插槽
自定义属性 info
在组件标签中,添加 template标签且添加slot-scope属性
-->
<script type="text/javascript">
Vue.component('fruit-list', {
props: ['list'],
template: `
<div>
<li :key='item.id' v-for='item in list'>
<slot :info='item'>{{item.name}}</slot>
</li>
</div>
`
});
var vm = new Vue({
el: '#app',
data: {
list: [{
id: 1,
name: 'apple'
}, {
id: 2,
name: 'orange'
}, {
id: 3,
name: 'banana'
}]
}
});
</script>
</body>
</html>
购物车案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<style type="text/css">
.container {}
.container .cart {
width: 300px;
margin: auto;
}
.container .title {
background-color: lightblue;
height: 40px;
line-height: 40px;
text-align: center;
/*color: #fff;*/
}
.container .total {
background-color: #FFCE46;
height: 50px;
line-height: 50px;
text-align: right;
}
.container .total button {
margin: 0 10px;
background-color: #DC4C40;
height: 35px;
width: 80px;
border: 0;
}
.container .total span {
color: red;
font-weight: bold;
}
.container .item {
height: 55px;
line-height: 55px;
position: relative;
border-top: 1px solid #ADD8E6;
}
.container .item img {
width: 45px;
height: 45px;
margin: 5px;
}
.container .item .name {
position: absolute;
width: 90px;
top: 0;
left: 55px;
font-size: 16px;
}
.container .item .change {
width: 100px;
position: absolute;
top: 0;
right: 50px;
}
.container .item .change a {
font-size: 20px;
width: 30px;
text-decoration: none;
background-color: lightgray;
vertical-align: middle;
}
.container .item .change .num {
width: 40px;
height: 25px;
}
.container .item .del {
position: absolute;
top: 0;
right: 0px;
width: 40px;
text-align: center;
font-size: 40px;
cursor: pointer;
color: red;
}
.container .item .del:hover {
background-color: orange;
}
</style>
</head>
<body>
<div id="app">
<div class="container">
<my-cart></my-cart>
</div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
var CartTitle = {
props: ['uname'],
template: `
<div class="title">{{uname}}的商品</div>
`
}
var CartList = {
props: ['list'],
template: `
<div>
<div :key='item.id' v-for='item in list' class="item">
<img :src="item.img"/>
<div class="name">{{item.name}}</div>
<div class="change">
<a href="" @click.prevent='sub(item.id)'>-</a>
<input type="text" class="num" :value='item.num' @blur='changeNum(item.id, $event)'/>
<a href="" @click.prevent='add(item.id)'>+</a>
</div>
<div class="del" @click='del(item.id)'>×</div>
</div>
</div>
`,
methods: {
changeNum: function (id, event) {
this.$emit('change-num', {
id: id,
type: 'change',
num: event.target.value
});
},
sub: function (id) {
this.$emit('change-num', {
id: id,
type: 'sub'
});
},
add: function (id) {
this.$emit('change-num', {
id: id,
type: 'add'
});
},
del: function (id) {
// 把id传递给父组件
this.$emit('cart-del', id);
}
}
}
var CartTotal = {
props: ['list'],
template: `
<div class="total">
<span>总价:{{total}}</span>
<button>结算</button>
</div>
`,
computed: {
total: function () {
// 计算商品的总价
var t = 0;
this.list.forEach(item => {
t += item.price * item.num;
});
return t;
}
}
}
Vue.component('my-cart', {
data: function () {
return {
uname: '张三',
list: [{
id: 1,
name: 'TCL彩电',
price: 1000,
num: 1,
img: 'img/a.jpg'
}, {
id: 2,
name: '机顶盒',
price: 1000,
num: 1,
img: 'img/b.jpg'
}, {
id: 3,
name: '海尔冰箱',
price: 1000,
num: 1,
img: 'img/c.jpg'
}, {
id: 4,
name: '小米手机',
price: 1000,
num: 1,
img: 'img/d.jpg'
}, {
id: 5,
name: 'PPTV电视',
price: 1000,
num: 2,
img: 'img/e.jpg'
}]
}
},
template: `
<div class='cart'>
<cart-title :uname='uname'></cart-title>
<cart-list :list='list' @change-num='changeNum($event)' @cart-del='delCart($event)'></cart-list>
<cart-total :list='list'></cart-total>
</div>
`,
components: {
'cart-title': CartTitle,
'cart-list': CartList,
'cart-total': CartTotal
},
methods: {
changeNum: function (val) {
// 因为统一用changeNum事件,所以分为三种情况:输入域变更、加号变更、减号变更
if (val.type == 'change') {
// 根据子组件传递过来的数据,跟新list中对应的数据
this.list.some(item => {
if (item.id == val.id) {
item.num = val.num;
// 终止遍历
return true;
}
});
} else if (val.type == 'sub') {
// 减一操作
this.list.some(item => {
if (item.id == val.id) {
item.num -= 1;
// 终止遍历
return true;
}
});
} else if (val.type == 'add') {
// 加一操作
this.list.some(item => {
if (item.id == val.id) {
item.num += 1;
// 终止遍历
return true;
}
});
}
},
delCart: function (id) {
// 根据id删除list中对应的数据
// 1、找到id所对应数据的索引
var index = this.list.findIndex(item => {
return item.id == id;
});
// 2、根据索引删除对应数据
this.list.splice(index, 1);
}
}
});
var vm = new Vue({
el: '#app',
data: {
}
});
</script>
</body>
</html>
推荐阅读
-
【分享】Vue 资源典藏(UI组件、开发框架、服务端、辅助工具、应用实例、Demo示例)
-
[前端开发]Vue组件化的思想
-
vue前端开发层次嵌套组件的通信详解
-
Vue组件大全包括(UI组件,开发框架,服务端,辅助工具,应用实例,Demo示例)
-
分享一款一直在维护的【网络开发运维|通用调试工具】: http请求, websocket,cmd, RSA,DES, 参数签名工具,脚本批量生成工具,google动态口令,端口检测,组件注册,js混淆...
-
vue组件化开发:调试工具、组件通信、插槽
-
LLDebugTool - 便捷的IOS调试工具(支持组件化)
-
【分享】Vue 资源典藏(UI组件、开发框架、服务端、辅助工具、应用实例、Demo示例)
-
[前端开发]Vue组件化的思想
-
分享一款一直在维护的【网络开发运维|通用调试工具】: http请求, websocket,cmd, RSA,DES, 参数签名工具,脚本批量生成工具,google动态口令,端口检测,组件注册,js混淆...