前端Vue框架系列—— 学习笔记总结Day02

编程入门 行业动态 更新时间:2024-10-11 11:19:17

前端Vue框架系列—— <a href=https://www.elefans.com/category/jswz/34/1770117.html style=学习笔记总结Day02"/>

前端Vue框架系列—— 学习笔记总结Day02

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

文章目录

  • 第一章 Vue组件化编程
    • 1.1 模块与组件、模块化与组件化
      • 1.1.1 模块
      • 1.1.2 组件
      • 1.1.3 模块化
      • 1.1.4 组件化
    • 1.2 非单文件组件
      • 1.2.1 基本使用
      • 1.2.2 几个注意点
      • 1.2.3 组件的嵌套
      • 1.2.4 VueComponent
      • 1.2.5 一个重要的内置关系
    • 1.3 单文件组件
  • 第二章 使用Vue脚手架
    • 2.1 初始化脚手架
      • 2.1.1 说明
      • 2.1.2 具体步骤
      • 2.1.3 分析脚手架结构
      • 2.1.4 render函数
      • 2.1.5 修改默认配置
    • 2.2 ref属性
    • 2.3 props配置
    • 2.4 mixin混入(合)
    • 2.5 plugin插件
    • 2.6 scoped样式
    • 2.7 TodoList案例
    • 2.8 浏览器本地存储
    • 2.9 TodoList_本地存储
    • 2.10 组件自定义事件
      • 2.10.1 绑定
      • 2.10.2 解绑
    • 2.11 TodoList_自定义事件
    • 2.12 全局事件总线(GlobalEventBus)
    • 2.13 TodoList_事件总线
    • 2.14 消息订阅与发布(pubsub)
    • 2.15 使用消息的订阅与发布优化Todo-List
    • 2.16 nextTick(回调函数)
    • 2.17 过度与动画

第一章 Vue组件化编程

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 组件化

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


1.2 非单文件组件

1.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"><hello></hello><hr><h1>{{msg}}</h1><hr><!-- 第三步:编写组件标签 --><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 class="demo"><h2>学校名称:{{schoolName}}</h2><h2>学校地址:{{address}}</h2><button @click="showName">点我提示学校名</button>	</div>`,// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。data(){return {schoolName:'光明学校',address:'广州'}},methods: {showName(){alert(this.schoolName)}},})//第一步:创建student组件const student = Vue.extend({template:`<div><h2>学生姓名:{{studentName}}</h2><h2>学生年龄:{{age}}</h2></div>`,data(){return {studentName:'张三',age:18}}})//第一步:创建hello组件const hello = Vue.extend({template:`<div>	<h2>你好啊!{{name}}</h2></div>`,data(){return {name:'Tom'}}})//第二步:全局注册组件Vueponent('hello',hello)//创建vmnew Vue({el:'#root',data:{msg:'你好啊!'},//第二步:注册组件(局部注册)components:{school,student}})new Vue({el:'#root2',})</script>
</html>

效果:


总结:

  • Vue中使用组件的三大步骤:

    1. 定义组件(创建组件)
    2. 注册组件
    3. 使用组件(写组件标签)
  • 如何定义一个组件:?
    使用 Vue.extend(options) 创建,其中 optionsnew Vue(options) 时传入的那个 options 几乎一样,但也有点区别:

    1. el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
    2. data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。

    备注:使用template可以配置组件结构。

  • 如何注册组件?

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


1.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 s = Vue.extend({name:'zhangsan',template:`<div><h2>学校名称:{{name}}</h2>	<h2>学校地址:{{address}}</h2>	</div>`,data(){return {name:'光明学校',address:'广州'}}})new Vue({el:'#root',data:{msg:'欢迎学习Vue!'},components:{school:s}})</script>
</html>

效果:


总结:

几个注意点:

  1. 关于组件名

    • 一个单词组成:

      • 第一种写法(首字母小写):school
      • 第二种写法(首字母大写):School
    • 多个单词组成:

      • 第一种写法(kebab-case命名):my-school
      • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
    • 备注:

      • 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
      • 可以使用name配置项指定组件在开发者工具中呈现的名字。
  2. 关于组件标签

    • 第一种写法:<school></school>
    • 第二种写法:<school/>
    • 备注:不用使用脚手架时,<school/> 会导致后续组件不能渲染。
  3. 一个简写方式

    • const school = Vue.extend(options) 可简写为:const school = options

