02-组件化编程与Vu额 Click脚手架

1.Vue组件化编程(只有1个数字是一级标题)

1.1 模块与组件、模块化与组件化(两个数字组成是二级标题)

1.1.1模块(三个数字是三级标题 依次类推)

  1. 理解:向外提供特定功能的 js 程序,一般就是一个 js 文件
  2. 为什么:js 文件很多很复杂
  3. 作用:复用 js,简化 js 的编写,提高 js 运行效率

1.1.2. 组件

  1. 定义:用来实现局部功能的代码和资源的集合(html/css/js/image…)
  2. 为什么:一个界面的功能很复杂
  3. 作用:复用编码,简化项目编码,提高运行效率

1.1.3. 模块化

当应用中的 js 都以模块来编写的,那这个应用就是一个模块化的应用

1.1.4. 组件化

当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用

2.2. 非单文件组件

2.2.1. 基本使用

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>基本使用</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<h1>{{msg}}</h1>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<school></school>
			<hr>
			<!-- 第三步:编写组件标签 -->
			<student></student>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false

		//第一步:创建school组件
		const school = Vue.extend({
            //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。
			template:`
				<div class="demo">
					<h2>学校名称:{{schoolName}}</h2>
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
			data(){
				return {
					schoolName:'尚硅谷',
					address:'北京昌平'
				}
			}
		})

		//第一步:创建student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生姓名:{{studentName}}</h2>
					<h2>学生年龄:{{age}}</h2>
				</div>
			`,
			data(){
				return {
					studentName:'JOJO',
					age:20
				}
			}
		})
		
		//创建vm
		new Vue({
			el:'#root',
			data:{
				msg:'你好,JOJO!'
			},
			//第二步:注册组件(局部注册)
			components:{
				school,
				student
			}
		})
	</script>
</html>

总结:

  • Vue中使用组件的三大步骤:
  1. 定义组件(创建组件)
  2. 注册组件
  3. 使用组件(写组件标签)
  • 如何定义一个组件?

        使用Vue.extend(options)创建,其中options和new Vue(options)时传入的options几乎一样,但也有点区别:  

        1.el不要写,为什么?   

        最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器  

        2.data必须写成函数,为什么?

        避免组件被复用时,数据存在引用关系

  •       如何注册组件?

  1. 局部注册:new Vue的时候传入components选项
  2. 全局注册:Vue.component('组件名',组件)
  • 编写组件标签:<school></school>

 2.2.2. 组件注意事项

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件注意事项</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<h1>{{msg}}</h1>
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		const school = Vue.extend({
			name:'atguigu',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			}
		})

		new Vue({
			el:'#root',
			data:{
				msg:'欢迎学习Vue!'
			},
			components:{
				school
			}
		})
	</script>
</html>

总结:

关于组件名:

  • 一个单词组成:
  1. 第一种写法(首字母小写):school
  2. 第二种写法(首字母大写):School
  • 多个单词组成:
  1. 第一种写法(kebab-case命名):my-school
  2. 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
  • 备注:
  1. 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行
  2. 可以使用name配置项指定组件在开发者工具中呈现的名字
  • 关于组件标签:
  1. 第一种写法:<school></school>
  2. 第二种写法:<school/>
  3. 备注:不使用脚手架时,<school/>会导致后续组件不能渲染
  • 一个简写方式:const school = Vue.extend(options)可简写为:const school = options
