视频链接:尚硅谷Vue2.0+Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili
P1-50:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P001-050]_小白桶子的博客-CSDN博客
P51-100:当前页面
P101-135:尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P101-135]_小白桶子的博客-CSDN博客
P51-60:
- P51 - 生命周期_销毁流程
课堂笔记:
(1)vm.$destroy() 问题:
(官方文档说明如下图)
①为什么vm销毁了,页面上还有内容?
vm确实销毁了,但是vm销毁之前的工作成果还在。只不过vm销毁之后,并没有东西去管理页面上的内容了。
②官网中 “清理它与其它实例的连接” 这句话并没有问题。因为Vue的官方文档始终站在组件化编码的思维基础上,给予提示,而目前还没接触到组件,所以还无法印证。
③为什么destroy之后,add事件还能打印?
官网中所谓的 “移除了所有的事件监听器” ,这个事件指的是自定义事件,而不是原生的DOM事件。
(2)在beforeDestroy中,data、methods、指令等都处于可用状态。虽然能访问到数据,也能调用到方法,但是所有对数据的修改(如本节中的 add )不会再触发更新了。
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h2 v-text="n"></h2><h2> 当前的n值是:{{n}}</h2><button @click="add">点我n+1</button><button @click="bye">点我销毁vm</button></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',// template:`// <div>// <h2> 当前的n值是:{{n}}</h2>// <button @click="add">点我n+1</button> // </div>// `,data:{n:1},methods: {add(){console.log('add')this.n ++},bye(){console.log('bye')this.$destroy()}},watch: {n(){console.log('n变了')}},beforeCreate() {console.log('beforeCreate')},created() {console.log('created')},beforeMount() {console.log('beforeMount')},mounted() {console.log('mounted')},beforeUpdate() {console.log('beforeUpdate')},updated() {console.log('updated')},beforeDestroy() {console.log('beforeDestroy')},destroyed() {console.log('destroyed')},})</script>
</body>
</html>
- P52 - 生命周期_总结
课堂笔记:
(1)生命周期类比图
(2)如果多个地方需要用到同个东西(如本节中,mounted和methods中都需要用到定时器),可以直接用 this.xxx 。
老师总结:
常用的生命周期钩子:
1.mounted:发送Ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
2.beforeDestroy:清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁Vue实例
1.销毁后借助Vue开发者工具看不到任何信息。
2.销毁后自定义事件会失效,但原生DOM事件依然有效。
3.一般不会再beforeDestroy操作数据,因为即便操作数据,也不会再触发更新流程了。
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h2 :style="{opacity}">欢迎学习Vue</h2><button @click="opacity = 1">透明度设置为1</button><button @click="stop">点我停止变换</button></div><script type="text/javascript">Vue.config.productionTip = falseconst vm = new Vue({el:'#root',data:{opacity:1},methods: {stop(){this.$destroy()}},// Vue完成模板的解析并把真实的DOM元素放入页面后(挂载完毕)调用mountedmounted(){console.log('mounted')this.timer = setInterval(() => {console.log('setInterval')this.opacity -= 0.01if(this.opacity <= 0) this.opacity = 1},16) },beforeDestroy() {console.log('vm即将驾鹤西游了')clearInterval(this.timer)},})</script>
</body>
</html>
- P53 - 对组件的理解
课堂笔记:
(1)老师的PPT图:
老师总结:
模块与组件、模块化与组件化:
1.模块:
(1)理解:向外提供特定功能的js程序,一般就是一个js文件
(2)为什么:js文件很多很复杂
(3)作用:复用js,简化js的编写,提高js运行效率
2.组件:
(1)理解:用来实现局部(特定)功能效果的代码集合(html/css/js/image...)
(2)为什么:一个界面的功能很复杂
(3)作用:复用编码,简化项目编码,提高运行效率
3.模块化:
当应用中的js都以模块来编写,那这个应用就是一个模块化的应用。
4.组件化:
当应用中的功能都是多组件的方式来编写的,那这个应用就是一个组件化的应用。
- P54 - 非单文件组件
老师总结:
Vue中使用组件的三大步骤:
一、定义组件(创建组件)
二、注册组件
三、使用组件(写组件标签)
一、如何定义一个组件?
使用Vue.extend(options)创建,其中 options 和 new Vue(options)时传入的那个options几乎一样,但区别如下:
1.el不要写,为什么?——最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
2.data必须写成函数,为什么?——避免组件被复用时,数据存在引用关系。
备注:使用template可以配置组件结构。
二、如何注册组件?
1.局部注册:靠 new Vue的时候传入components选项
2.全局注册:靠Vue.component('组件名',组件)
三、编写组件标签:
<school></school>
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/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><script type="text/javascript">Vue.config.productionTip = false// 第一步:创建school组件const school = Vue.extend({// el:'#root', //组件定义时,一定不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器。template:`<div><h2>学校名称:{{shcoolName}}</h2><h2>学校地址:{{address}}</h2><button @click="showName">点我提示学校名</button></div>`,data(){return{shcoolName:'尚硅谷',address:'北京昌平'}},methods: {showName(){alert(this.shcoolName)}},})// 第一步:创建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'}}})// 第二步:全局注册组件Vue.component('hello',hello)// 创建vmnew Vue({el:'#root',data:{msg:'你好啊!'},// 第二步:注册组件(局部注册)components: {school,student}})new Vue({el:'#root2'})</script>
</body>
</html>
- P55 - 组件的几个注意点
课堂笔记:
(1)本节中,有个简写方式。为什么简写成对象了,Vue依旧能访问到?
不是说简写中没有调用 Vue.extend ,最终就没有调用。而是在Vue底层(components)中写了判断,如果传入的值是对象(如本节的 s ),那么在components中会帮你执行 Vue.extend 。也就是说,写与不写都行,Vue最终会有判断,写了Vue就不管,没写会补上。
老师总结:
几个注意点:
1.关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第二种写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
备注:
(1)组件名尽可能回避HTML中已有的9元素名称,例如:h2、H2都不行。
(2)可以使用name配置项指定组件在开发者工具中呈现的名字。
2.关于组件标签:
第一种写法:<school></school>
第二种写法:<school/>
备注:不适用脚手架时,<school/>会导致后续组件不能渲染。
3.一个简写方式:
const school = Vue.extend(options) 可简写为:const school = options
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><h1>{{msg}}</h1><school></school><!-- <school/> --></div><script type="text/javascript">Vue.config.productionTip = false// 定义组件const s = {name:'atguigu',template:`<div><h2>学校名称:{{name}}</h2><h2>学校地址:{{address}}</h2></div>`,data(){return{name:'尚硅谷',address:'北京'}}}new Vue({el:'#root',data:{msg:'欢迎学习Vue!'},components:{// 'my-school':sschool:s}})</script>
</body>
</html>
- P56 - 组件的嵌套
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"></div><script type="text/javascript">Vue.config.productionTip = false// 定义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:'欢迎来到尚硅谷学习!'}}})// 定义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>
</body>
</html>
- P57 - VueComponent构造函数
课堂笔记:
(1)关于老师说的,每次调用返回的都是新的VueComponent,弹幕中看到有人说的不错:
就是东西都是一样的,但由于你所给的参数不同,最终所拿到的东西也不同。
老师总结:
关于VueComponent:
1.school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.exend生成的。
2.我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
3.特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent!!
4.关于this指向:
(1)组件配置中:
data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【VueComponent实例对象】
(2)new Vue()配置中:
data函数、methods中的函数、watch中的函数、computed中的函数,他们的this均是【Vue实例对象】
5.VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。
Vue的实例对象,以后简称vm。
(这节代码跟上面两节差不多,就不放上来了。自己一定要去验证、写写看)
- P58 - Vue实例与组件实例
课堂笔记:
(1)既然vc和vm中的内容一样,为什么不能把vc和vm直接划上等号?
vc是由VueComponent缔造的,vm是由Vue缔造的。这两个个缔造的过程肯定不可能完全一样。
比如说在创建组件实例对象、传入配置项的时候,是不可以写el的(回顾可看P54),但是在创建vm的时候,配置对象中就可以写el。也就是说vm可以写自己为哪个容器服务,但是vc不能指定服务容器,只能跟着vm走。
再比如说,vc的数据配置项(data)就必须写成函数。
可以说,它们俩身上99%的东西一样,但是有这1%就是不一样。就像是两个双胞胎,长得一模一样,但是不能说他们是同个人。
打开官网——学习——教程——组件基础——基本实例中,也有说到:组件是可复用的Vue实例......因为组件是可复用的Vue实例,所以它们与new Vue接收相同的选项......
- P59 - 一个重要的内置关系
课堂笔记:
(1)本节学习中,需要有一定原型的基础。如果这里不太理解,可以去补一下原型和原型链的理解。可以看这个:js原型和原型链的理解(透彻)_lixiaonaaa的博客-CSDN博客_js原型和原型链的理解
(2)分析Vue与VueComponent的关系图。
(3)实例的隐式原型属性,永远指向自己缔造者的原型对象。
老师总结:
1.一个重要的内置关系:VueComponent.prototype.__proto__ === Vue.prototype
2.为什么要有这个关系:让组件实例对象(vc)可以访问到Vue原型上的属性、方法。
本节代码(直接复制到空白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><script type="text/javascript" src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.1/vue.js"></script>
</head>
<body><div id="root"><school></school></div><script type="text/javascript">Vue.config.productionTip = falseVue.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)}},})// 创建一个vmnew Vue({el:'#root',data:{msg:'你好'},components:{school}})// console.log(school.prototype.__proto__ === Vue.prototype)// 定义一个构造函数/* 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.x) */</script>
</body>
</html>
- P60 - 单文件组件
课堂笔记:
(1)给 .vue 文件起名字的规则,和给组件起名的规则是一样的。
(2)老师安装的插件:Vetur,版本自己选择。
(3)如果ES6模块化、模块暴露不熟的同学,需要去补一下。可以看这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化
(4)看到弹幕有一条说的不错,可以帮助理解结构:
main:领导,App:包工头,其他组件:工人。
(本节代码在不放入脚手架前都是无用的,目前老师只是展示一个简单的结构,所以这里不贴代码了。)
P61-70:
- P61 - 创建Vue脚手架
课堂笔记:
(1)CLI就是 command line interface 的缩写。Vue CLI官网:Vue CLI
(2)安装过程:
(PS:
①老师已经提前安装过node.js了,没有安装的可以打开这个:Download | Node.js
②已经安装的可以检查node.js和npm版本及安装情况:
检查nodejs : node -v
检查npm : npm -v (这两个指令均出现版本号为安装成功)
)
①配置npm淘宝镜像:npm config set registryhttps://registry.npm.taobao.org
(这一步可以让vue的安装更加快速,可有可无,但是建议还是安装一下)
②第一步(仅第一次执行),全局安装@vue/cli : npm install -g @vue/cli
③第二步,切换到你要创建项目的目录,然后使用命令创建项目 : vue create xxxx (xxxx为项目名,最好不要取vue、jQuery这种名字)
④第三步,启动项目 : npm run serve
- P62 - 分析脚手架结构
课堂笔记:
(1)目录结构:
┌── node_modules (依赖包)
|
├── public ──┬── favicon.ico(网站的页签图标)
| └── index.html(vue是单页面应用,这就是总的入口文件)
|
| ┌── assets(一般用来放静态资源)
├── src ──┼── components(所有组件都往这里放)
| ├── App.vue(大哥)
| └── main.js (非常重要!该文件是整个项目的入口文件)
|
├── .gitignore (git的配置文件)
├── babel.config.js (babel的控制文件,虽然很重要,但是和我们没啥关系,不需要动,主要工作于 ES6 => ES5,如果想要配置的话,可以参考babel官网)
├── package-lock.json (包版本控制文件)
├── package.json (只要打开的功能是符合npm规范的,那么就一定会有这个文件,类似于包的说明书。其中会配置包的名字(name)、版本(version)、采用的依赖(dependencies)等。还有常用的命令:serve开始的时候配置全部;build所有工程完成之后转换;lint语法检查,几乎不用)
└── README.md (对整个工程进行说明、描述)
(2)整个流程:
执行npm run serve,随后来到src中,找到main.js,这个页面中引入了Vue、App.vue、关闭了提示等。
继续找到app.vue页面,看到这个页面中引入了school和student,于是就到components文件夹中找到这两个并执行,执行最终汇总到了App.vue页面。
再回到main.js页面,把App组件放入容器中。
再找到index.html,把东西放到这个里面。
至此完成了整个运行流程。
(3)我自己这边报了一个错:
Component name "School" should always be multi-word
看到弹幕上很多人也有这个问题,这个是说组件名最好由多个单词组成,可以找到vue.config.js文件,在module.exports里面添加 lintOnSave:false (关闭语法检查)即可。
完整的可以看:关于Vue报错“Component name “School“ should always be multi-word”的解决方法_指尖世界 £的博客-CSDN博客
或者自己按照规范重新命名。
- P63 - render函数
课堂笔记:
(1)在main.js中引入的vue是残缺版的。
(2)理解render这行:
render(createElement){return createElement('h1','你好啊')
}
// ↑ 也就可以简写成 ↓
render:q => q('h1','你好啊')
(3)为什么不直接引入vue.js,反而要vue文件夹那么多东西?
vue中包含了两种东西,核心(比如说生命周期、处理事件等)和模板解析器。如果没有其它的文件,只把vue放到一个页面中,可能会出现问题。因为vue中有三分之一都是模板解析器,之后webpack打包完成后,会生成一个非常大的文件,这个时候vue的模板解析器就不适合出现在这里,没有作用。(vue文件中,带有runtime的都表示运行时的vue,带有common的就是走commonJS)
简而言之,没有了模板解析器的vue体积很小,打包之后能够更加轻量,代价就是写的时候要用那行render去写。
老师的比喻:
老师总结:
关于不同版本的Vue:
1. vue.js 与 vue.runtime.xxx.js 的区别:
(1)vue.js 是完整版的Vue,包含:核心功能+模板解析器。
(2)vue.runtime.xxx.js 是运行版的Vue,只包含核心功能,没有模板解析器。
2.因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createrElement函数去指定具体内容。
- P64 - 修改默认配置
课堂笔记:
(1)Vue 脚手架隐藏了所有 webpack 相关的配置,即使不填写,也会有默认的配置。
(2)五个默认不能改的:public文件夹、favicon.ico、index.html、src文件夹、main.js
(3)如果还是要修改的话,打开Vue CLI官网——配置参考——左侧栏都是能改的。
(4)项目开发中需要关闭语法检查(具体的根据公司要求定),在 module.exports 里面添加 lintOnSave:false (详细的可以看62节我也有说)
(5)看md文件推荐使用Typroa。
老师总结:
1.脚手架文件结构:
(我在62节写了,老师这个会更精简一点,我写的那个包含了老师课上说的其它内容,如果要直接参考查看的话还是老师这个好,补充知识可以看我那个)
2.关于不同版本的Vue
(这个我也在63节写了,这里不弄上去了)
3. vue.config.js 配置文件
使用 vue inspect > output.js 可以查看到Vue脚手架的默认配置。
使用 vue.config.js 可以对脚手架进行个性化定制,详情见:https://cli.vuejs.org/zh
- P65 - ref属性
课堂笔记:
(1)对于传统的HTML而言,id和ref确实没有什么差别,但是对于组件来说就不一样了。
给组件加id,打印出获取的结果为组件所对应的完整DOM结构。
给组件加ref,打印出获取的结果就是VueComponent实例。
老师总结:
ref属性:
1.被用来给元素或子组件注册引用信息(id的代替者)
2.应用在html标签上获取的是真实的DOM元素,应用在组件标签上是组件实例对象(vc)
3.使用方式:
打标识:<h1 ref="xxx"> ... </h1>
获取: this.$refs.xxx
本节部分代码:
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),
})
App.vue页面:
<template><div><h1 v-text="msg" ref="title"></h1><button ref="btn" @click="showDOM">点我输出上方的DOM元素</button><School ref="sch"/></div>
</template><script>
// 引入School组件
import School from './components/School.vue'
export default {name: "App",components: { School },data() {return {msg:'欢迎学习Vue!'}},methods: {showDOM(){console.log(this.$refs.title) //真实DOM元素console.log(this.$refs.btn) //真实DOM元素console.log(this.$refs.sch) //School组件的实例对象(vc)}},
}
</script>
- P66 - props配置
老师总结:
配置项propos
功能:让组件接收外部传过来的数据
(1)传递数据:
<Demo name="xxx"/>
(2)接收数据:
第一种方式(只接收):
props:['name']
第二种方式(限制类型):
props:{
name:Number
}
第三种方式(限制类型、限制必要性、指定默认值):
props:{
name:{
type:String, // 类型
required:true, // 必要性
default:'老王' // 默认值
}
}
备注:props是只读的,Vue底层会监测你对props的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制props的内容到data中一份,然后去修改data中的数据。
本节部分代码:
App.vue页面:
<template><div><Student name="李四" sex="女" :age="18"/></div>
</template><script>
import Student from './components/Student.vue'
export default {name: "App",components: { Student },
}
</script>
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() {return {msg:'我是一个尚硅谷的学生',myAge:this.age}},methods: {updateAge(){this.myAge++}},// 简单声明接收props:['name','sex','age'] // 接收的同时对数据进行类型限制/* 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>
- P67 - mixin混入
课堂笔记:
(1)mixin.js页面中用了ES6模块化的分别暴露,可以看下这个:es6中的模块化_我不是你不是我的博客-CSDN博客_es6模块化
(2)mixin中的data数据会与页面中的data数据进行整合,如若有冲突的话,则以页面为准。
(3)mixin和页面中的生命周期钩子不以任何为主,都要。
老师总结:
mixin(混入)
功能:可以把多个组件共用的配置提取成一个混入对象
使用方式:
第一步定义混合,例如:
{
data(){ ... }
methods:{ ... }
...
}
第二步使用混入,例如:
(1)全局混入:Vue.mixin(xxx)
(2)局部混入:mixins:['xxx']
本节部分代码:
mixin.js页面:
export const mixin = {methods: {showName(){alert(this.name)}},mounted() {console.log('你好啊!')},
}export const mixin2 = {data(){return{x:100,y:200}}
}
main.js页面:
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
import {mixin,mixin2} from './mixin'
// 关闭Vue的生产提示
Vue.config.productionTip = falseVue.mixin(mixin)
Vue.mixin(mixin2)// 创建vm
new Vue({el:'#app',render: h => h(App),
})
Student.vue页面:
<template><div><h2 @click="showName">学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2></div>
</template><script>
// 引入了一个混合
import {mixin,mixin2} from '../mixin'
export default {name:'Student',data() {return {name:'张三',sex:'男'}},mixins:[mixin,mixin2]
}
</script>
- P68 - 插件
老师总结:
插件:
功能:用于增强Vue
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用者传递的数据。
定义插件:
对象.install = fuction (Vue, options){
// 1.添加全局过滤器
Vue.filter( ... )
// 2.添加全局指令
Vue.directive( ... )
}
本节部分代码:
pugins.js页面:
export default {install(Vue){console.log('111',Vue)// 全局过滤器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('你好啊!')}}
}
main.js页面:
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 引入插件
import plugins from './pugins'
// 关闭Vue的生产提示
Vue.config.productionTip = false
// 应用插件
Vue.use(plugins)
// 创建vm
new Vue({el:'#app',render: h => h(App),
})
School.vue页面(我将两个页面的引用插件都整合在一起了,比较方便看):
<template><div><h2>学校名称:{{name | mySlice}}</h2><h2>学校地址:{{address}}</h2><input type="text" v-fbind:value="name"><button @click="test">点我测试一个hello方法</button></div>
</template><script>
export default {name:'School',data() {return {name:'尚硅谷atguigu',address:'北京'}},methods: {test(){this.hello()}}
}
</script>
- P69 - scoped样式
课堂笔记:
(1)各个组件虽然样式是在自己的页面上写的,可是最后编译起来都会汇总到一起。
(2)脚手架在解析Vue文件的时候,顺序是:最先扫描import引入、然后再读取配置项、最后才解析模板。
(3)scoped原理:
其实就是在外层的div中,加了一个特殊的标签(随机生成值),通过标签+属性选择器,完成控制指定的div。
(4)App组件不适合用。
(5)安装less指令:npm i less-loader
查看less版本指令:npm view less-loader versions (less-loader可替换成别的,查看别的版本)
安装指定版本的less:npm i less-loader@7
(我自己是没指定版本就安装好了,依据个人情况定)
老师总结:
scoped样式
作用:让样式在局部生效,放置冲突。
写法:<style scoped>
- P70 - 静态
课堂笔记:
(1)老师的工作经验:如果你在拆分组件取名字的时候,发现很难取,说不定是因为你拆的不合理。
比如说课上的,将input框和list中的item放一起,就难命名,显然就是拆分不合理。
(2)把旧代码改成组件的流程:
①一个个div折叠,通过折叠判断哪个是一块儿的,然后剪切到新建组件中。
②剪切完之后,马上在剪切的地方写上新建组件,以免到时候忘记、搞混了。
③一个个组件分好之后,确认哪些样式是需要公用的,哪些样式是单独的,分配好。
(老师讲的流程虽然比较理想化,现实中需要整理的代码可能会更乱,但是这个流程确实是比较靠谱的,实际工作中就在这个流程上调整即可,多写注释造福你我他)
老师总结:
组件化编码流程(通用)
1.实现静态组件:抽取组件,使用组件实现静态页面效果
2.展示动态数据:
(1)数据的类型、名称是什么?
(2)数据保存在哪个组件?
3.交互——从绑定事件监听开始
P71-80:
- P71 - TodoList案例_初始化列表
课堂笔记:
(1)如何在Vue中,让一个标签动态拥有某个属性?
如本节中的checked,通过 v-bind:cheked=" "(true or false)完成动态展示。
(全部代码在77节)
- P72 - TodoList案例_添加
课堂笔记:
(1)uuid标准:用于生成全球唯一的字符串编码。
(因为这个包太大了,所以本节中用的是uuid的精简版,nanoid)
(2)nanoid安装指令:npm i nanoid
nanoid的库用了分别暴露,所以需要这样引用:
import {nanoid} from 'nanoid'
(3)子组件传值父组件简单原理:
①父亲先传给儿子一个函数。
②儿子在合适的时候调用,调用的时候父亲就能收到参数。
(4)整个添加的流程:
首先在MyHeader中,添加用了add方法,add方法调用 this.addTodo() 将 todoObj的值传给了App。
传给App后,addTodo方法获取值,操作data中的todos。
Vue收到todos变了,就开始重新解析模板。
重新解析的时候,显然todos已经变了,则MyList中收到的也会跟着变化。
MyList收到数据变化,也开始重新解析模板,通过比较数据,虚拟DOM增加,于是MyItem增加。
(全部代码在77节)
- P73 - TodoList案例_勾选
课堂笔记:
(1)数据在哪个页面,那么对数据的操作就应该在哪个页面。
(2)本节中的第二个做法:
<input type="checkbox" v-model="todo.done"/>
①因为v-model是双向绑定数据,所以修改有效。
②什么叫props被修改了?
本节中的举例:
Vue不能监测到的修改:let obj = {a:1,b:2},修改成 obj.a = 666
Vue能监测到的修改:let obj = {a:1,b:2},修改成 obj = {x:100,y:200}
也就是说Vue监视的是浅层次的修改。
③不建议这样做,因为这样会违反原则修改props,虽然并没有被Vue监测到。
(全部代码在77节)
- P74 - TodoList案例_删除
课堂笔记:
(1)在浏览器的Console中,可能会有未展示的消息。
在Default levels中选择需要看到的。老师建议勾选:Info、Warnings、Errors。
(全部代码在77节)
- P75 - TodoList案例_底部统计
课堂笔记:
(1)ES6中的reduce,条件统计,不熟的可以看:Array.prototype.reduce() - JavaScript | MDN
(全部代码在77节)
- P76 - TodoList案例_底部交互
课堂笔记:
(1)计算属性是允许套娃的。一个计算属性可以通过其它的两个或以上计算属性再进行计算。
(全部代码在77节)
- P77 - TodoList案例_总结
课堂笔记:
(1)看md文件,除了使用Typroa外,还可以在VScode中有个预览按钮可以查看,或者安装VScode插件,老师推荐的是Open in External App。
老师总结:
总结TodoList案例
1.组件化编码流程:
(1)拆分静态组件:组件要按照功能点拆分,命名不要与html元素冲突。
(2)实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
①一个组件在用:放在组件自身即可。
②一些组件在用:放在他们共同的父组件上(状态提升)
(3)实现交互:从绑定事件开始。
2.props适用于:
(1)父组件 ==> 子组件 通信
(2)子组件 ==> 父组件 通信 (要求父先给子一个函数)
3.使用v-model时要切记:v-model绑定的值不能是props传过来的值,因为props是不可以修改的!
4.props传过来的若是对象类型的值,修改对象中的属性时Vue不会报错,但不推荐这样做。
本节部分代码:
App.vue页面:
<template><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>
</template><script>
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'export default {name: "App",components: {MyHeader,MyList,MyFooter},data() {return {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-color: #fff;
}
.btn {display: inline-block;padding: 4px 12px;margin-bottom: 0;font-size: 14px;line-height: 20px;text-align: center;vertical-align: middle;cursor: pointer;box-shadow: inset 0 1px 0 rgba(255,255,255,0.2), 0 1px 2px rgba(0, 0,0,0.05);border-radius: 4px;
}
.btn-danger{color: #fff;background-color: #da4f49;border: 1px solid #bd362f;
}
.btn-danger:hover{color: #fff;background-color: #bd362f;
}
.btn:focus{outline: none;
}
.todo-container{width: 600px;margin: 0 auto;
}
.todo-container .todo-wrap{padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>
MyHeader.vue页面:
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.enter="add"v-model="title"></div></template><script>
import {nanoid} from 'nanoid'
export default {name:'MyHeader',props:['addTodo'],data() {return {title:''}},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>
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对象props:['todo','checkTodo','deleteTodo'],methods: {// 勾选or取消勾选handleCheck(id){// 通知App组件将对应的todo对象的done值取反this.checkTodo(id)},// 删除handleDelete(id){if(confirm('确定删除吗?')){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>
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},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>
MyFooter.vue页面:
<template><div class="todo-footer" v-show="total > 0"><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(){// 老师写的第一种土方法/* let i =0this.todos.forEach((todo) => {if(todo.done) i++});return i */// 高端方法/* 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},// 全选的第二种做法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>
- P78 - 浏览器本地存储
老师总结:
webStorage
1.存储内容大小一般支持5MB左右(不同浏览器可能还不一样)
2.浏览器端通过 Window.sessionStorage 和 Window.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。
- P79 - TodoList_本地存储
本节App.vue页面部分代码:
export default {......data() {return {todos:JSON.parse(localStorage.getItem('todos')) || []}},......watch: {todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}}
}
- P80 - 组件自定义事件_绑定
课堂笔记:
(1)本节在接收多个参数时,用到了ES6的解构赋值,可以看这个:解构赋值_永远的大白的博客-CSDN博客_解构赋值
(全部代码在82节)
P81-90:
- P81 - 组件自定义事件_解绑
无
(全部代码在82节)
- P82 - 组件自定义事件_总结
课堂笔记:
(1)this.$refs.student.$on问题:
①为什么在App.vue页面中,写如下的内容,打印出来的this却是Student组件的实例呢?
this.$refs.student.$on('atguigu', function(name,...params){console.log('App收到了学生名:', name, params)console.log(this)
})
因为Vue底层逻辑设计了,谁触发的那个atguigu事件,那么这个事件回调中的this就是谁。
②那为什么在写 this.$refs.student.$on('atguigu',this.getStudentName) 的时候,this能获取到App的方法呢?
因为Vue给过承诺,如果这个函数写在methods里面,并且是普通函数,那么这个this就会是App的实例。
老师总结:
组件的自定义事件
1.一种组件间通信的方式,适用于:子组件 ==> 父组件。
2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)。
3.绑定自定义事件:
(1)第一种方式,在父组件中:
<Demo @atguigu="test"/> 或 <Demo v-on:atguigu="test"/>
(2)第二种方式,在父组件中:
<Demo ref="demo">......mounted(){this.$refs.xxx.$on('atguigu',this.test)}
(3)若想让自定义事件只能触发一次,可以使用 once 修饰符或 $once 方法。
4.触发自定义事件:this.$emit('atguigu',数据)
5.解绑自定义事件:this.$off('atguigu')
6.组件上也可以绑定原生DOM事件,需要使用 native 修饰符。
7.注意:通过 this.$refs.xxx.$on('atguigu',回调) 绑定自定义事件时,回调要么配置在methods中,要么用箭头函数,否则this指向会出问题!
本节部分代码:
App.vue页面:
<template><div class="app"><h1>{{msg}} 学生姓名是:{{stdentName}}</h1><!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --><School :getSchoolName="getSchoolName"/><!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第一种写法,使用@或v-on) --><!-- <Student @atguigu="getStudentName" @demo="m1"/> --><!-- 通过父组件给子组件绑定一个自定义实现:子给父传递数据(第二种写法,使用ref) --><Student ref="student" @click.native="show"/></div>
</template><script>
import { NONAME } from 'dns'
import School from './components/School.vue'
import Student from './components/Student.vue'
export default {name: "App",components: { Student, School },data() {return {msg:'你好啊!',stdentName:''}},methods: {getSchoolName(name){console.log('App收到了学校名:', name)},getStudentName(name,...params){console.log('App收到了学生名:', name, params)this.stdentName = name},m1(){console.log('demo事件被触发了')},show(){alert(123)}},mounted() {// this.$refs.student.$on('atguigu', this.getStudentName) //绑定自定义事件this.$refs.student.$on('atguigu', (name,...params)=>{console.log('App收到了学生名:', name, params)this.stdentName = NONAME})// this.$refs.student.$once('atguigu', this.getStudentName) //绑定自定义事件(一次性)},
}
</script><style scoped>
.app{background-color: gray;
}
</style>
Student.vue页面:
<template><div class="student"><h2>学生姓名:{{name}}</h2><h2>学生性别:{{sex}}</h2><h2>当前求和为:{{number}}</h2><button @click="add">点我number++</button><button @click="sendStudentName">把学生名给App</button><button @click="unbind">解绑atguigu事件</button><button @click="death">销毁当前Student组件的实例(vc)</button></div>
</template><script>
export default {name:'Student',data() {return {name:'张三',sex:'男',number:0}},methods: {add(){console.log('add回调被调用了')this.number++},sendStudentName(){// 触发Student组件实例身上的atguigu事件this.$emit('atguigu',this.name,666,888,999)// this.$emit('demo')},unbind(){this.$off('atguigu') //解绑一个自定义事件// this.$off(['atguigu','demo']) //解绑多个自定义事件// this.$off() //解绑所有的自定义事件},death(){this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。}},
}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>
- P83 - TodoList案例_自定义事件
无
(这节太简单了不贴代码,最后所有的TodoList统一放在P90)
- P84 - 全局事件总线1
无
- P85 - 全局事件总线2
课堂笔记:
(1)对以下代码的理解:
const Demo = Vue.extend({})
const d = new Demo()
Vue.prototype.x = d
第一行(回顾54节)创建Demo全局组件。
第二行,获取组件的实例对象。
因为需要写了标签才能使用组件,然而不能在main.js中引用标签。所以这一行亲自new一个。
第三行(回顾59节)获得了原型vc。
(2)对以下代码的理解:
new Vue({......beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})
第三行(回顾49节)此时beforeCreate时无法访问到data中的数据的,只是初始化。
第四行,已知这个时候vm原型还未生成,所以不能直接等于vm,但是Vue中有个this本身就是vue实例。$bus为总线。
(3)为什么bus需要销毁,自定义事件不用销毁?
举个例子,比如说Student组件被销毁了,那么整个vc都没有了,所以自定义事件就直接没有了。
但是bus不是这样的,就算Student组件被销毁了,bus也会一直在,所以bus用完了最好要销毁。
老师总结:
全局事件总线(GlobalEventBus)
1.一种组件通信的方式,适用于任意组件间通信。
2.安装全局事件总线。
new Vue({......beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})
3.使用事件总线:
(1)接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
methods(){demo(data){......}
}
......
moutnted(){this.$bus.$on('xxx',数据)
}
(2)提供数据:this.$bus.$emit( 'xxx', 数据 )
4.最好在beforeDestroy钩子中,用$off去解绑当前组件所用到的事件。
本节部分代码:
main.js页面:
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false// 创建vm
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this //安装全局事件总线}
})
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>
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('hello',this.name)}},
}
</script><style scoped>.student{background-color: pink;padding: 5px;margin-top: 30px;}
</style>
- P86 - TodoList案例_事件总线
课堂笔记:
(1)如果在Vue控制台的事件中,看到触发事件是 by 的话,那么多半意味着这是个事件总线。
(这节太简单了不贴代码,最后所有的TodoList统一放在P90)
- P87 - 消息订阅与发布_pubsub
课堂笔记:
(1)老师的比喻:
(2)对以下代码的理解:
mounted() {this.pubId = pubsub.subscribe('hello',(msgName,data)=>{console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)})
},
beforeDestroy() {pubsub.unsubscribe(this.pubId)
},
pubsub在取消订阅的时候,需要通过Id去取消。
但是beforeDestroy不能直接获取到methods中的id,所以这里需要用this.id。
又因为pubsub是外部函数,所以这里使用箭头函数。
老师总结:
消息订阅与发布(pubsub)
1.一种组件通信的方式,适用于任意组件间通信。
2.使用步骤:
(1)安装pubsub: npm i pubsub-js
(2)引入:import pubsub from 'pubsub-js'
(3)接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
methods: {demo(msgName,data){......}
},
mounted() {this.pubId = pubsub.subscribe('xxx',this.demo) //订阅消息
},
(4)提供数据:pubsub.publish( 'xxx',数据 )
(5)最好在beforeDestroy钩子中,用 pubsub.unsubscribe(pid) 去取消订阅。
本节部分代码:
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('hello消息收到了',data)}},mounted() {// this.$bus.$on('hello',(data)=>{// console.log('我是School组件,收到了数据',data)// })this.pubId = pubsub.subscribe('hello',this.demo)},beforeDestroy() {// this.$bus.$off('hello')pubsub.unsubscribe(this.pubId)},
}
</script><style scoped>.school{background-color: skyblue;padding: 5px;}
</style>
Student.vue页面部分代码:
methods: {sendStudentName(){// this.$bus.$emit('hello',this.name)pubsub.publish('hello',666)}},
- P88 - TodoList案例_pubsub
无
(这节太简单了不贴代码,最后所有的TodoList统一放在P90)
- P89 - TodoList案例_编辑
课堂笔记:
(1)hasOwnProperty,判断一个属性定义在对象本身而不是继承原型链的方法,主要用于判断某个对象中是否有某个属性,返回值为布尔值 。
(全部代码在P90)
- P90 - $nextTick
课堂笔记:
(1)为什么直接写无效?
因为Vue需要等整个函数走完,才去重新渲染DOM,这就和之前一样(回顾46节),还没渲染就focus,显然是无效的。
老师总结:
nextTick
1.语法:this.$nextTick( 回调函数 )
2.作用:在下一次DOM更新结束后执行其指定的回调。
3.什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行。
本节部分代码:
main.js页面:
// 引入Vue
import Vue from 'vue'
// 引入App
import App from './App.vue'
// 关闭Vue的生产提示
Vue.config.productionTip = false// 创建vm
new Vue({el:'#app',render: h => h(App),beforeCreate() {Vue.prototype.$bus = this}
})
App.vue页面:
<template><div class="todo-container"><div class="todo-wrap"><MyHeader @addTodo="addTodo"/><MyList :todos="todos"/><MyFooter :todos="todos" @checkAllTodo="checkAllTodo" @clearAllTodo="clearAllTodo"/></div></div>
</template><script>
import pubsub from 'pubsub-js'
import MyHeader from './components/MyHeader'
import MyList from './components/MyList'
import MyFooter from './components/MyFooter'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});},// 更新一个todoupdateTodo(id,title){this.todos.forEach((todo) => {if(todo.id === id) todo.title = title});},// 删除一个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})}},watch: {todos:{deep:true,handler(value){localStorage.setItem('todos',JSON.stringify(value))}}},mounted() {this.$bus.$on('checkTodo',this.checkTodo)this.$bus.$on('updateTodo',this.updateTodo)// this.$bus.$on('deleteTodo',this.deleteTodo)this.pubId = pubsub.subscribe('deleteTodo',this.deleteTodo)},beforeDestroy() {this.$bus.$off('checkTodo')this.$bus.$off('updateTodo')// this.$bus.$off('deleteTodo')pubsub.unsubscribe(this.pubId)},
}
</script><style>
/* base */
body {background-color: #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-edit{color: #fff;background-color: skyblue;border: 1px solid rgb(103,159,180);margin-right: 5px;
}
.btn-edit:hover{color: #fff;background-color: rgb(103,159,180);
}
.btn:focus{outline: none;
}
.todo-container{width: 600px;margin: 0 auto;
}
.todo-container .todo-wrap{padding: 10px;border: 1px solid #ddd;border-radius: 5px;
}
</style>
MyHeader.vue页面:
<template><div class="todo-header"><input type="text" placeholder="请输入你的任务名称,按回车键确认" @keyup.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 alert('输入不能为空')// 将用户的输入包装成一个todo对象const todoObj = {id:nanoid(),title:this.title,done:false}// 通知App组件去添加一个todo对象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>
MyList.vue页面:
<template><ul class="todo-main"><MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" /></ul>
</template><script>
import MyItem from './MyItem'
export default {name:'MyList',components:{MyItem},props:['todos']
}
</script><style scoped>
/* main */
.todo-main{margin-left: 0px;border: 1px solid #ddd;border-radius: 2px;padding: 0px;
}
.todo-empty{height: 40px;line-height: 40px;border: 1px solid #ddd;border-radius: 2px;padding-left: 5px;margin-top: 10px;
}
</style>
MyItem.vue页面:
<template><li><label><input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/><!-- 如下代码也能实现功能,但是不太推荐,因为有点违反原则,修改了props --><!-- <input type="checkbox" v-model="todo.done"/> --><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)">删除</button><button v-show="!todo.isEdit" class="btn btn-edit" @click="handleEdit(todo)">编辑</button></li>
</template><script>
import pubsub from 'pubsub-js'
export default {name:'MyItem',// 声明接收todo对象props:['todo'],methods: {// 勾选or取消勾选handleCheck(id){// 通知App组件将对应的todo对象的done值取反this.$bus.$emit('checkTodo',id)},// 删除handleDelete(id){if(confirm('确定删除吗?')){// 通知App组件将对应的todo对象删除// this.deleteTodo(id) //其中的deleteTodo是父组件传来的函数// this.$bus.$emit('deleteTodo',id) //其中的deleteTodo是自定义事件的时间名pubsub.publish('deleteTodo',id) //其中的deleteTodo是订阅消息的消息名}},// 编辑handleEdit(todo){if(todo.hasOwnProperty('isEdit')){todo.isEdit = true}else{this.$set(todo,'isEdit',true)}this.$nextTick(function(){this.$refs.inputTitle.focus() })},// 失去焦点回调(真正执行修改逻辑)handleBlur(todo,e){todo.isEdit = falseif(!e.target.value.trim()) return alert('输入不能为空')this.$bus.$emit('updateTodo',todo.id,e.target.value)}},
}
</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>
MyFooter.vue页面:
<template><div class="todo-footer" v-show="total > 0"><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'],computed:{total(){return this.todos.length},doneTotal(){// 老师写的第一种土方法/* let i =0this.todos.forEach((todo) => {if(todo.done) i++});return i */// 高端方法/* 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},// 全选的第二种做法set(value){this.$emit('checkAllTodo',value)}}},methods: {// 全选的第一种做法/* checkAll(e){this.checkAllTodo(e.target.checked)} */clearAll(){this.$emit('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>
P91-100:
- P91 - 动画效果
无
本节部分代码:
Test.vue页面:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition name="hello" appear><h1 v-show="isShow">你好啊!</h1></transition></div>
</template><script>
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;
}
.hello-enter-active{animation: atguigu 0.5s linear;
}
.hello-leave-active{animation: atguigu 0.5s linear reverse;
}
@keyframes atguigu {from{transform: translateX(-100%);}to{transform: translateX(0px);}
}
</style>
- P92 - 过度效果
无
(代码在P93)
- P93 - 多个元素过度
无
本节部分代码:
Test2.vue页面:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-group name="hello" appear><h1 v-show="!isShow" key="1">你好啊!</h1><h1 v-show="isShow" key="2">尚硅谷!</h1></transition-group></div>
</template><script>
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;transition: 0.5s linear;
}
/* 进入的起点、离开的终点 */
.hello-enter , .hello-leave-to{transform: translateX(-100%);
}
/* .hello-enter-active , .hello-leave-active{transition: 0.5s linear;
} */
/* 进入的终点、离开的起点 */
.hello-enter-to , .hello-leave{transform: translateX(0);
}
</style>
- P94 - 集成第三方动画
课堂笔记:
(1)npm官网:npm
Animate官网:Animate.css | A cross-browser library of CSS animations.
(2)Animate安装指令:npm i animate (我从npm上复制来的,跟老师的不太一样,哪种都行)
本节部分代码:
Tset3.vue页面:
<template><div><button @click="isShow = !isShow">显示/隐藏</button><transition-groupappearname="animate__animated animate__bounce"enter-active-class="animate__swing"leave-active-class="animate__backOutUp"><h1 v-show="!isShow" key="1">你好啊!</h1><h1 v-show="isShow" key="2">尚硅谷!</h1></transition-group></div>
</template><script>
import 'animate'
export default {name:'Test',data() {return {isShow:true}},
}
</script><style scoped>
h1{background-color: orange;transition: 0.5s linear;
}
</style>
- P95 - 总结过度与动画
老师总结:
Vue封装的过度与动画
1.作用:在插入、更新或移除DOM元素时,在合适的时候给元素添加样式类名。
2.图示:
3.写法:
(1)准备好样式:
• 元素进入的样式:
①v-enter:进入的起点
②v-enter-active:进入的过程
③v-enter-to:进入的终点
• 元素离开的样式:
①v-leave:进入的起点
②v-leave-active:进入的过程
③v-leave-to:进入的终点
(2)使用<transition>包裹要过度的元素,并配置name属性:
<transition name="hello"><h1 v-show="isShow">你好啊!</h1>
</transition>
(3)备注:若有多个元素需要过度,则需要使用:,且每个元素都要指定key值。
(MyList.vue页面的代码不贴了,跟Test2的一样,稍微修改一下就好了)
- P96 - 配置代理_方式一
课堂笔记:
(1)回顾几种请求:
① xhr:也就是 new XMLHttpRequest() ,用于与服务器交互数据,是ajax功能实现所依赖的对象。
② JQuery:是对 xhr 的封装:$.get 、$.post。
③ axios:也是对 xhr 的封装。
④ fetch:在window的内置对象中直接有的方法,兼容性稍差。
(2)axios安装指令:npm i axios
(3)常见的跨域解决:
① cors:其实就是给你加几个特殊的响应头。拓展链接:CORS解决跨域问题_亦木丶的博客-CSDN博客_cors跨域
② jsonp:借助了script标签的src属性,在引入外部资源的时候,不受同源限制的特点。需要前后端都调整,且只能解决get请求(平时很少用,但是面试常问到)。拓展链接:使用jsonp解决跨域问题_是啥也不会酱的博客-CSDN博客_jsonp解决跨域问题
③配置代理服务器
(4)Vue CLI官网——配置参考——devServer.proxy:配置参考 | Vue CLI
(5)public文件夹就相当于8080服务器的根路径。public文件夹里有的东西都算是8080中有的。
- P97 - 配置代理_方式二
课堂笔记:
(1)方法二部分分析:
'/foo' 是在 '/api' 的基础上精简而来的。
pathRewrite:官网上没有加,需要自己加上去,重写路径。
ws:用于支持websocket。
老师总结:
Vue脚手架配置代理
方法一:
在vue.config.js中添加如下配置:
devServer: {proxy: 'http://localhost:5000'
}
说明:
1.优点:配置简单,请求资源时直接发给前端(8080)即可。
2.缺点:不能配置多个代理,不能灵活的控制请求是否走代理。
3.工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
方法二:
编写vue.config.js配置具体代理规则:
module.exports = {devServer: {proxy: {'/api1': { //匹配所有以'/api1'开头的请求路径target: 'http://localhost:5000', //代理目标的基础路径changeOrigin: true,pathRewrite:{'^/api1':''}},'/api2': { //匹配所有以'/api2'开头的请求路径target: 'http://localhost:5001' //代理目标的基础路径changeOrigin: true,pathRewrite:{'^/api2':''}}}}
}
/* changeOrigin设置为true时,服务器收到的请求头中的host为:localhost:5000changeOrigin设置为false时,服务器收到的请求头中的host为:localhost:8080changeOrigin默认值为true
*/
说明:
1.优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
2.缺点:配置略微繁琐,请求资源时必须加前缀。
本节部分代码:
vue.config.js页面:
const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({transpileDependencies: true,lintOnSave:false, //关闭语法检测// 开启代理服务器(方式一)/* devServer: {proxy: 'http://localhost:5000'} */// 开启代理服务器(方式二)devServer: {proxy: {'/atguigu': {target: 'http://localhost:5000',pathRewrite:{'^/atguigu':''},// ws: true, //用于支持websocket// changeOrigin: true //用于控制请求头中host值},'/demo': {target: 'http://localhost:5001',pathRewrite:{'^/demo':''},}}}
})
- P98 - github案例_静态组件
课堂笔记:
(1)bootstrap官网:Bootstrap中文网
(2)vue中引入样式的两种方法:
①在src文件夹中新建assets文件夹,再新建css文件夹,放入css文件,在.vue页面中import。缺点就是,Vue会逐条检查,如果有本节类似缺失字体部分,就很麻烦。
②在public文件夹中新建css文件夹,放入css文件,在index.html页面中用link引入。
<!-- 引入第三方样式 -->
<link rel="stylesheet" href="<%= BASE_URL %>css/bootstrap.css">
(代码在P100)
- P99 - github案例_列表展示
课堂笔记:
(1)课件提供的接口地址:
https://api.github.com/search/users?q=xxx
(2)写请求的时候提到了ES6中的模板字符串,可以看下:什么是模板字符串?_紫微前端的博客-CSDN博客_模板字符串
(代码在P100)
- P100 - github案例_完善案例
本节部分代码:
App.vue页面:
<template><div class="container"><Search/><List/></div>
</template><script>
import Search from './components/Search.vue'
import List from './components/List.vue'
export default {name:'App',components:{Search,List}
}
</script>
Search.vue页面:
<template><section class="jumbotron"><h3 class="jumbotron-heading">Search Github Users</h3><div><input type="text" placeholder="enter the name you search"v-model="keyWord"/> <button @click="searchUsers">Search</button></div></section>
</template><script>
import axios from 'axios'
export default {name:'Search',data() {return {keyWord:''}},methods: {searchUsers(){// 请求前更新List的数据this.$bus.$emit('updateListData',{isFirst:false, //是否为第一次展示isLoading:true, //是否处于加载中errMsg:'', //报错信息users:[]})axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(response => {console.log('请求成功了')// 请求成功后更新List的数据this.$bus.$emit('updateListData',{ isLoading:false, errMsg:'', users:response.data.items})},error => {// 请求失败后更新List的数据console.log('请求失败了',error.message)this.$bus.$emit('updateListData',{isLoading:false, errMsg:error.message, users:[]})})}}
}
</script><style></style>
List.vue页面:
<template><div class="row"><!-- 展示用户列表 --><div v-show="info.users.length" class="card" v-for="user in info.users" :key="user.login"><a :href="user.html_url" target="_blank"><img :src="user.avatar_url" style="width:100px" /></a><p class="card-text">{{user.login}}</p></div><!-- 展示欢迎词 --><h1 v-show="info.isFirst">欢迎使用!</h1><!-- 展示加载中 --><h1 v-show="info.isLoading">Loading···</h1><!-- 展示错误信息 --><h1 v-show="info.errMsg">{{info.errMsg}}</h1></div>
</template><script>
export default {name:'List',data() {return {info:{isFirst:true, //是否为第一次展示isLoading:false, //是否处于加载中errMsg:'', //报错信息users:[]}}},mounted() {this.$bus.$on('updateListData',(dataObj)=>{this.info = {...this.info,...dataObj}})},
}
</script><style scoped>
.album {min-height: 50rem; /* Can be removed */padding-top: 3rem;padding-bottom: 3rem;background-color: #f7f7f7;
}
.card {float: left;width: 33.333%;padding: .75rem;margin-bottom: 2rem;border: 1px solid #efefef;text-align: center;
}
.card > img {margin-bottom: .75rem;border-radius: 100px;
}
.card-text {font-size: 85%;
}
</style>
——————————————————————————————————————
——————————————原创不易,转载请声明——————————————