vue基础第3天
一、侦听器watch
1.作用:
可以侦听data/computed属性值的改变
2.语法
watch: {
"被侦听的属性名" (newVal,oldVal) {}
}
//
<div>
<input type="text" v-model="name">
</div>
export default {
data(){
return {
name:""
}
},
watch: {
name(newVal,oldVal) {
console.log(newVal,oldVal)
}
}
}
3.深度侦听和立即执行
(1)作用:
侦听复杂类型, 或者立即执行侦听函数
(2)语法:
wtach: {
"要侦听的属性名": {
immediate:true,// 立刻执行
deep:true, // 深度监听复杂类型的数据变化
handler(newVal,oldVal) {
}
}
}
4.品牌案例-数据缓存
(1)监听list变化, 同步到浏览器本地
(2)需求1:
把品牌管理的数据实时同步到本地缓存
(3)分析:
① 在watch侦听list变化的时候, 把最新的数组list转成JSON字符串存入到localStorage本地
② data里默认把list变量从本地取值, 如果取不到给个默认的空数组
(4)效果:
新增/删除 – 刷新页面 – 数据还在
(5)关键代码
data() {
return {
list: JSON.parse(localStorage.getItem("list")) || [],
type: "all",
// 定义一个展示数组, 根据list 和 type 来显示结果
// showList: []
};
},
watch: {
list: {
deep: true,
handler(newVal) {
localStorage.setItem("list", JSON.stringify(newVal)); // 注意本地储存只能储存字符串格式
},
},
},
二、vue组件基础
1.组件概念:
(1)组件是可复用的 Vue 实例, 封装标签, 样式和JS代码 --- 一个vue文件就是一个组件
(2)组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护
(3)一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为
(html, css和js)
2.作用:
每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件
3.组件的使用
(1)创建组件, 封装要复用的标签, 样式, JS代码
(2)注册组件
①全局注册:main.js文件中
import Vue from "vue"
import 组件对象 from "vue文件路径"
Vue.component("组件名",组件对象)
②局部注册:子组件.vue文件中
import 组件对象 from "vue文件路径"
export default {
components: {
"组件名":组件对象
}
}
(3)使用组件(简写:<组件名/>)
<template>
<div>
<组件名></组件名>
<组件名></组件名>
</div>
</template>
4.组件-scoped作用
(1)准备: 会给当前组件内标签添加 data-v-hash值 的属性
(2)获取: css选择器都被添加 [data-v-hash值] 的属性选择器
三、vue组件通信-父传子
1.父传子:props
目标:父组件 -> 子组件 传值
2.父传子基础
(1)父传子步骤
- 父组件以属性的形式传入数据
- 子组件 props 定义需要接受的数据
- 直接当成 data 使用即可
(2)父传子代码
父组件:
// 局部写法
import 子组件名 from "子组件路径"
export default {
components: {
子组件名,
}
}
子组件:
export default {
props:[属性名,属性名...]
}
3.组件通信_父向子-配合循环
(1)目标:父组件 -> 子组件 循环使用-传值
(2)代码
父组件:
<template>
<div>
<组件名 v-for="item in list" :key="list.id" :title="list.name" :name= "name"></组件名>
</div>
</template>
<javascript>
export default {
data() {
return {
list:[
{ id:1 , name:"sss" , price:"xxx" },
{ id:1 , name:"sss" , price:"xxx" },
{ id:1 , name:"sss" , price:"xxx" },
]
}
}
}
</javascript>
四、vue组件通信-子传父
1.单项数据流
子组件修改, 不通知父级, 造成数据不一致性,Vue规定props里的变量,本身是只读的
重点`:从父到子的数据流向, 叫单向数据流
2.子组件中自定义事件方法
目标:子组件中自定义事件方法
(1)子组件主动触发自定义事件 this.$emit('自定义事件名')
(2)父组件 监听 v-on 就是 @自定义事件名
(3)父组件给个对应的处理函数
父组件:
<组件名 @subprice="fn"></组件名>
export default {
methods:{
fn(index,price){
}
}
}
子组件:
<button @click="kanfn"> 砍价 </button>
export default {
methods:{
kanfn() {
this.$emit("subprice",this.index,1)
}
}
}
五、组件通信-EventBus
1.作用:
App.vue里引入a.vue和b.vue,用于子组件之间数据传递
2.常用于跨组件通信时使用
3.语法
(1)src/EventBus/index.js – 创建空白Vue对象并导出
(2)在要传递值的组件(a.vue) eventBus.$emit('事件名', 值)
(3)在要接收值的组件(b.vue) eventBus.$on('事件名', 函数体)
a.vue
import eventBus from "../EventBus"
export default {
methods:{
sendFn() {
eventBus.$emit("send","发送的数据")
}
}
}
b.vue
import eventBus from "../EventBus"
export default {
created() {
eventBus.$on("send",item=>{
console.log("接受的数据",item)
})
}
}
六、todos案例=创建工程和组件
1.案例分析
(1)初始化todo工程
vue created 项目名称
(2)创建3个组件和里面代码(在预习资料.md复制)
在component文件夹中,新建三个文件:todoHeader、todoMain、todoFooter
(3)把styles的样式文件准备好(从预习资料复制)
(4)App.vue引入注册使用, 最外层容器类名todoapp
2.功能-注册组件
<template>
<section class="todoapp">
<TodoHeader></TodoHeader>
<TodoMain></TodoMain>
<TodoFooter></TodoFooter>
</section>
</template>
<script>
import TodoHeader from "./components/TodoHeader";
import TodoMain from "./components/TodoMain";
import TodoFooter from "./components/TodoFooter";
export default {
components: {
TodoHeader,
TodoMain,
TodoFooter,
}
}
</script>
3.功能-todoHeader点击回车传输数据(子传父)
todoHeader.vue
<template>
<header class="header">
<h1>todos</h1>
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
v-model="isAllChecked"
/>
<label for="toggle-all"></label>
<input
class="new-todo"
placeholder="输入任务名称-回车确认"
autofocus
v-model="task"
@keydown.enter="addTask"
/>
</header>
</template>
// 接收父list
props: ["list"],
data() {
return {
task: "",
};
},
methods: {
addTask() {
if (!this.task) {
alert("请输入内容");
return;
}
// 每当回车通知父页面
this.$emit("addTask", this.task);
this.task = "";
},
},
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
methods: {
addTask(task) {
this.list.push({
id: Date.now(),
name: task,
isDone: false,
});
},
4.功能-todoMain点击删除
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
delTask(id) {
this.list = this.list.filter((item) => item.id !== id);
},
todoMain.vue
<template>
<ul class="todo-list">
<!-- completed: 完成的类名 -->
<li :class="{ completed: item.isDone }" v-for="item in list" :key="item.id">
<div class="view">
<input class="toggle" type="checkbox" v-model="item.isDone" />
<label>{{ item.name }}</label>
<button class="destroy" @click="delTask(item.id)"></button>
</div>
</li>
</ul>
</template>
methods: {
// index版本
// delTask(index) {
// this.$emit("delTask", index);
// },
// id版本
delTask(id) {
this.$emit("delTask", id);
},
},
5.功能-todofooter全部、未完成、已完成按钮点击功能
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
changeType(type) {
// 接收子传来的 点击状态
this.type = type;
}
todoFooter.vue
<template>
<footer class="footer">
<span class="todo-count"
>剩余<strong>{{ list.length }}</strong></span
>
<ul class="filters">
<li>
<a
:class="{ selected: type === 'all' }"
href="javascript:;"
@click="type = 'all'"
>全部</a
>
</li>
<li>
<a
:class="{ selected: type === 'no' }"
href="javascript:;"
@click="type = 'no'"
>未完成</a
>
</li>
<li>
<a
:class="{ selected: type === 'yes' }"
href="javascript:;"
@click="type = 'yes'"
>已完成</a
>
</li>
</ul>
<button class="clear-completed" @click="clearList">清除已完成</button>
</footer>
</template>
watch: {
type(newVal) {
// 每当类型发生变化, 通知父页面
this.$emit("changeType", newVal);
},
},
data() {
return {
type: "all",
};
},
6.功能-todofooter清除按钮点击功能
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
clearList() {
// 点击清空已完成,其实就是过滤列表只留下 isDone 为 false 即可
this.list = this.list.filter((item) => !item.isDone);
},
todofooter.vue
<template>
<footer class="footer">
<span class="todo-count"
>剩余<strong>{{ list.length }}</strong></span
>
<ul class="filters">
<li>
<a
:class="{ selected: type === 'all' }"
href="javascript:;"
@click="type = 'all'"
>全部</a
>
</li>
<li>
<a
:class="{ selected: type === 'no' }"
href="javascript:;"
@click="type = 'no'"
>未完成</a
>
</li>
<li>
<a
:class="{ selected: type === 'yes' }"
href="javascript:;"
@click="type = 'yes'"
>已完成</a
>
</li>
</ul>
<button class="clear-completed" @click="clearList">清除已完成</button>
</footer>
</template>
methods: {
clearList() {
this.$emit("clearList");
},
},
7.功能-todoHeader全选
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
computed: {
showList() {
// 声明一个函数, 函数名可以当做 data 直接渲染
// 渲染的结果就是这里的返回值
let showList = [];
if (this.type === "all") {
showList = this.list;
} else if (this.type === "yes") {
showList = this.list.filter((item) => item.isDone);
} else {
showList = this.list.filter((item) => !item.isDone);
}
return showList;
},
},
todoHeader.vue
<template>
<header class="header">
<h1>todos</h1>
<input
id="toggle-all"
class="toggle-all"
type="checkbox"
v-model="isAllChecked"
/>
<label for="toggle-all"></label>
<input
class="new-todo"
placeholder="输入任务名称-回车确认"
autofocus
v-model="task"
@keydown.enter="addTask"
/>
</header>
</template>
computed: {
// isAllChecked() {
// return this.list.every((item) => item.isDone);
// },
isAllChecked: {
get() {
return this.list.length > 0 && this.list.every((item) => item.isDone);
},
set(data) {
this.list.forEach((element) => {
element.isDone = data;
});
},
},
},
8.功能-储存本地数据
App.vue
<template>
<section class="todoapp">
<!-- 除了驼峰, 还可以使用-转换链接 -->
<TodoHeader @addTask="addTask" :list="list"></TodoHeader>
<!-- 接收子传来的delTask,并声明delTask方法 -->
<TodoMain @delTask="delTask" :list="showList"></TodoMain>
<TodoFooter
@clearList="clearList"
@changeType="changeType"
:list="showList"
></TodoFooter>
</section>
</template>
data() {
return {
list: JSON.parse(localStorage.getItem("list")) || [],
type: "all",
// 定义一个展示数组, 根据list 和 type 来显示结果
// showList: []
};
},
watch: {
list: {
deep: true,
handler(newVal) {
localStorage.setItem("list", JSON.stringify(newVal));
},
},
},