Vue脚手架(续)
3.8 TodoList案例
实现一个简单的记事本小Demo:Github仓库
-
组件化编码流程:
(1).拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2).实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
1).一个组件在用:放在组件自身即可。
2). 一些组件在用:放在他们共同的父组件上(状态提升)。
(3).实现交互:从绑定事件开始。
-
props适用于:
(1).父组件 ==> 子组件 通信
(2).子组件 ==> 父组件 通信(要求父先给子一个函数,然后子调用这个函数去传递数据给父组件)
1. 在父组件定义函数
methods: {
addTodo(todoObj) {
this.todos.unshift(todoObj);
},
},
2.将父组件函数传递给子组件
<MyHeader :addTodo="addTodo"></MyHeader>
3.子组件接收父组件传递的函数
props: ["addTodo"],
4.调用这个函数,通知App组件去添加一个todo对象
this.addTodo(todoObj);
-
使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
-
props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
3.9 webStorage
-
存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
-
浏览器端通过 Window.sessionStorage 和 Window.localStorage 属性来实现本地存储机制。
3. 相关API:
1. ```xxxxxStorage.setItem('key', 'value');```
该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。
值为字符串,如果存入的值为对象,则将其转换为字符串后存入
2. ```xxxxxStorage.getItem('person');```
该方法接受一个键名作为参数,返回键名对应的值。对象转字符串后存入,取出后需将其重新转化为对象
3. ```xxxxxStorage.removeItem('key');```
该方法接受一个键名作为参数,并把该键名从存储中删除。
4. ``` xxxxxStorage.clear()```
该方法会清空存储中的所有数据。
4. 备注:
-
SessionStorage存储的内容会随着浏览器窗口关闭而消失。
-
LocalStorage存储的内容,需要手动清除才会消失。
-
```xxxxxStorage.getItem(xxx)```如果xxx对应的value获取不到,那么getItem的返回值是null。
-
```JSON.parse(null)```的结果依然是null。
-
这两者的API用法一致
一个localStorage.html文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>localStorage</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript">
let p = { name: '张三', age: 18 }
function saveData() {
localStorage.setItem('msg', 'hello!!!')
localStorage.setItem('msg2', 666)
localStorage.setItem('person', JSON.stringify(p))
}
function readData() {
console.log(localStorage.getItem('msg'))
console.log(localStorage.getItem('msg2'))
const result = localStorage.getItem('person')
JSON.parse(result)
// console.log(localStorage.getItem('msg3'))
}
function deleteData() {
localStorage.removeItem('msg2')
}
function deleteAllData() {
localStorage.clear()
}
</script>
</body>
</html>
一个sessionStorage.html文件
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>sessionStorage</title>
</head>
<body>
<h2>sessionStorage</h2>
<button onclick="saveData()">点我保存一个数据</button>
<button onclick="readData()">点我读取一个数据</button>
<button onclick="deleteData()">点我删除一个数据</button>
<button onclick="deleteAllData()">点我清空一个数据</button>
<script type="text/javascript" >
let p = {name:'张三',age:18}
function saveData(){
sessionStorage.setItem('msg','hello!!!')
sessionStorage.setItem('msg2',666)
sessionStorage.setItem('person',JSON.stringify(p))
}
function readData(){
console.log(sessionStorage.getItem('msg'))
console.log(sessionStorage.getItem('msg2'))
const result = sessionStorage.getItem('person')
console.log(JSON.parse(result))
// console.log(sessionStorage.getItem('msg3'))
}
function deleteData(){
sessionStorage.removeItem('msg2')
}
function deleteAllData(){
sessionStorage.clear()
}
</script>
</body>
</html>
3.10 组件的自定义事件
-
一种组件间通信的方式,适用于:子组件 ===> 父组件
-
使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
-
绑定自定义事件:
-
第一种方式,在父组件中:
<Demo @atguigu="test"/>
或<Demo v-on:atguigu="test"/>
-
第二种方式,在父组件中:
<Demo ref="demo"/> ...... mounted(){ this.$refs.xxx.$on('atguigu',this.test) }
-
若想让自定义事件只能触发一次,可以使用
once
修饰符,或$once
方法。
-
-
触发自定义事件:
this.$emit('atguigu',数据)
-
解绑自定义事件
this.$off('atguigu')
-
组件上也可以绑定原生DOM事件,需要使用
native
修饰符,否则会被当成自定义事件。 -
注意:通过
this.$refs.xxx.$on('atguigu',回调)
绑定自定义事件时,回调要么配置在methods中,使用this调用回调名,要么用箭头函数在mounted中书写函数体,否则this指向会出问题!
App.vue
<template>
<div class="app">
<h1>{{ msg }},学生姓名是:{{ studentName }}</h1>
<!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
<School :getSchoolName="getSchoolName" />
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(方法一,使用@或v-on) -->
<!-- <Student @atguigu="getStudentName" @demo="m1" /> -->
<!-- 方法一:2.给student组件的实例对象vc绑定一个事件 -->
<!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(方法二,使用ref) -->
<Student ref="student" @click.native="show" />
<!-- 方法二:2.给student绑定ref获取组件实例对象 -->
</div>
</template>
<script>
import Student from "./components/Student";
import School from "./components/School";
export default {
name: "App",
components: { School, Student },
data() {
return {
msg: "你好啊!",
studentName: "",
};
},
methods: {
getSchoolName(name) {
console.log("App收到了学校名:", name);
},
getStudentName(name, ...params) {
//方法一/二:1.定义一个方法
console.log("App收到了学生名:", name, params);
// 接收name和一个数组对象,数组内存储着其余参数
this.studentName = name;
},
m1() {
console.log("demo事件被触发了!");
},
show() {
alert(123);
},
},
mounted() {
// 方法二:3.App.vue挂载完毕,获取student的组件实例对象,绑定自定义事件
this.$refs.student.$on("atguigu", this.getStudentName); //绑定自定义事件
// this.$refs.student.$once('atguigu',this.getStudentName) //绑定自定义事件(一次性)
},
};
</script>
<style scoped>
.app {
background-color: gray;
padding: 5px;
}
</style>
student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<h2>当前求和为:{{ number }}</h2>
<button @click="add">点我number++</button>、
<!-- 方法一:3.定义 sendStudentlName点击事件-->
<!-- 方法二:4.定义 sendStudentlName点击事件-->
<button @click="sendStudentlName">把学生名给App</button>
<button @click="unbind">解绑atguigu事件</button>
<button @click="death">销毁当前Student组件的实例(vc)</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
number: 0,
};
},
methods: {
add() {
console.log("add回调被调用了");
this.number++;
},
sendStudentlName() {
//方法一:4.触发Student组件实例身上的atguigu事件
//方法二:5.触发Student组件实例身上的atguigu事件
this.$emit("atguigu", this.name, 666, 888, 900);
// this.$emit("demo");
// 触发Student组件实例身上的demo事件
// this.$emit('click')
},
unbind() {
this.$off("atguigu"); //解绑一个自定义事件
// this.$off(['atguigu','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件
},
death() {
this.$destroy();
//销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效,
// 原生DOM事件不受影响,但页面响应式丢了。
},
},
};
</script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
school.vue
<template>
<div class="school">
<h2>学校名称:{{name}}</h2>
<h2>学校地址:{{address}}</h2>
<button @click="sendSchoolName">把学校名给App</button>
</div>
</template>
<script>
export default {
name:'School',
props:['getSchoolName'],
data() {
return {
name:'尚硅谷',
address:'北京',
}
},
methods: {
sendSchoolName(){
this.getSchoolName(this.name)
}
},
}
</script>
<style scoped>
.school{
background-color: skyblue;
padding: 5px;
}
</style>
3.11 全局事件总线(GlobalEventBus)
1.Vue原型对象上包含事件处理的方法
1)$on(eventName,listener):绑定自定义事件监听
2)$emit(eventName,data):分发自定义事件
3)$off(eventName):解绑自定义事件监听
4)$once(eventName,listener):绑定事件监听,但只能处理一次
2.所有组件实例对象的原型对象的原型对象就是Vue的原型对象
1)所有组件对象都能看到Vue原型对象上的属性和方法
2)Vue.prototype.$bus=new Vue(),所有的组件对象都能看到$bus这个属性对象
3.全局事件总线
1)包含事件处理相关方法的对象(只有一个)
2)所有的组件都可以得到
-
一种组件间通信的方式,适用于任意组件间通信。
-
安装全局事件总线:
new Vue({ ...... beforeCreate() { Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm }, ...... })
-
使用事件总线:
-
接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){ demo(data){......} } ...... mounted() { this.$bus.$on('xxxx',this.demo) //回调methods提供的demo方法或直接使用箭头函数 }
-
提供数据:
//在提供数据的组件的methods中书写 this.$bus.$emit('xxxx',数据)
-
-
最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。 main.js
//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//关闭Vue的生产提示
Vue.config.productionTip = false
//创建vm
new Vue({
el:'#app',
render: h => h(App),
beforeCreate() {
Vue.prototype.$bus = this //安装全局事件总线
},
})
App.vue
<template>
<div class="app">
<h1>{{msg}}</h1>
<School/>
<Student/>
</div>
</template>
<script>
import Student from './components/Student'
import School from './components/School'
export default {
name:'App',
components:{School,Student},
data() {
return {
msg:'你好啊!',
}
}
}
</script>
<style scoped>
.app{
background-color: gray;
padding: 5px;
}
</style>
Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
mounted() {
// console.log('Student',this.x)
},
methods: {
sendStudentName() {
this.$bus.$emit("hello", this.name);
//提供数据,将student组件中的数据传递给school
},
},
};
</script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
mounted() {
// console.log('School',this)
this.$bus.$on("hello", (data) => {
console.log("我是School组件,收到了数据", data);
});
},
beforeDestroy() {
this.$bus.$off("hello");
// 最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件,一定要写解绑的事件名,否则就全部解绑了
},
};
</script>
<style scoped>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
3.12 消息订阅与发布(pubsub)
1.这种方式的思想与全局事件总线很相似
2.它包含以下操作:
(1)订阅消息--对应绑定事件监听
(2)发布消息--分发事件
(3)取消消息订阅--解绑事件监听
3.需要引入一个消息订阅与发布的第三方实现库
1.在线文档
2.下载:npminstall-Spubsub-js
3.相关语法:
(1)import PubSub from 'pubsub-js'//引入
(2)PubSub.subscribe(‘msgName’,functon(msgName,data){})订阅消息
(3)PubSub.publish(‘msgName’,data):发布消息,触发订阅的回调函数调用
(4)PubSub.unsubscribe(token):取消消息的订阅
-
一种组件间通信的方式,适用于任意组件间通信。
-
使用步骤:
-
安装pubsub:
npm i pubsub-js
-
在订阅和发布消息的文件中引入:
import pubsub from 'pubsub-js'
-
接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
-
methods: {
demo(msgName, data) {
console.log(
"有人发布了hello消息,hello消息的回调执行了",
msgName,
data,
this
);
},
},
......
this.pubId = pubsub.subscribe("hello", this.demo);
- 提供数据:B组件传递数据,则在B组件中发布消息
methods: {
sendStudentName() {
// 发布消息:pubsub.publish(事件名,参数),
pubsub.publish("hello", 666);
},
},
- 最好在beforeDestroy钩子中,用
PubSub.unsubscribe(pid)
去取消订阅。 Student.vue
<template>
<div class="student">
<h2>学生姓名:{{ name }}</h2>
<h2>学生性别:{{ sex }}</h2>
<button @click="sendStudentName">把学生名给School组件</button>
</div>
</template>
<script>
import pubsub from "pubsub-js";
// 引入pubsub-js,pubsub是一个对象
export default {
name: "Student",
data() {
return {
name: "张三",
sex: "男",
};
},
methods: {
sendStudentName() {
// 发布消息:pubsub.publish(事件名,参数),
pubsub.publish("hello", 666);
},
},
};
</script>
<style scoped>
.student {
background-color: pink;
padding: 5px;
margin-top: 30px;
}
</style>
School.vue
<template>
<div class="school">
<h2>学校名称:{{ name }}</h2>
<h2>学校地址:{{ address }}</h2>
</div>
</template>
<script>
import pubsub from "pubsub-js";
// 引入pubsub-js,pubsub是一个对象
export default {
name: "School",
data() {
return {
name: "尚硅谷",
address: "北京",
};
},
methods: {
demo(msgName, data) {
console.log(
"有人发布了hello消息,hello消息的回调执行了",
msgName,
data,
this
);
},
},
mounted() {
/*订阅消息:pubsub.subscribe(消息名,回调函数function(消息名,传入的参数){
console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
console.log(this);
这里this为undefined,想要避免有如下两种方法
})
*/
// 方法一:this调用methods方法
// this.pubId = pubsub.subscribe("hello", this.demo);
// 方法二:使用箭头函数
this.pubId = pubsub.subscribe("hello", (msgName, data) => {
console.log("有人发布了hello消息,hello消息的回调执行了", msgName, data);
console.log(this);
});
},
beforeDestroy() {
// 取消订阅
pubsub.unsubscribe(this.pubId);
},
};
</script>
<style scoped>
.school {
background-color: skyblue;
padding: 5px;
}
</style>
剩余文件跟全局事件总线一致,只是不需要安装全局事件总线
3.13 .sync修饰符
在不依赖双向绑定的标签或者 v-model 方法下,父子数据的更新就是通过绑定函数在父级组件中更新数据(自定义事件,逆向props)。
应用场景--修改父亲传递过来的props属性
- 是在父子组件传值的,且子组件要修改这个数据的时候使用。它的原理是利用EventBus,子组件触发事件,父组件响应事件并实现数据的更新,避免由子组件直接修改父组件传过来的内容。(如果子组件直接操作,vue会有警告提示)
用一个现实中的例子来说:正常来说,钱都是在父亲身上的,儿子要用钱是告诉父亲要用钱,然后父亲从身上拿出钱给儿子使用。如果说子组件直接操作父父组件传过来的值,等同于儿子没经过通知父亲这一步就拿钱用了,这等于是偷钱,不合理的。
其实父子组件传值的过程等同于是父亲告诉儿子,我有这么些个钱可以用,不是让子组件直接操作这个值。你要用多少,告诉我,然后把用完后会剩余多少告诉我就可以了。
搞清楚了这个逻辑,那么来看一个例子:
child.vue(子组件)
<template>
<div class="child">
{{ money }}
{{ name }}
{{ age }}
<button @click="changeMoney">
<span>花钱</span>
</button>
<button @click="changeAge">
<span>成长</span>
</button>
</div>
</template>
<script>
export default {
props: ["money","name","age"],
methods: {
changeMoney() {
this.$emit("update:money", this.money - 100);
},
changeAge() {
this.$emit("update:age", this.age + 1);
},
},
};
</script>
<style>
.child {
border: 3px solid green;
}
</style>
parent.vue(父组件)
<template>
<div class="app">
App.vue
我是 {{doc.name}}
今年 {{doc.age}}
我现在有 {{total}}
<hr>
简写--语法糖
<Child :money.sync="total"/>
<Child v-bind.sync="doc"/>
<hr>
正常写法
<!-- <Child :money="total" @update:money="total = $event" /> -->
<Child :money="total" @update:money="changeTotal($event)" />
<Child :name="doc.name" :age="doc.age" @update:age="doc.age = $events" />
<!-- <Child :name="doc.name" :age="doc.age" @update:age="changeAge($event)" /> -->
</div>
</template>
<script>
import Child from "./Child.vue";
export default {
data() {
return {
total: 10000,
doc:{
name:"zgc",
age:18
}
};
},
components: { Child: Child },
methods:{
changeTotal(events){
this.total = events
},
changeAge(events){
this.doc.age = events
},
}
};
</script>
<style>
.app {
border: 3px solid red;
padding: 10px;
}
</style>
这个代码就是上述内容的解释,实现的原理是利用eventBus,在子组件使用$emit('update:money', money-100)
来通知父组件去响应,而父组件则通过$event
来接收经过子组件修改后的值。
注意v-on:update:money
这里事件必须写为update:mondy
,update
是vue规定的语法书写格式,money
是被绑定事件的属性。正式这样的规定使
<Child :money="total" v-on:update:money="total = $event"/>
这么长的语句可以缩写为:
<Child :money.sync="total"/>
而子组件内也必须用'update:money'
事件名去触发响应
<button @click="$emit('update:money', money-100)">
3.14 nextTick
- 语法:
this.$nextTick(回调函数)
- 作用:在下一次 DOM 更新结束后执行其指定的回调。
- 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
3.15 Vue封装的过度与动画
-
作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。
-
a图示:
-
写法:
-
准备好样式:
- 元素进入的样式:
- v-enter:进入的起点
- v-enter-active:进入过程中
- v-enter-to:进入的终点
- 元素离开的样式:
- v-leave:离开的起点
- v-leave-active:离开过程中
- v-leave-to:离开的终点
- 元素进入的样式:
-
使用
<transition>
包裹要过度的元素,并配置name属性:<transition name="hello"> <h1 v-show="isShow">你好啊!</h1> </transition>
appear是页面第一次刷新时出现的样式:
- 备注:若有多个元素需要过度,则需要使用
<transition-group>
,且每个元素都要指定key
值。
-
用动画实现:
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition name="hello" appear>
<h1 v-show="isShow">你好啊!</h1>
</transition>
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style scoped>
h1 {
background-color: orange;
}
/* 用动画实现 */
.hello-enter-active {
animation: atguigu 0.5s linear;
}
/* transition添加name属性之后
默认的v-enter-active改为name-enter-active
*/
.hello-leave-active {
animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0px);
}
}
</style>
用过渡实现:
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<transition-group name="hello" appear>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
<!-- 多个元素过渡 -->
</div>
</template>
<script>
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style scoped>
h1 {
background-color: orange;
}
/* 用过度实现 */
/* 进入的起点、离开的终点 */
.hello-enter,
.hello-leave-to {
transform: translateX(-100%);
}
/* 过渡过程 */
.hello-enter-active,
.hello-leave-active {
transition: 0.5s linear;
}
/* 进入的终点、离开的起点 */
.hello-enter-to,
.hello-leave {
transform: translateX(0);
}
</style>
用第三方动画库实现:
<template>
<div>
<button @click="isShow = !isShow">显示/隐藏</button>
<!--在name中加入 animate__animated animate__bounce-->
<transition-group
appear
name="animate__animated animate__bounce"
enter-active-class="animate__backInUp"
leave-active-class="animate__backOutUp"
>
<h1 v-show="!isShow" key="1">你好啊!</h1>
<h1 v-show="isShow" key="2">尚硅谷!</h1>
</transition-group>
</div>
</template>
<script>
import "animate.css";
export default {
name: "Test",
data() {
return {
isShow: true,
};
},
};
</script>
<style scoped>
h1 {
background-color: orange;
}
</style>