# Vue2
初始Vue 课程简介
Vue简介 Vue是什么
谁开发的Vue
Vue的特点
Vue官网使用指南
Vue.js官网 : https://cn.vuejs.org/index.html
搭建Vue开发环境 vuejs的引入 1 <script src ="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js" > </script >
关闭生产提醒 1 Vue .config .productionTip = false
页签图标
Hello小案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <div id ="app" > <h1 > Hello,{{name.toUpperCase()}},{{address}}</h1 > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#app' , data :{ name :'atguigu' , address :'北京' } }) </script > </body >
对Hello案例的分析
01 想让 Vue 工作,就必须创建一个 Vue 实例,且要传入一个配置对象;
02 app 容器里的代码依然符合 html 规范,只不过混入了一些特殊的 Vue 语法;
03 app 容器里的代码被称为【Vue模板】;
04 Vue 实例和容器是一一对应的;
05 真实开发中只有一个 Vue 实例,并且会配合着组件一起使用;
06 {{xxx}}
中的 xxx 要写 js 表达式,且 xxx 可以自动读取到 data 中的所有属性;
07 一旦 data 中的数据发生改变,那么页面中用到该数据的地方也会自动更新;
08 js 表达式 和 js 代码(语句)的区别:
表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方:
(1). a
(2). a+b
(3). demo(1)
(4). x === y ? ‘a’ : ‘b’
js 代码(语句)
(1). if(){ }
(2). for(){ }
09 vue 实例与容器的对应
01 vue 实例与容器只能一一对应
02 一个 Vue 实例管理多个容器,只会接管第一个容器
03 多个 vue 实例管理一个容器,第一个容器能正常解析,第二个报错
模板语法
Vue 模板语法有 2 大类:
01 插值语法:
功能:用于解析标签体内容。
写法:{{xxx}}
,xxx 是 js 表达式,且可以直接读取到 data 中的所有属性。
02 指令语法:
功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…..)。
举例:v-bind:href="xxx"
或 简写为 :href="xxx"
,xxx 同样要写 js 表达式,且可以直接读取到 data 中的所有属性。
备注:Vue 中有很多的指令,且形式都是:v-????,此处我们只是拿 v-bind 举个例子。
插值语法 1 2 3 4 5 <div id ="root" > <h1 > 插值语法</h1 > <h3 > 你好,{{name}}</h3 > </div >
1 2 3 4 5 6 7 8 9 10 <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'jack' } }) </script >
指令语法 1 2 3 4 5 6 <div id ="root" > <h1 > 指令语法</h1 > <a v-bind:href ="school.url.toUpperCase()" x ="hello" > 点我去{{school.name}}学习1</a > <a :href ="school.url" x ="hello" > 点我去{{school.name}}学习2</a > </div >
1 2 3 4 5 6 7 8 9 10 11 12 <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ school :{ name :'尚硅谷' , url :'http://www.atguigu.com' , } } }) </script >
数据绑定
Vue 中有 2 种数据绑定的方式:
01 **单向绑定(v-bind)**:数据只能从 data 流向页面。
02 **双向绑定(v-model)**:数据不仅能从 data 流向页面,还可以从页面流向 data。
03 备注:
01 双向绑定一般都应用在表单类元素上(如:input、select等)
02 v-model:value
可以简写为 v-model,因为 v-model 默认收集的就是value值 。
单向数据绑定 1 2 3 4 5 6 7 8 9 10 11 <div id ="root" > 单向数据绑定:<input type ="text" v-bind:value ="name" > <br /> 单向数据绑定:<input type ="text" :value ="name" > <br /> </div >
1 2 3 4 5 6 7 8 9 10 <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' } }) </script >
双向数据绑定 1 2 3 4 5 6 7 8 9 10 11 <div id ="root" > 双向数据绑定:<input type ="text" v-model:value ="name" > <br /> 双向数据绑定:<input type ="text" v-model ="name" > <br /> </div >
1 2 3 4 5 6 7 8 9 10 <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' } }) </script >
el和data的两种写法
data 与 el 的2种写法
01 el 有 2 种写法
(1). new Vue 时候配置 el 属性。
(2). 先创建 Vue 实例,随后再通过 vm.$mount('#root')
指定 el 的值。
02 data 有 2 种写法
(1). 对象式
(2). 函数式
如何选择:目前哪种写法都可以,以后学习到组件时,data 必须使用函数式,否则会报错。
03 一个重要的原则:
由 Vue 管理的函数,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了,而是指向 window。
1 2 3 4 <div id ="root" > <h1 > 你好,{{name}}</h1 > </div >
el的两种写法 1 2 3 4 5 6 7 8 9 10 11 12 <script type ="text/javascript" > Vue .config .productionTip = false const app = new Vue ({ data :{ name :'尚硅谷' } }) app.$mount('#root' ) </script >
data的两种写法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data ( ){ console .log ('@@@' ,this ) return { name :'尚硅谷' } } }) </script >
理解MVVM
Vue中的MVVM
MVVM 模型
M:模型(Model) :data 中的数据
V:视图(View) :模板代码
VM:视图模型(ViewModel):Vue 实例
观察发现:
01 data 中所有的属性,最后都出现在了 vm 身上。
02 vm 身上所有的属性 及 Vue 原型上所有属性,在 Vue 模板中都可以直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <body > <div id ="root" > <h1 > 学校名称:{{name}}</h1 > <h1 > 学校地址:{{address}}</h1 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , address :'北京' , } }) console .log (vm) </script >
数据代理 Object.defineProperty方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <script type ="text/javascript" > let person = { name :'张三' , sex :'男' , } Object .defineProperty (person,'age' ,{ value :18 , enumerable :true , writable :true , configurable :true }) console .log (Object .keys (person)) console .log (person) </script > </body >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <body > <script type ="text/javascript" > let number = 18 let person = { name : '张三' , sex : '男' , } Object .defineProperty (person, 'age' , { get ( ){ console .log ('有人读取age属性了' ) return number }, set (value ){ console .log ('有人修改了age属性,且值是' ,value) number = value } }) console .log (Object .keys (person)) console .log (person) </script > </body >
理解数据代理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <body > <script type ="text/javascript" > let obj = {x :100 } let obj2 = {y :200 } Object .defineProperty (obj2,'x' ,{ get ( ){ return obj.x }, set (value ){ obj.x = value } }) </script > </body >
Vue中的数据代理
01 Vue 中的数据代理:
通过 vm 对象来代理 data 对象中属性的操作(读/写)
02 Vue 中数据代理的好处:
03 基本原理:
通过 Object.defineProperty() 把 data 对象中所有属性添加到 vm 上。
为每一个添加到 vm 上的属性,都指定一个 getter/setter。
在 getter/setter 内部去操作(读/写)data 中对应的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <div id ="root" > <h2 > 学校名称:{{name}}</h2 > <h2 > 学校地址:{{address}}</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , address :'宏福科技园' } }) </script >
事件处理 事件的基本使用
01 使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件类型;
02 事件的回调需要配置在 methods 对象中,最终会放在 vm 上;
03 methods中 配置的函数,不要用箭头函数!否则 this 就不是 Vue 的实例对象 vm 了, 而是 window;
04 methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或 组件实例对象;
05 @click="demo"
和 @click="demo($event)"
效果一致,但后者可以传参;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body > <div id ="root" > <h2 > 欢迎来到{{name}}学习</h2 > <button v-on:click ="showInfo" > 点我提示信息</button > <button @click ="showInfo" > 点我提示信息</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , }, methods :{ showInfo (event ){ console .log (event.target .innerText ) console .log (this ) alert ('同学你好!' ) }, } }) </script >
事件的传参 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <body > <div id ="root" > <h2 > 欢迎来到{{name}}学习</h2 > <button @click ="showInfo1" > 点我提示信息1(不传参)</button > <button @click ="showInfo2($event,66)" > 点我提示信息2(传参)</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , }, methods :{ showInfo1 (event ){ console .log (event.target .innerText ) alert ('同学你好!' ) }, showInfo2 (event,number ){ console .log (event,number) alert ('同学你好!!' ) } } }) </script >
事件修饰符
Vue 中的事件修饰符:
1 .prevent
:阻止默认事件(常用);
2 .stop
:阻止事件冒泡(常用);
3 .once
:事件只触发一次(常用);
4 .capture
:使用事件的捕获模式;
5 .self
:只有 event.target 是当前操作的元素时才触发事件;
6 .passive
:事件的默认行为立即执行,无需等待事件回调执行完毕;
阻止默认事件 .prevent 1 2 <a href ="http://www.atguigu.com" @click.prevent ="showInfo" > 点我提示信息</a >
阻止事件冒泡 .stop 1 2 3 4 5 6 7 <div class ="demo1" @click ="showInfo" > <button @click.stop ="showInfo" > 点我提示信息</button > <a href ="http://www.atguigu.com" @click.prevent.stop ="showInfo" > 点我提示信息</a > </div >
事件只触发一次 .once 1 2 <button @click.once ="showInfo" > 点我提示信息</button >
事件的捕获模式 .capture 1 2 3 4 5 6 7 <div class ="box1" @click.capture ="showMsg(1)" > div1 <div class ="box2" @click ="showMsg(2)" > div2 </div > </div >
只有是操作的元素时才触发 1 2 3 4 <div class ="demo1" @click.self ="showInfo" > <button @click ="showInfo" > 点我提示信息</button > </div >
默认行为立即执行 1 2 3 4 5 6 7 <ul @wheel.passive ="demo" class ="list" > <li > 1</li > <li > 2</li > <li > 3</li > <li > 4</li > </ul >
键盘事件
01 Vue 中常用的按键别名:
回车 => enter
删除 => delete (捕获“删除”和“退格”键)
退出 => esc
空格 => space
换行 => tab (特殊,必须配合 keydown 去使用,因为配合 keyup 使用时,没触发事件,焦点就已经切走)
上 => up
下 => down
左 => left
右 => right
02 Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名)
03 系统修饰键(用法特殊):ctrl、alt、shift、meta
(1).配合 keyup 使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
(2).配合 keydown 使用:正常触发事件。
04 也可以使用 keyCode 去指定具体的按键(不推荐)
05 Vue.config.keyCodes.自定义键名 = 键码
,可以去定制按键别名
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <body > <div id ="root" > <h2 > 欢迎来到{{name}}学习</h2 > <input type ="text" placeholder ="按下回车提示输入" @keydown.huiche ="showInfo" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 new Vue ({ el :'#root' , data :{ name :'尚硅谷' }, methods : { showInfo (e ){ console .log (e.key ,e.keyCode ) console .log (e.target .value ) } }, }) </script >
计算属性 姓名案例 插值语法实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br /> <br /> 名:<input type ="text" v-model ="lastName" > <br /> <br /> 全名:<span > {{firstName}}-{{lastName}}</span > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' } }) </script >
methods方法实现
在 {{..}}
中写方法自调用,是将该方法的返回值插入此处,需要加括号,不同于前面的事件回调.
模板每次重新解析,该方法就会重新执行,没有缓存, 不同于计算属性只执行一次,计算属性有缓存
一但 data 中的数据改变,则模块将会重新解析,用到 data 数据的地方就会重新渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br /> <br /> 名:<input type ="text" v-model ="lastName" > <br /> <br /> 全名:<span > {{fullName()}}</span > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' }, methods : { fullName ( ){ console .log ('@---fullName' ) return this .firstName + '-' + this .lastName } }, }) </script >
计算属性-完整写法
计算属性定义:要用的属性不存在,要通过已有属性计算得来。
计算属性原理:底层借助了Objcet.defineproperty 方法提供的 getter 和 setter 。
get 函数什么时候执行
初次读取时会执行一次。
当依赖的属性发生改变时会被再次调用。
优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。若依赖的属性不变,则多次调用也只会执行一次
备注:
计算属性最终会出现在 vm 上,直接读取使用即可。
如果计算属性要被修改,那必须写 set 函数去响应修改,且 set 中要引起计算时依赖的数据发生改变。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br /> <br /> 名:<input type ="text" v-model ="lastName" > <br /> <br /> 全名:<span > {{fullName}}</span > <br /> <br /> 全名:<span > {{fullName}}</span > <br /> <br /> 全名:<span > {{fullName}}</span > <br /> <br /> 全名:<span > {{fullName}}</span > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' }, computed :{ fullName :{ get ( ){ console .log (this ) return this .firstName + '-' + this .lastName }, set (value ){ const arr = value.split ('-' ) this .firstName = arr[0 ] this .lastName = arr[1 ] } } } }) </script >
计算属性简写 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br /> <br /> 名:<input type ="text" v-model ="lastName" > <br /> <br /> 全名:<span > {{fullName}}</span > <br /> <br /> </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' , }, computed :{ fullName ( ){ console .log ('get被调用了' ) return this .firstName + '-' + this .lastName } } }) </script >
监视属性 天气案例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <body > <div id ="root" > <h2 > 今天天气很{{info}}</h2 > <button @click ="changeWeather" > 切换天气</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods : { changeWeather ( ){ this .isHot = !this .isHot } }, }) </script >
监视属性
监视属性 watch:
监视属性,可以监视 data 中的数据改变,也可以监视计算属性的改变
当被监视的属性变化时, 回调函数自动调用, 进行相关操作
监视的属性必须存在,才能进行监视!!若监视的属性不存在,不会报错,新值旧值都是 undefined
监视的两种写法:
new Vue 时传入watch 配置
通过 vm.$watch 监视
监视属性 写法一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <div id ="root" > <h2 > 今天天气很{{info}}</h2 > <button @click ="changeWeather" > 切换天气</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods : { changeWeather ( ){ this .isHot = !this .isHot } }, watch :{ isHot :{ immediate :true , handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } } } }) </script >
监视属性 写法二 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <body > <div id ="root" > <h2 > 今天天气很{{info}}</h2 > <button @click ="changeWeather" > 切换天气</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods : { changeWeather ( ){ this .isHot = !this .isHot } } }) vm.$watch('isHot' ,{ immediate :true , handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } }) </script >
深度监视
深度监视:
Vue 中的 watch 默认不监测对象内部值的改变(一层)。
配置 deep:true
可以监测对象内部值改变(多层)。
备注:
Vue 自身可以监测对象内部值的改变,但 Vue 提供的 watch 默认不可以!
使用 watch 时根据数据的具体结构,决定是否采用深度监视。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <div id ="root" > <h3 > a的值是:{{numbers.a}}</h3 > <button @click ="numbers.a++" > 点我让a+1</button > <h3 > b的值是:{{numbers.b}}</h3 > <button @click ="numbers.b++" > 点我让b+1</button > <button @click ="numbers = {a:666,b:888}" > 彻底替换掉numbers</button > <h3 > {{numbers.c.d.e}} </h3 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ numbers :{ a :1 , b :1 , c :{ d :{ e :100 } } } }, watch :{ numbers :{ deep :true , handler ( ){ console .log ('numbers改变了' ) } } } }) </script >
监视属性简写 写法一 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <body > <div id ="root" > <h2 > 今天天气很{{info}}</h2 > <button @click ="changeWeather" > 切换天气</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ isHot :true , }, computed :{ info ( ){ return this .isHot ? '炎热' : '凉爽' } }, methods : { changeWeather ( ){ this .isHot = !this .isHot } }, watch :{ isHot (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue,this ) } } }) </script >
监视属性简写 写法二 1 2 3 4 5 6 7 8 9 10 11 12 13 vm.$watch('isHot' ,{ immediate :true , deep :true , handler (newValue,oldValue ){ console .log ('isHot被修改了' ,newValue,oldValue) } }) vm.$watch('isHot' ,(newValue,oldValue )=> { console .log ('isHot被修改了' ,newValue,oldValue,this ) })
计算属性与监视属性的对比
computed 和 watch 之间的区别:
computed 能完成的功能,watch 都可以完成。
watch 能完成的功能,computed 不一定能完成,例如:watch 可以进行异步操作。
两个重要的小原则:
所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或 组件实例对象。
所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数等、Promise 的回调函数),最好写成箭头函数,这样 this 的指向才是 vm 或 组件实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <body > <div id ="root" > 姓:<input type ="text" v-model ="firstName" > <br /> <br /> 名:<input type ="text" v-model ="lastName" > <br /> <br /> 全名:<span > {{fullName}}</span > <br /> <br /> </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ firstName :'张' , lastName :'三' , fullName :'张-三' }, watch :{ firstName (val ){ setTimeout (()=> { console .log (this ) this .fullName = val + '-' + this .lastName },1000 ); }, lastName (val ){ this .fullName = this .firstName + '-' + val } } }) </script >
绑定样式 绑定class类名样式
绑定样式:
class 样式
写法:class=”xxx” , xxx 可以是字符串、对象、数组。
字符串写法适用于:类名不确定,要动态获取。
对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。
数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。
style 样式
:style=”{fontSize: xxx}” 其中 xxx 是动态值。
:style=”[a,b]” 其中 a、b 是样式对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <div id ="root" > <div class ="basic" :class ="mood" @click ="changeMood" > {{name}}</div > <br /> <br /> <div class ="basic" :class ="classArr" > {{name}}</div > <br /> <br /> <div class ="basic" :class ="classObj" > {{name}}</div > <br /> <br /> </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , mood :'normal' , classArr :['atguigu1' ,'atguigu2' ,'atguigu3' ], classObj :{ atguigu1 :false , atguigu2 :false , } }, methods : { changeMood ( ){ const arr = ['happy' ,'sad' ,'normal' ] const index = Math .floor (Math .random ()*3 ) this .mood = arr[index] } }, }) </script >
绑定style样式
style 样式
:style=”{fontSize: xxx}” 其中 xxx 是动态值。
:style=”[a,b]” 其中 a、b 是样式对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <body > <div id ="root" > <div class ="basic" :style ="styleObj" > {{name}}</div > <br /> <br /> <div class ="basic" :style ="styleArr" > {{name}}</div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :"hello world~~" , styleObj :{ fontSize : '40px' , color :'red' , }, styleArr :[ { fontSize : '40px' , color :'blue' , }, { backgroundColor :'gray' } ] }, }) </script >
条件渲染 v-if与v-show
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <body > <div id ="root" > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > <h2 v-show ="false" > 欢迎来到{{name}}</h2 > <h2 v-show ="1 === 1" > 欢迎来到{{name}}</h2 > <h2 v-if ="false" > 欢迎来到{{name}}</h2 > <h2 v-if ="1 === 1" > 欢迎来到{{name}}</h2 > <div v-if ="n === 1" > Angular</div > <div v-else-if ="n === 2" > React</div > <div v-else-if ="n === 3" > Vue</div > <div v-else > 哈哈</div > <template v-if ="n === 1" > <h2 > 你好</h2 > <h2 > 尚硅谷</h2 > <h2 > 北京</h2 > </template > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ name :'尚硅谷' , n :0 } }) </script >
v-if与v-show的区别
v-if 为 false 时,其控制的结构在从 DOM 树中移除
v-show 为 false 时,其控制的结构不会从 DOM 树中移除,只是给结构标签添加了行内样式 display:none;
列表渲染 v-for指令
用于展示列表数据
语法:v-for="(item, index) in xxx" :key="yyy"
可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 <body > <div id ="root" > <h2 > 人员列表(遍历数组)</h2 > <ul > <li v-for ="(p,index) of persons" :key ="index" > {{p.name}}-{{p.age}} </li > </ul > <h2 > 汽车信息(遍历对象)</h2 > <ul > <li v-for ="(value,k) of car" :key ="k" > {{k}}-{{value}} </li > </ul > <h2 > 测试遍历字符串(用得少)</h2 > <ul > <li v-for ="(char,index) of str" :key ="index" > {{char}}-{{index}} </li > </ul > <h2 > 测试遍历指定次数(用得少)</h2 > <ul > <li v-for ="(number,index) of 5" :key ="index" > {{index}}-{{number}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'张三' ,age :18 }, {id :'002' ,name :'李四' ,age :19 }, {id :'003' ,name :'王五' ,age :20 } ], car :{ name :'奥迪A8' , price :'70万' , color :'黑色' }, str :'hello' } }) </script > </body >
key作用与原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <div id ="root" > <h2 > 人员列表(遍历数组)</h2 > <button @click.once ="add" > 添加一个老刘</button > <ul > <li v-for ="(p,index) of persons" :key ="index" > {{p.name}}-{{p.age}} <input type ="text" > </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'张三' ,age :18 }, {id :'002' ,name :'李四' ,age :19 }, {id :'003' ,name :'王五' ,age :20 } ] }, methods : { add ( ){ const p = {id :'004' ,name :'老刘' ,age :40 } this .persons .unshift (p) } }, }) </script >
列表过滤案例(补) 用watch实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <ul > <li v-for ="(p,index) of filPerons" :key ="index" > {{p.name}}-{{p.age}}-{{p.sex}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ keyWord :'' , persons :[ {id :'001' ,name :'马冬梅' ,age :19 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :20 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :21 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :22 ,sex :'男' } ], filPerons :[] }, watch :{ keyWord :{ immediate :true , handler (val ){ this .filPerons = this .persons .filter ((p )=> { return p.name .indexOf (val) !== -1 }) } } } }) }) </script >
用computed实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <ul > <li v-for ="(p,index) of filPerons" :key ="index" > {{p.name}}-{{p.age}}-{{p.sex}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ keyWord :'' , persons :[ {id :'001' ,name :'马冬梅' ,age :19 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :20 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :21 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :22 ,sex :'男' } ] }, computed :{ filPerons ( ){ return this .persons .filter ((p )=> { return p.name .indexOf (this .keyWord ) !== -1 }) } } }) </script >
列表排序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <button @click ="sortType = 2" > 年龄升序</button > <button @click ="sortType = 1" > 年龄降序</button > <button @click ="sortType = 0" > 原顺序</button > <ul > <li v-for ="(p,index) of filPerons" :key ="p.id" > {{p.name}}-{{p.age}}-{{p.sex}} <input type ="text" > </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ keyWord :'' , sortType :0 , persons :[ {id :'001' ,name :'马冬梅' ,age :30 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :31 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :18 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :19 ,sex :'男' } ] }, computed :{ filPerons ( ){ const arr = this .persons .filter ((p )=> { return p.name .indexOf (this .keyWord ) !== -1 }) if (this .sortType ){ arr.sort ((p1,p2 )=> { return this .sortType === 1 ? p2.age -p1.age : p1.age -p2.age }) } return arr } } }) </script >
更新时的一个问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <body > <div id ="root" > <h2 > 人员列表</h2 > <button @click ="updateMei" > 更新马冬梅的信息</button > <ul > <li v-for ="(p,index) of persons" :key ="p.id" > {{p.name}}-{{p.age}}-{{p.sex}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ persons :[ {id :'001' ,name :'马冬梅' ,age :30 ,sex :'女' }, {id :'002' ,name :'周冬雨' ,age :31 ,sex :'女' }, {id :'003' ,name :'周杰伦' ,age :18 ,sex :'男' }, {id :'004' ,name :'温兆伦' ,age :19 ,sex :'男' } ] }, methods : { updateMei ( ){ this .persons .splice (0 ,1 ,{id :'001' ,name :'马老师' ,age :50 ,sex :'男' }) } } }) </script >
Vue检测数据的原理_对象(?)
Vue.set()方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <div id ="root" > <h1 > 学生信息</h1 > <button @click ="addSex" > 添加一个性别属性,默认值是男</button > <h2 > 姓名:{{student.name}}</h2 > <h2 v-if ="student.sex" > 性别:{{student.sex}}</h2 > <h2 > 年龄:真实{{student.age.rAge}},对外{{student.age.sAge}}</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ student :{ name :'tom' , age :{ rAge :40 , sAge :29 , } } }, methods : { addSex ( ){ this .$set(this .student ,'sex' ,'男' ) } } }) </script >
Vue检测数据的原理_数组(?)
Vue监视数据总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 <body > <div id ="root" > <h1 > 学生信息</h1 > <button @click ="student.age++" > 年龄+1岁</button > <br /> <button @click ="addSex" > 添加性别属性,默认值:男</button > <br /> <button @click ="student.sex = '未知' " > 修改性别</button > <br /> <button @click ="addFriend" > 在列表首位添加一个朋友</button > <br /> <button @click ="updateFirstFriendName" > 修改第一个朋友的名字为:张三</button > <br /> <button @click ="addHobby" > 添加一个爱好</button > <br /> <button @click ="updateHobby" > 修改第一个爱好为:开车</button > <br /> <button @click ="removeSmoke" > 过滤掉爱好中的抽烟</button > <br /> <h3 > 姓名:{{student.name}}</h3 > <h3 > 年龄:{{student.age}}</h3 > <h3 v-if ="student.sex" > 性别:{{student.sex}}</h3 > <h3 > 爱好:</h3 > <ul > <li v-for ="(h,index) in student.hobby" :key ="index" > {{h}} </li > </ul > <h3 > 朋友们:</h3 > <ul > <li v-for ="(f,index) in student.friends" :key ="index" > {{f.name}}--{{f.age}} </li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el :'#root' , data :{ student :{ name :'tom' , age :18 , hobby :['抽烟' ,'喝酒' ,'烫头' ], friends :[ {name :'jerry' ,age :35 }, {name :'tony' ,age :36 } ] } }, methods : { addSex ( ){ this .$set(this .student ,'sex' ,'男' ) }, addFriend ( ){ this .student .friends .unshift ({name :'jack' ,age :70 }) }, updateFirstFriendName ( ){ this .student .friends [0 ].name = '张三' }, addHobby ( ){ this .student .hobby .push ('学习' ) }, updateHobby ( ){ this .$set(this .student .hobby ,0 ,'开车' ) }, removeSmoke ( ){ this .student .hobby = this .student .hobby .filter ((h )=> { return h !== '抽烟' }) } } }) </script >
收集表单数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <body > <div id ="root" > <form @submit.prevent ="demo" > 账号:<input type ="text" v-model.trim ="userInfo.account" > <br /> <br /> 密码:<input type ="password" v-model ="userInfo.password" > <br /> <br /> 年龄:<input type ="number" v-model.number ="userInfo.age" > <br /> <br /> 性别: 男<input type ="radio" name ="sex" v-model ="userInfo.sex" value ="male" > 女<input type ="radio" name ="sex" v-model ="userInfo.sex" value ="female" > <br /> <br /> 爱好: 学习<input type ="checkbox" v-model ="userInfo.hobby" value ="study" > 打游戏<input type ="checkbox" v-model ="userInfo.hobby" value ="game" > 吃饭<input type ="checkbox" v-model ="userInfo.hobby" value ="eat" > <br /> <br /> 所属校区 <select v-model ="userInfo.city" > <option value ="" > 请选择校区</option > <option value ="beijing" > 北京</option > <option value ="shanghai" > 上海</option > <option value ="shenzhen" > 深圳</option > <option value ="wuhan" > 武汉</option > </select > <br /> <br /> 其他信息: <textarea v-model.lazy ="userInfo.other" > </textarea > <br /> <br /> <input type ="checkbox" v-model ="userInfo.agree" > 阅读并接受<a href ="http://www.atguigu.com" > 《用户协议》</a > <button > 提交</button > </form > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ userInfo :{ account :'' , password :'' , age :18 , sex :'female' , hobby :[], city :'beijing' , other :'' , agree :'' } }, methods : { demo ( ){ console .log (JSON .stringify (this .userInfo )) } } }) </script >
过滤器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <body > <div id ="root" > <h2 > 显示格式化后的时间</h2 > <h3 > 现在是:{{fmtTime}}</h3 > <h3 > 现在是:{{getFmtTime()}}</h3 > <h3 > 现在是:{{time | timeFormater}}</h3 > <h3 > 现在是:{{time | timeFormater('YYYY_MM_DD') | mySlice}}</h3 > <h3 :x ="msg | mySlice" > 尚硅谷</h3 > </div > <div id ="root2" > <h2 > {{msg | mySlice}}</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false Vue .filter ('mySlice' ,function (value ){ return value.slice (0 ,4 ) }) new Vue ({ el :'#root' , data :{ time :1621561377603 , msg :'你好,尚硅谷' }, computed : { fmtTime ( ){ return dayjs (this .time ).format ('YYYY年MM月DD日 HH:mm:ss' ) } }, methods : { getFmtTime ( ){ return dayjs (this .time ).format ('YYYY年MM月DD日 HH:mm:ss' ) } }, filters :{ timeFormater (value,str='YYYY年MM月DD日 HH:mm:ss' ){ return dayjs (value).format (str) } } }) new Vue ({ el :'#root2' , data :{ msg :'hello,atguigu!' } }) </script >
内置指令 v-text指令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <body > <div id ="root" > <div > 你好,{{name}}</div > <div v-text ="name" > </div > <div v-text ="str" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' , str :'<h3>你好啊!</h3>' } }) </script >
v-html指令
谷歌浏览器插件 : Cookie-Editor , 用于批量导出谷歌浏览器的 cookie 信息
v-html 指令:
作用:向指定节点中渲染包含 html 结构的内容。
与插值语法的区别:
v-html 会替换掉节点中所有的内容, 则不会。
v-html 可以识别 html 结构。
严重注意:v-html 有安全性问题!!!!
在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。
一定要在可信的内容上使用 v-html,永不要用在用户提交的内容上!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <body > <div id ="root" > <div > 你好,{{name}}</div > <div v-html ="str" > </div > <div v-html ="str2" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' , str :'<h3>你好啊!</h3>' , str2 :'<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>兄弟我找到你想要的资源了,快来!</a>' , } }) </script >
v-cloak指令
v-cloak 指令(没有值):
本质是一个特殊属性,Vue 实例创建完毕并接管容器后,会删掉 v-cloak 属性。
使用 css 配合 v-cloak 可以解决网速慢时页面展示出 的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <style > [v-clock] { display :none; } </style > <body > <div id ="root" > <h2 v-cloak > {{name}}</h2 > </div > <script type ="text/javascript" src ="http://localhost:8080/resource/5s/vue.js" > </script > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' } }) </script >
v-once指令
v-once 指令:
v-once 所在节点在初次动态渲染后,就视为静态内容了。
以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <body > <div id ="root" > <h2 v-once > 初始化的n值是:{{n}}</h2 > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 } }) </script >
v-pre指令
v-pre 指令:
跳过其所在节点的编译过程 。
可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <body > <div id ="root" > <h2 v-pre > Vue其实很简单</h2 > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ n :1 } }) </script >
自定义指令 自定义指令-函数式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <div id ="root" > <h2 > {{name}}</h2 > <h2 > 当前的n值是:<span v-text ="n" > </span > </h2 > <h2 > 放大10倍后的n值是:<span v-big ="n" > </span > </h2 > <button @click ="n++" > 点我n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ name :'尚硅谷' , n :1 }, directives :{ big (element,binding ){ console .log ('big' ,this ) element.innerText = binding.value * 10 } } }) </script >
自定义指定-对象式
需求1:定义一个 v-big 指令,和 v-text 功能类似,但会把绑定的数值放大 10 倍。
需求2:定义一个 v-fbind 指令,和 v-bind 功能类似,但可以让其所绑定的 input 元素默认获取焦点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 <body > <div id ="root" > <h2 > {{name}}</h2 > <h2 > 当前的n值是:<span v-text ="n" > </span > </h2 > <h2 > 放大10倍后的n值是:<span v-big ="n" > </span > </h2 > <button @click ="n++" > 点我n+1</button > <hr /> <input type ="text" v-fbind:value ="n" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false Vue .directive ('fbind' ,{ bind (element,binding ){ element.value = binding.value }, inserted (element,binding ){ element.focus () }, update (element,binding ){ element.value = binding.value } }) new Vue ({ el :'#root' , data :{ name :'尚硅谷' , n :1 }, directives :{ big (element,binding ){ console .log ('big' ,this ) element.innerText = binding.value * 10 }, fbind :{ bind (element,binding ){ element.value = binding.value }, inserted (element,binding ){ element.focus () }, update (element,binding ){ element.value = binding.value } } } }) </script >
自定义指令-总结
配置对象中常用的 3 个回调:
bind:指令与元素成功绑定时调用。
inserted:指令所在元素被插入页面时调用。
update:指令所在模板结构被重新解析时调用。
备注:
指令定义时不加 v-,但使用时要加 v-;
指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。
生命周期 生命周期
引出生命周期
生命周期:
又名:生命周期回调函数、生命周期函数、生命周期钩子。
是什么:Vue 在关键时刻帮我们调用的一些特殊名称的函数。
生命周期函数的名字不可更改,但函数的具体内容是程序员根据需求编写的。
生命周期函数中的 this 指向是 vm 或 组件实例对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <body > <div id ="root" > <h2 v-if ="a" > 你好啊</h2 > <h2 :style ="{opacity}" > 欢迎学习Vue</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ a :false , opacity :1 }, methods : { }, mounted ( ){ console .log ('mounted' ,this ) setInterval (() => { this .opacity -= 0.01 if (this .opacity <= 0 ) this .opacity = 1 },16 ) }, }) </script >
分析生命周期 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <body > <div id ="root" :x ="n" > <h2 v-text ="n" > </h2 > <h2 > 当前的n值是:{{n}}</h2 > <button @click ="add" > 点我n+1</button > <button @click ="bye" > 点我销毁vm</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el : '#root' , 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 >
生命周期总结
常用的生命周期钩子:
mounted: 发送 ajax 请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
关于销毁 Vue 实例
销毁后借助 Vue 开发者工具看不到任何信息。
销毁后自定义事件会失效,但原生 DOM 事件依然有效。
一般不会在 beforeDestroy 操作数据,因为即便操作数据,也不会再触发更新流程了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <body > <div id ="root" > <h2 :style ="{opacity}" > 欢迎学习Vue</h2 > <button @click ="opacity = 1" > 透明度设置为1</button > <button @click ="stop" > 点我停止变换</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false new Vue ({ el :'#root' , data :{ opacity :1 }, methods : { stop ( ){ this .$destroy() } }, mounted ( ){ console .log ('mounted' ,this ) this .timer = setInterval (() => { console .log ('setInterval' ) this .opacity -= 0.01 if (this .opacity <= 0 ) this .opacity = 1 },16 ) }, beforeDestroy ( ) { clearInterval (this .timer ) console .log ('vm即将驾鹤西游了' ) }, }) </script >
组件 对组件的理解
非单文件组件
三、使用组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 <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 const school = Vue .extend ({ template :` <div class="demo"> <h2>学校名称:{{schoolName}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">点我提示学校名</button> </div> ` , data ( ){ return { schoolName :'尚硅谷' , address :'北京昌平' } }, methods : { showName ( ){ alert (this .schoolName ) } }, }) const student = Vue .extend ({ template :` <div> <h2>学生姓名:{{studentName}}</h2> <h2>学生年龄:{{age}}</h2> </div> ` , data ( ){ return { studentName :'张三' , age :18 } } }) const hello = Vue .extend ({ template :` <div> <h2>你好啊!{{name}}</h2> </div> ` , data ( ){ return { name :'Tom' } } }) Vue .component ('hello' ,hello) new Vue ({ el :'#root' , data :{ msg :'你好啊!' }, components :{ school, student } }) new Vue ({ el :'#root2' , }) </script >
组件的几个注意点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 <body > <div id ="root" > <h1 > {{msg}}</h1 > <school > </school > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const s = 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 :s } }) </script >
组件的嵌套 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <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:'北京' } }, //注册组件(局部) 将 student 组件注册为 school 组件的子组件 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 组件注册为 app 组件的子组件 school, hello } }) //创建vm new Vue({ template:'<app > </app > ', el:'#root', //注册组件(局部) components:{app} }) </script >
VueComponent
关于 VueComponent:
school 组件本质是一个名为 VueComponent 的构造函数 ,且不是程序员定义的,是 Vue.extend 生成的。
我们只需要写<school/>
或<school></school>
,Vue 解析时会帮我们创建 school 组件的实例对象,即 Vue 帮我们执行的:new VueComponent(options)。
特别注意:每次调用 Vue.extend,返回的都是一个全新的 VueComponent!!!!
关于 this 指向:
组件配置中:data 函数、methods 中的函数、watch 中的函数、computed 中的函数 它们的 this 均是【VueComponent 实例对象】。
new Vue(options) 配置中:data函数、methods中的函数、watch中的函数、computed中的函数, 它们的 this 均是【Vue 实例对象】。
VueComponent 的实例对象,以后简称 vc(也可称之为:组件实例对象)。Vue 的实例对象,以后简称 vm。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <body > <div id ="root" > <school > </school > <hello > </hello > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false 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>atguigu</span>` }) const hello = Vue .extend ({ template :` <div> <h2>{{msg}}</h2> <test></test> </div> ` , data ( ){ return { msg :'你好啊!' } }, components :{test} }) const vm = new Vue ({ el :'#root' , components :{school,hello} }) </script >
一个重要的内置关系
单文件组件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <template> <div class="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-color: orange; } </style>
通常:有一个 .html 文件,引入 main.js 文件,在 main.js 文件中,创建 Vue 实例对象,将 App.vue 组件注册在 main.js 中,其他次一级的组件注册在 App.vue 组件中
脚手架 安装脚手架
用脚手架创建项目
脚手架结构分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ├── 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:包版本控制文件
项目的运行流程
执行 npm run serve 命令时,会执行 package.json 文件中的 scripts 下的 serve 配置项的 配置命令 : vue-cli-service serve
执行 vue-cli-service serve 命令,就会最先执行 main.js 入口文件
render函数(?)
修改默认配置
vue.config.js 配置文件 入口 关闭eslin校验
ref属性
被用来给元素或子组件注册引用信息(id 的替代者)
应用在 html 标签上获取的是真实 DOM 元素,应用在组件标签上是组件实例对象(vc)
使用方式:
打标识:<h1 ref="xxx">.....</h1>
或 <School ref="xxx"></School>
获取:this.$refs.xxx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <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' 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>
props 配置
功能:让组件接收外部传过来的数据
传递数据:<Demo name="xxx"/>
接收数据:
第一种方式(只接收):props:['name']
第二种方式(限制类型):props:{name:String}
第三种方式(限制类型、必要性、指定默认值):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 //app.vue,向子组件传递数据 <template> <div> <Student name="李四" sex="女" :age="18"/> </div> </template> <script> import Student from './components/Student' export default { name:'App', components:{Student} } </script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 // student组件:接收父组件传递过来的数据 <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:'我是一个尚硅谷的学生', myAge:this.age } }, methods: { updateAge(){ this.myAge++ } }, //01.简单声明接收 // props:['name','age','sex'] //02.接收的同时对数据进行类型限制 /* props:{ name:String, age:Number, sex:String } */ //03.接收的同时对数据:进行类型限制+默认值的指定+必要性的限制 props:{ name:{ type:String, //name的类型是字符串 required:true, //name是必要的 }, age:{ type:Number, default:99 //默认值 }, sex:{ type:String, required:true } } } </script>
备注:props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请复制 props 的内容到 data 中一份,然后去修改 data 中的数据。
mixin混入-全局混合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 export const hunhe = { methods : { showName ( ){ alert (this .name ) } }, mounted ( ) { console .log ('你好啊!' ) }, } export const hunhe2 = { data ( ) { return { x :100 , y :200 } }, }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import Vue from 'vue' import App from './App.vue' import {hunhe,hunhe2} from './mixin' Vue .config .productionTip = false Vue .mixin (hunhe)Vue .mixin (hunhe2)new Vue ({ el :'#app' , render : h => h (App ) })
1 2 3 4 5 6 7 //school.vue: 在子组件中就可以使用混合中的数据和方法 <template > <div > <h2 @click ="showName" > 学校名称:{{name}}</h2 > <h2 > 学校地址:{{address}}</h2 > </div > </template >
mixin混入_局部混合 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template> <div> <!--3.使用混合中的属性和方法,如果有数据冲突,则使用组件data中的数据--> <h2 @click="showName">学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> //1.引入hunhe import {hunhe,hunhe2} from '../mixin' export default { name:'School', data() { return { name:'尚硅谷', address:'北京', x:666 } }, //2.使用混合 mixins:[hunhe,hunhe2], } </script>
插件 创建插件 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 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 .prototype .hello = ()=> {alert ('你好啊' )} } }
导入插件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import Vue from 'vue' import App from './App.vue' import plugins from './plugins' Vue .config .productionTip = false Vue .use (plugins,1 ,2 ,3 )new Vue ({ el :'#app' , render : h => h (App ) })
使用插件
1 2 3 4 5 6 7 8 //student.vue <template > <div > <h2 > 学生姓名:{{name}}</h2 > <h2 > 学生性别:{{sex}}</h2 > <input type ="text" v-fbind:value ="name" > </div > </template >
1 2 3 4 5 6 7 8 //school.vue <template > <div > <h2 > 学校名称:{{name | mySlice}}</h2 > <h2 > 学校地址:{{address}}</h2 > <button @click ="test" > 点我测试一个hello方法</button > </div > </template >
scoped样式
scroped: 添加该属性,则样式只能该组件使用(局部),未添加 scoped 属性,其他组件也能使用该样式(全局)
lang:添加该属性表示语言,如若写 less ,则需要加 lang=”less”, 不写 lang 属性,默认是 css,若直接写 less 代码,就会直接报警告
1 2 3 4 5 6 7 8 <style lang="less" scoped> .demo { background-color : pink; .atguigu { font-size : 40px ; } } </style>
todolist案例 静态组件的拆分
拆分过程省略,仅提供项目结构 和 HTML结构,CSS样式
在 App.vue 中引入注册 MyHeader MyList MyFooter 组件,在 MyLis t 组件中引入注册 MyItem 组件
1 2 3 4 5 6 7 8 - src | - components | ∟ MyHeader .vue | ∟ MyList .vue | ∟ MyFooter .vue | ∟ MyItem .vue | - App .vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <!doctype html > <html lang ="en" > <head > <meta charset ="utf-8" > <title > React App</title > <link rel ="stylesheet" href ="index.css" > </head > <body > <div id ="root" > <div class ="todo-container" > <div class ="todo-wrap" > <div class ="todo-header" > <input type ="text" placeholder ="请输入你的任务名称,按回车键确认" /> </div > <ul class ="todo-main" > <li > <label > <input type ="checkbox" /> <span > xxxxx</span > </label > <button class ="btn btn-danger" style ="display:none" > 删除</button > </li > <li > <label > <input type ="checkbox" /> <span > yyyy</span > </label > <button class ="btn btn-danger" style ="display:none" > 删除</button > </li > </ul > <div class ="todo-footer" > <label > <input type ="checkbox" /> </label > <span > <span > 已完成0</span > / 全部2 </span > <button class ="btn btn-danger" > 清除已完成任务</button > </div > </div > </div > </div > </body > </html >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 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 ; } .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 ); } .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 ; } 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; } .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 ; }
初始化列表
01 在 MyList.vue 中定义数据,并根据数据,动态遍历展示 MyItem 组件,并传递遍历出来的数据给 MyItem 组件
1 2 3 4 5 6 7 <template > <div > <ul class ="todo-main" > <my-item v-for ="todoobj in todos" :key ="todoobj.id" :todo ="todoobj" > </my-item > </ul > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <script> import MyItem from "../components/MyItem.vue" ;export default { name :"MyList" , components : { MyItem , }, data ( ) { return { todos :[ {id :"001" ,title :"抽烟" ,done :true }, {id :"002" ,title :"喝酒" ,done :false }, {id :"003" ,title :"开车" ,done :true }, ] }; }, methods : {}, }; </script>
02 在 MyItem 组件中接收数据,并动态展示数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div> <li> <label> <input type="checkbox" :checked="todo.done"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" style="display: none">删除</button> </li> </div> </template> <script> export default { name:"MyItem", props:["todo"], components: {}, data() { return {}; }, methods: {}, }; </script>
添加功能
01 将上面定义的todos数据,移到 App.vue 组件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <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 :true }, {id :"002" ,title :"喝酒" ,done :false }, {id :"003" ,title :"开车" ,done :true }, ] } }, }; </script >
02 在 App.vue 组件中将 todos 数据传递给 MyList.vue 组件
1 2 3 4 5 6 7 8 9 10 11 <template > <div id ="root" > <div class ="todo-container" > <div class ="todo-wrap" > <my-header > </my-header > <my-list :todos ="todos" > </my-list > <my-footer > </my-footer > </div > </div > </div > </template >
03 在 MyList.vue 组件中接收数据,并动态展示
1 2 3 4 5 6 7 8 9 10 <script > import MyItem from "../components/MyItem.vue" ; export default { name :"MyList" , props :["todos" ], components : { MyItem , }, }; </script >
04 在 App.vue 中传递一个自定义方法给 MyHeader.vue 组件
1 2 3 4 5 6 7 8 9 10 11 <template > <div id ="root" > <div class ="todo-container" > <div class ="todo-wrap" > <my-header :addTodo ="addTodo" > </my-header > <my-list :todos ="todos" > </my-list > <my-footer > </my-footer > </div > </div > </div > </template >
05 在 MyHeader.vue 组件中,
05.1 接收App,vue组件传递过来的自定义方法,
05.2 给输入框添加键盘事件,在事件处理回调中,将用户输入的值,包装成一个对象,
05.3 将对象传递给 App.vue组件传递过来的自定义方法 调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/> </div> </div> </template> <script> import {nanoid} from "nanoid" export default { props:["addTodo"], components: {}, data() { return {title:""}; }, methods: { add(){ //校验数据 if(!this.title.trim()){ return alert("输入不能为空") } //将输入的数据包装成一个todo对象 也可以使用: title:e.target.value const todoObj = {id:nanoid(),title:this.title,done:false} //通知App组件添加一个todo对象 this.addTodo(todoObj) //清空输入框内容 this.title = "" } }, }; </script>
06 在 App.vue 组件中调用 自定义方法,并接收 MyHeader.vue 组件传递过来的参数,并将该参数添加到 todos 数组中
1 2 3 4 5 6 methods : { addTodo (todoObj ){ this .todos .unshift (todoObj) } },
勾选和取消勾选
01 在 MyItem.vue 组件中,
01 给复选框添加事件,并传递 todo.id 给事件回调,
02 接收由 App.vue 组件经逐层传递来的自定义方法
03 在回调函数中触发由 App.vue 组件经逐层传递来的自定义方法,并传递 id 给该方法并调用
1 2 3 4 5 6 7 8 9 10 11 <template > <div > <li > <label > <input type ="checkbox" :checked ="todo.done" @change ="handleCheck(todo.id)" /> <span > {{todo.title}}</span > </label > <button class ="btn btn-danger" style ="display: none" > 删除</button > </li > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script > export default { name :"MyItem" , props :["todo" ,"checkTodo" ], components : {}, data ( ) { return {}; }, methods : { handleCheck (id ){ this .checkTodo (id) } }, }; </script >
02 在 App.vue 组件中,自定义一个方法,用于对done进行取反
注意:
数据在哪里,则操作数据的方法就在哪里
在 MyItem.vue 中双向数据绑定 todo.id 也可以实现,但是不推荐,因为通过 props 接收的数据,不应被修改
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 methods : { addTodo (todoObj ){ this .todos .unshift (todoObj) }, checkTodo (id ){ this .todos .forEach ((todo ) => { if (todo.id === id){ todo.done = !todo.done } }) } },
03 在 App.vue 组件中,将自定义的方法传递给 MyList.vue 组件
1 2 3 4 5 6 7 8 9 10 11 <template > <div id ="root" > <div class ="todo-container" > <div class ="todo-wrap" > <my-header :addTodo ="addTodo" > </my-header > <my-list :todos ="todos" :checkTodo ="checkTodo" > </my-list > <my-footer > </my-footer > </div > </div > </div > </template >
04 在 MyList.vue 组件中接收 App.vue 组件 传递过来的自定义 checkTodo 方法,并再次传递给 MyItem.vue 组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template > <div > <ul class ="todo-main" > <my-item v-for ="todoobj in todos" :key ="todoobj.id" :todo ="todoobj" :checkTodo ="checkTodo" > </my-item > </ul > </div > </template > <script > import MyItem from "../components/MyItem.vue" ;export default { name :"MyList" , props :["todos" ,"checkTodo" ], components : { MyItem , }, }; </script >
05 在MyItem.vue 组件中接收并调用 自定义 checkTodo 方法 (同01)
删除功能
01 在MyItem.vue 组件中默认隐藏删除按钮(略),当鼠标移动到li标签上时,添加li的背景色,并显示button按钮:
1 2 3 4 5 6 li :hover { background-color : #ddd ; } li :hover button { display : block; }
02 在MyItem.vue 组件中,给button按钮添加点击事件,并将todo.id传递给事件回调
1 2 3 4 5 6 7 8 9 10 11 <template > <div > <li > <label > <input type ="checkbox" :checked ="todo.done" @change ="handleCheck(todo.id)" /> <span > {{todo.title}}</span > </label > <button class ="btn btn-danger" @click ="handleDelete(todo.id)" > 删除</button > </li > </div > </template >
03 在MyItem.vue 组件中,设置事件回调,并传递id给由 App.vue 逐层传递过来的 deleteTodo 自定义事件,并调用该事件
1 2 3 4 5 6 7 handleDelete (id ){ if (confirm ('确定删除吗?' )){ this .deleteTodo (id) } }
04 在 App.vue 组件中,自定义方法 deleteTodo ,根据传递过来的id值,过滤 todos
1 2 3 4 5 6 deleteTodo (id ){ this .todos = this .todos .filter ((todo ) => { return todo.id !== id }) }
05 在App.vue 组件中,将自定义方法 deleteTodo 逐层传递给 MyItem.vue 组件
1 2 3 4 5 6 7 8 9 10 11 <template > <div id ="root" > <div class ="todo-container" > <div class ="todo-wrap" > <my-header :addTodo ="addTodo" > </my-header > <my-list :todos ="todos" :checkTodo ="checkTodo" :deleteTodo ="deleteTodo" > </my-list > <my-footer > </my-footer > </div > </div > </div > </template >
06 在MyItem.vue 组件中接收由 App.vue 逐层传递过来的 deleteTodo 自定义事件,并触发(同03)
1 props :["todo" ,"checkTodo" ,"deleteTodo" ],
底部统计
01 在App.vue 组件中,将todos数据传递给 MyFooter.vue 组件
1 <my-footer :todos ="todos" > </my-footer >
02 在 MyFooter.vue 组件中接收 todos 数据,并根据todos数据得出数组长度,并计算统计出已完成的个数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <template> <div> <div class="todo-footer"> <label> <input type="checkbox" /> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{todos.length}}</span> <button class="btn btn-danger">清除已完成任务</button> </div> </div> </template> <script> export default { props:["todos"], computed:{ doneTotal(){ return this.todos.reduce((pre,current) => { return pre + (current.done ? 1 : 0) },0) } } }; </script>
底部交互
01 在 MyFooter.vue 组件中,通过计算属性,计算出总个数和已完成个数
02 根据总个数和已完成个数是否相等,得出全选框的true/false,并绑定到全选框的checked属性
03 给全选框添加事件,并在事件回调中将checked的值,通过App.vue传递过来的自定义方法checkAlltodo,传递给App.vue
04 在App.vue 的自定义方法checkAlltodo中,根据全选复选框的true/false,遍历todos,并修改done,并将checkAlltodo自定义方法传递给 MyFooter.vue 组件
05 在 MyFooter.vue 组件中,给清除已完成任务的button按钮添加事件 clearAll ,并在事件回调中触发 App.vue传递过来的自定义方法 clearAllTodo
06 在App.vue 的自定义方法clearAllTodo中,对todos数组进行筛选,清除 done 值为 true 的数据
MyFooter.vue 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 <template> <div> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" :checked="isAll" @change="checkAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}}</span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </div> </template> <script> export default { props:["todos","checkAlltodo","clearAllTodo"], computed:{ //计算总个数 total(){ return this.todos.length }, //计算已完成个数 doneTotal(){ return this.todos.reduce((pre,current) => { return pre + (current.done ? 1 : 0) },0) }, //当已完成个数与总个数相等时,并且总数大于0时,设置全选框为true isAll(){ return this.doneTotal === this.total && this.total > 0 } }, methods: { //全选 checkAll(e){ this.checkAlltodo(e.target.checked) }, //清除已完成 clearAll(){ this.clearAllTodo() } }, }; </script>
App.vue 组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <my-header :addTodo="addTodo"></my-header> <my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list> <my-footer :todos="todos" :checkAlltodo="checkAlltodo" :clearAllTodo="clearAllTodo"></my-footer> </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:true}, {id:"002",title:"喝酒",done:false}, {id:"003",title:"开车",done:true}, ] } }, methods: { //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选 or 取消勾选 checkTodo(id){ //对todos中的每一项进行遍历,根据传递过来的id进行done的取反 this.todos.forEach((todo) => { if(todo.id === id){ todo.done = !todo.done } }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter((todo) => { return todo.id !== id }) }, //全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done checkAlltodo(done){ this.todos.forEach((todo) => { todo.done = done }) }, //清除所有已完成的 clearAllTodo(){ this.todos = this.todos.filter((todo) => { return !todo.done }) } }, }; </script>
完整代码App 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <my-header :addTodo="addTodo"></my-header> <my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list> <my-footer :todos="todos" :checkAlltodo="checkAlltodo" :clearAllTodo="clearAllTodo"></my-footer> </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:true}, {id:"002",title:"喝酒",done:false}, {id:"003",title:"开车",done:true}, ] } }, methods: { //添加一个todo addTodo(todoObj){ this.todos.unshift(todoObj) }, //勾选 or 取消勾选 checkTodo(id){ //对todos中的每一项进行遍历,根据传递过来的id进行done的取反 this.todos.forEach((todo) => { if(todo.id === id){ todo.done = !todo.done } }) }, //删除一个todo deleteTodo(id){ this.todos = this.todos.filter((todo) => { return todo.id !== id }) }, //全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done checkAlltodo(done){ this.todos.forEach((todo) => { todo.done = done }) }, //清除所有已完成的 clearAllTodo(){ 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <template> <div> <div class="todo-header"> <input type="text" placeholder="请输入你的任务名称,按回车键确认" v-model="title" @keyup.enter="add"/> </div> </div> </template> <script> import {nanoid} from "nanoid" export default { props:["addTodo"], components: {}, data() { return {title:""}; }, methods: { add(){ //校验数据 if(!this.title.trim()){ return alert("输入不能为空") } //将输入的数据包装成一个todo对象 也可以使用: title:e.target.value 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>
完整代码MyList 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <div> <ul class="todo-main"> <my-item v-for="todoobj in todos" :key="todoobj.id" :todo="todoobj" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-item> </ul> </div> </template> <script> import MyItem from "../components/MyItem.vue"; export default { name:"MyList", props:["todos","checkTodo","deleteTodo"], components: { MyItem, }, }; </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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 <template> <div> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)"/> <span>{{todo.title}}</span> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)">删除</button> </li> </div> </template> <script> export default { name:"MyItem", props:["todo","checkTodo","deleteTodo"], components: {}, data() { return {}; }, methods: { //勾选or取消勾选 handleCheck(id){ this.checkTodo(id) }, //删除 handleDelete(id){ if(confirm('确定删除吗?')){ //通知App.vue,删除todos中的指定id的数据 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>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 <template> <div> <div class="todo-footer" v-show="total"> <label> <input type="checkbox" :checked="isAll" @change="checkAll"/> </label> <span> <span>已完成{{doneTotal}}</span> / 全部{{total}}</span> <button class="btn btn-danger" @click="clearAll">清除已完成任务</button> </div> </div> </template> <script> export default { props:["todos","checkAlltodo","clearAllTodo"], computed:{ //计算总个数 total(){ return this.todos.length }, //计算已完成个数 doneTotal(){ return this.todos.reduce((pre,current) => { return pre + (current.done ? 1 : 0) },0) }, //当已完成个数与总个数相等时,并且总数大于0时,设置全选框为true isAll(){ return this.doneTotal === this.total && this.total > 0 } }, 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>
WebStorage
localStorage本地存储 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <body > <h2 > localStorage</h2 > <button onclick ="saveData()" > 点我保存一个数据</button > <button onclick ="readData()" > 点我读取一个数据</button > <button onclick ="deleteData()" > 点我删除一个数据</button > <button onclick ="deleteAllData()" > 点我清空一个数据</button > <script type ="text/javascript" > let p = {name :'张三' ,age :18 } function saveData ( ){ localStorage .setItem ('msg' ,'hello!!!' ) localStorage .setItem ('msg2' ,666 ) localStorage .setItem ('person' ,JSON .stringify (p)) } function readData ( ){ console .log (localStorage .getItem ('msg' )) console .log (localStorage .getItem ('msg2' )) const result = localStorage .getItem ('person' ) console .log (JSON .parse (result)) } function deleteData ( ){ localStorage .removeItem ('msg2' ) } function deleteAllData ( ){ localStorage .clear () } </script > </body >
sessionStorage会话存储 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <body > <h2 > sessionStorage</h2 > <button onclick ="saveData()" > 点我保存一个数据</button > <button onclick ="readData()" > 点我读取一个数据</button > <button onclick ="deleteData()" > 点我删除一个数据</button > <button onclick ="deleteAllData()" > 点我清空一个数据</button > <script type ="text/javascript" > let p = {name :'张三' ,age :18 } function saveData ( ){ sessionStorage .setItem ('msg' ,'hello!!!' ) sessionStorage .setItem ('msg2' ,666 ) sessionStorage .setItem ('person' ,JSON .stringify (p)) } function readData ( ){ console .log (sessionStorage .getItem ('msg' )) console .log (sessionStorage .getItem ('msg2' )) const result = sessionStorage .getItem ('person' ) console .log (JSON .parse (result)) } function deleteData ( ){ sessionStorage .removeItem ('msg2' ) } function deleteAllData ( ){ sessionStorage .clear () } </script > </body >
todolist案例的本地存储
01 深度监视todos
02 将todos的最新值,转为json对象,保存到本地
03 读取本地存储的todos,后面的空数组表示本地存储没有值时的默认值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 <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 : { addTodo (todoObj ){ this .todos .unshift (todoObj) }, checkTodo (id ){ this .todos .forEach ((todo ) => { if (todo.id === id){ todo.done = !todo.done } }) }, deleteTodo (id ){ this .todos = this .todos .filter ((todo ) => { return todo.id !== id }) }, checkAlltodo (done ){ this .todos .forEach ((todo ) => { todo.done = done }) }, clearAllTodo ( ){ this .todos = this .todos .filter ((todo ) => { return !todo.done }) } }, watch :{ todos :{ deep :true , handler (value ){ localStorage .setItem ('todos' ,JSON .stringify (value)) } } } }; </script >
自定义事件 自定义方法_子传父组件
01 在父组件 App.vue 中定义个一个自定义方法,并传递给子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="app"> <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --> <!--2.传递自定义方法--> <School :getSchoolName="getSchoolName" /> </div> </template> <script> import School from "./components/School"; export default { name: "App", components: { School }, methods: { //1.定义自定义方法 //5.通过形参接收子组件传递的数据 getSchoolName(name) { console.log("App收到了学校名:", name); } }, }; </script>
02 在子组件 School.vue 中通过 props 接收父组件传递过来的自定义事件
03 在事件回调方法中,调用父组件传递过来的自定义事件,并将子组件中的数据作为参数传递给父组件,在父组件的 methods 方法定义中通过形参接收
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="sendSchoolName">把学校名称给App</button> </div> </template> <script> export default { name:'School', //3.接收自定义方法 props:["getSchoolName"], data() { return { name:'尚硅谷', address:'北京', } }, methods: { sendSchoolName(){ //4.在事件回调中调用自定义方法并传参 this.getSchoolName(this.name) } }, } </script>
自定义事件_子传父组件01
01 在子组件 Student.vue 中的点击事件回调中,通过 $emit 方法发射自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <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() { //(触发Student组件实例身上的atguigu事件) //1.子组件中触发一个自定义事件,并携带参数 this.$emit("atguigu",this.name); }, }, }; </script>
02 在父组件 App.vue 中,给子组件的组件实例对象绑定一个自定义事件
03 在自定义事件的回调中,接收子组件传递的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <template> <div class="app"> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第一种写法,使用@或v-on) --> <!--(给子组件的组件实例对象绑定自定义事件)--> <!-- 2.在父组件中,在子组件的组件实例对象上绑定自定义事件,并设置事件回调--> <Student v-on:atguigu.once="getStudentName"/> </div> </template> <script> import Student from "./components/Student"; export default { name: "App", components: { Student }, methods: { //3.自定义事件的回调,并接收子组件传递的参数 getStudentName(name,...params) { console.log("App收到了学生名:", name,params); }, }, }; </script>
自定义事件_子传父组件02
01 在子组件 Student.vue 中的点击事件回调中,通过 $emit 方法发射自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <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() { //(触发Student组件实例身上的atguigu事件) //1.子组件中触发一个自定义事件,并携带参数 this.$emit("atguigu",this.name); }, }, }; </script>
02 当 App.vue 组件挂载完毕时,触发自定义事件及其回调,(可以加定时器延时触发自定义事件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 <template> <div class="app"> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) --> <Student ref="student"/> </div> </template> <script> import Student from "./components/Student"; export default { name: "App", components: { Student }, methods: { getStudentName(name,...params) { console.log("App收到了学生名:", name,params); }, }, mounted() { // 当组件挂载完毕时,触发自定义事件及其回调,(可以加定时器延时触发自定义事件) // 若第二个参数写成普通函数,则函数内的this指向触发自定义事件atguigu的组件实例对象 // 若第二个参数写成箭头函数,这函数类的this指向当前的组件实例对象 this.$refs.student.$on('atguigu',this.getStudentName)//推荐写法,可以多次触发该自定义事件 // this.$refs.student.$once('atguigu',this.getStudentName)//只能触发一次该自定义事件 }, }; </script>
解绑自定义事件
01 this.$off(“atguigu”) 解绑单个自定义事件
02 this.$off([“atguigu”,”demo”]) 解绑多个自定义事件
03 this.$off() 解绑所有自定义事件
04 this.$destroy(); 销毁组件实例也会解绑所有自定义事件
Student.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="student"> <h2>学生姓名:{{ name }}</h2> <h2>学生性别:{{ sex }}</h2> <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: "男", }; }, methods: { sendStudentName() { //触发Student组件实例身上的atguigu事件 this.$emit("atguigu", this.name); this.$emit("demo"); }, unbind() { //this.$off("atguigu")//1.解绑单个自定义事件 //this.$off(["atguigu","demo"])//2.解绑多个自定义事件 this.$off(); //3.解绑所有自定义事件 }, death() { //销毁当前Student组件的实例,销毁后所有Student实例的自定义事件全都失效。 //(数据的响应式失效,但是原生的点击事件还能用) //5.若 App.vue 组件被销毁,则其所有的子组件实例也会被销毁 this.$destroy(); //4.销毁组件实例 }, }, }; </script>
.native修饰符
01 若给组件标签添加原生 dom 事件,则会被解析成自定义事件,需要使用自定义事件的触发方法触发
02 添加 .native
修饰符,就能作为原生 dom 事件使用
1 <Student ref="student" @click.native="show"/>
todolist案例_自定义事件
1 2 3 4 5 6 7 8 9 10 11 12 13 <template> <div id="root"> <div class="todo-container"> <div class="todo-wrap"> <!--1.绑定自定义事件 addTodo --> <my-header @addTodo="addTodo"></my-header> <my-list :todos="todos" :checkTodo="checkTodo" :deleteTodo="deleteTodo"></my-list> <!--2.绑定自定义事件 checkAlltodo 和 clearAllTodo --> <my-footer :todos="todos" @checkAlltodo="checkAlltodo" @clearAllTodo="clearAllTodo"></my-footer> </div> </div> </div> </template>
02 MyHeader.vue: 不再需要props接收,仅需在对应的键盘事件回调中发射自定义事件即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <script> import {nanoid} from "nanoid" export default { components: {}, data() { return {title:""}; }, methods: { add(){ //校验数据 if(!this.title.trim()){ return alert("输入不能为空") } //将输入的数据包装成一个todo对象 也可以使用: title:e.target.value const todoObj = {id:nanoid(),title:this.title,done:false} //通知App组件添加一个todo对象 //3.发射自定义事件 this.$emit('addTodo',todoObj) //清空输入框内容 this.title = "" } }, }; </script>
03 **Myfooter.vue: **不再需要props接收,仅需在对应的键盘事件回调中发射自定义事件即可
1 2 3 4 5 6 7 8 9 10 methods : { checkAll (e ){ this .$emit("checkAlltodo" ,e.target .checked ) }, clearAll ( ){ this .$emit("clearAllTodo" ) } },
全局事件总线 全局事件总线的作用
全局事件总线的使用
01 在入口文件 main.js 中安装全局事件总线
1 2 3 4 5 6 new Vue ({ render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this }, }).$mount('#app' )
02 在 student.vue 组件中: 在提供数据的组件中,发射一个自定义事件,并携带参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <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() { //2.发射一个自定义事件,并携带参数 this.$bus.$emit("hello", this.name); }, }, }; </script>
03 在 school.vue 组件中: 在需要数据的组件中触发一个自定义事件,并接收参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <template> <div class="school"> <h2>学校名称:{{ name }}</h2> <h2>学校地址:{{ address }}</h2> </div> </template> <script> export default { name: "School", data() { return { name: "尚硅谷", address: "北京", }; }, mounted() { //3.触发一个自定义事件,并接收参数 this.$bus.$on("hello", (data) => { console.log("我是School组件,收到了数据", data); }); }, beforeDestroy() { //4.在组件实例销毁前解绑自定义事件 this.$bus.$off("hello"); }, }; </script>
todolist案例-全局事件总线
01 在入口文件 main.js 中安装全局事件总线
1 2 3 4 5 6 new Vue ({ render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this }, }).$mount('#app' )
02 在 App.vue 组件中的生命周期函数中:即获取数据的一方
03 绑定自定义事件及其回调函数,以及解绑自定义事件,无需再传递两个方法给 MyList.vue 组件
1 2 3 4 5 6 7 8 9 10 mounted ( ) { this .$bus .$on('checkTodo' ,this .checkTodo ) this .$bus .$on('deleteTodo' ,this .deleteTodo ) }, beforeDestroy ( ) { this .$bus .$off("checkTodo" ) this .$bus .$off("deleteTodo" ) },
04 在 MyItem.vue 组件中:在事件回调函数中触发自定义事件,并携带参数,即提供数据的一方
1 2 3 4 5 6 7 8 9 10 11 12 13 14 methods : { handleCheck (id ) { this .$bus .$emit("checkTodo" , id); }, handleDelete (id ) { if (confirm ("确定删除吗?" )) { this .$bus .$emit("deleteTodo" , id); } }, },
消息的订阅与发布 消息订阅与发布的作用
消息订阅与发布的使用
01 安装 (npm i pubsub-js) 并导入 pubsub-js 包
02 订阅消息,一但订阅的消息发布,就执行后面的回调,返回一个 id,返回的 id 用于取消订阅
04 取消消息的订阅
School.vue: 即数据的接收者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <template> <div class="school"> <h2>学校名称:{{ name }}</h2> <h2>学校地址:{{ address }}</h2> </div> </template> <script> //1.npm i pubsub-js 并导入pubsub-js包 import pubsub from 'pubsub-js' export default { name: "School", data() { return { name: "尚硅谷", address: "北京", }; }, mounted() { //2.订阅了hello消息,一但发布,就执行后面的回调,返回一个id,返回的id用于取消订阅, // 即消息的接受者 // 对于回调函数:写成普通函数,这this为undefined,写成箭头函数,this指向组件实例对象 // 事件回调也可以写在 methods 中,就没有this指向混乱的问题 // 回调参数一:消息名 参数二:传递过来的数据 this.pubId = pubsub.subscribe('hello',(msgName,data) => { console.log(this);//VueComponent console.log("发布了hello消息,回调执行~~",msgName,data); }) }, beforeDestroy() { //4.取消消息的订阅 pubsub.unsubscribe(this.pubId) }, }; </script>
Student.vue: 即数据的提供者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 <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() { //03.发布了hello的消息,并携带参数,即消息的提供者 pubsub.publish("hello",this.name) }, }, }; </script>
todolist案例_订阅与发布
01 在 App.vue 组件中导入 pubsub 包
1 import pubsub from 'pubsub-js'
02 在 App.vue 组件生命周期函数中订阅和取消订阅消息(需要在deleteTodo方法的形参最前面添加一个下划线占位,因为该回调有两个参数)
1 2 3 4 5 6 7 8 9 10 11 12 mounted ( ) { this .$bus .$on('checkTodo' ,this .checkTodo ) this .pubId = pubsub.subscribe ("deleteTodo" ,this .deleteTodo ) }, beforeDestroy ( ) { this .$bus .$off("checkTodo" ) pubsub.unsubscribe (this .pubId ) },
03 在 MyItem.vue 组件中导入pubsub 包
1 import pubsub from "pubsub-js" ;
04 在 MyItem.vue 组件中,在点击事件的回调函数中发布消息
1 2 3 4 5 6 7 handleDelete (id ) { if (confirm ("确定删除吗?" )) { pubsub.publish ("deleteTodo" , id); } },
todolist的编辑_$nextTick
01 添加编辑按钮, (DOM 结构见后面,提供 CSS 样式,CSS 样式放在 App.vue 中)
1 2 3 4 5 6 .btn-edit { color : #fff ; background-color : #49a5da ; border : 1px solid #1c69c0 ; margin-right : 4px ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 <template> <div> <li> <label> <input type="checkbox" :checked="todo.done" @change="handleCheck(todo.id)" /> <span v-show="!todo.isEdit">{{ todo.title }}</span> <!-- 04 添加input输入框并绑定失去焦点事件及其回调 --> <!-- 05 通过todo.isEdit控制输入框和文字的交替显示--> <input v-show="todo.isEdit" type="text" :value="todo.title" @blur="handleBlur(todo,$event)" ref="inputTitle" /> </label> <button class="btn btn-danger" @click="handleDelete(todo.id)"> 删除 </button> <!-- 01.添加编辑按钮并绑定点击事件及其回调 --> <button class="btn btn-edit" @click="handleEdit(todo)" v-show="!todo.isEdit">编辑</button> </li> </div> </template> <script> import pubsub from "pubsub-js"; export default { name: "MyItem", props: ["todo"], components: {}, data() { return {}; }, methods: { //勾选or取消勾选 handleCheck(id) { this.$bus.$emit("checkTodo", id); }, //删除 handleDelete(id) { if (confirm("确定删除吗?")) { pubsub.publish("deleteTodo", id); } }, //编辑 //02 编辑按钮的点击事件回调 handleEdit(todo) { //03 判断todo对象中是否有isEdit属性 if (Object.prototype.hasOwnProperty.call(todo,'isEdit')) { todo.isEdit = true; } else { this.$set(todo, "isEdit", true); } //DOM节点更新到页面完毕后执行回调 this.$nextTick(function(){ this.$refs.inputTitle.focus() }) }, //06.输入框失去焦点事件回调 handleBlur(todo,e) { //6.1 isEdit 控制输入框的隐藏,文字的显示 todo.isEdit = false; //6.2 判空校验 if(!e.target.value.trim()) return alert('输入不能为空!') //6.3 通过全局事件总线,发射一个自定义事件,并携带参数 this.$bus.$emit("updateTodo",todo.id,e.target.value) }, }, }; </script>
03 在App.vue 中绑定自定义事件,修改todo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 <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 { //03 读取本地存储的todos,后面的空数组表示本地存储没有值时的默认值 todos: JSON.parse(localStorage.getItem("todos")) || [], }; }, methods: { //添加一个todo addTodo(todoObj) { this.todos.unshift(todoObj); }, //勾选 or 取消勾选 checkTodo(id) { //对todos中的每一项进行遍历,根据传递过来的id进行done的取反 this.todos.forEach((todo) => { if (todo.id === id) { todo.done = !todo.done; } }); }, //09.更新todo updateTodo(id,title) { //对todos中的每一项进行遍历,根据传递过来的id进行done的取反 this.todos.forEach((todo) => { if (todo.id === id) { todo.title = title } }); }, //删除一个todo deleteTodo(_, id) { this.todos = this.todos.filter((todo) => { return todo.id !== id; }); }, //全选 or 取消全选 根据全选复选框的true/false,遍历todos,并修改done checkAlltodo(done) { this.todos.forEach((todo) => { todo.done = done; }); }, //清除所有已完成的 clearAllTodo() { this.todos = this.todos.filter((todo) => { return !todo.done; }); }, }, watch: { //01 深度监视todos todos: { deep: true, handler(value) { //02 将todos的最新值保存到本地 localStorage.setItem("todos", JSON.stringify(value)); }, }, }, mounted() { //绑定自定义事件 this.$bus.$on("checkTodo", this.checkTodo); //订阅消息 this.pubId = pubsub.subscribe("deleteTodo", this.deleteTodo); //07. 绑定updateTodo自定义事件及其回调 this.$bus.$on("updateTodo", this.updateTodo); }, beforeDestroy() { //解绑自定义事件 this.$bus.$off("checkTodo"); //取消消息的订阅 pubsub.unsubscribe(this.pubId); //08. 解绑updateTodo自定义事件 this.$bus.$off("updateTodo"); }, }; </script>
过渡与动画 动画1
01 那个结构需要动画,就用 transition 包裹即可,transition 标签不会渲染到页面中
02 name 属性用于设置对应动画的类名,不写 name 属性,则使用类名: .v-enter-active{ },若有多个动画,会有影响
03 appear 属性:用于控制页面刷新就立即执行一次进入动画
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 <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>
动画2 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <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,.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>
动画3_多个元素的动画
01 使用 transition-group 标签,且每个元素都要指定 key 值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="!isShow" key="1">你好啊!</h1> <h1 v-show="isShow" key="2">尚硅谷!</h1> </transition-group> </div> </template> <script> export default { name:'Test', data() { return { isShow:true } }, } </script> <style scoped> h1{ background-color: orange; } /* 进入的起点、离开的终点 */ .hello-enter,.hello-leave-to{ transform: translateX(-100%); } .hello-enter-active,.hello-leave-active{ transition: 0.5s linear; } /* 进入的终点、离开的起点 */ .hello-enter-to,.hello-leave{ transform: translateX(0); } </style>
第三方动画库的使用
animate.css : https://animate.style/
01 安装: npm install animate.css –save
02 导入: import “animate.css”;
03 在 transition 标签的 name 属性添加 animate_ _animated animate__bounce
04 所要使用的动画效果的类名,写在 enter-active-class 和 leave-active-class 中,类名去 https://animate.style/ 找
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__rubberBand" leave-active-class="animate__backOutUp" > <h1 v-show="isShow" key="2">尚硅谷!</h1> </transition-group> </div> </template> <script> import "animate.css"; export default { name: "Test", data() { return { isShow: true, }; }, }; </script> <style scoped> h1 { background-color: orange; } </style>
todolist案例_动画
01 添加动画css样式
02 给MyItem组件添加动画
MyList.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <template> <div> <ul class="todo-main"> <transition-group name="todo" appear> <MyItem v-for="todoObj in todos" :key="todoObj.id" :todo="todoObj" /> </transition-group> </ul> </div> </template> <script> import MyItem from "../components/MyItem.vue"; export default { name:"MyList", props:["todos"], components: { MyItem, }, }; </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; } .todo-enter-active{ animation: atguigu 0.5s linear; } .todo-leave-active{ animation: atguigu 0.5s linear reverse; } @keyframes atguigu { from{ transform: translateX(100%); } to{ transform: translateX(0px); } } </style>
配置代理服务器
01 什么是跨域: 协议,主机,端口号只要有一个不同,就算跨域
02 跨域是发起了请求,服务器收到了请求,作出响应返回了数据,但是浏览器发现跨域了,不给用户显示数据
axios的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <template> <div id="app"> <button @click="getStudents">获取学生信息</button> </div> </template> <script> import axios from 'axios' export default { name: 'App', methods: { getStudents(){ // 此处的地址是本地代理服务器的地址,代理服务器的地址,与本地服务器地址一样,但是需要写完整接口路径 axios.get("http://localhost:8080/students").then((res) => { console.log("请求数据成功",res.data); },(err) => { console.log("请求数据失败",err.message); }) } }, } </script> <style> </style>
配置代理服务器
01 若请求的资源,在本地(8080 端口)有,就不会走代理服务器,比如 public 文件夹中的资源,(该文件夹中的资源,都算是 8080 端口的资源) ,
02 不能配置多个目标服务器,即只能配置一个代理,且无法灵活控制是否走代理
在 vue.config.js 中:
1 2 3 4 5 6 7 8 module .exports = { devServer : { proxy : 'http://localhost:5000' , } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const express = require ('express' )const app = express ()app.use ((request,response,next )=> { console .log ('有人请求服务器1了' ); next () }) app.get ('/students' ,(request,response )=> { const students = [ {id :'001' ,name :'tom' ,age :18 }, {id :'002' ,name :'jerry' ,age :19 }, {id :'003' ,name :'tony' ,age :120 }, ] response.send (students) }) app.listen (5000 ,(err )=> { if (!err) console .log ('服务器1启动成功了,请求学生信息地址为:http://localhost:5000/students' ); })
配置多代理服务器
01 前端发起请求 (向前端本地接口发起请求即可,前缀要紧跟端口号)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <template> <div id="app"> <button @click="getStudents">获取学生信息</button> <button @click="getCars">获取汽车信息</button> </div> </template> <script> import axios from 'axios' export default { name: 'App', methods: { getStudents(){ axios.get("http://localhost:8080/atguigu/students").then((res) => { console.log("请求数据成功",res.data); },(err) => { console.log("请求数据失败",err.message); }) }, getCars(){ axios.get("http://localhost:8080/demo/cars").then((res) => { console.log("请求数据成功",res.data); },(err) => { console.log("请求数据失败",err.message); }) } }, } </script>
02 在 vue.config.js 中配置多代理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 module .exports = { devServer : { proxy : { "/atguigu" : { target : "http://localhost:5000" , pathRewrite : { "^/atguigu" : "" }, ws : true , changeOrigin : false , }, "/demo" : { target : "http://localhost:5001" , pathRewrite : { "^/demo" : "" }, ws : true , changeOrigin : false , }, }, }, };
gitHub案例
App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="container"> <Search></Search> <List></List> </div> </template> <script> import Search from "./components/Search.vue"; import List from "./components/List.vue"; export default { components: { Search, List, }, methods: {}, }; </script> <style></style>
List.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <template> <div> <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">加载中...</h1> <!-- 展示错误信息 --> <h1 v-show="info.errMsg">{{ info.errMsg }}</h1> </div> </div> </template> <script> export default { name: "list", components: {}, data() { return { info: { users: [], isFirst: true, isLoading: false, errMsg: "", }, }; }, methods: {}, mounted() { this.$bus.$on("updateListData", (dataObj) => { this.info = { ...this.info, ...dataObj }; }); }, }; </script> <style scoped> .album { min-height: 50rem; /* Can be removed; just added for demo purposes */ padding-top: 3rem; padding-bottom: 3rem; background-color: #f7f7f7; } .card { float: left; width: 33.333%; padding: 0.75rem; margin-bottom: 2rem; border: 1px solid #efefef; text-align: center; } .card > img { margin-bottom: 0.75rem; border-radius: 100px; } .card-text { font-size: 85%; } </style>
Search.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <template> <div> <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> </div> </template> <script> import axios from "axios"; export default { name: "search", components: {}, 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( (res) => { //请求成功后更新List的数据 this.$bus.$emit("updateListData", { isLoading: false, errMsg: "", users: res.data.items, }); }, (err) => { //请求失败后更新List的数据 this.$bus.$emit("updateListData", { isLoading: false, errMsg: err.message, users: [], }); } ); }, }, }; </script> <style scoped lang="less"></style>
main.js:
1 2 3 4 5 6 new Vue ({ render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this } }).$mount('#app' )
vue-resource插件发起请求
1 2 3 4 5 6 7 import vueResource from "vue-resource" ;Vue .config .productionTip = false ;Vue .use (vueResource);
03 发起请求 与上面gitHub案例的不同之处就是不用再引入axios,发起请求时只需要将 axios 字样改成 this.$http 即可,其他一模一样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 this .$http .get (`https://api.github.com/search/users?q=${this .keyword} ` ).then ( (res ) => { this .$bus .$emit("updateListData" , { isLoading : false , errMsg : "" , users : res.data .items , }); }, (err ) => { this .$bus .$emit("updateListData" , { isLoading : false , errMsg : err.message , users : [], }); } );
插槽 默认插槽 App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <template> <div class="container"> <Category title="美食" > <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt=""> </Category> <Category title="游戏" > <ul> <li v-for="(g,index) in games" :key="index">{{g}}</li> </ul> </Category> <Category title="电影"> <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, data() { return { foods:['火锅','烧烤','小龙虾','牛排'], games:['红色警戒','穿越火线','劲舞团','超级玛丽'], films:['《教父》','《拆弹专家》','《你好,李焕英》','《尚硅谷》'] } }, } </script> <style scoped> .container{ display: flex; justify-content: space-around; } </style>
Category.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot> </div> </template> <script> export default { name:'Category', props:['title'] } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
具名插槽 App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 <template> <div class="container"> <Category title="美食"> <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="" /> <a slot="footer" href="http://www.atguigu.com">更多美食</a> </Category> <Category title="游戏"> <ul slot="center"> <li v-for="(g, index) in games" :key="index">{{ g }}</li> </ul> <div class="foot" slot="footer"> <a href="http://www.atguigu.com">单机游戏</a> <a href="http://www.atguigu.com">网络游戏</a> </div> </Category> <Category title="电影"> <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4" ></video> <template v-slot:footer> <div class="foot"> <a href="http://www.atguigu.com">经典</a> <a href="http://www.atguigu.com">热门</a> <a href="http://www.atguigu.com">推荐</a> </div> <h4>欢迎前来观影</h4> </template> </Category> </div> </template> <script> import Category from "./components/Category"; export default { name: "App", components: { Category }, data() { return { foods: ["火锅", "烧烤", "小龙虾", "牛排"], games: ["红色警戒", "穿越火线", "劲舞团", "超级玛丽"], films: ["《教父》", "《拆弹专家》", "《你好,李焕英》", "《尚硅谷》"], }; }, }; </script> <style scoped> .container, .foot { display: flex; justify-content: space-around; } h4 { text-align: center; } </style>
Category.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 <template> <div class="category"> <h3>{{title}}分类</h3> <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) --> <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot> <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot> </div> </template> <script> export default { name:'Category', props:['title'] } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>
作用域插槽 App.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template> <div class="container"> <Category title="游戏"> <template scope="atguigu"> <ul> <li v-for="(g,index) in atguigu.games" :key="index">{{g}}</li> </ul> </template> </Category> <Category title="游戏"> <template scope="{games}"> <ol> <li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li> </ol> </template> </Category> <Category title="游戏"> <template slot-scope="{games}"> <h4 v-for="(g,index) in games" :key="index">{{g}}</h4> </template> </Category> </div> </template> <script> import Category from './components/Category' export default { name:'App', components:{Category}, } </script> <style scoped> .container,.foot{ display: flex; justify-content: space-around; } h4{ text-align: center; } </style>
Category.vue:
通过作用域插槽,将组件 Category.vue 中的数据通过插槽传递给 App.vue 组件的的使用者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 <template> <div class="category"> <h3>{{title}}分类</h3> <slot :games="games" msg="hello">我是默认的一些内容</slot> </div> </template> <script> export default { name:'Category', props:['title'], data() { return { games:['红色警戒','穿越火线','劲舞团','超级玛丽'], } }, } </script> <style scoped> .category{ background-color: skyblue; width: 200px; height: 300px; } h3{ text-align: center; background-color: orange; } video{ width: 100%; } img{ width: 100%; } </style>