1.2.3 组件的嵌套

代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>组件的嵌套</title><!-- 引入Vue --><script type="text/javascript" src="../js/vue.js"></script></head><body><!-- 准备好一个容器--><div id="root"></div></body><script type="text/javascript">Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。//定义student组件const student = Vue.extend({name:'student',template:`<div><h2>学生姓名:{{name}}</h2>	<h2>学生年龄:{{age}}</h2>	</div>`,data(){return {name:'张三',age:18}}})//定义school组件const school = Vue.extend({name:'school',template:`<div><h2>学校名称:{{name}}</h2>	<h2>学校地址:{{address}}</h2>	<student></student></div>`,data(){return {name:'光明学校',address:'广州'}},//注册组件(局部)components:{student}})//定义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}})//创建vmnew Vue({template:'<app></app>',el:'#root',//注册组件(局部)components:{app}})</script>
</html>

效果:


1.2.4 VueComponent

代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>VueComponent</title><script type="text/javascript" src="../js/vue.js"></script></head><body><!-- 准备好一个容器--><div id="root"><school></school><hello></hello></div></body><script type="text/javascript">Vue.config.productionTip = false//定义school组件const school = Vue.extend({name:'school',template:`<div><h2>学校名称:{{name}}</h2>	<h2>学校地址:{{address}}</h2>	<button @click="showName">点我提示学校名</button></div>`,data(){return {name:'光明学校',address:'广州'}},methods: {showName(){console.log('showName',this)}},})const test = Vue.extend({template:`<span>张三</span>`})//定义hello组件const hello = Vue.extend({template:`<div><h2>{{msg}}</h2><test></test>	</div>`,data(){return {msg:'你好啊!'}},components:{test}})// console.log('@',school)// console.log('#',hello)//创建vmconst vm = new Vue({el:'#root',components:{school,hello}})</script>
</html>

效果:


总结:

关于 VueComponent

  1. school组件本质是一个名为 VueComponent 的构造函数,且不是程序员定义的,是 Vue.extend 生成的。

  2. 我们只需要写 <school/><school></school>,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)

  3. 特别注意:每次调用 Vue.extend,返回的都是一个全新的VueComponent

  4. 关于this指向:

    1. 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
    2. new Vue(options) 配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。
  5. VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。

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

1.2.5 一个重要的内置关系

代码:

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>一个重要的内置关系</title><!-- 引入Vue --><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 在启动时生成生产提示。Vue.prototype.x = 99//定义school组件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)}},})//创建一个vmconst vm = new Vue({el:'#root',data:{msg:'你好'},components:{school}})//定义一个构造函数/* function Demo(){this.a = 1this.b = 2}//创建一个Demo的实例对象const d = new Demo()console.log(Demo.prototype) //显示原型属性console.log(d.__proto__) //隐式原型属性console.log(Demo.prototype === d.__proto__)//程序员通过显示原型属性操作原型对象,追加一个x属性,值为99Demo.prototype.x = 99console.log('@',d) */</script>
</html>

效果:


总结:

  1. 一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
  2. 为什么要有这个关系:让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

1.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:'张三',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>
    

第二章 使用Vue脚手架

2.1 初始化脚手架

2.1.1 说明

  1. Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。
  2. 最新的版本是 4.x。
  3. 文档: /

2.1.2 具体步骤

  1. 如果下载缓慢请配置 npm 淘宝镜像:npm config set registry
  2. 全局安装@vue/cli:npm install -g @vue/cli
  3. 切换到你要创建项目的目录,然后使用命令创建项目:vue create xxxx
  4. 选择使用vue的版本
  5. 启动项目:npm run serve
  6. 暂停项目:Ctrl+C

备注:
Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:vue inspect > output.js


2.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: 包版本控制文件

2.1.4 render函数

import Vue from 'vue'
import App from './App.vue'Vue.config.productionTip = falsenew Vue({el:'#app',// 简写形式render: h => h(App),// 完整形式// render(createElement){//     return createElement(App)// }
})

总结:

关于不同版本的函数:

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

    1. vue.js 是完整版的 Vue,包含:核心功能+模板解析器
    2. vue.runtime.xxx.js 是运行版的 Vue,只包含核心功能,没有模板解析器
  2. 因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容


2.1.5 修改默认配置

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

