1.组件:减少代码冗杂,提高代码复用
1.1 App.vue文件(单文件组件)的三个组成部分
三个组成部分:
template:结构(有且只能有一个根元素,若想设置多个相同标签,可将其嵌套在div标签中)
script:js逻辑;
style:样式(可支持less,需要装包)
注意:template是虚拟标签,只起到包裹作用,不会在网页显示
支持less,使嵌套的p标签生效
1.2 普通组件的注册使用
1.2.1 局部注册
App.vue中嵌套着HmFooter、HmHeader、HmMain组件
1.2.1.1 App.vue
<template>
<div class="app">
<!-- 在页面中使用组件 -->
<!-- 可以使用单标签写:<HmHeader/> -->
<!-- 也可以使用横杠标签写 <hm-header/>-->
<HmHeader></HmHeader>
<HmMain></HmMain>
<HmFooter></HmFooter>
</div>
</template>
<script>
// // 局部注册组件,三个步骤
// 1.导入
// 2.注册组件--注册成html标签(这样标签就代表组件了)
// 3.页面中使用组件(标签)about:blank#blocked
// <
// 导入组件
import HmHeader from './components/HmHeader.vue';
import HmMain from './components/HmMain.vue';
import HmFooter from './components/HmFooter.vue';
export default{
// 注册组件
components:{
// 标签名:导入的变量
HmHeader:HmHeader,
HmMain:HmMain,
HmFooter:HmFooter,
// 或简写为标签名
// HmHeader,
// HmFooter,
// HmFooter
},
}</script>
<style lang="less" scoped>
.app{
padding: 10px;
background-color: #87ceeb;
}
</style>
1.2.2.2 HmHeader
<template>
<div class="header">
</div>
</template>
<script>
export default {
name: 'HmHeader'
}
</script>
<style lang="less" scoped>
.header{
height: 100px;
background-color: #8064a2;
}
</style>
1.2.2.3 HmMain
<template>
<div class="main">
</div>
</template>
<script>
export default {
name: 'HmMain'
}
</script>
<style lang="less" scoped>
.main{
height: 300px;
background-color: #f79646;
}
</style>
1.2.2.4 HmFooter
<template>
<div class="footer">
</div>
</template>
<script>
export default {
name: 'HmFooter'
}
</script>
<style lang="less" scoped>
.footer{
height: 100px;
background-color:#4f81bd;
}
</style>
结果:
1.2.2 全局注册
全局组件:可以放在任何地方使用 需要再main文件中设置
1.2.2.1 HmButton
<template>
<button>按钮
</button>
</template>
<script>
export default {
name: 'HmButton'
}
</script>
<style lang="less" scoped>
</style>
1.2.2.2 main.js
import Vue from 'vue' //导入Vue
// import App from './App.vue' //导入根组件(app是最大的组件)
import App from './App.vue'
// 全局注册组件,在main中注册
// 1.导入组件
import HmButton from './components/HmButton.vue'
// 2.全局注册组件
Vue.component('HmButton', HmButton)
Vue.config.productionTip = false //关闭温馨提示
new Vue({
render: h => h(App), //渲染函数(渲染App组件)
}).$mount('#app')//创建Vue实例,功能等同于new Vue({el:'#app'})
结果:
1.3 scoped解决样式冲突
只要给script标签加一个scoped就可以解决不同组件的样式冲突了
1.4 data必须是函数(不能写为对象)
1.5 组件通行-父子通讯
1.4中提到为了保证在组件复用时,复用的组件之间数据为了不互相影响,data必须要是函数。但如果想要实现组件之间的通信(即数据传递)需引入以下概念
1.5.1 父传子
总结:
1.5.2 子传父
2.案例——小黑记事本
使用父传子传递list列表数据
使用子传父实现增删改查
2.1 APP.vue
<template>
<!-- 主体区域 -->
<section id="app">
<!-- 输入框 -->
<!-- 1.3.将子组件导入页面中 -->
<TodoHeader @add="handelAdd"></TodoHeader>
<!-- 2.2父传子,将App中的数据传递给子组件TodoMain -->
<!-- 2.3 子传父 将子组件数据传给父组件 (del是子组件的事件名,handelDel是方法名) -->
<TodoMain :list="list" @del="handleDel"></TodoMain>
<!-- 5.3 清空功能,直接把list清空,设置为空数组 -->
<TodoFooter :list="list" @clear="handleClear"></TodoFooter>
</section>
</template>
<script>
// 建立父子关系,将父组件和子组件进行关联
// 1.1.将子组件导入App.vue中
import TodoHeader from './components/TodoHeader.vue';
import TodoMain from './components/TodoMain.vue';
import TodoFooter from './components/TodoFooter.vue';
export default {
data() {
// 2.1 创建数据
return {
// 使用本地数组,若没有本地数据,则使用后面的数据
list:JSON.parse(localStorage.getItem('todoList')) || [
{id:1,name:'吃饭'},
{id:2,name:'睡觉'},
{id:3,name:'打代码'}
]
}
},
// 6.1 使用监听器进行本地存储
watch:{
// 存储之前要转为JSON格式
list:{
handler(val) {
localStorage.setItem('todoList',JSON.stringify(val))
},
deep:true
}
},
methods: {
// 2.3 子传父,将子组件数据传递给父组件
// id是子组件传递的id属性
handleDel(id) {
// 2.4 删除数据
// filter():是JavaScript的内置数组方法,用于过滤数组,并返回一个新的数组。
// 如果当前的ID不等于当前遍历的item的ID,则返回true,表示保留该元素。
this.list = this.list.filter(item => item.id !== id)
},
// 4.2 添加数据(子传父)
handelAdd(todoname){
this.list.unshift({
id:Date.now(),
name:todoname,
})
},
// 5.3 清空数据(子传父)
handleClear() {
console.log('清空所有任务');
this.list = [];
}
},
// 1.2.注册子组件
components: {
TodoHeader,
TodoMain,
TodoFooter
}
}
</script>
<style>
</style>
2.2 TodoHeader
<template>
<header class="header">
<h1>小黑记事本</h1>
<!-- 4.1 添加功能-双向绑定 -->
<input placeholder="请输入任务" class="new-todo" v-model="todoname"/>
<button class="add" @click="post">添加任务</button>
</header>
</template>
<script>
export default {
data() {
return {
todoname:'' //输入框的值
}
},
methods:{
post(){
this.$emit('add',this.todoname) //子传父
this.todoname = '' //清空输入框
}
}
}
</script>
<style lang="scss" scoped>
</style>
2.3 TodoMain
<template>
<!-- 列表区域 -->
<section class="main">
<ul class="todo-list">
<!-- 2.4 遍历数据 -->
<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 class="destroy" @click="$emit('del',item.id, index)"></button>
<!-- 3.1 删除 按钮(使用子传父) -->
</div>
</li>
</ul>
</section>
</template>
<script>
export default {
// 注意:只有App才有数据修改权,而此处的props是用来接收数据的
// 2.3 接收从App.vue传过来的数据
props:['list'],
methods: {
handleDelete(id, index) {
console.log('TodoMain - 点击删除按钮');
console.log('TodoMain - item.id:', id);
console.log('TodoMain - index:', index);
// 触发父组件事件
this.$emit('del', id, index);
}
}
}
</script>
<style lang="scss" scoped>
</style>
2.4 TodoFooter
<template>
<!-- 统计和清空 -->
<footer class="footer">
<!-- 统计 -->
<!-- 5.1父传子,将数组list传递给子组件todofooter -->
<!-- <span class="todo-count">合 计:<strong> {{list.length}}</strong></span> -->
<span class="todo-count">合 计:<strong> {{list ? list.length : 0}}</strong></span>
<!-- 清空 -->
<!-- 5.3 点击按钮清空 -->
<button @click="$emit('clear')" class="clear-completed">
清空任务
</button>
</footer>
</template>
<script>
export default {
// 5.2 子组件接收父组件数据
props:['list']
}
</script>
<style lang="scss" scoped>
</style>