组件的三大组成部分
组件的样式冲突 scoped
<template>
<div class="base-one">
BaseOne
</div>
</template>
<script>
export default {
}
</script>
<style scoped>
/*
1.style中的样式 默认是作用到全局的
2.加上scoped可以让样式变成局部样式
组件都应该有独立的样式,推荐加scoped(原理)
-----------------------------------------------------
scoped原理:
1.给当前组件模板的所有元素,都会添加上一个自定义属性
data-v-hash值
data-v-5f6a9d56 用于区分开不通的组件
2.css选择器后面,被自动处理,添加上了属性选择器
div[data-v-5f6a9d56]
*/
div{
border: 3px solid blue;
margin: 30px;
}
</style>
data 是一个函数
App.vue
<template>
<div class="app">
<baseCount></baseCount>
<baseCount></baseCount>
<baseCount></baseCount>
</div>
</template>
<script>
import baseCount from './components/BaseCount'
export default {
components: {
baseCount,
},
}
</script>
<style>
</style>
<template>
<div class="base-count">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
// data必须是一个函数 -> 保证每个组件实例,维护独立的一个数据
data() {
return {
count: 100,
}
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
组件通信
不同的组件关系 和 组件通信方案分类
组件通信解决方案
什么是 prop
props 校验
prop & data、单向数据流
综合案例
//App.vue
<template>
<!-- 主体区域 -->
<section id="app">
<ToDoHeader @add="handleAdd"></ToDoHeader>
<ToDoMain @del="handleDel" :list="list"></ToDoMain>
<ToDoFooter @clear="handleClear" :list="list"></ToDoFooter>
</section>
</template>
<script>
import ToDoHeader from './components/ToDoHeader.vue'
import ToDoMain from './components/ToDoMain.vue'
import ToDoFooter from './components/ToDoFooter.vue'
// 渲染功能
// 1. 提供数据 -> 在App.vue
// 2. 通过父传子,将数据传递给ToDoMain
// 3. 利用v-for渲染
// 添加功能
// 1. 收集表单数据 -> v-model
// 2. 监听事件 (回车+点击 都要进行添加)
// 3. 通过子传父,将任务名称传递给父组件App.vue
// 删除功能
// 1. 监听删除点击事件(携带id)
// 2. 子传父,将删除id传递给App.vue
// 3. 进行删除filter
// 底部合计:父传子list -> 渲染
// 清空功能:子传父 -> 父组件进行清空操作
// 持久化存储:watch深度监视list的变化 -> 往本地存储 -> 进入页面优先读取本地
export default {
data () {
return {
list:JSON.parse(localStorage.getItem('list')) || [
{ id: 1, name:'打篮球'},
{ id: 2, name:'看电影'},
{ id: 3, name:'逛街'},
]
}
},
methods:{
handleAdd(todoName){
this.list.unshift({
id:+new Date(),
name:todoName
})
},
handleDel(id){
this.list = this.list.filter(item=>item.id!==id)
},
handleClear(){
this.list=[]
}
},
watch:{
list:{
deep:true,
handler (newValue) {
localStorage.setItem('list',JSON.stringify(newValue))
}
}
},
components: {
ToDoHeader,
ToDoMain,
ToDoFooter
}
}
</script>
<style>
</style>
//ToDoHeader
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input @keyup.enter="handleAdd" v-model="todoName" placeholder="请输入任务" class="new-todo" />
<button @click="handleAdd" class="add">添加任务</button>
</header>
</template>
<script>
export default {
data(){
return {
todoName: ''
}
},
methods:{
handleAdd(){
if(this.todoName.trim() === ''){
alert('输入任务不能为空')
return
}
this.$emit('add',this.todoName)
this.todoName=''
}
}
}
</script>
<style>
</style>
//ToDoMain
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li v-for="(item,index) in list" :key="item.id" class="todo">
<div class="view">
<span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
<button @click="handleDel(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
props:{
list:Array
},
methods:{
handleDel(id){
this.$emit('del',id)
}
}
}
</script>
<style>
</style>
//ToDoFooter
<template>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<span class="todo-count">合 计:<strong> {{ list.length }} </strong></span>
<!-- 清空 -->
<button @click="clear" class="clear-completed">
清空任务
</button>
</footer>
</template>
<script>
export default {
props:{
list:Array
},
methods:{
clear(){
this.$emit('clear')
}
}
}
</script>
<style>
</style>
非父子通信 (拓展) - event bus 事件总线
<template>
<div class="base-a">
我是A组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
// 在A组件(接收方),进行监听Bus的事件(订阅消息)
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-a {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
<template>
<div class="base-b">
<div>我是B组件(发布方)</div>
<button @click="sendMsgFn">发送消息</button>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
methods: {
sendMsgFn() {
// 在B组件(发送方),触发事件的方式传递参数(发布消息)
Bus.$emit('sendMsg', '今天天气不错,适合旅游')
},
},
}
</script>
<style scoped>
.base-b {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
<template>
<div class="base-c">
我是C组件(接受方)
<p>{{msg}}</p>
</div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
data() {
return {
msg: '',
}
},
created() {
Bus.$on('sendMsg', (msg) => {
// console.log(msg)
this.msg = msg
})
},
}
</script>
<style scoped>
.base-c {
width: 200px;
height: 200px;
border: 3px solid #000;
border-radius: 3px;
margin: 10px;
}
</style>
进阶语法
v-model 原理
子组件
<template>
<div>
<select :value="value" @change="handleChange">
<option value="101">北京</option>
<option value="102">上海</option>
<option value="103">武汉</option>
<option value="104">广州</option>
<option value="105">深圳</option>
</select>
</div>
</template>
<script>
export default {
props:{
value:String
},
methods:{
handleChange(e){
this.$emit('input',e.target.value)
}
}
}
</script>
<style>
</style>
父组件
<template>
<div class="app">
<BaseSelect :v-model="selectId"></BaseSelect>
</div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
data() {
return {
selectId: '102',
}
},
components: {
BaseSelect,
},
}
</script>
<style>
</style>
.sync 修饰符
子组件
<template>
<div v-show="visible" class="base-dialog-wrap">
<div class="base-dialog">
<div class="title">
<h3>温馨提示:</h3>
<button @click="close" class="close">x</button>
</div>
<div class="content">
<p>你确认要退出本系统么?</p>
</div>
<div class="footer">
<button @click="yes">确认</button>
<button @click="no">取消</button>
</div>
</div>
</div>
</template>
<script>
export default {
props:{
visible:Boolean
},
methods:{
close(){
this.$emit('update:visible',false)
},
yes(){
this.$emit('update:visible',false)
},
no(){
this.$emit('update:visible',false)
}
}
}
</script>
<style scoped>
.base-dialog-wrap {
width: 300px;
height: 200px;
box-shadow: 2px 2px 2px 2px #ccc;
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding: 0 10px;
}
.base-dialog .title {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #000;
}
.base-dialog .content {
margin-top: 38px;
}
.base-dialog .title .close {
width: 20px;
height: 20px;
cursor: pointer;
line-height: 10px;
}
.footer {
display: flex;
justify-content: flex-end;
margin-top: 26px;
}
.footer button {
width: 80px;
height: 40px;
}
.footer button:nth-child(1) {
margin-right: 10px;
cursor: pointer;
}
</style>
父组件
<template>
<div class="app">
<button @click="isShow = true">退出按钮</button>
<BaseDialog
:visible.sync="isShow"
></BaseDialog>
</div>
</template>
<script>
import BaseDialog from "./components/BaseDialog.vue"
export default {
data() {
return {
isShow:false
}
},
methods: {
},
components: {
BaseDialog,
},
}
</script>
<style>
</style>
ref 和 $refs