2.2 ref属性

代码:

  • School.vue

    <template><div class="school"><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
    </template><script>export default {name:'School',data() {return {name:'光明学校',address:'广州'}},}
    </script><style>.school{background-color: gray;}
    </style>
    
  • App.vue

    <template><div><h1 v-text="msg" ref="title" id="msg"></h1><button ref="btn" @click="showDOM">点我输出上方的DOM元素</button><School ref="sch"/></div>
    </template><script>//引入School组件import School from './components/School'export default {name:'App',components:{School},data() {return {msg:'欢迎学习Vue!'}},methods: {showDOM(){console.log(document.getElementById("msg")) //传统中用于获取指定id的DOM元素console.log(this.$refs.title) //真实DOM元素console.log(this.$refs.btn) //真实DOM元素console.log(this.$refs.sch) //School组件的实例对象(vc)}},}
    </script>
    

效果:


总结:

ref属性:

  1. 被用来给元素或子组件注册引用信息(id的替代者:document.getElementById()
  2. 应用在 html标签 上获取的是 真实DOM元素 ,应用在 组件 标签上是 组件实例对象(vc)
  3. 使用方式:
    1. 打标识:<h1 ref="xxx">.....</h1><School ref="xxx"></School>
    2. 获取:this.$refs.xxx

2.3 props配置

代码:

  • Student.vue

    <template><div><h1>{{msg}}</h1><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><h2>学生年龄:{{myAge+1}}</h2><button @click="updateAge">尝试修改收到的年龄</button></div>
    </template><script>export default {name:'Student',data() {console.log(this)return {msg:'我在学Vue!',myAge:this.age}},methods: {updateAge(){this.myAge++}},//简单声明接收// props:['name','age','sex'] //接收的同时对数据进行类型限制/* props:{name:String,age:Number,sex:String} *///接收的同时对数据:进行类型限制+默认值的指定+必要性的限制props:{name:{type:String, //name的类型是字符串required:true, //name是必要的},age:{type:Number,default:99 //默认值},sex:{type:String,required:true}}}
    </script>
    
  • App.vue

    <template><div><Student name="李四" sex="女" :age="18"/></div>
    </template><script>import Student from './components/Student'export default {name:'App',components:{Student}}
    </script>
    

效果:


总结:
props配置项:

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

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

  3. 接收数据:

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

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

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

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

    备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。


2.4 mixin混入(合)

1. 局部混入
代码:

  • School.vue

    <template><div><h2 @click="showName">学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
    </template><script>//引入一个hunheimport {hunhe,hunhe2} from '../mixin'export default {name:'School',data() {return {name:'光明学校',address:'广州',x:666}},mixins:[hunhe,hunhe2],}
    </script>
    
  • Student.vue

    <template><div><h2 @click="showName">学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2></div>
    </template><script>import {hunhe,hunhe2} from '../mixin'export default {name:'Student',data() {return {name:'张三',sex:'男'}},mixins:[hunhe,hunhe2]}
    </script>
    
  • mixin.js

    export const hunhe = {methods: {showName(){alert(this.name)}},mounted() {console.log('你好啊!')},
    }
    export const hunhe2 = {data() {return {x:100,y:200}},
    }

效果:


2. 全局混入

代码:

  • School.vue

    <template><div><h2 @click="showName">学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
    </template><script>//引入一个hunhe// import {hunhe,hunhe2} from '../mixin'export default {name:'School',data() {return {name:'光明学校',address:'广州',x:666}},// mixins:[hunhe,hunhe2],}
    </script>
    
  • Student.vue

    <template><div><h2 @click="showName">学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2></div>
    </template><script>// import {hunhe,hunhe2} from '../mixin'export default {name:'Student',data() {return {name:'张三',sex:'男'}},// mixins:[hunhe,hunhe2]}
    </script>
    
  • main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    import {hunhe,hunhe2} from './mixin'
    //关闭Vue的生产提示
    Vue.config.productionTip = falseVue.mixin(hunhe)
    Vue.mixin(hunhe2)//创建vm
    new Vue({el:'#app',render: h => h(App)
    })
    

效果:


总结:

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

  2. 使用方式:

    第一步定义混合:

    export const xxx = {data() {},methods: {}....
    }
    

    第二步使用混入:

    ​ 全局混入:Vue.mixin(xxx)
    ​ 局部混入:mixins:['xxx']

  3. mixin的优点:

    • 复用组件间的共同行为和属性
    • 避免重复代码
    • 易维护
  4. 注意事项:

    • mixin对象中的方法和数据会与组件自身产生冲突,需要避免名称重复
    • mixin会影响整个组件树,应谨慎使用全局mixin
    • 过多mixin会增加组件的复杂度

2.5 plugin插件

  • src/components/School.vue

    <template><div><h2>学校名称:{{name | mySlice}}</h2><h2>学校地址:{{address}}</h2><button @click="test">点我测试一个hello方法</button></div>
    </template><script>export default {name:'School',data() {return {name:'光明学校',address:'广州',}},methods: {test(){this.hello()}},}
    </script>
    
  • src/components/Student.vue

    <template><div><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><input type="text" v-fbind:value="name"></div>
    </template><script>export default {name:'Student',data() {return {name:'张三',sex:'男'}},}
    </script>
    
  • 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.directive('fbind',{//指令与元素成功绑定时(一上来)bind(element,binding){element.value = binding.value},//指令所在元素被插入页面时inserted(element,binding){element.focus()},//指令所在的模板被重新解析时update(element,binding){element.value = binding.value}})//定义混入Vue.mixin({data() {return {x:100,y:200}},})//给Vue原型上添加一个方法(vm和vc就都能用了)Vue.prototype.hello = ()=>{alert('你好啊')}}
    }
    
  • src/main.js

    //引入Vue
    import Vue from 'vue'
    //引入App
    import App from './App.vue'
    //引入插件
    import plugins from './plugins'
    //关闭Vue的生产提示
    Vue.config.productionTip = false//应用(使用)插件
    Vue.use(plugins,1,2,3)
    //创建vm
    new Vue({el:'#app',render: h => h(App)
    })
    

效果:


总结:

  1. 功能:用于增强Vue

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

  3. 定义插件:

    对象.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()


2.6 scoped样式

  • src/components/Student.vue

    <template><div class="demo"><h2 class="title">学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>
    </template><script>export default {name:'School',data() {return {name:'光明学校',address:'广州',}}}
    </script><style scoped>.demo{background-color: skyblue;}
    </style>
    
  • src/components/School.vue

    <template><div class="demo"><h2 class="title">学生姓名:{{name}}</h2><h2 class="sex">学生性别:{{sex}}</h2></div>
    </template><script>export default {name:'Student',data() {return {name:'张三',sex:'男'}}}
    </script><style lang="less" scoped>.demo{background-color: pink;.sex{font-size: 40px;}}
    </style>
    
  • App.vue

    <template><div><h1 class="title">你好啊</h1><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>.title{color: red;}
    </style>

效果:


总结:

在传统的CSS中,样式规则是全局的,会对整个页面中所有匹配的元素生效。这可能导致样式冲突和命名空间污染的问题。

为了解决这些问题,HTML5中引入了 scoped 样式。当在 <style>标签中添加 scoped 属性时,样式规则只会应用于当前组件或元素及其子元素,而不会泄漏到其他组件或元素中。

  1. 定义:scoped 样式是一种用于指定样式仅应用于当前组件或元素的CSS样式作用域。
  2. 作用:让样式在局部生效,防止冲突。
  3. 写法:<style scoped>

注意: scoped 样式一般不会在 App.vue 中使用。


2.7 TodoList案例

  • src/components/MyHeader.vue

    <template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/></div>
    </template><script>import {nanoid} from 'nanoid'export default {name:'MyHeader',//接收从App传递过来的addTodoprops:['addTodo'],data() {return {//收集用户输入的titletitle:''}},methods: {add(){//校验数据if(!this.title.trim()) return alert('输入不能为空')//将用户的输入包装成一个todo对象const todoObj = {id:nanoid(),title:this.title,done:false}//通知App组件去添加一个todo对象this.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/MyList.vue

    <template><ul class="todo-main"><MyItem v-for="todoObj in todos":key="todoObj.id" :todo="todoObj" :checkTodo="checkTodo":deleteTodo="deleteTodo"/></ul>
    </template><script>import MyItem from './MyItem'export default {name:'MyList',components:{MyItem},//声明接收App传递过来的数据,其中todos是自己用的,checkTodo和deleteTodo是给子组件MyItem用的props:['todos','checkTodo','deleteTodo']}
    </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>
    
  • src/components/MyItem.vue

    <template><li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,因为修改了props --><!-- <input type="checkbox" v-model="todo.done"/> --><span>{{todo.title}}</span></label><button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button></li>
    </template><script>export default {name:'MyItem',//声明接收todo、checkTodo、deleteTodoprops:['todo','checkTodo','deleteTodo'],methods: {//勾选or取消勾选handleCheck(id){//通知App组件将对应的todo对象的done值取反this.checkTodo(id)},//删除handleDelete(id){if(confirm('确定删除吗?')){//通知App组件将对应的todo对象删除this.deleteTodo(id)}}},}
    </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: #ddd;}li:hover button{display: block;}
    </style>
    
  • src/components/MyFooter.vue

    <template><div class="todo-footer" v-show="total"><label><!-- <input type="checkbox" :checked="isAll" @change="checkAll"/> --><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: {//总数total(){return this.todos.length},//已完成数doneTotal(){//此处使用reduce方法做条件统计/* const x = this.todos.reduce((pre,current)=>{console.log('@',pre,current)return pre + (current.done ? 1 : 0)},0) *///简写return this.todos.reduce((pre,todo)=> pre + (todo.done ? 1 : 0) ,0)},//控制全选框isAll:{//全选框是否勾选get(){return this.doneTotal === this.total && this.total > 0},//isAll被修改时set被调用set(value){this.checkAllTodo(value)}}},methods: {/* checkAll(e){this.checkAllTodo(e.target.checked)} *///清空所有已完成clearAll(){this.clearAllTodo()}},}
    </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>
    
  • 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'import MyList from './components/MyList'import MyFooter from './components/MyFooter.vue'export default {name:'App',components:{MyHeader,MyList,MyFooter},data() {return {//由于todos是MyHeader组件和MyFooter组件都在使用,所以放在App中(状态提升)todos:[{id:'001',title:'学习',done:true},{id:'002',title:'吃饭',done:false},{id:'003',title:'看剧',done:true}]}},methods: {//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter( todo => todo.id !== id )},//全选or取消全选checkAllTodo(done){this.todos.forEach((todo)=>{todo.done = done})},//清除所有已经完成的todoclearAllTodo(){this.todos = this.todos.filter((todo)=>{return !todo.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>

效果:



总结:

  1. 组件化编码流程:

    ​ (1)拆分静态组件:组件要按照 功能 点拆分,命名不要与html元素冲突。

    ​ (2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:

    • (1)一个组件在用:放在组件自身即可。

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

    ​ (3)实现交互:从绑定事件开始。

  2. props 适用于:

    ​ (1)父组件 ==> 子组件 通信

    ​ (2)子组件 ==> 父组件 通信(要求父先给子一个函数)

  3. 使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!

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


2.8 浏览器本地存储

代码:
localStorage.html

<!DOCTYPE html>
<html><head><meta charset="UTF-8" /><title>localStorage</title></head><body><h2>localStorage</h2><button οnclick="saveData()">点我保存一个数据</button><button οnclick="readData()">点我读取一个数据</button><button οnclick="deleteData()">点我删除一个数据</button><button οnclick="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')console.log(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 οnclick="saveData()">点我保存一个数据</button><button οnclick="readData()">点我读取一个数据</button><button οnclick="deleteData()">点我删除一个数据</button><button οnclick="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>

效果:


总结:

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

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

  3. 相关API:

    1. xxxxxStorage.setItem('key', 'value')
      该方法接受一个键和值作为参数,会把键值对添加到存储中,如果键名存在,则更新其对应的值。

    2. xxxxxStorage.getItem('person')

      ​ 该方法接受一个键名作为参数,返回键名对应的值。

    3. xxxxxStorage.removeItem('key')

      ​ 该方法接受一个键名作为参数,并把该键名从存储中删除。

    4. xxxxxStorage.clear()

      ​ 该方法会清空存储中的所有数据。

  4. 备注:

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

2.9 TodoList_本地存储

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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){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>

2.10 组件自定义事件

2.10.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/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>
    
  • src/components/Student.vue

    <template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><button @click="sendStudentName">点我传递学生姓名给App</button> </div>
    </template><script>export default {name:'Student',data() {return {name:'张三',sex:'男'}},methods:{sendStudentName(){this.$emit('jojo',this.name)}}}
    </script><style scoped>.student{background-color: chartreuse;padding: 5px;margin-top: 30px;}
    </style>

效果:


2.10.2 解绑

  • src/App.vue

    <template><div class="app"><Student @jojo="getStudentName"/></div>
    </template><script>
    import School from './components/School.vue'import Student from './components/Student.vue'export default {name:'App',components: { Student, School },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">点我传递学生姓名给App</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 @custom-event="test"/><Demo v-on:custom-event="test"/>

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

      <Demo ref="demo"/>
      ......
      mounted(){this.$refs.xxx.$on('custom-event',this.test)
      }
      
    3. 若想让自定义事件只能触发一次,可以使用once修饰符,或$once方法。

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

  5. 解绑自定义事件this.$off('custom-event')

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

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


2.11 TodoList_自定义事件

  • 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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){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()) returnconst 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>
    

2.12 全局事件总线(GlobalEventBus)

  • src/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 //安装全局事件总线,$bus就是当前应用的vm},
    })
    
  • src/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>
    
  • 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:'广州',}},mounted() {// console.log('School',this)this.$bus.$on('hello',(data)=>{console.log('我是School组件,收到了数据:',data)})},beforeDestroy() {this.$bus.$off('hello') //解绑当前组件用到的事件},}
    </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:'男',}},mounted() {// console.log('Student',this.x)},methods: {sendStudentName(){this.$bus.$emit('hello',this.name)}},}
    </script><style lang="less" scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
    </style>
    

效果:


总结:

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

  2. 安装全局事件总线:

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

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

      methods(){demo(data){......}
      }
      ......
      mounted() {this.$bus.$on('xxxx',this.demo)
      }
      
    2. 提供数据:this.$bus.$emit('xxxx',数据)

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


2.13 TodoList_事件总线

  • src/mian.js

    import Vue from 'vue'
    import App from './App.vue'Vue.config.productionTip = falsenew 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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){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>
    

2.14 消息订阅与发布(pubsub)

  • 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:'张三',sex:'男',}},methods: {sendStudentName(){pubsub.publish('demo',this.name) //发布消息}}}
    </script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
    </style>

效果:


总结:

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

  2. 使用步骤:

    1. 安装pubsub:npm i pubsub-js

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

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

      methods(){demo(data){......}
      }
      ......
      mounted() {this.pid = pubsub.subscribe('xxx',this.demo) //订阅消息
      }
      
    4. 提供数据:pubsub.publish('xxx',数据)

    5. 最好在 beforeDestroy 钩子中,用 PubSub.unsubscribe(pid) 去取消订阅。


2.15 使用消息的订阅与发布优化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:{//添加一个todoaddTodo(todoObj){this.todos.unshift(todoObj)},//勾选or取消勾选一个todocheckTodo(_,id){this.todos.forEach((todo)=>{if(todo.id === id) todo.done = !todo.done})},//删除一个tododeleteTodo(id){this.todos = this.todos.filter(todo => todo.id !== id)},//全选or取消勾选checkAllTodo(done){this.todos.forEach(todo => todo.done = done)},//删除已完成的todoclearAllTodo(){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>
    

2.16 nextTick(回调函数)

  1. 语法:this.$nextTick(回调函数)
  2. 作用:在下一次 DOM 更新结束后执行其指定的回调。
  3. 什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。

2.17 过度与动画

  1. 作用:在插入、更新或移除 DOM元素时,在合适的时候给元素添加样式类名。

  2. 写法:

    1. 准备好样式:

      • 元素进入的样式:
        1. v-enter:进入的起点
        2. v-enter-active:进入过程中
        3. v-enter-to:进入的终点
      • 元素离开的样式:
        1. v-leave:离开的起点
        2. v-leave-active:离开过程中
        3. v-leave-to:离开的终点
    2. 使用<transition>包裹要过度的元素,并配置name属性:

      <transition name="hello"><h1 v-show="isShow">你好啊!</h1>
      </transition>
      
    3. 备注:若有多个元素需要过度,则需要使用:<transition-group>,且每个元素都要指定key值。


 
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 分享👥 留言💬thanks!!!

更多推荐

前端Vue框架系列—— 学习笔记总结Day02

本文发布于:2023-12-05 12:21:29,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1664198.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:学习笔记   框架   系列   Vue

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!