2.2.3. 组件的嵌套
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>组件的嵌套</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		
		//定义student组件
		const student = Vue.extend({
			template:`
				<div>
					<h2>学生名称:{{name}}</h2>	
					<h2>学生年龄:{{age}}</h2>	
				</div>
			`,
			data(){
				return {
					name:'JOJO',
					age:20
				}
			}
		})

		//定义school组件
		const school = Vue.extend({
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<student></student>
				</div>
			`,
			components:{
				student
			},
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			}
		})

		//定义hello组件
		const hello = Vue.extend({
			template:`
				<h1>{{msg}}</h1>
			`,
			data(){
				return {
					msg:"欢迎学习尚硅谷Vue教程!"
				}
			}
		})

		//定义app组件
		const app = Vue.extend({
			template:`
				<div>
					<hello></hello>
					<school></school>
				</div>
			`,
			components:{
				school,
				hello
			}
		})

		//创建vm
		new Vue({
			template:`
				<app></app>
			`,
			el:'#root',
			components:{
				app
			}
		})
	</script>
</html>

2.2.4. VueComponent

关于VueComponent:

  1. school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的
  2. 我们只需要写<school/>或<school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)
  3. 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!
  4. 关于this指向:
  • 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是VueComponent实例对象
  • new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是Vue实例对象

5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)

        Vue的实例对象,以后简称vm

只有在本笔记中VueComponent的实例对象才简称为vc

2.2.5. 一个重要的内置关系
<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8" />
		<title>一个重要的内置关系</title>
		<script type="text/javascript" src="../js/vue.js"></script>
	</head>
	<body>
		<div id="root">
			<school></school>
		</div>
	</body>

	<script type="text/javascript">
		Vue.config.productionTip = false
		Vue.prototype.x = 99

		const school = Vue.extend({
			name:'school',
			template:`
				<div>
					<h2>学校名称:{{name}}</h2>	
					<h2>学校地址:{{address}}</h2>	
					<button @click="showX">点我输出x</button>
				</div>
			`,
			data(){
				return {
					name:'尚硅谷',
					address:'北京'
				}
			},
			methods: {
				showX(){
					console.log(this.x)
				}
			},
		})

		const vm = new Vue({
			el:'#root',
			data:{
				msg:'你好'
			},
			components:{school}
		})
	</script>
</html>

  1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue 原型上的属性、方法
2.3. 单文件组件
  • School.vue:
<template>
    <div id='Demo'>
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        methods: {
            showName(){
                alert(this.name)
            }
        },
    }
</script>

<style>
    #Demo{
        background: orange;
    }
</style>
  • Student.vue:
<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
                age:20
            }
        },
    }
</script>
  • App.vue:
<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
    import School from './School.vue'
    import Student from './Student.vue'

    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>
  • main.js:
import App from './App.vue'

new Vue({
    template:`<App></App>`,
    el:'#root',
    components:{App}
})
  • index.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>单文件组件练习</title>
</head>
<body>
    <div id="root"></div>
    <script src="../../js/vue.js"></script>
    <script src="./main.js"></script>
</body>
</html>

3. 使用Vue CLI脚手架

3.1. 初始化脚手架
3.1.1. 说明
  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)
  2. 最新的版本是 4.x
  3. 文档:Vue CLI
3.1.2. 具体步骤
  1. 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry http://registry.npm.taobao.org
  2. 全局安装@vue/cli:npm install -g @vue/cli
  3. 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  4. 选择使用vue的版本
  5. 启动项目:npm run serve
  6. 暂停项目:Ctrl+C

3.1.3. 分析脚手架结构

脚手架文件结构:

.文件目录
├── node_modules 
├── public
│   ├── favicon.ico: 页签图标
│   └── index.html: 主页面
├── src
│   ├── assets: 存放静态资源
│   │   └── logo.png
│   │── component: 存放组件
│   │   └── HelloWorld.vue
│   │── App.vue: 汇总所有组件
│   └── main.js: 入口文件
├── .gitignore: git版本管制忽略的配置
├── babel.config.js: babel的配置文件
├── package.json: 应用包配置文件 
├── README.md: 应用描述文件
└── package-lock.json: 包版本控制文件

src/components/School.vue:

<template>
    <div id='Demo'>
        <h2>学校名称:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>
        <button @click="showName">点我提示学校名</button>
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
                address:'北京'
            }
        },
        methods: {
            showName() {
                alert(this.name)
            }
        },
    }
</script>

<style>
    #Demo{
        background: orange;
    }
</style>

src/components/Student.vue:

<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生年龄:{{age}}</h2>
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
                age:20
            }
        },
    }
</script>

 src/App.vue:

<template>
    <div>
        <School></School>
        <Student></Student>
    </div>
</template>

<script>
    import School from './components/School.vue'
    import Student from './components/Student.vue'

    export default {
        name:'App',
        components:{
            School,
            Student
        }
    }
</script>

src/main.js

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:'#app',
    render: h => h(App),
})

 public/index.html:

<!DOCTYPE html>
<html lang="">
    <head>
        <meta charset="UTF-8">
        <!-- 针对IE浏览器的特殊配置,含义是让IE浏览器以最高渲染级别渲染页面 -->
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <!-- 开启移动端的理想端口 -->
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <!-- 配置页签图标 -->
        <link rel="icon" href="<%= BASE_URL %>favicon.ico">
        <!-- 配置网页标题 -->
        <title><%= htmlWebpackPlugin.options.title %></title>
    </head>
    <body>
        <!-- 容器 -->
        <div id="app"></div>
    </body>
</html>

 

3.1.4. render函数 

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:'#app',
    // 简写形式
	render: h => h(App),
    // 完整形式
	// render(createElement){
	//     return createElement(App)
	// }
})

总结: 

 关于不同版本的函数:

    1.vue.js 与 vue.runtime.xxx.js的区别:

        1.  vue.js 是完整版的 Vue,包含:核心功能+模板解析器

        2. vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器

    2.因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render函数接收到的createElement 函数去指定具体内容

 3.1.5. 修改默认配置 

  • vue.config.js 是一个可选的配置文件,如果项目的(和 package.json 同级的)根目录中存在这个文件,那么它会被 @vue/cli-service 自动加载
  • 使用 vue.config.js 可以对脚手架进行个性化定制,详见配置参考 | Vue CLI
module.exports = {
    pages: {
        index: {
            // 入口
            entry: 'src/index/main.js'
        }
    },
  // 关闭语法检查
  lineOnSave:false
}

                            

 3.2. ref属性

<template>
    <div>
        <h1 ref="title">{{msg}}</h1>
        <School ref="sch"/>
        <button @click="show" ref="btn">点我输出ref</button>
    </div>
</template>

<script>
    import School from './components/School.vue'
    export default {
        name:'App',
        components: { School },
        data() {
            return {
                msg:'欢迎学习Vue!'
            }
        },
        methods:{
            show(){
                console.log(this.$refs.title)
                console.log(this.$refs.sch)
                console.log(this.$refs.btn)
            }
        }
    }
</script>
————————————————

                           

 

 总结:

 ref属性:

  1. 被用来给元素或子组件注册引用信息(id的替代者)
  2. 应用在html标签上获取的是真实DOM元素,应用在组件标签上获取的是组件实例对象(vc)
  3. 使用方式:

            1.打标识:<h1 ref="xxx"></h1> 或 <School ref="xxx"></School>
            2.获取:this.$refs.xxx

 3.3. props配置项

 src/App.vue:

<template>
    <div>
        <Student name="JOJO" sex="男酮" :age="20" />
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    export default {
        name:'App',
        components: { Student },
    }
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/qq_55593227/article/details/119717498

 src/components/Student.vue:

<template>
    <div>
        <h1>{{msg}}</h1>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <h2>学生年龄:{{age}}</h2>     
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                msg:"我是一名来自枝江大学的男酮,嘿嘿,我的金轮~~",
            }
        },
        // 简单声明接收
		// props:['name','age','sex']

        // 接收的同时对数据进行类型限制
		/* props:{
			name:String,
			age:Number,
			sex:String
		} */

        // 接收的同时对数据进行类型限制 + 指定默认值 + 限制必要性
		props:{
			name:{
				type:String,
				required:true,
			},
			age:{
				type:Number,
				default:99
			},
			sex:{
				type:String,
				required:true
			}
		}
    }
</script>


                            

 

 总结:

 props配置项:

  1. 功能:让组件接收外部传过来的数据

  2. 传递数据:<Demo name="xxx"/>

  3. 接收数据:                           

  •       1.第一种方式(只接收):props:['name']  

  •       2.第二种方式(限制数据类型):props:{name:String} 

  •       3.第三种方式(限制类型、限制必要性、指定默认值):   

props:{
    name:{
    	type:String, //类型
        required:true, //必要性
        default:'JOJO' //默认值
    }
}

   

3.4. mixin混入

 局部混入:

 src/mixin.js:

export const mixin = {
    methods: {
        showName() {
            alert(this.name)
        }
    },
    mounted() {
        console.log("你好呀~")
    }
}

src/components/School.vue 

<template>
    <div>
        <h2 @click="showName">学校姓名:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    //引入混入
    import {mixin} from '../mixin'
    
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
				address:'北京'
            }
        },
        mixins:[mixin]
    }
</script>

 src/components/Student.vue

<template>
    <div>
        <h2 @click="showName">学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>   
    </div>
</template>

<script>
    //引入混入
    import {mixin} from '../mixin'
    
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
		mixins:[mixin]
    }
</script>

 src/App.vue:

<template>
    <div>
        <School/>
        <hr/>
        <Student/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    import School from './components/School.vue'

    export default {
        name:'App',
        components: { Student,School },
    }
</script>

 全局混入:

 src/main.js:

import Vue from 'vue'
import App from './App.vue'
import {mixin} from './mixin'

Vue.config.productionTip = false
Vue.mixin(mixin)

new Vue({
    el:"#app",
    render: h => h(App)
})

 

 总结:

 mixin(混入):

  1. 功能:可以把多个组件共用的配置提取成一个混入对象

  2. 使用方式:

               1.定义混入:

const mixin = {
    data(){....},
    methods:{....}
    ....
}

                2.使用混入:

                      1.全局混入:Vue.mixin(xxx)  

                      2.局部混入:mixins:['xxx']

    3.备注:

             1.组件和混入对象含有同名选项时,这些选项将以恰当的方式进行“合并”,在发生冲突时以组件优先。

var mixin = {
	data: function () {
		return {
    		message: 'hello',
            foo: 'abc'
    	}
  	}
}

new Vue({
  	mixins: [mixin],
  	data () {
    	return {
      		message: 'goodbye',
            bar: 'def'
    	}
    },
  	created () {
    	console.log(this.$data)
    	// => { message: "goodbye", foo: "abc", bar: "def" }
  	}
})

         2.同名生命周期钩子将合并为一个数组,因此都将被调用。另外,混入对象的钩子将在组件自身钩子之前调用。

var mixin = {
  	created () {
    	console.log('混入对象的钩子被调用')
  	}
}

new Vue({
  	mixins: [mixin],
  	created () {
    	console.log('组件钩子被调用')
  	}
})

// => "混入对象的钩子被调用"
// => "组件钩子被调用"

 3.5. plugin插件

 src/plugin.js:

export default {
	install(Vue,x,y,z){
		console.log(x,y,z)
		//全局过滤器
		Vue.filter('mySlice',function(value){
			return value.slice(0,4)
		})

		//定义混入
		Vue.mixin({
			data() {
				return {
					x:100,
					y:200
				}
			},
		})

		//给Vue原型上添加一个方法(vm和vc就都能用了)
		Vue.prototype.hello = ()=>{alert('你好啊')}
	}
}

 src/main.js

import Vue from 'vue'
import App from './App.vue'
import plugin from './plugin'

Vue.config.productionTip = false
Vue.use(plugin,1,2,3)

new Vue({
    el:"#app",
    render: h => h(App)
})

 src/components/School.vue:

<template>
    <div>
        <h2>学校姓名:{{name | mySlice}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷atguigu',
				address:'北京'
            }
        }
    }
</script>

src/components/Student.vue:

<template>
    <div>
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2> 
        <button @click="test">点我测试hello方法</button>  
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
        methods:{
            test() {
                this.hello()
            }
        }
    }
</script>

总结:

插件:

  1. 功能:用于增强Vue

  2. 本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据

  3. 定义插件:

plugin.install = function (Vue, options) {
        // 1. 添加全局过滤器
        Vue.filter(....)
    
        // 2. 添加全局指令
        Vue.directive(....)
    
        // 3. 配置全局混入
        Vue.mixin(....)
    
        // 4. 添加实例方法
        Vue.prototype.$myMethod = function () {...}
        Vue.prototype.$myProperty = xxxx
    }

  4.使用插件:Vue.use(plugin)

 3.6. scoped样式

 src/components/School.vue:

<template>
    <div class="demo">
        <h2>学校姓名:{{name}}</h2>
        <h2>学校地址:{{address}}</h2>   
    </div>
</template>

<script>
    export default {
        name:'School',
        data() {
            return {
                name:'尚硅谷',
				address:'北京'
            }
        }
    }
</script>

<style scoped>
    .demo{
        background-color: blueviolet;
    }
</style>

 src/components/Student.vue

<template>
    <div class="demo">
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2> 
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        }
    }
</script>

<style scoped>
    .demo{
        background-color: chartreuse;
    }
</style>

 src/App.vue:

<template>
    <div>
        <School/>
        <Student/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    import School from './components/School.vue'

    export default {
        name:'App',
        components: { Student,School },
    }
</script>

 

总结: 

  scoped样式:

  1. 作用:让样式在局部生效,防止冲突
  2. 写法:<style scoped>

 

 3.7. Todo-List案例

 src/components/MyHeader.vue:

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                title:''
            }
        },
        methods:{
            add(){
                if(!this.title.trim()) return
                const todoObj = {id:nanoid(),title:this.title,done:false}
                this.addTodo(todoObj)
                this.title = ''
            }
        },
        props:['addTodo']
    }
</script>

<style scoped>
    .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>

 src/components/MyItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    export default {
        name:'MyItem',
        props:['todo','checkTodo','deleteTodo'],
        methods:{
            handleCheck(id){
                this.checkTodo(id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.deleteTodo(id)
                }
            }
        }
    }
</script>

<style scoped>
    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: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

src/components/MyList.vue

<template>
    <ul class="todo-main">
        <MyItem 
            v-for="todo in todos" 
            :key="todo.id" 
            :todo="todo" 
            :checkTodo="checkTodo"
            :deleteTodo="deleteTodo"
        />
    </ul>
</template>

<script>
    import MyItem from './MyItem.vue'

    export default {
        name:'MyList',
        components:{MyItem},
        props:['todos','checkTodo','deleteTodo']
    }
</script>

<style scoped>
    .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>

 src/components/MyFooter.vue:

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos','checkAllTodo','clearAllTodo'],
        computed:{
            doneTotal(){
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },
            total(){
                return this.todos.length
            },
            isAll:{
                get(){
                    return this.total === this.doneTotal && this.total > 0
                },
                set(value){
                    this.checkAllTodo(value)
                }
            }
        },
        methods:{
            clearAll(){
                this.clearAllTodo()
            }
        }
    }
</script>

<style scoped>
    .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>

 src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
            </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:'抽烟',done:false},
                    {id:'002',title:'喝酒',done:false},
                    {id:'003',title:'烫头',done:false},
                ]
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        }
    }
</script>

<style>
    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>

 效果:

总结:

  • 组件化编码流程:
  1. 拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突
  2. 实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用: 

           1.一个组件在用:放在组件自身即可

           2.  一些组件在用:放在他们共同的父组件上(状态提升) 

           3.实现交互:从绑定事件开始

  •  props适用于:
  1. 父组件 ==> 子组件 通信
  2. 子组件 ==> 父组件 通信(要求父组件先给子组件一个函数)
  • 使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的

  • props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做

 3.8. WebStorage

<!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>localStorage</title>
</head>
<body>
    <h2>localStorage</h2>
    <button onclick="saveDate()">点我保存数据</button><br/>
    <button onclick="readDate()">点我读数据</button><br/>
    <button onclick="deleteDate()">点我删除数据</button><br/>
    <button onclick="deleteAllDate()">点我清空数据</button><br/>

    <script>
        let person = {name:"JOJO",age:20}

        function saveDate(){
            localStorage.setItem('msg','localStorage')
            localStorage.setItem('person',JSON.stringify(person))
        }
        function readDate(){
            console.log(localStorage.getItem('msg'))
            const person = localStorage.getItem('person')
            console.log(JSON.parse(person))
        }
        function deleteDate(){
            localStorage.removeItem('msg')
            localStorage.removeItem('person')
        }
        function deleteAllDate(){
            localStorage.clear()
        }
    </script>
</body>
</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>sessionStorage</title>
</head>
<body>
    <h2>sessionStorage</h2>
    <button onclick="saveDate()">点我保存数据</button><br/>
    <button onclick="readDate()">点我读数据</button><br/>
    <button onclick="deleteDate()">点我删除数据</button><br/>
    <button onclick="deleteAllDate()">点我清空数据</button><br/>

    <script>
        let person = {name:"JOJO",age:20}

        function saveDate(){
            sessionStorage.setItem('msg','sessionStorage')
            sessionStorage.setItem('person',JSON.stringify(person))
        }
        function readDate(){
            console.log(sessionStorage.getItem('msg'))
            const person = sessionStorage.getItem('person')
            console.log(JSON.parse(person))
        }
        function deleteDate(){
            sessionStorage.removeItem('msg')
            sessionStorage.removeItem('person')
        }
        function deleteAllDate(){
            sessionStorage.clear()
        }
    </script>
</body>
</html>

总结: 

  1. 存储内容大小一般支持5MB左右(不同浏览器可能还不一样)

  2. 浏览器端通过Window.sessionStorageWindow.localStorage属性来实现本地存储机制

  3. 相关API:

        1.xxxStorage.setItem('key', 'value'):该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值
        2. xxxStorage.getItem('key'):该方法接受一个键名作为参数,返回键名对应的值
        3. xxxStorage.removeItem('key'):该方法接受一个键名作为参数,并把该键名从存储中删除
        4.xxxStorage.clear():该方法会清空存储中的所有数据

4.备注: 

        1.SessionStorage存储的内容会随着浏览器窗口关闭而消失
        2.LocalStorage存储的内容,需要手动清除才会消失
        3.xxxStorage.getItem(xxx)如果 xxx 对应的 value 获取不到,那么getItem()的返回值是null
        4.JSON.parse(null)的结果依然是null

 使用本地存储优化Todo-List:

 src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader :addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" :checkAllTodo="checkAllTodo" :clearAllTodo="clearAllTodo"/>
            </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 {
                //若localStorage中存有'todos'则从localStorage中取出,否则初始为空数组
                todos:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                //由于todos是对象数组,所以必须开启深度监视才能发现数组中对象的变化
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
    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>

 3.9. 自定义事件

 3.9.1. 绑定

 src/App.vue:

<template>
    <div class="app">
        <!-- 通过父组件给子组件传递函数类型的props实现子给父传递数据 -->
        <School :getSchoolName="getSchoolName"/>

        <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第一种写法,使用@或v-on) -->
        <!-- <Student @jojo="getStudentName"/> -->

        <!-- 通过父组件给子组件绑定一个自定义事件实现子给父传递数据(第二种写法,使用ref) -->
		<Student ref="student"/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'
    import School from './components/School.vue'

    export default {
        name:'App',
        components: { Student,School },
        methods:{
            getSchoolName(name){
                console.log("已收到学校的名称:"+name)
            },
            getStudentName(name){
                console.log("已收到学生的姓名:"+name)      
            }
        },
        mounted(){
            this.$refs.student.$on('jojo',this.getStudentName)
        }
    }
</script>


<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

 src/components/Student.vue:

<template>
    <div class="student">
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <button @click="sendStudentName">点我传递学生姓名</button> 
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
        methods:{
            sendStudentName(){
                this.$emit('jojo',this.name)
            }
        }
    }
</script>

<style scoped>
    .student{
        background-color: chartreuse;
        padding: 5px;
		margin-top: 30px;
    }
</style>
————————————————

 

3.9.2. 解绑 

 src/App.vue:

<template>
    <div class="app">
        <Student @jojo="getStudentName"/>
    </div>
</template>

<script>
    import Student from './components/Student.vue'

    export default {
        name:'App',
        components: { Student },
        methods:{
            getStudentName(name){
                console.log("已收到学生的姓名:"+name)      
            }
        }
    }
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

 src/components/Student.vue:

<template>
    <div class="student">
        <h2>学生姓名:{{name}}</h2>
        <h2>学生性别:{{sex}}</h2>
        <button @click="sendStudentName">点我传递学生姓名</button> 
        <button @click="unbind">解绑自定义事件</button> 
    </div>
</template>

<script>
    export default {
        name:'Student',
        data() {
            return {
                name:'JOJO',
				sex:'男'
            }
        },
        methods:{
            sendStudentName(){
                this.$emit('jojo',this.name)
            },
            unbind(){
                // 解绑一个自定义事件
                // this.$off('jojo')
                // 解绑多个自定义事件
                // this.$off(['jojo'])
                // 解绑所有自定义事件
                this.$off()
            }
        }
    }
</script>

<style scoped>
    .student{
        background-color: chartreuse;
        padding: 5px;
		margin-top: 30px;
    }
</style>

总结: 

 组件的自定义事件:

  1. 一种组件间通信的方式,适用于:==子组件 > 父组件

  2. 使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)

  3. 绑定自定义事件:

              1.第一种方式,在父组件中:<Demo @atguigu="test"/> 或 <Demo von:atguigu="test"/>

              2.第二种方式,在父组件中:

<Demo ref="demo"/>
...
mounted(){
    this.$refs.demo.$on('atguigu',data)
}

             3.若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法

     4.触发自定义事件:this.$emit('atguigu',数据)

     5.解绑自定义事件:this.$off('atguigu')

     6.组件上也可以绑定原生DOM事件,需要使用native修饰符

     7.注意:通过this.$refs.xxx.$on('atguigu',回调)绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!

使用自定义事件优化Todo-List: 

 src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </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:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        }
    }
</script>

<style>
    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>

 src/components/MyHeader.vue:

<template>
    <div class="todo-header">
        <input type="text" placeholder="请输入你的任务名称,按回车键确认" @keydown.enter="add" v-model="title"/>
    </div>
</template>

<script>
    import {nanoid} from 'nanoid'
    export default {
        name:'MyHeader',
        data() {
            return {
                title:''
            }
        },
        methods:{
            add(){
                if(!this.title.trim()) return
                const todoObj = {id:nanoid(),title:this.title,done:false}
                this.$emit('addTodo',todoObj)
                this.title = ''
            }
        }
    }
</script>

<style scoped>
    /*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>

 src/components/MyFooter:

<template>
    <div class="todo-footer" v-show="total">
        <label>
            <input type="checkbox" v-model="isAll"/>
        </label>
        <span>
            <span>已完成{{doneTotal}}</span> / 全部{{total}}
        </span>
        <button class="btn btn-danger" @click="clearAll">清除已完成任务</button>
    </div>
</template>

<script>
    export default {
        name:'MyFooter',
        props:['todos'],
        computed:{
            doneTotal(){
                return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)
            },
            total(){
                return this.todos.length
            },
            isAll:{
                get(){
                    return this.total === this.doneTotal && this.total > 0
                },
                set(value){
                    this.$emit('checkAllTodo',value)
                }
            }
        },
        methods:{
            clearAll(){
                this.$emit('clearAllTodo')
            }
        }
    }
</script>

<style scoped>
    .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>

3.10. 全局事件总线 

 

 src/main.js:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
	el:'#app',
	render: h => h(App),
	beforeCreate() {
		Vue.prototype.$bus = this //安装全局事件总线
	}
})

 src/App.vue:

<template>
	<div class="app">
		<School/>
		<Student/>
	</div>
</template>

<script>
	import Student from './components/Student'
	import School from './components/School'

	export default {
		name:'App',
		components:{School,Student}
	}
</script>

<style scoped>
	.app{
		background-color: gray;
		padding: 5px;
	}
</style>

 src/components/School.vue:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		methods:{
			demo(data) {
				console.log('我是School组件,收到了数据:',data)
			}
		},
		mounted() {
			this.$bus.$on('demo',this.demo)
		},
		beforeDestroy() {
			this.$bus.$off('demo')
		},
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

 src/components/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:'男'
			}
		},
		methods: {
			sendStudentName(){
				this.$bus.$emit('demo',this.name)
			}
		}
	}
</script>

<style scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

 总结:

 全局事件总线(GlobalEventBus):

  1. 一种组件间通信的方式,适用于任意组件间通信

  2. 安装全局事件总线:

new Vue({
   	...
   	beforeCreate() {
   		Vue.prototype.$bus = this //安装全局事件总线,$bus就是当前应用的vm
   	},
    ...
}) 

    3.使用事件总线:

              1.接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件   自身

export default {
    methods(){
        demo(data){...}
    }
    ...
    mounted() {
        this.$bus.$on('xxx',this.demo)
    }
}

               2.提供数据:this.$bus.$emit('xxx',data)

     4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件

 使用自定义事件优化Todo-List:

 src/mian.js:

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
    el:"#app",
    render: h => h(App),
    beforeCreate() {
        Vue.prototype.$bus = this
    }
})

 src/components/App.vue

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </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:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },
        mounted(){
            this.$bus.$on('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        beforeDestroy(){
            this.$bus.$off(['checkTodo','deleteTodo'])
        }
    }
</script>

<style>
    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>

 src/components/MyItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    export default {
        name:'MyItem',
        props:['todo'],
        methods:{
            handleCheck(id){
                this.$bus.$emit('checkTodo',id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.$bus.$emit('deleteTodo',id)
                }
            }
        }
    }
</script>

<style scoped>
    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: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

 3.11. 消息的订阅与发布

 src/components/School.vue:

<template>
	<div class="school">
		<h2>学校名称:{{name}}</h2>
		<h2>学校地址:{{address}}</h2>
	</div>
</template>

<script>
	import pubsub from 'pubsub-js'

	export default {
		name:'School',
		data() {
			return {
				name:'尚硅谷',
				address:'北京',
			}
		},
		methods:{
			demo(msgName,data) {
				console.log('我是School组件,收到了数据:',data)
			}
		},
		mounted() {
			this.pubId = pubsub.subscribe('demo',this.demo) //订阅消息
		},
		beforeDestroy() {
			pubsub.unsubscribe(this.pubId) //取消订阅
		}
	}
</script>

<style scoped>
	.school{
		background-color: skyblue;
		padding: 5px;
	}
</style>

 src/components/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'

	export default {
		name:'Student',
		data() {
			return {
				name:'JOJO',
				sex:'男',
			}
		},
		methods: {
			sendStudentName(){
				pubsub.publish('demo',this.name) //发布消息
			}
		}
	}
</script>

<style scoped>
	.student{
		background-color: pink;
		padding: 5px;
		margin-top: 30px;
	}
</style>

总结: 

 消息订阅与发布(pubsub):

  1. 消息订阅与发布是一种组件间通信的方式,适用于任意组件间通信

  2. 使用步骤:

               1.安装pubsub:npm i pubsub-js

               2.引入:import pubsub from 'pubsub-js'

               3.接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身

export default {
    methods(){
        demo(data){...}
    }
    ...
    mounted() {
		this.pid = pubsub.subscribe('xxx',this.demo)
    }
}

              4.提供数据:pubsub.publish('xxx',data)

              5.最好在beforeDestroy钩子中,使用pubsub.unsubscribe(pid)取消订阅

使用消息的订阅与发布优化Todo-List: 

 src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    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:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(_,id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },
        mounted(){
            this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
        },
        beforeDestroy(){
            pubsub.unsubscribe(this.pubId)
            this.$bus.$off('deleteTodo')
        }
    }
</script>

<style>
    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>

 src/components/myItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span>{{todo.title}}</span>
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
    </li>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyItem',
        props:['todo'],
        methods:{
            handleCheck(id){                    
                pubsub.publish('checkTodo',id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.$bus.$emit('deleteTodo',id)
                }
            }
        }
    }
</script>

<style scoped>
    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: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

 3.12. $nextTick

 使用$nextTick优化Todo-List:

 src/App.vue:

<template>
    <div id="root">
        <div class="todo-container">
            <div class="todo-wrap">
            <MyHeader @addTodo="addTodo"/>
            <MyList :todos="todos"/>
            <MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/>
            </div>
        </div>
    </div>
</template>

<script>
    import pubsub from 'pubsub-js'
    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:JSON.parse(localStorage.getItem('todos')) || []
            }
        },
        methods:{
            //添加一个todo
            addTodo(todoObj){
                this.todos.unshift(todoObj)
            },
            //勾选or取消勾选一个todo
            checkTodo(_,id){
                this.todos.forEach((todo)=>{
                    if(todo.id === id) todo.done = !todo.done
                })
            },
            //删除一个todo
            deleteTodo(id){
                this.todos = this.todos.filter(todo => todo.id !== id)
            },
            //更新一个todo
			updateTodo(id,title){
				this.todos.forEach((todo)=>{
					if(todo.id === id) todo.title = title
				})
			},
            //全选or取消勾选
            checkAllTodo(done){
                this.todos.forEach(todo => todo.done = done)
            },
            //删除已完成的todo
            clearAllTodo(){
                this.todos = this.todos.filter(todo => !todo.done)
            }
        },
        watch:{
            todos:{
                deep:true,
                handler(value){
                    localStorage.setItem('todos',JSON.stringify(value))
                }
            }
        },
        mounted(){
            this.pubId = pubsub.subscribe('checkTodo',this.checkTodo)
            this.$bus.$on('deleteTodo',this.deleteTodo)
            this.$bus.$on('updateTodo',this.updateTodo)
        },
        beforeDestroy(){
            pubsub.unsubscribe(this.pubId)
            this.$bus.$off('deleteTodo')
            this.$bus.$off('updateTodo')
        }
    }
</script>

<style>
    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: #e04e49;
        border: 1px solid #bd362f;
    }

    .btn-danger:hover {
        color: #fff;
        background-color: #bd362f;
    }

    .btn-info {
        color: #fff;
        background-color: rgb(50, 129, 233);
        border: 1px solid rgb(1, 47, 212);
        margin-right: 5px;
    }

    .btn-info:hover {
        color: #fff;
        background-color: rgb(1, 47, 212);
    }

    .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>

 src/components/MyItem.vue:

<template>
    <li>
        <label>
            <input type="checkbox" :checked="todo.done" @click="handleCheck(todo.id)"/>
            <span v-show="!todo.isEdit">{{todo.title}}</span>
            <input type="text" v-show="todo.isEdit" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle">
        </label>
        <button class="btn btn-danger" @click="handleDelete(todo.id,todo.title)">删除</button>
        <button class="btn btn-info" v-show="!todo.isEdit" @click="handleEdit(todo)">编辑</button>
    </li>
</template>

<script>
    import pubsub from 'pubsub-js'
    export default {
        name:'MyItem',
        props:['todo'],
        methods:{
            handleCheck(id){                    
                pubsub.publish('checkTodo',id)
            },
            handleDelete(id,title){
                if(confirm("确定删除任务:"+title+"吗?")){
                    this.$bus.$emit('deleteTodo',id)
                }
            },
            handleEdit(todo){
                // 如果todo自身有isEdit属性就将isEdit改成true
				if(Object.prototype.hasOwnProperty.call(todo,'isEdit')){
					todo.isEdit = true
				}else{
                    // 如果没有就向todo中添加一个响应式的isEdit属性并设为true
					this.$set(todo,'isEdit',true)
				}
                // 当Vue重新编译模板之后执行$nextTick()中的回调函数
                this.$nextTick(function(){
                    // 使input框获取焦点
                    this.$refs.inputTitle.focus()
                })
			},
            // 当input框失去焦点时更新
            handleBlur(todo,event){
                todo.isEdit = false
				if(!event.target.value.trim()) return alert('输入不能为空!')
				this.$bus.$emit('updateTodo',todo.id,event.target.value)
            }
        }
    }
</script>

<style scoped>
    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: #eee;
    }

    li:hover button{
        display: block;
    }
</style>

 Todo-List最终效果:

总结:

 $nextTick:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/445964.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【性能测试】性能测试各知识第1篇:性能测试大纲【附代码文档】

性能测试完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;性能测试大纲。。。。。。。。。。。。。。 全套笔记资料代码移步&#xff1a; 前往gitee仓库查看 感兴趣的小伙伴可以自取哦&#xff0c;欢迎大家点赞转发~ 性能测试大纲 |序号|阶段|概述| |--…

【C++】三大特性之继承

1 继承的概念及定义 1.1 继承的概念 继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展、增加功能&#xff0c;这样产生新的类&#xff0c;称派生类&#xff08;或子类&#xff09;。而被继承的…

【NR 定位】3GPP NR Positioning 5G定位标准解读(九)-增强的小区ID定位

前言 3GPP NR Positioning 5G定位标准&#xff1a;3GPP TS 38.305 V18 3GPP 标准网址&#xff1a;Directory Listing /ftp/ 【NR 定位】3GPP NR Positioning 5G定位标准解读&#xff08;一&#xff09;-CSDN博客 【NR 定位】3GPP NR Positioning 5G定位标准解读&#xff08;…

云服务器python版本冲突解决(awd平台搭建)

文章目录 yum和apt-getdockerpython环境问题 大家在使用python时&#xff0c;难免会使用他人的代码&#xff0c;自己是python3&#xff0c;而别人的是python2.我们直接运行会报错(比如print函数括号的问题)。但是去修改代码又很麻烦。这里给大家推荐conda。我以我搭建awd平台为…

toB开发范式

前言 B端开发&#xff0c;也被称为后台开发或者企业级开发&#xff0c;是针对企业或者组织的业务需求进行的软件开发。在 B 端开发中&#xff0c;我们通常关注的是系统的功能性、稳定性、可扩展性以及安全性&#xff0c;从面向过程编程 -> 面向对象编程 组合式编程 以下是…

【谈一谈】并发_Synchronized

Synchronized 又到周末了,最近的话(有点子小日子不好过,哈哈哈!~)但是,我还是报之以歌哈哈哈 本次写关于并发_Synchronized的优化以及底层实现原理 说说心里话~其实是非常的累,原因应该怎么说呢?我发现自己在如今的这家公司,我处于一种活多钱少以及关键现在给的或自己不想干,因…

新版Android Studio火烈鸟 在新建项目工程时 无法选java的语言模板解决方法

前言 最近下载最新版androidstudio时 发现不能勾选java语言模板了 如果快速点击下一步 新建项目 默认是kotlin语言模板 这可能和google主推kt语言有关 勾选1 如图所示 如果勾选 No Activity 这个模板 是可以选java语言模板的 但是里面没有默认的Activity 勾选2 和以前的用法…

关于安卓ZXing条码识别(一)引入源码

背景 从0-1引入安卓zxing&#xff0c;实现条码识别 环境 win10 as4 jdk8 引入 首先&#xff0c;官方网站&#xff0c;就是源码。链接 选择你要引入的分支&#xff0c;这里博主选择的是最近更新的分支&#xff0c;如下图&#xff1a; 上图中&#xff0c;1和2都需要引入&am…

车载诊断协议DoIP系列 —— 诊断报文和诊断报文应答传输层安全(TLS)

车载诊断协议DoIP系列 —— 诊断报文和诊断报文应答&传输层安全(TLS) 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师(Wechat:gongkenan2013)。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 本就是小人物,输了就是输了,不要在意别人怎…

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书

2021年江苏省职业院校技能大赛高职组 “信息安全管理与评估”赛项任务书 一、赛项时间&#xff1a;二、赛项信息三、竞赛内容&#xff1a;第一阶段任务书&#xff08;300分&#xff09;任务1&#xff1a;网络平台搭建&#xff08;60分&#xff09;任务2&#xff1a;网络安全设备…

23 经典卷积神经网络 LeNet【李沐动手学深度学习v2课程笔记】 (备注:提到如何把代码从CPU改到在GPU上使用)

目录 1. LeNet 2. 实现代码 3. 模型训练 4. 小结 本节将介绍LeNet&#xff0c;它是最早发布的卷积神经网络之一&#xff0c;因其在计算机视觉任务中的高效性能而受到广泛关注。 这个模型是由AT&T贝尔实验室的研究员Yann LeCun在1989年提出的&#xff08;并以其命名&…

第十九天-分布式爬虫scrapy-redis

1.scrapy-redis介绍 1.scrapy框架程 2.分布式爬虫将多个主机组合起乱来&#xff0c;完成一个爬虫任务&#xff0c;快速高效的提高爬虫效率 3.scrapy-redis框架&#xff0c; 优点&#xff1a;1.加快项目的运行速度2.单节点不稳定不影响整个系统的稳定性 3.断点续爬 缺点&…

基于C++和Qt Creator实现的仿制网易云音乐播放器

目录 总体介绍开发环境技术介绍项目目录项目介绍特殊说明Gitee地址 总体介绍 仿照网易云播放器界面实现&#xff0c;目的在于锻炼C编程能力&#xff0c;熟练掌握Qt Creator各种组件的使用及样式设置、界面布局、QtPlugin技术、QXml读写XML文件方法、Qss文件的编写及使用等。 …

【笔记】全国大学生GIS应用技能大赛练习总结

该总结笔记为小组成员在练习完毕了历届题目后自我总结的结果&#xff0c;如有不足之处可以在评论区提出&#xff0c;排版较乱往谅解 绘制带空洞的面要素&#xff1a; 法一&#xff1a; 1、矢量化整个区域。2、矢量化空洞区域。3、将矢量化空洞区域进行合并&#xff08;编辑器…

【MySql学习之路】window环境下MySql安装和安装过程中出现的问题

environment:windows software:mysql 本文主要分享mysql关系型数据库在干净的环境下,第一次安装以及在安装过程中出现的常见问题和解决方法。目前官网给出的安装包有两种格式,一个是msi格式,一个是zip格式的。很多人下了zip格式的解压发现没有setup.exe,面对一堆文件无从…

HTTPS如何保证数据传输的安全性 以及CA签发证书验签

暴力输出&#xff1a; 越看会越深入&#xff0c;睡前难以想通&#xff0c;后深入研究。得之。 有问题 请留言。 ----------追求内心的富足与平和。日行一善。 亓苏姑娘

面试经典150题【61-70】

文章目录 面试经典150题【61-70】61.旋转链表86.分隔链表104. 二叉树的最大深度100.相同的树226.翻转二叉树101.对称二叉树105.从前序与中序遍历序列构造二叉树106.从后序和中序遍历序列构造二叉树117.填充每个节点的下一个右侧节点指针II114.二叉树展开为链表 面试经典150题【…

【软考】图的遍历

目录 1. 概念2. 深度优先搜索2.1 说明2.2 步骤 3. 深度优先搜索例子3.1 无向图3.2 代码示例3.3 结果示例3.4 过程 4. 广度优先搜索4.1 说明4.2 步骤 5. 广度优先搜索例子5.1 无向图5.2 代码示例5.3 结果示例5.4 过程5.5 例题5.5.1 题目1 1. 概念 1.图的遍历是指从某个顶点出发…

Day32-计算机基础2

Day32-计算机基础2 1. 什么是网络拓扑(Network Topology)&#xff1f;2. 网络拓扑3种经典模型2.1 网络拓扑结构-总线型2.2 网络拓扑结构-环形2.3 星型&#xff1a;2.4 网络拓扑结构总结 3.OSI网络模型概念*****3.1 OSI的概念&#xff1a;open system interconnect 开放系统互连…

第五十三天| 1143.最长公共子序列、1035.不相交的线、53. 最大子序和

Leetcode 1143.最长公共子序列 题目链接&#xff1a;1143 最长公共子序列 题干&#xff1a;给定两个字符串 text1 和 text2&#xff0c;返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 &#xff0c;返回 0 。 一个字符串的 子序列 是指这样一个新的字符串&…