组件三大部分
1.style中的样式 默认是作用到全局的
2.加上scoped可以让样式变成局部样式
scoped原理:
1.给当前组件模板的所有元素,都会添加上一个自定义属性data-v-hash值 用于区分开不通的组件
2.css选择器后面,被自动处理,添加上了属性选择器 div[data-v-hash值]
一个组件的 data 选项必须是一个函数。→ 保证每个组件实例,维护独立的一份数据对象。
<template>
<div class="base-count">
<button @click="count--">-</button>
<span>{{ count }}</span>
<button @click="count++">+</button>
</div>
</template>
<script>
export default {
// data() {
// console.log('函数执行了')
// return {
// count: 100,
// }
// },
data: function () {
return {
count: 100,
}
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
组件通信
父组件向子组件传值
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是父组件
<!-- 1.给组件标签,添加属性方式 赋值 -->
<Son :title="myTitle"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端',
}
},
components: {
Son,
},
}
</script>
<style>
</style>
<template>
<div class="son" style="border:3px solid #000;margin:10px">
<!-- 3.直接使用props的值 -->
我是子组件 {{title}}
</div>
</template>
<script>
export default {
name: 'Son-Child',
// 2.通过props来接受
props:['title']
}
</script>
<style>
</style>
子组件向父组件传值
<template>
<div class="app" style="border: 3px solid #000; margin: 10px">
我是父组件
<!-- 2.父组件对子组件的消息进行监听 -->
<Son :title="myTitle" @changTitle="handleChange"></Son>
</div>
</template>
<script>
import Son from './components/Son.vue'
export default {
name: 'App',
data() {
return {
myTitle: '学前端',
}
},
components: {
Son,
},
methods: {
// 3.提供处理函数,提供逻辑
handleChange(newTitle) {
this.myTitle = newTitle
},
},
}
</script>
<style>
</style>
<template>
<div class="son" style="border: 3px solid #000; margin: 10px">
我是Son组件 {{ title }}
<button @click="changeFn">修改title</button>
</div>
</template>
<script>
export default {
name: 'Son-Child',
props: ['title'],
methods: {
changeFn() {
// 通过this.$emit() 向父组件发送通知
this.$emit('changTitle','传智教育')
},
},
}
</script>
<style>
</style>
props
<template>
<div class="app">
我是父组件
<BaseProgress :w="width"></BaseProgress>
</div>
</template>
<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
data() {
return {
width: 30,
}
},
components: {
BaseProgress,
},
}
</script>
<style>
</style>
<template>
<div class="base-progress">
我是子组件
<div class="inner" :style="{ width: w + '%' }">
<span>{{ w }}%</span>
</div>
</div>
</template>
<script>
export default {
// 1.基础写法(类型校验)
// props: {
// w: Number,
// },
// 2.完整写法(类型、默认值、非空、自定义校验)
props: {
w: {
type: Number,
required: true,
default: 0,
validator(val) {
// console.log(val)
if (val >= 100 || val <= 0) {
console.error('传入的范围必须是0-100之间')
return false
} else {
return true
}
},
},
},
}
</script>
<style scoped>
.base-progress {
height: 26px;
width: 400px;
border-radius: 15px;
background-color: #272425;
border: 3px solid #272425;
box-sizing: border-box;
margin-bottom: 30px;
}
.inner {
position: relative;
background: #379bff;
border-radius: 15px;
height: 25px;
box-sizing: border-box;
left: -3px;
top: -2px;
}
.inner span {
position: absolute;
right: 0;
top: 26px;
}
</style>
单向数据流
<template>
<div class="app">
<BaseCount :count="count" @changeCount="handleChange"></BaseCount>
</div>
</template>
<script>
import BaseCount from './components/BaseCount.vue'
export default {
components:{
BaseCount
},
data(){
return {
count:100
}
},
methods:{
handleChange(newVal){
// console.log(newVal);
this.count = newVal
}
}
}
</script>
<style>
</style>
<template>
<div class="base-count">
<button @click="handleSub">-</button>
<span>{{ count }}</span>
<button @click="handleAdd">+</button>
</div>
</template>
<script>
export default {
// 1.自己的数据随便修改 (谁的数据 谁负责)
// data () {
// return {
// count: 100,
// }
// },
// 2.外部传过来的数据 不能随便修改
props: {
count: {
type: Number,
},
},
methods: {
handleSub() {
this.$emit('changeCount', this.count - 1)
},
handleAdd() {
this.$emit('changeCount', this.count + 1)
},
},
}
</script>
<style>
.base-count {
margin: 20px;
}
</style>
综合案例
App.vue
<template>
<section id="app">
<!--3. 使用组件-->
<Header @add="add"></Header>
<Main :list="list" @del="del"></Main>
<Footer :list="list" @clear="clear"></Footer>
</section>
</template>
<script>
// 渲染功能:
// 1.提供数据: 提供在公共的父组件 App.vue
// 2.通过父传子,将数据传递给TodoMain
// 3.利用 v-for渲染
// 添加功能:
// 1.手机表单数据 v-model
// 2.监听事件(回车+点击都要添加)
// 3.子传父,讲任务名称传递给父组件 App.vue
// 4.进行添加 unshift(自己的数据自己负责)
// 5.清空文本框输入的内容
// 6.对输入的空数据 进行判断
// 删除功能
// 1.监听事件(监听删除的点击) 携带id
// 2.子传父,讲删除的id传递给父组件的App.vue
// 3.进行删除filter(自己的数据 自己负责)
// 底部合计:父传子 传list 渲染
// 清空功能:子传父 通知父组件 → 父组件进行更新
// 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
// 1. 导入组件
import Header from "@/components/Header";
import Main from "@/components/Main";
import Footer from "@/components/Footer";
export default {
// 2. 引用组件
components: {Footer, Main, Header},
data() {
return {
list: JSON.parse(localStorage.getItem('list')) || [
{id: 1, name: '打篮球'},
{id: 2, name: '看电影'},
{id: 3, name: '逛街'},
]
}
},
methods: {
add(name) {
if (name.trim() === '') {
alert("请输入名称")
return
}
this.list.unshift({
id: +new Date(),
name: name
})
},
del(id) {
this.list = this.list.filter((item) => item.id !== id)
},
clear(list) {
this.list = list
}
},
// 持久化到本地需要
watch: {
list: {
deep: true, //深度监视
handler(newVal) {
localStorage.setItem('list', JSON.stringify(newVal))
}
}
}
}
</script>
<style>
</style>
Header.vue
<template>
<!-- 输入框 -->
<header class="header">
<h1>小黑记事本</h1>
<input v-model="name" @keyup.enter="add" placeholder="请输入任务" class="new-todo"/>
<button class="add" @click="add">添加任务</button>
</header>
</template>
<script>
export default {
name: "Header",
data() {
return {
name: ''
}
},
methods: {
add() {
this.$emit("add",this.name)
this.name = ''
}
}
}
</script>
<style scoped>
</style>
Main.vue
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<li class="todo" v-for="(item,index) in list" :key="item.id">
<div class="view">
<span class="index">{{ index + 1 }}.</span> <label>{{ item.name }}</label>
<button @click="del(item.id)" class="destroy"></button>
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
name: "Main",
props: {
list: {
type: Array
}
},
methods: {
del(id) {
this.$emit("del",id)
}
}
}
</script>
<style scoped>
</style>
Footer.vue
<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 {
name: "Footer",
props: {
list: {
type: Array
}
},
methods: {
clear() {
this.$emit("clear",[])
}
}
}
</script>
<style scoped>
</style>
由于我的子组件命名不规范(没有写成大驼峰的形式)所以需要在vue.config.js中添加一行代码 lintOnSave: false // 关闭语法检查错误
非父子通信
指令进阶
v-model
<template>
<div class="app">
<input type="text" v-model="msg1" />
<br />
<!-- v-model的底层其实就是:value和 @input的简写
$event 用于在模板中,获取事件的形参
-->
<input type="text" :value="msg2" @input="msg2 = $event.target.value" />
</div>
</template>
<script>
export default {
data() {
return {
msg1: '',
msg2: '',
}
},
}
</script>
<style>
</style>
sync修饰符
ref 和 $refs
<template>
<div class="app">
<h4>父组件 -- <button>获取组件实例</button></h4>
<BaseForm ref="baseForm"></BaseForm>
</div>
</template>
<script>
import BaseForm from './components/BaseForm.vue'
export default {
components: {
BaseForm,
},
methods: {
getMsg() {
this.$refs.baseForm.getFormData()
},
resetMsg() {
this.$refs.baseForm.resetFormData()
}
}
}
</script>
<style>
</style>
<template>
<div class="app">
<div>
账号: <input v-model="username" type="text">
</div>
<div>
密码: <input v-model="password" type="text">
</div>
<div>
<button @click="getFormData">获取数据</button>
<button @click="resetFormData">重置数据</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
username: 'admin',
password: '123456',
}
},
methods: {
getFormData() {
console.log('获取表单数据', this.username, this.password);
},
resetFormData() {
this.username = ''
this.password = ''
console.log('重置表单数据成功');
},
}
}
</script>
<style scoped>
.app {
border: 2px solid #ccc;
padding: 10px;
}
.app div{
margin: 10px 0;
}
.app div button{
margin-right: 8px;
}
</style>
$nextTick
<template>
<div class="app">
<div v-if="isShowEdit">
<input type="text" v-model="editValue" ref="inp" />
<button>确认</button>
</div>
<div v-else>
<span>{{ title }}</span>
<button>编辑</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
title: '大标题',
isShowEdit: false,
editValue: '',
}
},
methods: {
handleEdit() {
// 1. 显示输入框(异步dom更新)
this.isShowEdit = true
// 2. 让 输入框 获取焦点 $nextTick 异步dom更新后立刻执行
this.$nextTick(() => {
this.$refs.inp.focus()
})
}
},
}
</script>
<style>
</style>