2.vue组件化编程
2.1模块与组件,模块化与组件化
组件定义:
实现应用中局部功能代码(html,css,js)和资源(mp3,mp4)的集合。
2.1.1 模块
2.1.2 组件
2.1.3 模块化
2.1.4 组件化
2.2 非单文件组件
2.2.1 初识
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root1">
<school></school>
<hr>
<student></student>
</div>
<div id="root2">
<hello></hello>
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 创建school组件
const school=Vue.extend({
template:`
<div>
<h1>schoolInfo</h1>
<h2>name:{{schoolName}}</h2>
<h2>addr:{{schoolAddress}}</h2>
<button @click='btn'>点我提示学校名</button>
</div>
`,
// 不写el,最终所有组件被一个vm管理,由vm决定服务于哪个容器
// el:'#root1',
data(){
return {
schoolName:'atguigu',
schoolAddress:'beijing'
}
},
methods:{
btn(){
alert(this.schoolName)
}
}
})
// 创建student组件
const student=Vue.extend({
template:`
<div>
<h1>studentInfo</h1>
<h2>name:{{studentName}}</h2>
<h2>age:{{studentAge}}</h2>
</div>
`,
data(){
return {
studentName:'coderhao',
studentAge:25
}
}
})
// 全局组件
const hello=Vue.extend({
template:`
<div>
<h1>hello</h1>
</div>
`,
// 不写el,最终所有组件被一个vm管理,由vm决定服务于哪个容器
// el:'#root1',
data(){
return{} ;
},
methods:{
}
})
Vue.component('hello',hello)
new Vue({
el:'#root1',
// 组件注册(局部注册)
components:{
school:school,
// 简写
student
}
})
new Vue({
el:'#root2',
})
</script>
</html>
2.2.2 总结
2.2.3组件嵌套
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root1">
<!-- 可以在vm里写标签 -->
<!-- <app></app> -->
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
const student={
template:`
<div>
<h1>studentInfo</h1>
<h2>name:{{name}}</h2>
<h2>age:{{age}}</h2>
</div>
`,
data(){
return{
name:'coderhao',
age:25,
}
}
};
const school={
template:`
<div>
<h1>schoolInfo</h1>
<h2>name:{{schoolName}}</h2>
<h2>addr:{{achoolAddr}}</h2>
<student></student>
</div>
`,
data(){
return{
schoolName:'atguigu',
achoolAddr:'beijing',
}
},
components:{
student
}
}
const hello={
template:`
<div>
<h2>{{msg}}</h2>
</div>
`,
data(){
return{
msg:'welcome to atguigu'
}
}
}
const app={
template:`
<div>
<hello></hello>
<school></school>
</div>
`,
components:{
hello,
school
}
}
new Vue({
el:'#root1',
data:{
},
components:{
app
},
template:`<app></app>`
})
</script>
</html>
2.2.4 VueComponent
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root1">
<app></app>
</div>
</body>
<template id='schoolTemplate'>
<div>
<h1>schoolInfo</h1>
<h2>name:{{schoolName}}</h2>
<h2>addr:{{schoolAddr}}</h2>
</div>
</template>
<template id="appTemplate">
<div>
<school></school>
</div>
</template>
<script type="text/javascript">
Vue.config.productionTip = false;
const school=Vue.extend({
template:schoolTemplate,
data(){
return{
schoolName:'atguigu',
schoolAddr:'beijing'
}
}
})
const app={
template:appTemplate,
components:{
school
}
}
// @ ƒ VueComponent (options) {this._init(options);}
console.log('@',school);
new Vue({
el:'#root1',
data:{
},
components:{
app
}
})
</script>
</html>
2.2.5 一个重要的内置关系
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root1">
</div>
</body>
<script type="text/javascript">
Vue.config.productionTip = false;
// 定义一个构造函数
function Demo(){
this.a=1
this.b=2
}
// 创建一个Demo的实例对象
const d=new Demo()
// 显式原型属性
console.log(Demo.prototype);
// 隐式原型属性
console.log(d.__proto__);
//通过显式原型属性操作原型对象,追加x=99
Demo.prototype.x=99
console.log(d);
console.log(d.x);
const vc=Vue.extend({})
new Vue({
el:'#root1',
data:{
}
})
//true
console.log(vc.prototype.__proto__===Vue.prototype);
</script>
</html>
实例的隐式原型属性,永远指向自己缔造者的原型对象:
2.3 单文件组件
注:
单单词.vue文件名形式:大写开头
多单词.vue文件名形式:1.使用大驼峰 2.小写加短斜杠组合
思路:index.html->main.js->App.vue->Student.vue->School.vue
index.html:组件所在的html页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>单文件开发demo</title>
</head>
<body>
<div id="root">
</div>
<script src="../js/vue.js"></script>
<script src="./main.js"></script>
</body>
</html>
main.js:创建Vue实例的js页面:
import App from './App.vue'
new Vue({
el:'#root',
template:`<app></app>`,
components:{
App
}
})
App.vue:父组件:
<template>
<div class="app">
<Student></Student>
</div>
</template>
<script>
import Student from './Student.vue';
export default {
name:'App',
components:{
Student
}
}
</script>
<style>
.app{
background-color: coral;
}
</style>
Student.vue:App.vue的子组件
<template>
<div class="student">
<h1>studentInfo</h1>
<h2>name:{{name}}</h2>
<h2>age:{{age}}</h2>
<School></School>
</div>
</template>
<script>
import School from './School.vue'
export default {
name:'Student',
data(){
return{
name:'coderhao',
age:25
}
},
components:{
School
}
}
</script>
<style>
.student{
background-color: blueviolet;
}
</style>
School.vue:Student.vue的子组件:
<template>
<!-- 组件结构 -->
<div class='school'>
<h1>schoolInfo</h1>
<h2>name:{{name}}</h2>
<h2>addr:{{addr}}</h2>
<button @click="showName">alert(this.name)</button>
</div>
</template>
<script>
// 组件交互代码
export default {
name:'School',
data(){
return{
name:'atguigu',
addr:'beijing'
}
},
methods:{
showName(){
alert(this.name)
}
}
}
</script>
<style>
/* 组件样式 */
.school{
background-color: aqua;
}
</style>
这里只是一个演示demo,运行index.html是不奏效的,因为浏览器解析不了es6的导入导出组件等操作,需要webpack或者vuecli(脚手架)来处理,转换成浏览器懂的基本的js,css,html。
关于脚手架知识,下面就详细说明。
3. 使用Vue脚手架(VueCLI)
3.1 初始化脚手架
3.1.1 说明
3.1.2 安装步骤
1.(仅第一次执行)全局安装vuecli
npm install -g @vue/cli
注:npm配成淘宝镜像,不然vuecli下载特别缓慢
npm config set registry https://registry.npm.taobao.org
重新打开cmd,输入vue命令,如下所示就安装成功了vuecli:
2.cd到需要项目所在的目录,然后创建项目(这里项目名为cli_test):
vue create cli_test
3.按照说明启动项目
可以看到cli准备的一个helloworld项目:
停止项目就控制台ctrl+c
3.1.3分析cli_test
外部文件:
main.js:
app.vue:
index.html:
3.1.4 将2.3中的代码替换helloworld,看能否成功显示
替换app.vue,加入Student.vue和School.vue,重新 npm run serve :
显示成功!
同时由于cli是热部署的,随时改变随时ctrl+s都会重新自动 npm run serve.
3.1.5 分析3.1.3中app.vue遗留的render代码
所以使用render代替了template的写法。只有创建vm的时候有这个操作,组件里面的template标签有别的东西帮忙解析了。
3.1.6 脚手架修改默认配置
红色的不能改,粉色的可以改:
能改的项官网已经说清楚了,除此之外的不能改:
https://cli.vuejs.org/zh/config/
配置文件是vue.config.js,针对这个文件的修改,生效需要重新运行npm run serve.
教学阶段关闭语法检查:
vue.config.js:
module.exports = {
lintOnSave: false
}
3.1.7 小总结
这样在热部署教学的时候不会因为js里对象没被使用等奇怪的问题编译报错。
3.2 ref与props
ref
App.vue:
<template>
<div>
<h1 v-text="msg" ref="title"></h1>
<button @click="logDOM" ref="btn">click me log DOM element</button>
<School ref="sch"></School>
</div>
</template>
<script>
import School from './components/School.vue'
export default {
name:'App',
components:{
School
},
data(){
return{
msg:'welcome to learn vue'
}
},
methods:{
logDOM(){
console.log(this.$refs.title);//真实DOM
console.log(this.$refs.btn);//真实DOM
console.log(this.$refs.sch);//School组件的组件实例对象(VC)
}
}
}
</script>
<style>
</style>
props: 数据传递,父传子
App.vue:
<template>
<div>
<h1 v-text="msg"></h1>
<!-- 通过data的过渡,表单里面的改变影响不到数据呈现,但如果不过渡直接使用传入的参数,肯定是影响的 -->
studentName:<input v-model="student.name"><br>
studentAge:<input type="number" v-model.number="student.age"><br>
schoolName:<input v-model="student.school.name"><br>
schoolAddr:<input v-model="student.school.addr"><br>
<Student :student='student'></Student>
</div>
</template>
<script>
import Student from './components/Student.vue'
export default {
name:'App',
components:{
Student
},
data(){
return{
msg:'welcome to learn vue',
student:{
name:'coderhao',
age:25,
school:{
name:'atguigu',
addr:'beijing'
}
}
}
},
methods:{
}
}
</script>
<style>
</style>
Student.vue:
<template>
<div>
<div>
<h1>{{title}}</h1>
<h2>name:{{name}}</h2>
<!-- <h2>age:{{this.student.age}}</h2> -->
<h2>age:{{age}}</h2>
<button @click="increaseAge">age++</button>
<!-- <School :school='school'></School> -->
<!-- 不传参,使用默认参数 -->
<School ></School>
</div>
</div>
</template>
<script>
import School from './School.vue'
export default {
name:'Student',
components:{
School
},
data(){
return{
title:'studentInfo',
name:this.student.name,
age:this.student.age,
school:this.student.school
}
},
props:['student'],
methods:{
increaseAge(){
//最好不要操作源数据,请操作数据的备份
// this.student.age++,
this.age++
}
}
}
</script>
<style>
</style>
School.vue:
<template>
<div class="school">
<h1>{{title}}</h1>
<h2>name:{{name}}</h2>
<h2>addr:{{addr}}</h2>
</div>
</template>
<script>
export default {
name:'School',
data(){
return{
title:'schoolInfo',
name:this.school.name,
addr:this.school.addr
}
},
// 简单接收
// props:['name','addr']
// 类型限制接收
// props:{
// name:String,
// addr:String
// },
// 最详细设置:类型限制+默认值+必要性限制
// props:{
// name:{
// type:String,
// required: true
// },
// addr:{
// type:String,
// required:false,//不是必须传过来的数据
// default:'shanghai'//没有数据时的默认值
// }
// }
props:{
school:{
type: Object,
required:false,
// Props with type Object/Array must use a factory function to return the default value.
default(){
return{
name:'atguigu',
addr:'beijing'
}
}
}
}
}
</script>
<style>
.school{
background-color: aqua;
}
</style>
3.3 混入:mixin
定义混合:
局部使用:
全局使用,所有的vc和vm带混合的代码:
3.4 插件
plugins.js:
export default{
install(Vue){
console.log('install()');
console.log(Vue);
// 可以在这里写全局过滤器和全局自定义组件,全局混入,Vue原型上添加方法(vm,vc都能用)等。。。
// Vue.filter
// Vue.directive
// Vue.mixin({})
// Vue.prototype.hello=()=>{alert('hello !')}
}
}
main.js:
import Vue from 'vue'
import App from './App.vue'
Vue.config.productionTip=false
//引入插件
import plugins from './plugins'
// 使用插件
Vue.use(plugins)
new Vue({
el:'#app',
render:h=>h(App)
})
插曲:style标签上的scoped、lang属性
scoped
在多个组件引入同一个.vue文件里面时,样式名难免冲突,会导致样式冲突,而在组件的style标签上添加scoped就会把样式限制在本vue的template那里。
App.vue里面的样式基本上很多组件都在用,所以一般不加scoped,所有组件都会用的App.vue的样式。
lang
代表style是用什么语法写的,css,less之类的,默认css
less编译不了,要装less-loader
3.5 todo-list案例
案例实现预期:
(本3.5章节最后有整体代码)
前端组件化编码流程:
1.实现静态组件
即template和css的实现。
2.展示动态数据
数据放到App中,通过props给List数据,通过props一个方法,得到header的返回的数据。
header中输入的东西,可以添加到list中:
3.交互,绑定事件监听
@click等等。。。。
小总结
代码:
App.vue:
<template>
<div>
<div id="root">
<div class="todo-container">
<div class="todo-wrap">
<MyHeader :addTodo="addTodo"></MyHeader>
<MyList
:todos="todos"
:changeDone="changeDone"
:removeTodo="removeTodo"
></MyList>
<MyFooter
:todos="todos"
:removeTodo="removeTodo"
:checkAllTodo="checkAllTodo"
></MyFooter>
</div>
</div>
</div>
</div>
</template>
<script>
import MyHeader from "./components/MyHeader.vue";
import MyList from "./components/MyList.vue";
import MyFooter from "./components/MyFooter.vue";
export default {
name: "App",
components: {
MyHeader,
MyList,
MyFooter,
},
data() {
return {
todos: [
{ id: "001", title: "eat", done: true },
{ id: "002", title: "sleep", done: false },
{ id: "003", title: "play", done: false },
],
};
},
methods: {
// 给MyHeader传的方法,MyHeader调用时会触发
addTodo(todoObj) {
this.todos.unshift(todoObj);
},
// 单个todo选择or不选择方法
changeDone(todoId) {
this.todos.forEach((todo) => {
if (todo.id === todoId) {
todo.done = !todo.done;
}
});
},
// 移除todo的方法
removeTodo(todoId) {
this.todos = this.todos.filter((todo) => {
return todo.id !== todoId;
});
},
// 全选or全不选方法
checkAllTodo(done) {
this.todos.forEach((todo) => {
todo.done = done;
});
},
},
};
</script>
<style>
/*base*/
body {
background: #fff;
}
.btn {
display: inline-block;
padding: 4px 12px;
margin-bottom: 0;
font-size: 14px;
line-height: 20px;
text-align: center;
vertical-align: middle;
cursor: pointer;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 1px 2px rgba(0, 0, 0, 0.05);
border-radius: 4px;
}
.btn-danger {
color: #fff;
background-color: #da4f49;
border: 1px solid #bd362f;
}
.btn-danger:hover {
color: #fff;
background-color: #bd362f;
}
.btn:focus {
outline: none;
}
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
MyHeader.vue:
<template>
<div>
<div class="todo-header">
<input
type="text"
placeholder="请输入你的任务名称,按回车键确认"
@keyup.enter="add"
v-model="title"
/>
</div>
</div>
</template>
<script>
import { nanoid } from "nanoid";
export default {
name: "MyHeader",
data() {
return {
title: "",
};
},
props: ["addTodo"],
methods: {
add() {
if (this.title.trim() !== "") {
console.log(this.title.trim);
// 将用户输入的东西包装成为一个todo对象
// 需要安装nanoid:npm i nanoid
const todoObj = { id: nanoid(), title: this.title, done: false };
this.addTodo(todoObj);
this.title = "";
} else {
alert("输入不能为空");
}
},
},
};
</script>
<style>
/*header*/
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
}
.todo-header input:focus {
outline: none;
border-color: rgba(82, 168, 236, 0.8);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
0 0 8px rgba(82, 168, 236, 0.6);
}
</style>
MyList.vue:
<template>
<div>
<ul class="todo-main">
<MyItem
v-for="todoObj in todos"
:key="todoObj.id"
:todo="todoObj"
:changeDone="changeDone"
:removeTodo="removeTodo"
></MyItem>
</ul>
</div>
</template>
<script>
import MyItem from "./MyItem.vue";
export default {
name: "MyList",
components: {
MyItem,
},
data() {
return {};
},
props: ["todos", "changeDone", "removeTodo"],
};
</script>
<style scoped>
/*main*/
.todo-main {
margin-left: 0px;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0px;
}
.todo-empty {
height: 40px;
line-height: 40px;
border: 1px solid #ddd;
border-radius: 2px;
padding-left: 5px;
margin-top: 10px;
}
</style>
MyItem.vue:
<template>
<li>
<label>
<input
type="checkbox"
:checked="todo.done"
@click="handleCheck(todo.id)"
/>
<!--:checked和@click可以直接用v-model代替,但不建议,违反了props只读的原则 -->
<span>{{ todo.title }}</span>
</label>
<button class="btn btn-danger" @click="removeTodoById(todo.id)">
删除
</button>
</li>
</template>
<script>
export default {
name: "MyItem",
props: ["todo", "changeDone", "removeTodo"],
methods: {
handleCheck(todoId) {
this.changeDone(todoId);
},
removeTodoById(todoId) {
if (confirm("确认删除?")) {
// console.log(todoId);
this.removeTodo(todoId);
}
},
},
};
</script>
<style scoped>
/*item*/
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
float: right;
display: none;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
li:hover {
background-color: coral;
}
li:hover button {
display: block;
}
</style>
MyFooter.vue:
<template>
<div>
<div class="todo-footer" v-show="total">
<label>
<!-- <input type="checkbox" :checked="isAll" @click="checkAll" /> -->
<input type="checkbox" v-model="isAll" />
</label>
<span>
已完成{{ doneTotal }} / 全部{{ total }}
</span>
<button class="btn btn-danger" @click="removeDone">清除已完成任务</button>
</div>
</div>
</template>
<script>
export default {
name: "MyFooter",
props: ["todos", "removeTodo", "checkAllTodo"],
computed: {
isAll: {
get() {
return this.total === this.doneTotal && this.total > 0;
},
set(value) {
this.checkAllTodo(value);
},
},
total() {
return this.todos.length;
},
doneTotal() {
let num = 0;
// 笨方法,遍历
// this.todos.forEach(todo => {
// if(todo.done==true){
// num++
// }
// });
// 巧一点的方法,reduce函数
num = this.todos.reduce((pre, todo) => {
return pre + (todo.done ? 1 : 0);
}, 0);
return num;
},
},
methods: {
// 移除全部选择的todo
removeDone() {
this.todos.forEach((todo) => {
if (todo.done === true) {
this.removeTodo(todo.id);
}
});
},
checkAll(event) {
this.checkAllTodo(event.target.checked);
},
},
};
</script>
<style scoped>
/*footer*/
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
margin-right: 5px;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
插曲:浏览器本地存储
比如在未登陆的情况下有些搜索框会存储搜索历史记录,用到的就是浏览器本地存储。
浏览器关闭不丢失数据的是localStorage;
数据生命周期仅在一个窗口内的是sessionStorage。
localStorage的api
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<h2>localStorage</h2>
<button onclick="saveData()">saveData()</button>
<button onclick="readData()">readData()</button>
<button onclick="deleteData()">deleteData()</button>
<script>
function saveData(){
let p={name:'coderhao',age:25}
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'));
str=localStorage.getItem('person')
console.log(JSON.parse(str));
//null
console.log(localStorage.getItem('unknow'));
};
function deleteData(){
localStorage.removeItem('msg')
// 清空
// localStorage.clear()
}
</script>
</body>
</html>
sessionStorage的api和localStorage的一致,只是名字换一下:
例:
localStorage.xxxx>>>>>>>>sessionStorage.xxxx
小结:
对todoList案例的补充
todoList刷新会丢失数据,通过本地化存储防止数据丢失。
3.6 组件的自定义事件
App.vue:
<template>
<div class="app">
<!-- 方法1 传入自定义事件 -->
<Student @getStudentName="getStudentName"></Student>
<h2>studentName:{{ studentName }}</h2>
<!-- 方法2 -->
<School ref="school"></School>
<h2>schoolName:{{ schoolName }}</h2>
</div>
</template>
<script>
import Student from "./components/Student.vue";
import School from "./components/School.vue";
export default {
name: "App",
components: {
Student,
School,
},
data() {
return {
studentName: "",
schoolName: "",
};
},
methods: {
getStudentName(studentName) {
this.studentName = studentName;
},
getSchoolName(schoolName) {
this.schoolName = schoolName;
},
},
mounted() {
this.$refs.school.$on("getSchoolName", this.getSchoolName);
},
};
</script>
<style>
.app {
background-color: crimson;
padding: 5px;
margin-top: 5px;
}
</style>
Student.vue:
<template>
<div class="student">
<h1>{{ title }}</h1>
<h2>name:{{ name }}</h2>
<h2>age:{{ age }}</h2>
<button @click="sendStudentName()">this.$emit('getStudentName',this.name)</button>
</div>
</template>
<script>
export default {
name: "Student",
data() {
return {
title: "studentInfo",
name: "coderhao",
age: 25,
};
},
methods: {
sendStudentName(){
this.$emit('getStudentName',this.name)
}
},
};
</script>
<style>
.student {
background-color: coral;
padding: 5px;
margin-top: 5px;
}
</style>
School.vue:
<template>
<div class="school">
<h1>{{ title }}</h1>
<h2>name:{{ name }}</h2>
<h2>addr:{{ addr }}</h2>
<button @click="sendSchoolName()">this.$emit('getSchoolName',this.name)</button>
</div>
</template>
<script>
export default {
name: "School",
data() {
return {
title: "schoolInfo",
name: "atguigu",
addr: "beijing",
};
},
props:[],
methods:{
sendSchoolName(){
this.$emit('getSchoolName',this.name)
}
}
};
</script>
<style>
.school {
background-color: aqua;
padding: 5px;
margin-top: 5px;
}
</style>
自定义事件改写todoList案例
这里以MyHeader组件为例
App.vue:
MyHeader.vue:
调用事件时vue开发者工具可以看到调用细节:
3.7全局事件总线
原理:给组件ABCD通过给x绑定事件的方式,然后通过组件ABCD触发X中的事件来将数据传给相应的组件。
X得满足两个条件:首先别的组件能给X绑事件,说明所有组件得能看到X;然后X可以绑定、解绑、触发事件。
为了满足所有组件看得到,使用vc/vm做x,而且也可以绑定事件。
这里以两个兄弟组件student和school的信息传递为例:两个版本(vc和vm)
vc版本:
main.js:
school.vue:
student.vue:
vm版本:
main.js:
student.vue和school.vue同上。
一般也不叫x,叫$bus,总线:
总结:
全局事件总线改写todoList
在todoList的案例中,单个任务的删除方法是从App.vue–>MyList.vue–>MyItem.vue,没必要经过MyList.vue,适合使用全局事件总线。
main.js:
App.vue:
MyItem.vue:
3.8消息订阅与发布:pubsub
实现消息订阅与发布的有很多js库,这里使用pubsub-js实现。
安装库:
npm i pubsub-js
消息发布:
消息订阅:
pubsub改写todoList
App.vue:
todoList增加编辑按钮
App.vue增加更新方法,这里用的消息总线方式
MyItem.vue:
<template>
<li>
<label>
<input
type="checkbox"
:checked="todo.done"
@click="handleCheck(todo.id)"
/>
<span v-show="!todo.isEdit">{{ todo.title }}</span>
<input
v-show="todo.isEdit"
type="text"
:value="todo.title"
@blur="handlerBlur(todo,$event)"
ref="inputTitle"
/>
</label>
<button v-show="!todo.isEdit" @click="updateTitle(todo)">update</button>
<button @click="deleteById(todo.id)">delete</button>
</li>
</template>
<script>
export default {
name: "MyItem",
props: ["todo", "deleteTodoObj", "changeDone"],
methods: {
deleteById(id) {
if (confirm("确认删除?")) {
this.deleteTodoObj(id);
}
},
handleCheck(id) {
this.changeDone(id);
},
updateTitle() {
// 添加isEdit属性
this.$set(this.todo, "isEdit", true);
// 下一轮执行,会在Dom节点更新完毕后执行,不然页面没渲染完,获取焦点无效
this.$nextTick(function() {
this.$refs.inputTitle.focus()
})
},
// 失去焦点方法
handlerBlur(todo,e){
this.todo.isEdit=false
if(!e.target.value.trim()) return alert('输入不能为空')
// 失去焦点后更新数据
this.$bus.$emit('updateTodo',todo.id,e.target.value)
}
},
};
</script>
<style>
</style>
3.9vue过渡与动画效果
3.9.1动画效果使得msg出现和消失带动画
Test.Vue:
<template>
<div>
<!-- 过渡标签transition包裹,可自动使用进场离场动画 -->
<!-- v-if,v-show均可使用 -->
<!-- :appear="true":刚出现即带动画 -->
<transition :appear="true">
<h2 v-show="isShow">{{msg}}</h2>
</transition>
<button @click="showOrNot">showOrNot</button>
</div>
</template>
<script>
export default {
name:'Test',
data(){
return{
msg:'hello vue',
isShow:true
}
},
methods:{
showOrNot(){
this.isShow=!this.isShow
}
}
}
</script>
<style>
h2{
background-color: burlywood;
}
/* 进场动画 */
.v-enter-active{
animation: atguigu 1s linear;
}
/* 离场动画 */
.v-leave-active{
animation: atguigu 1s reverse;
}
/* transition标签如果带有name属性(<!-- <transition name="a1"> -->),则需要改变style中的对应名 */
/* .v-enter-active》》》.a1-enter-active */
/* 进场动画 */
.a1-enter-active{
/* 动画的操作名atguigu和下面的@keyframes atguigu对应 */
animation: atguigu 1s linear;
}
/* 离场动画 */
.a1-leave-active{
animation: atguigu 1s reverse;
}
@keyframes atguigu{
from{
transform: translateX(-100%);
}
to{
transform: translateX(0%);
}
}
</style>
3.9.2过渡效果使得msg出现消失带动画
Test2.vue:
<template>
<div>
<!-- 过渡标签transition包裹,可自动使用进场离场动画 -->
<!-- v-if,v-show均可使用 -->
<!-- :appear="true":刚出现即带动画 -->
<transition :appear="true" name="a1">
<h2 v-show="isShow">{{msg}}</h2>
</transition>
<button @click="showOrNot">showOrNot</button>
</div>
</template>
<script>
export default {
name:'Test2',
data(){
return{
msg:'hello vue',
isShow:true
}
},
methods:{
showOrNot(){
this.isShow=!this.isShow
}
}
}
</script>
<style>
h2{
background-color: burlywood;
}
/* 进入的起点,离开的终点 */
.a1-enter,.a1-leave-to{
transform: translateX(-100%);
}
/* 进入,离开的过程中 */
.a1-enter-active,.a1-leave-active{
transition: 10s linear;
}
/* 进入的终点,离开的起点 */
.a1-enter-to,.a1-leave{
transform: translateX(0%);
}
</style>
多元素过渡:
3.9.3集成第三方动画
npm 安装:
npm install animate.css
Test3.vue:
<template>
<div>
<transition
name="animate__animated animate__bounce"
appear
enter-active-class="animate__swing"
leave-active-class="animate__backOutUp"
>
<h2 v-show="isShow">{{ msg }}</h2>
</transition>
<button @click="showOrNot">showOrNot</button>
</div>
</template>
<script>
// 引入样式
import "animate.css";
export default {
name: "Test3",
data() {
return {
msg: "hello vue",
isShow: true,
};
},
methods: {
showOrNot() {
this.isShow = !this.isShow;
},
},
};
</script>
<style>
h2 {
background-color: burlywood;
}
</style>
总结