特点
采用组件化模式,提高代码复用率,且让代码更好维护
声明式编码而非命令式编码,让编码人员无需直接操作 DOM,提高开发效率
使用虚拟 DOM 加优秀的 Diff 算法,尽量复用 DOM 节点
模板语法
数据绑定,主要通过 css 选择器来进行标签属性对象绑定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Vue .config .productionTip =false const v=new Vue ({ el :"#root" , data :{ name :"ivan" , url :"http://wanstarge.github.io" } data :function ( ){ return { name :"ivan" } } }) v.$mount("#root" );
attention: 由 Vue 所管理的函数比如 data,一定不要写箭头函数,一旦写了箭头函数,this 就不再是 Vue 实例了
插值语法:用于解析标签体内容,{{xxx}},其中 xxx 为 js 表达式,可以直接读到 data 中的所有属性
指令语法:用于解析标签(包括标签属性,标签体内容,绑定事件…),
举例: v-bind:href="xxx" 或简写为 :href="xxx",xxx 同样要写 js 表达式,且可以直接读 data 中的所有属性
attention: Vue 中有很多的指令,形式都是 v-xxx,此处只是用 v-bind 举例
单向数据绑定 v-bind ,页面随着数据变化,数据不随着页面变化
双向数据绑定 v-model ,页面和数据互相影响,仅可以用于表单类元素,也就是可以输入的元素,带有 value 属性,v-model:value 可以简写为 v-model,如果绑定的是 checkbox 属性,且正好是布尔值,那么就根据布尔值来决定勾选还是不勾选
MVVM 模型
其中 M: 模型 model,对应 data 中的数据;V:视图 View,模板;VM:视图模型(ViewModel):View 实例对象, 常用 VM 来代指 Vue 实例对象
note: data 中的所有属性最后都出现在了 vm 身上,vm 身上的所有属性以及 vue 原型上的所有属性,在 Vue 模板中都可以直接使用
数据代理
通过一个对象代理对另一个对象中属性的操作即为数据代理,vm._data 中存储了 data 中的数据,指向了同一块地址,仅仅是浅拷贝
知识背景:为对象添加属性 Object.defineProperty
1 2 3 4 5 6 7 8 9 10 11 12 13 <script type ="text/javascript" > let person={ name :"张三" , sex :"男" } Object .defineProperty (person,"age" ,{ value :18 , enumerable :true , configurable :true , writable :true }) console .log (person) </script >
将数据从正常状态变为 getter 和 setter 的状态可以称之为数据劫持 ,即对数据进行操作处理
事件处理
事件的基本使用:
使用 v-on:xxx 或 @xxx 绑定事件,其中 xxx 是事件名,
事件的回调需要配置在 methods 对象中,最终会在 vm 上:
methods 中配置的函数,不要用箭头函数!否则 this 就不是 vm 了;
methods 中配置的函数,都是被 Vue 所管理的函数,this 的指向是 vm 或组件实例对象;
@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 26 27 28 <body > <div id ="root" > <h3 > hello, {{name}}</h3 > <a :href ="url" > 来看我的博客</a > <button v-on:click ="showMsg" > 点我提示信息1</button > <button @click ="showMsg2(37,$event)" > 点我提示信息2</button > </div > <script type ="text/javascript" > Vue .config .productionTip =false const vm=new Vue ({ el :"#root" , data :{ name :"jack" , url :"http://wanstarge.github.io" }, methods :{ showMsg (event ){ console .log (event.target .innerText ) alert ("同学你好" ) }, showMsg2 (number,event ){ console .log (event.target .innerText ,number) alert ("同学你好!!" ) } } }) </script > </body >
事件修饰符
Vue 中的事件修饰符,修饰符可以连续输入,链式调用:
修饰符
作用
prevent
阻止默认事件(常用)
stop
阻止事件冒泡(常用)
once
事件只触发一次(常用)
capture
使用事件的捕获模式
self
只有 event,target 是当前操作的元素时才触发事件
passive
事件的默认行为立即执行,无需等待事件回调执行完毕
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <script src ="js/vue.js" > </script > <style > *{ margin : 20px ; } .demo1 { height : 60px ; background-color : blueviolet; } .box1 { padding : 5px ; background-color : rgb (138 , 90 , 138 ); } .box2 { padding : 5px ; background-color : rgb (73 , 210 , 217 ); } .list { width : 200px ; height : 200px ; background-color : peru; overflow : auto; } li { height : 70px ; } </style > <title > 万星阁</title > </head > <body > <div id ="root" > <h3 > hello, {{name}}</h3 > <hr /> <a :href ="url" @click.prevent ="showInfo" > 来看我的博客</a > <div class ="demo1" @click ="showInfo" > <button @click.stop ="showInfo" > 点我原本会弹两次</button > </div > <button @click.once ="showInfo" > 点我原本点一次弹一次</button > <div class ="box1" @click.capture ="showMsg(1)" > div1 <div class ="box2" @click ="showMsg(2)" > div2</div > </div > <div class ="demo1" @click.self ="showInfo" > <button @click.stop ="showInfo" > 点我原本会弹两次</button > </div > <ul @scroll.passive ="demo" class ="list" > <li > 0</li > <li > 1</li > <li > 2</li > <li > 3</li > </ul > </div > <script type ="text/javascript" > Vue .config .productionTip =false const vm=new Vue ({ el :"#root" , data :{ name :"jack" , url :"http://wanstarge.github.io" }, methods :{ showInfo (event ){ alert ("同学你好" ) console .log (event.target ) }, showMsg (msg ){ console .log (msg) }, demo ( ){ for (let i=0 ;i<100000 ;i++){ console .log ("#" ) } console .log ("滚完了" ) } } }) </script > </body > </html >
键盘事件
Vue 为常用的按键值提供了一些别名,如下表所示:
按键
别名
回车
enter
删除
delete,捕获删除和退格键
退出
esc
换行
tab
上
up
下
down
左
left
右
right
而对于 Vue 未提供别名的按键,可以使用按键原始的 key 值去绑定,但注意要转为 kebab-case(短横线命名);
系统修饰键(用法特殊):ctrl,alt.shift,meta(允许链式调用)
配合 kyup 使用:按下修饰键的同时,再按下其他健,随后释放其他键,事件才被触发。
配合 keydown 使用:正常触发事件。
也可以使用 keyCode 去指定具体的按键(不推荐)
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 <body > <div id ="root" > <h3 > hello, {{name}}</h3 > <hr /> <input type ="text" placeholder ="按下回车提示输出" @keyup.enter ="showInfo" > <input type ="text" placeholder ="按下切换大小写提示输出" @keyup.caps-lock ="showInfo" > <input type ="text" placeholder ="按下tab提示输出" @keydown.tab ="showInfo" > <input type ="text" placeholder ="使用键值绑定" @keydown.13 ="showInfo" > <input type ="text" placeholder ="使用别名绑定" @keydown.huiche ="showInfo" > </div > <script type ="text/javascript" > Vue .config .productionTip =false Vue .config .keyCodes .huiche =13 const vm=new Vue ({ el :"#root" , data :{ name :"jack" , url :"http://wanstarge.github.io" }, methods :{ showInfo (event ){ console .log (event.target .value ) } } }) </script > </body >
计算属性
定义:要用的属性不存在,要通过已布属阳计算得来
原理:底层借助了 Objcet.defineproperty 方法提供的 getter 和 setter。
get 函数什么时候执行?
(1) 初次读取时会执行一次。
(2) 当依赖的数据发生改变时会被再次调用。
优势:与 methods 实现相比,内部有缓存机制(复用),效率更高,调试方便。
note: 备注:计算属性最终会出现在 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 <body> <div id="root"> <h3>hello, {{name}}</h3> <hr/> 姓:<input type="text" v-model="surname"> <br><br> 名:<input type="text" v-model="name"> <br><br> 全名:<span> {{fullName}} </span> </div> <script type="text/javascript"> Vue.config.devtools=true Vue.config.productionTip=false Vue.config.keyCodes.huiche=13 // 定义了一个别名按键 const vm=new Vue({ el:"#root", data:{ name:"jack", surname:"star", url:"http://wanstarge.github.io" }, computed:{ fullName:{ // 当有人读取fullName时,get就会被调用,且返回值就作为fullName的值,初次读取fullName,所依赖的数据发生变化时get会被调用 get(){ return this.surname+"-"+this.name }, set(value){ const arr=value.split("-") this.surname=arr[0] this.name=arr[1] } } // 当不使用set时计算属性可以被简写 fullName(){ return this.surname + "-" + this.name } } }) </script> </body>
监视属性
监视属性 watch:
当被监视的属性变化时,回调函数自动调用,进行相关操作
监视的属性必须存在,才能进行监视!!
监视的两种写法:
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 38 39 40 41 42 43 44 45 46 <div id ="root" > <h3 > 今天天气很{{info}}</h3 > <hr /> <button @click ="changeWeather" > 切换天气</button > </div > <script type ="text/javascript" > Vue .config .devtools =true Vue .config .productionTip =false Vue .config .keyCodes .huiche =13 const vm=new Vue ({ el :"#root" , data :{ ishot :"cold" }, computed :{ info ( ){ return this .ishot ? '炎热' : '凉爽' } }, methods :{ changeWeather ( ){ this .ishot = !this .ishot } }, watch :{ ishot :{ immediate : true , handler (newValue,oldValue ){ console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) } } } }) vm.$watch('ishot' ,{ immediate : true , handler (newValue,oldValue ){ console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) } }) </script > </body >
深度监视
Vue 中的 watch 默认不监测对象内部值的改变(一层。
配置 deep:true 可以监测对象内部值改变(多层)。
note: >- 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 <body > <div id ="root" > <h3 > a:{{numbers.a}},b:{{numbers.b}}</h3 > <hr /> <button @click ="numbers.a++" > 点我让a加1</button > <button @click ="numbers.b++" > 点我让b加1</button > </div > <script type ="text/javascript" > Vue .config .devtools =true Vue .config .productionTip =false Vue .config .keyCodes .huiche =13 const vm=new Vue ({ el :"#root" , data :{ numbers :{ a :1 , b :1 } }, watch :{ 'numbers.a' :{ handler (newValue,oldValue ){ console .log (`a变化了,现在是${newValue} ,之前是${oldValue} ` ) }, }, 'numbers' : { deep : true , handler (newValue, oldValue ) { console .log (`numbers变化了,现在是${newValue} ,之前是${oldValue} ` ) }, } } }) </script > </body >
简写方法
只有 handler 函数,而没有其他属性时才能使用简写,类似于计算属性的简写
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 watch : { ishot : { handler (newValue, oldValue ) { console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) } }, ishot (newValue, oldValue ) { console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) } } vm.$watch('ishot' , { handler (newValue, oldValue ) { console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) } }) vm.$watch('ishot' , function (newValue, oldValue ) { console .log (`ishot变化了,现在是${newValue} ,之前是${oldValue} ` ) })
note: - 计算属性和监视属性的区别
computedi 能完成的功能,watch 都可以完成。
watch 能完成的功能,computed.不一定能完成,例如:watch 可以进行异步操作。
两个重要的小原则:
3. 所被 Vue 管理的函数,最好写成普通函数,这样 this 的指向才是 vm 或组件实例对象。
4. 所有不被 Vue 所管理的函数(定时器的回调函数、ajax 的回调函数、Promise 的回调函数等),最好写成箭头函数,这样 this 的指向才是 vm 或组件实例对象。
绑定样式
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 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <body > <div id ="root" > <div class ="basic" :class ="mood" @click ="changeMode" > {{name}}</div > <div class ="basic" :class ="classarr" @click ="changeMode" > {{name}}</div > <div class ="basic" :class ="classobj" @click ="changeMode" > {{name}}</div > <div class ="basic" :style ="styobj" > {{name}}</div > <div class ="basic" :style ="[styobj, styobj2]" > {{name}}</div > <div class ="basic" :style ="styarr" > {{name}}</div > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 const vm = new Vue ({ el : "#root" , data : { name :"万星阁" , mood :"normal" , classarr :['sty1' ,'sty2' ,'sty3' ], classobj :{ atguigu1 :false , atguigu2 :true }, styobj :{ fontSize : '40px' , color :'red' }, styobj2 : { backgroundColor :"orange" }, styarr :[ { fontSize : '40px' , color : 'red' }, { backgroundColor : "orange" }, ] }, methods : { changeMode ( ) { const arr=['happy' ,'sad' ,'normal' ] this .mood =arr[Math .floor (Math .random ()*arr.length )] } } }) </script > </body >
条件渲染
语句
写法
适用范围
特点
注意项
v-if
v-if="表达式"v-else-if="表达式"v-e1se="表达式"
切换频率较低的场景。
不展示的 DOM 元素直接被移除。
v-if 可以和:v-else-if、v-else 一起使用,但要求结构不能被“打断”。
v-show
v-show="表达式"
切换频率较高的场景。
不展示的 D0M 元素未被移除,仅仅是使用样式隐藏掉
使用 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 <body > <div id ="root" > <h2 v-show ="dispTitle" > {{name}},更高处见</h2 > <h2 v-show ="1===3" > {{name}},更高处见</h2 > <h2 v-if ="dispTitle" > {{name}},更高处见</h2 > <h2 v-if ="1===3" > {{name}},更高处见</h2 > <h2 > 当前的n值为: {{n}}</h2 > <button @click ="n++" > 点我n+1</button > <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 > MX</div > <template v-if ="n===1" > <h2 > 牧星</h2 > <h2 > 凌霄</h2 > <h2 > 万星阁</h2 > </template > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 const vm = new Vue ({ el : "#root" , data : { name :"万星阁" , dispTitle : true , n :0 }, methods : { } }) </script > </body >
列表渲染
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 <body > <div id ="root" > <h2 > 人员列表(遍历列表)</h2 > <ul > <li v-for ="(person,index) in persons" :key ="person.id" > {{index}}-{{person.name}}-{{person.age}} </li > </ul > <h2 > 汽车信息(遍历对象)</h2 > <ul > <li v-for ="(info,key) in car" :key ="key" > {{key}}-{{info}} </li > </ul > <h2 > 姓名(遍历字符串)</h2 > <ul > <li v-for ="(info,index) in name" :key ="index" > {{index}}-{{info}} </li > </ul > <h2 > 测试(遍历指定次数)</h2 > <ul > <li v-for ="(number,index) in 5" :key ="index" > {{index}}-{{number}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 const vm = new Vue ({ el : "#root" , data : { name : "万星阁" , persons : [ { id : '001' , name : "张三" , age : 18 }, { id : '002' , name : "李四" , age : 19 }, { id : '003' , name : "王五" , age : 20 } ], car :{ name :"奥迪A8" , price :'70万' , color :'black' } }, }) </script > </body >
key
react、vue 中的 key 有什么作用?(key 的内部原理)
虚拟 D0N 中 key 的作用:key 是虚拟 Do 州对象的标识,当状态中的数据发生变化时,Vue 会根据【新数据】生成【新的虚拟 DOM】,随后 Vue 进行【新虚拟 DoM】与【旧虚拟 Do 州】的差异比较,比较规则如下:
旧虚拟 D0M 中找到了与新虚拟 Do 州相同的 key:
若虚拟 DOM 中内容没变,直接使用之前的真实 DOM
若虚拟 DOM 中内容变了,则生成新的真实 DOM,随后替换掉页面中之前的真实 D0 州。
旧虚拟 DOM 中未找到与新虚拟 DOM 相同的 key,创建新的真实 DO 州,随后渲染到到页面。
用 index 作为 key 可能会引发的问题:
若对数据进行:逆序添加、逆序刚除等破坏顺序操作:会产生没有必要的真实 DOM 更新==>界面效果没问题,但效率低。
如果结构中还包含输入类的 DOM:会产生错误 DOM 更新=>界面有问题
开发中如何选择 key?:
最好使用每条数据的唯一标识作为 ky,比如 id、手机号、身份证号、学号等唯一值。
如果不存在对数据的逆序添加、逆序刚除等破坏顺序操作,仅用于渲染列表用于展示,使用 index 作为 key 是没有问题的。
attention: 倘若不指定 key,Vue 会默认将 index 作为 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 <body > <div id ="root" > <h2 > 人员列表(遍历列表)</h2 > <button @click.once ="add" > 添加一个老刘</button > <ul > <li v-for ="(person,index) in persons" :key ="index" > {{index}}-{{person.name}}-{{person.age}} <input type ="text" > </li > </ul > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 const vm = new Vue ({ el : "#root" , data : { name : "万星阁" , 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 > </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 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 <body > <div id ="root" > <h2 > 人员列表</h2 > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <button @click ="sortType=0" > 原顺序</button > <button @click ="sortType=1" > 年龄降序</button > <button @click ="sortType=2" > 年龄升序</button > <ul > <li v-for ="(person,index) in filPersons" :key ="person.id" > {{person.name}}-{{person.age}}-{{person.sex}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .config .keyCodes .huiche = 13 const vm = new Vue ({ el : "#root" , data : { keyWord : "" , sortType :0 , persons : [ { id : '004' , name : "温兆伦" , age : 21 , sex : '男' }, { id : '001' , name : "马冬梅" , age : 18 , sex : '女' }, { id : '002' , name : "周冬雨" , age : 19 , sex : '女' }, { id : '003' , name : "周杰伦" , age : 20 , sex : '男' }, ], }, watch : { keyWord : { immediate : true , handler (newVal ) { this .filPersons = this .persons .filter (person => person.name .includes (newVal)) } } }, computed : { filPersons : { get ( ) { const arr=this .persons .filter (person => person.name .includes (this .keyWord )) if (this .sortType ){ arr.sort ((a,b )=> this .sortType ===1 ?b.age -a.age :a.age -b.age ); } return arr; } } } }) </script > </body >
数据更新监测原理探究
数据监测实质
收到 data 后先对数据进行加工处理,而后将 _data=data,其加工过程大概可以按照如下所示,只是简单的示例,Vue 还可以实现,直接对属性名进行赋值,以及递归嵌套识别属性放入 _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 let data = { name : "ivan" , address : "众星之上" } const obs = new Observer (data);let vm = {}vm._data = data = obs function Observer (obj ) { const keys = Object .keys (obj); keys.forEach (key => { Object .defineProperty (this , key, { get ( ) { return obj[k] }, set (val ) { console .log (`属性${key} 的值发生了变化,准备解析模板,生成虚拟DOM` ); obj[key] = val } }) }) }
数据监测原理
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 <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 ="student.hobby.push('游泳')" > 添加一个爱好</button > <br /> <button @click ="updateHobby" > 修改第一个爱好为:开车</button > <br /> <h2 > 姓名:{{student.name}}</h2 > <h2 > 年龄:{{student.age}}</h2 > <h2 v-if ="student.sex" > 性别:{{student.sex}}</h2 > <h2 > 爱好</h2 > <ul > <li v-for ="(hob,index) in student.hobby" :key ="index" > {{hob}} </li > </ul > <h2 > 朋友们</h2 > <ul > <li v-for ="(friend,index) in student.friends" :key ="index" > {{friend.name}}-{{friend.age}} </li > </ul > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false const vm = new Vue ({ el : "#root" , data : { student :{ name : 'mx' , age : 18 , hobby : ['篮球' , '足球' , '编程' ], friends : [ { name : 'star' , age : 15 }, { name : 'ivan' , age : 20 }, ] } }, methods :{ addSex ( ){ this .$set(this .student ,'sex' ,'男' ) }, addFriend ( ){ this .student .friends .unshift ({name :'博文' ,age :25 }) }, updateFirstFriendName ( ){ this .student .friends [0 ].name ='张三' this .student .friends [0 ].age = 21 }, updateHobby ( ){ this .$set(this .student .hobby , '0' , '开车' ) } } }) </script > </body >
summary: Vue 监视数据的原理
vue 会监视 data 中所有层次的数据。
通过 setter 实现监视监测对象中的数据,且要在 new Vue 时就传入要监测的数据。
对象中后追加的属性,Vue 默认不做响应式处理
如需给后添加的属性做响应式,请使用如下 API:
Vue.set(target,propertyName/index,value)
vm.$set(target,propertyName/index,value)
如何监测数组中的数据: 通过包裹数组更新元素的方法实现,本质就是做了两件事:
调用原生对应的方法对数组进行更新。
重新解析模板,进而更新页面。
在 Vue 修改数组中的某个元素一定要用如下方法:
使用这些 API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
Vue.set(),vm.$set()
attention: 特别注意:Vue.set() 和 vm.$set() 不能给 vm 或 vm 的根数据对象添加属性!!!
收集表单数据
若:<input type="text"/>,则 v-modele 收集的是 value 值,用户输入的就是 value 值。
若:<input type:="radio"/>,则 v-model 收集的是 value 值,且要给标签配置 value 值。
若:<input type="checkbox"/>
没有配置 input 的 value 属性,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
配置 input 的 value 属性:
v-model 的初始值是非数组,那么收集的就是 checked(勾选 or 未勾选,是布尔值)
v-model 的初始值是数组,那么收集的的就是 value 组成的数组
note: - v-model 的三个修饰符:
修饰符
作用
lazy
失去焦点再收集数据
number
输入字符串转为有效的数字
trim
输入首尾空格过滤
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 <body > <div id ="root" > <form @submit.prevent ="submitForm" > <label for ="demo" > 账号:</label > <input type ="text" id ="demo" v-model.trim ="account" > <br > <br > 密码:<input type ="password" v-model ="password" > <br > <br > 性别:<input type ="radio" name ="gender" v-model ="gender" value ="male" > 男 <input type ="radio" name ="gender" v-model ="gender" value ="female" > 女<br > <br > 年龄:<input type ="number" v-model.number ="age" > <br > <br > 爱好: <input type ="checkbox" value ="reading" v-model ="hobby" > 读书 <input type ="checkbox" value ="swimming" v-model ="hobby" > 游泳 <input type ="checkbox" value ="running" v-model ="hobby" > 跑步<br > <br > 生日:<input type ="date" v-model ="birthday" > <br > <br > 所属校区:<select name ="school" id ="sarea" v-model ="area" > <option value ="" > 请选择校区</option > <option value ="龙子湖" > 龙子湖校区</option > <option value ="新校区" > 新校区</option > <option value ="老校区" > 老校区</option > </select > <br > <br > 备注:<textarea v-model.lazy ="note" > </textarea > <br > <br > <input type ="checkbox" v-model ="agree" > 阅读并接受 <a href ="http://www.wanxingge.site" > 用户协议</a > <br > <br > <button > 提交</button > </form > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false const vm = new Vue ({ el : "#root" , data : { account :"" , password :"" , gender :"female" , age :18 , hobby :[], area :"龙子湖" , note :"" , agree :"" , birthday :"" , }, methods :{ submitForm ( ){ console .log (JSON .stringify (this ._data )) } } }) </script > </body >
* 过滤器
对要显示的数据进行恃定格式化后再显示 (适用于一些简单逻辑的处理),语法:
注册过滤器:Vue.filter(name,cal1back) 或 new Vue{filters:()}
使用过滤器:{{xxx|过滤器名}} 或 v-bind:属性="xxx|过滤器名"
note: >3. 过滤器也可以接收额外参数、多个过滤器也可以串联
4. 并没有改变原本的数据,是产生新的对应的数据
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 > <h3 > 计算属性:现在是:{{fmtTime}}</h3 > <h3 > 方法:现在是:{{getFmtTime()}}</h3 > <h3 > 过滤器:现在是:{{time | timeFmt}}</h3 > <h3 > 过滤器参数:现在是:{{time | timeFmt("YYYY_MM_DD")|mxSlice}}</h3 > <h3 :data ="msg | mxSlice" > 属性过滤</h3 > </div > <div id ="root2" > <h2 > {{msg|mxSlice}}</h2 > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .filter ('mxSlice' , function (value ) { return value.slice (0 , 4 ) }) const vm = 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 : { timeFmt (value, ctr = "YYYY-MM-DD HH:mm:ss" ) { return dayjs (value).format (ctr) }, } }) new Vue ({ el : "#root2" , data : { msg : "你好,万星阁" }, }) </script > </body >
内置指令
指令
作用
用法
v-bind
单向绑定解析表达式,可简写为:xxx
v-bind:value="name" 或:value="name"
v-model
双向数据绑定
由于其仅绑定 value 属性,故可以直接写为 v-model="name"
v-for
遍历数组,对象,字符串
v-for="(p,index) in persons"
v-on
绑定事件监听,可以简写为@
v-on:click="updateAge" 或 @click="updateAge"
v-if
条件渲染(动态控制节点是否存存在),不展示的 DOM 元素直接被移除。
v-if 可以和:v-else-if、v-else 一起使用,但要求结构不能被“打断”。v-if="person.agree"
v-show
条件渲染,不展示的 D0M 元素未被移除,仅仅是使用样式隐藏掉
v-show="person.agree"
v-text
向其所在的节点中渲染文本内容。与插值语法的区别:v-text 会替换掉节点中的内容,{{xx}} 则不会。
v-text="name"
v-html
与 v-text 相比,此指令支持 html 结构的解析
注意: 会有安全性问题,可以在其中执行 js 代码,导致 XSS 攻击,如果必须使用,一定要在可信的内容上,永远不要在用户提交的内容上
v-cloak
实现延迟解析,用于等待各种情况导致的 Vue 加载阻塞
配合 css[v-cloak]{display:none;} 来进行实现
v-once
v-once 所在节点在初次动态渲染后,就视为静态内容了。 以后数据的改变不会引起 v-once 所在结构的更新,可以用于优化性能。
<h2 v-once></h2>
v-pre
跳过其所在节点的编译过程。 可利用它跳过:没有使用指令语法、没有使用插值语法的节点,会加快编译。
<h2 v-pre></h2>
自定义指令
定义语法:
局部指令:new Vue({ directives:(指令名:配置对象)}) 或者 new Vue({directives(){}})
全局指令:Vue.directive(指令名,配置对象) 或 Vue.directive(指令名,回调函数)
配置对象中常用的 3 个回调:
bind: 指令与元素成功绑定时调用。
inserted: 指令所在元素被插入页面时调用。
update: 指令所在模板结构被重新解析时调用。
备注
指令定义时不加 v-,但使用时要加 v-;
指令名如果是多个单词,要使用 kebab-case 命名方式,不要用 camelCase 命名。
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 <body > <div id ="root" > <h2 > 当前的n值为<span v-text ="n" > </span > </h2 > <h2 > 放大十倍后的n值为<span v-big ="n" > </span > </h2 > <h2 > 放大十倍后的n值为<span v-big-number ="n" > </span > </h2 > <button @click ="n++" > 点我n++</button > <hr > <input type ="text" v-fbind:value ="n" > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .directive ('big-number' , function (element, binding ) { element.innerText = binding.value * 10 ; }) const vm = new Vue ({ el : "#root" , data : { n : 1 }, directives : { big (element, binding ) { element.innerText = binding.value * 10 ; }, fbind : { bind (element, binding ) { element.value = binding.value console .log ('big' , this ) }, inserted (element, binding ) { element.focus () }, update (element, binding ) { element.value = binding.value } } } }) </script > </body >
生命周期
又名生命周期回调函数、生命周期函数、生命周期钩子。
是什么: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 31 32 33 34 35 36 37 38 39 <body > <div id ="root" > <h2 :style ="{opacity}" > 欢迎学习</h2 > <button @click ="stop" > 点我停止变换</button > <button @click ="stop" > 点我暴力停止变换</button > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .directive ('big-number' , function (element, binding ) { element.innerText = binding.value * 10 ; }) const vm = new Vue ({ el : "#root" , data : { opacity : 1 }, methods : { stop ( ){ this .$destroy() } }, mounted ( ) { console .log ('mounted' ); this .timer =setInterval (() => { this .opacity -= 0.01 ; if (this .opacity <= 0 ) { this .opacity = 1 ; } }, 16 ) }, beforeDestroy ( ){ console .log ('vm 将会被删除' ); clearInterval (this .timer ); } }) </script > </body >
分析生命周期
Vue 实例有⼀个完整的⽣命周期,也就是从 new Vue()、初始化事件 (.once 事件) 和生命周期、编译模版、挂载 Dom -> 渲染、更新 -> 渲染、卸载 等⼀系列过程,称这是 Vue 的⽣命周期。
beforeCreate(创建前) :数据监测 (getter 和 setter) 和初始化事件还未开始,此时 data 的响应式追踪、event/watcher 都还没有被设置,也就是说不能访问到 data、computed、watch、methods 上的方法和数据。
created(创建后) :实例创建完成,实例上配置的 options 包括 data、computed、watch、methods 等都配置完成,但是此时渲染得节点还未挂载到 DOM,所以不能访问到 $el 属性。
beforeMount(挂载前) :在挂载开始之前被调用,相关的 render 函数首次被调用。此阶段 Vue 开始解析模板,生成虚拟 DOM 存在内存中,还没有把虚拟 DOM 转换成真实 DOM,插入页面中。所以网页不能显示解析好的内容。
!mounted(挂载后) :在 el 被新创建的 vm.$el(就是真实 DOM 的拷贝)替换,并挂载到实例上去之后调用(将内存中的虚拟 DOM 转为真实 DOM,真实 DOM 插入页面)。此时页面中呈现的是经过 Vue 编译的 DOM,这时在这个钩子函数中对 DOM 的操作可以有效,但要尽量避免。一般在这个阶段进行:开启定时器,发送网络请求,订阅消息,绑定自定义事件等等
beforeUpdate(更新前) :响应式数据更新时调用,此时虽然响应式数据更新了,但是对应的真实 DOM 还没有被渲染(数据是新的,但页面是旧的,页面和数据没保持同步呢)。
updated(更新后) :在由于数据更改导致的虚拟 DOM 重新渲染和打补丁之后调用。此时 DOM 已经根据响应式数据的变化更新了。调用时,组件 DOM 已经更新,所以可以执行依赖于 DOM 的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
!beforeDestroy(销毁前) :使用 $destroy 执行实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例,但是不会再渲染页面,一般不进行数据操作。在这个阶段一般进行关闭定时器,取消订阅消息,解绑自定义事件,DOM 事件不会被解绑。
destroyed(销毁后) :实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务端渲染期间不被调用。
template
有 template 选项(属性)
如果 el 绑定的容器没有任何内容,就一个空壳子,但在 Vue 实例中写了 template,就会编译解析这个 template 里的内容,生成虚拟 DOM,最后将 虚拟 DOM 转为 真实 DOM 插入页面(其实就可以理解为 template 替换了 el 绑定的容器的内容)
没有 template 选项(属性)没有 template,就编译解析 el 绑定的容器,生成虚拟 DOM,后面就顺着生命周期执行下去,也就是我们之前常用的方式
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" > </div > <script type ="text/javascript" > Vue .config .devtools = true Vue .config .productionTip = false Vue .directive ('big-number' , function (element, binding ) { element.innerText = binding.value * 10 ; }) const vm = new Vue ({ el : "#root" , template :` <div> <h2>当前n的值为:{{n}}</h2> <button @click="n++">点我n+1</button> </div>` , data : { n : 1 , }, }) </script > </body >
attention: 在 Vue2 中 template 属性中不允许放 template 标签
组件
模块 :向外提供特定功能的 js 程序,一般就是一个 js 文件;组件 :用来实现局部(特定)功能效果的代码集合 (html/css/js/,image…)
* 非单文件组件
一个文件中包含有 n 个组件,使用组件有三大步骤:
定义组件 : 使用 Vue.extend(options) 创建,其中 options 和 new Vue(options) 时传入的那个 options 几乎一样,但也有点区别:
el 不要写,因为最终所有的组件都要经过一个 vm 的管理,由 vm 中的 el 决定服务哪个容器。
data 必须写成函数,避免组件被复用时,数据存在引用关系。(备注:使用 template 可以配置组件结构。)
简写的方式:const school = Vue.extend(options) 可以简写为 const school = options
可以在 options 中使用 name 属性指定在开发者工具中呈现的组件名
注册组件 : 分为全局注册和局部注册:
局部注册:靠 new Vue 的时候传入 components 选项
* 全局注册:靠 Vue.component('组件名',组件)
使用组件 : 写组件标签,<组件名></组件名>,也可以使用 <school/> 但是要求使用脚手架,不是用脚手架时会导致后续组件不能渲染。
attention: 组件名如果是多个单词,可以使用全小写的方式 mxschool 或者中间用 - 连接,mx-school,或者每一个单词的首字母均大写(!要求使用脚手架)MxSchool;组件名尽可能回避 HTML 中已有的元素名称,例如:h2、H2 都不行。可以使用 name 配置项指定组件在开发者工具中呈现的名字。
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 <body > <div id ="root" > <xuexiao > </xuexiao > <hr > <student > </student > </div > <script type ="text/javascript" > Vue.config.devtools = true Vue.config.productionTip = false /* 第一步:创建组件 */ const hello = Vue.extend({ template: ` <div > <h2 > 你好啊,初学者</h2 > </div > `, }) const school = Vue.extend({ template: ` <div > <h2 > 学校名称: {{name }} </h2 > <h2 > 学校地址: {{address }} </h2 > <button @click ="showName" > 点我提示学校名</button > </div > `, data() { // el: '#root' 组件定义时不要写el配置项,因为最终所有的组件都要被一个vm管理,由vm决定服务于哪个容器 return { name: '万星阁', address: '众星之上', } }, methods:{ showName(){ alert(this.name) } } }) const student = { name:"student" template: ` <div > <h2 > 学生姓名: {{name }} </h2 > <h2 > 学生年龄: {{age }} </h2 > </div > `, data() { return { name: '张三', age: 20 } } } Vue.component('hello',hello) /* 第二步:注册组件(全局注册),所有vm均可以使用 */ const vm = new Vue({ el: "#root", /* 第二步:注册组件(局部注册) */ components: { xuexiao:school, // 或者直接名称相同时school,以前者为准 student, } }) </script > </body >
组件的嵌套
被谁所包含,就在谁的 template 属性中写标签,同时在谁的 component 属性中注册,一般 vm 下方会直接引领一个 App,而后在 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 <body > <div id ="root" > </div > <script type ="text/javascript" > Vue.config.devtools = true Vue.config.productionTip = false const student = Vue.extend({ template: ` <div > <h2 > 学生姓名: {{name }} </h2 > <h2 > 学生年龄: {{age }} </h2 > </div > `, data() { return { name: '张三', age: 20 } } }) const school = Vue.extend({ template: ` <div > <h2 > 学校名称: {{name }} </h2 > <h2 > 学校地址: {{address }} </h2 > <student > </student > <button @click ="showName" > 点我提示学校名</button > </div > `, data() { return { name: '万星阁', address: '众星之上', } }, methods: { showName() { alert(this.name) } }, components: { student } }) // 定义APP组件 const app = Vue.extend({ template: ` <school > </school > `, components: { school } }) const vm = new Vue({ el: "#root", template:`<app > </app > `, components: { app } }) </script > </body >
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() 配置中:data 函数、methods 中的函数、watch 中的函数、computed 中的函数它们的 this 均是【Vue 实例对象】。
VueComponent 的实例对象,以后简称vc (也可称之为:组件实例对象)。Vue 的实例对象,以后简称vm 。
内置关系
一个重要的内置关系:VueComponent.prototype._proto==Vue.prototype
为什么要有这个关系:让组件实例对象 (vc) 可以访问到 Vue 原型上的属性、方法。
单文件组件
一个文件中只包含有 1 个组件,file.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 <template > <div > <h2 class ="demo" > 学校名称:{{ 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 : chartreuse; } </style >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template lang ="" > <div > <School > </School > </div > </template > <script > import School from './School.vue' ;export default { name : 'App' , components : { School } } </script >
1 2 3 4 5 6 import { App } from "./App.vue" ;new Vue ({ el :'#root' , template :'<App></App>' , components :{App }, })
1 2 3 4 5 6 7 8 9 10 11 12 13 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > 测试单文件组件语法</title > </head > <body > <div id ="root" > </div > <script src ="./js/vue.js" > </script > <script src ="./main.js" > </script > </body > </html >
脚手架
Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台),Vue-CLI (command line interface)
安装
使用 nodejs 来安装 vue 脚手架,nodejs 的安装参考 NodeJS
全局安装 @vue/cli:npm install -g @vue/cli
切换到需要创建工程的目录,并使用 vue create 项目名 来创建项目
启动项目:npm run serve
note: Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的 webpakc 配置,请执行:vue inspect>output.js
工程分析
文件名
作用
babel.config.js
用于配置 babel 来实现 ES6 到 ES5 的转换
package.json
nodejs 自动对工程进行的配置文件,包含了当前工程所需的依赖以及其对应的版本等等,以及定义的一些短命令均在此处设置(build 进行构建,lint 进行语法检查)
package-lock.json
nodejs 的包版本管理文件,用于锁定当前项目依赖的包版本
vue.config.js
原则上 public,src 文件夹,图标名称,index.html 以及 main.js 不可以修改,但是可以通过这个文件进行修改,参考 Vue2 ,Vue3
main.js
该文件是整个项目的入口文件,引入 Vue 以及 APP 管理组件,render 代码实现了将 APP 组件放入容器中
App.vue
管理其他组件的文件,对组件进行注册结构进行管理等等
assets
该文件夹常用于存放静态资源
components
所有除了 App 以外的组件都放到该文件夹下
public
包含了页面以及公共使用的文件
index.html
浏览器的初始页面,<meta http-equiv="X-UA-Compatible" content="IE=edge"> 是为了让 IE 以最高级别渲染界面,<meta name="viewport" content="width=device-width,initial-scale=1.0"> 开启移动端的理想视口,<%= BASE_URL =%> 指的是 public 文件夹所在的路径,<% htmlWebpackPlugin.options.title %> 为使用 webpack 插件来获取 package.json 文件中指定的工程名
如果不使用上述 main.js 文件中提到的 render 函数而按照 [[编程语言/JavaScript/功能框架/Vue#单文件组件|单文件组件]] 中提到的直接写 template,其会报错要求使用 render 函数或者使用完整版的 vue,通过修改 main.js 文件中引入的 vue 路径为 vue/dist/vue 即为完整版的 vue(核心 + 模板解析器),此时问题解决,但是在打包时文件会比较大,同时保留解析器不是开发的做法
render 为渲染的意思,可以在没有编译器的 vue 的情况下创建并修改页面元素,使用下面的代码可以直接在页面创建元素 render(createElement){return createElement('hi','你好啊')
summary: >vue.js 与 vue.runtime.xxx.js 的区别:vue.js 是完整版的 Vue,包含:核心功能 + 模板解析器;vue.runtime.xxx.js 是运行版的 Vue,只包含:核心功能:没有模板解析器。
因为 vue.runtime.xxx.js 没有模板解析器,所以不能使用 template 配置项,需要使用 render 函数接收到的 createElement 函数去指定具体内容。
ref 属性
被用来给元素或子组件注册引用信息 (id 的替代者)
应用在 html 标签上获取的是真实 D0M 元素,应用在组件标签上是组件实例对象 (vc)
使用方式:打标识:<h1 ref="xxx">......</h1> 或 <School ref="xxx"></Schoo1>; 获取元素的方法: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 <template lang ="" > <div > <h1 ref ="title" > Welcome to MX Vue Study</h1 > <School ref ="sch" /> <button @click ="showDOM" ref ="btn" > 点我输出DOM元素</button > </div > </template > <script > import School from './components/School.vue' ;export default { name : 'App' , components : { School }, methods : { showDOM ( ) { console .log (this .$refs .title ); console .log (this .$refs .sch ); console .log (this .$refs .btn ); } } } </script >
props 属性
功能:让组件接收外部传过来的数据
传递数据:<Demo name="xxx"/>
接收数据 (有三种方式):
只接收:props:['name']
限制类型:props:{name:String,age:Number}
限制类型、限制必要性、指定默认值:
1 2 3 4 5 6 7 8 9 10 props :{ name :{ type :String , required :true , }, age :{ type :Number , default :18 } }
note: props 是只读的,Vue 底层会监测你对 props 的修改,如果进行了修改,就会发出警告,若业务需求确实需要修改,那么请 复制 props 的内容到 data中一份,然后去修改 data 中的数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <template lang ="" > <div > <Student name ="允异" :age ="18" > </Student > </div > </template > <script > import Student from './components/Student.vue' ;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 <template > <div > <h2 > 学生姓名:{{ name }}</h2 > <h2 > 学生年龄:{{ mxAge +1 }}</h2 > <h2 > 学生性别:{{ sex }}</h2 > <button @click ="updateAge" > 修改年龄</button > </div > </template > <script > export default { name : "School-item" , data ( ) { return { msg :"are you ok" , mxAge :this .age }; }, methods : { updateAge ( ) { this .mxAge ++; } }, props :{ name :{ type :String , required :true , }, age :{ type :Number , default :18 }, sex :{ type :String , default :"男" } } }; </script >
mixin
可以把多个组件共用的配置提取成一个混入对象,以进行简写:
使用方式:
定义混合,例如:
1 2 3 4 { data ( ){...}, methods :{...} }
使用混入,例如:
全局混入:Vue.mixin(xxx)
局部混入:Vue.mixins:['xxx']
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export const hunhe = { methods : { showName ( ) { alert (this .name ); } }, }; 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 20 21 22 23 24 25 <template > <div > <h2 @click ="showName" > 学校名称:{{ name }}</h2 > <h2 > 学校地址:{{ address }}</h2 > </div > </template > <script > import { hunhe,hunhe2 } from "../mixin" ;export default { name : "School-item" , data ( ) { return { name : "万星阁" , address : "众星之上" , }; }, methods : { showName ( ) { alert (this .name ); }, }, mixins : [hunhe,hunhe2], }; </script >
插件
主要用于增强 Vue,其本质是包含 install 方法的一个对象,install 的第一个参数是 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 export default { install (Vue ){ console .log ("@@@install" ,Vue ); Vue .filter ('mxSlice' , function (value ) { return value.slice (0 , 4 ) }) Vue .directive ('big-number' , function (element, binding ) { element.innerText = binding.value * 10 ; }) Vue .mixin ({ methods : { showName ( ) { alert (this .name ); } }, }) Vue .prototype .hello = ()=> { alert ("hello world" ); } } }
使用插件:Vue.use()
1 2 3 4 5 6 7 8 9 10 import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false import plugins from "./plugins" ;Vue .use (plugins);new Vue ({ render : h => h (App ), }).$mount('#app' )
而后就可以在各个单独的组件里面使用前面全局自定义的指令和混合了
Scoped 样式
当在不同的 Vue 组件文件中定义了同名称的样式时,浏览器最终会将所有的样式汇总到一个文件中,这时所显示的样式就咦在 main.js 中先导入的那个为准,这时我们就可以通过 scoped 属性来进行区分,指定当前样式的作用域,让样式在局部生效,防止冲突。写法:<style scoped>
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 class ="test" > <h2 > 学校名称:{{ name }}</h2 > <h2 > 学校地址:{{ address }}</h2 > </div > </template > <script > export default { name : "School-item" , data ( ) { return { name : "万星阁" , address : "众星之上" , age : 10 }; }, }; </script > <style scoped > .test { background-color : blueviolet; } </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 <template > <div class ="test" > <h2 > 学生姓名:{{ name }}</h2 > <h2 > 学生性别:{{ sex }}</h2 > </div > </template > <script > export default { name : "Student-item" , data ( ) { return { msg :"are you ok" , name : "牧星" , sex : "男" , }; }, }; </script > <style scoped > .test { background-color : red; } </style >
项目拆分
组件化编码流程:
拆分静态组件:组件要按功能点拆分,命名不要与 html 元素冲突。
实现动态组件:考虑好数据的存放位置,数据是一个组件在用,还是一些组件在用:
一个组件在用:放在组件自身即可。
一些组件在用:放在他们共同的父组件上(状态提升)。
实现交互:从绑定事件开始。
props 适用于:
父组件=>子组件通信
子组件==>父组件通信(要求父先给子一个函数),具体实现参考 自定义事件
使用 v-model 时要切记:v-model 绑定的值不能是 props 传过来的值,因为 props 是不可以修改的!
props 传过来的若是对象类型的值,修改对象中的属性时 Vue 不会报错,但不推荐这样做。
组件间通信
基础通信
第一种方式:最基础的同级组件间的数据传递:可以通过红线到橙线的方式进行传递,由 APP 进行中转,对于橙线来说,APP 将数据通过标签属性的方式传给 List 作为接收参数;而对于红线来说,App 声明一个接收参数的函数,并将该函数作为参数传递给 Header,Header 调用该参数即可实现将数据传递给 APP
自定义事件
一种组件间通信的方式,适用于 子组件=>父组件
使用场景:A 是父组件,B 是子组件,B 想给 A 传数据,那么就要在 A 中给 B 绑定自定义事件(事件的回调在 A 中)。
绑定自定义事件:
第一种方式,在父组件中:<Demo @wxg="test"/> 或 <Demo v-on:wxg="test"/>
第二种方式,在父组件中如下示例
若想让自定义事件只能触发一次,可以使用 once 修饰符,或 $once 方法。
1 2 3 4 5 6 7 8 9 10 11 <template > <Student ref ="student" /> </template > <script > mounted ( ) { this .$refs .student .$on('wxg' , this .getStudentName ); }, </script >
触发自定义事件: this.$emit('wxg',this.test)
解绑自定义事件:
解绑一个自定义事件 : this.$off("wxg");
解绑多个自定义事件 : this.$off(["wxg", "demo"]);
解绑所有自定义事件 : this.$off();
组件上也可以绑定原生 DOM 事件,需要使用 native 修饰符: {html}<Student ref="student" @click.native="show"/>
attention: >7. 通过 this.$refs.xxx.$on('wxg',回调) 绑定自定义事件时,回调要么配置在 methods 中,要么用箭头函数,否则 this 指向会出问题!
8. 给谁绑定的事件,最后就由谁来触发
示例
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 <template lang ="" > <div class ="app" > <h1 > {{msg}},学生姓名:{{studentName}}</h1 > <School :getSchoolName ="getSchoolName" /> <hr > <Student name ="mh" ref ="student" @click.native ="show" /> </div > </template > <script > import School from './components/School.vue' ;import Student from './components/Student.vue' ;export default { name : 'App' , components : { School , Student }, data ( ) { return { msg : 'Hello Vue' , studentName : '' } }, methods : { getSchoolName (name ){ console .log ('App收到了学校名:' ,name); }, getStudentName (name,...params ){ console .log ('App收到了学生名:' ,name,params); this .studentName = name; }, m1 ( ){ console .log ("demo事件被触发了" ); }, show ( ) { alert ("click the Student" ); } }, mounted ( ) { this .$refs .student .$on('wxg' , this .getStudentName ); }, } </script > <style lang ="css" > .app { background-color : gray; } </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 <template > <div class ="school" > <h2 > 学校名称:{{ name }}</h2 > <h2 > 学校地址:{{ address }}</h2 > <button @click ="sendSchoolName" > 把学校名给App</button > </div > </template > <script > export default { name : "School-item" , data ( ) { return { name : "万星阁" , address : "众星之上" , age : 10 }; }, props : ["getSchoolName" ], methods : { sendSchoolName ( ) { this .getSchoolName (this .name ); } }, }; </script > <style scoped > .school { background-color : blueviolet; padding : 5px ; margin : 5px ; margin-top : 30px ; } </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 <template > <div class ="student" > <h2 > 学生姓名:{{ name }}</h2 > <h2 > 学生性别:{{ sex }}</h2 > <button @click ="sendStudentName" > 把学生名给App</button > <button @click ="unbind" > 解绑wxg事件</button > </div > </template > <script > export default { name : "Student-item" , data ( ) { return { msg :"are you ok" , name : "牧星" , sex : "男" , }; }, methods : { sendStudentName ( ) { this .$emit("wxg" , this .name ); this .$emit("demo" ); }, unbind ( ){ this .$off("wxg" ); this .$off(["wxg" , "demo" ]); this .$off(); }, }, }; </script > <style scoped > .student { background-color : red; } </style >
全局事件总线
其目的是为了实现任意组件间的通信,先给出一个如下图所示的例子:
==⚠ Switch to EXCALIDRAW VIEW in the MORE OPTIONS menu of this document. ⚠== You can decompress Drawing data with the command palette: ‘Decompress current Excalidraw file’. For more info check in plugin settings under ‘Saving’
Excalidraw Data
Text Elements
App ^rsOKNBNN
A ^raXgcmMX
B ^NWERw7U2
C ^YjZnoxKC
D ^2HhRrE4b
X ^g7BjquH9
demo ^gWNyyOSH
demo,666 ^33jSC4NV
test ^9pEnPyXq
test,8 ^tpg8HxMh
666 ^dIOHCXve
8 ^PtwYQC5Y
%%
Drawing
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 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 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 h7gQAA==
%%
A 给 X 绑定了一些自定义事件,回调函数 demo 在 A 中
D 触发 A 所绑定的自定义事件,并携带参数,即可以实现与 A 的通信
B 也给 X 绑定了一些自定义事件,回调函数 demo 在 B 中
D 触发 B 所绑定的自定义事件,并携带参数,即可以实现与 B 的通信
attention: 此时要求 X 可以被所有组件看到,并且其可以调用 $emit,$off,$on 等方法
具体实现
安装全局事件总线
1 2 3 4 5 new Vue ({ beforeCreate ( ){ Vue .prototype .$bus =this }, })
使用事件总线:
接收数据:A 组件想接收数据,则在 A 组件中给 $bus 绑定自定义事件,事件的回调留在 A 组件自身。
提供数据:this.$bus.$emit('事件名',数据)
1 2 3 4 5 6 7 methods ( ){ demo (data ){......} } ...... mounted ( ){ this .$bus .$on('事件名' ,回调函数) }
示例
1 2 3 4 5 6 7 8 9 10 import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false new Vue ({ render : h => h (App ), beforeCreate ( ) { Vue .prototype .$bus = this }, }).$mount('#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 <template > <div class ="school" > <h2 > 学校名称:{{ name }}</h2 > <h2 > 学校地址:{{ address }}</h2 > </div > </template > <script > export default { name : "School-item" , data ( ) { return { name : "万星阁" , address : "众星之上" , age : 10 }; }, mounted ( ) { this .$bus .$on("student-item-event" , (msg ) => { console .log ("School 收到了:" , msg); }); }, beforeDestroy ( ) { this .$bus .$off("student-item-event" ); }, }; </script > <style scoped > .school { background-color : blueviolet; padding : 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 <template > <div class ="student" > <h2 > 学生姓名:{{ name }}</h2 > <h2 > 学生性别:{{ sex }}</h2 > <button @click ="sendMsgToSchool" > 发送给School信息</button > </div > </template > <script > export default { name : "Student-item" , data ( ) { return { msg :"are you ok" , name : "牧星" , sex : "男" , }; }, methods : { sendMsgToSchool ( ) { this .$bus .$emit("student-item-event" , this .name ); } }, }; </script > <style scoped > .student { background-color : red; padding : 5px ; margin-top : 30px ; } </style >
消息订阅与发布
原生 JS 没有这些功能,可以借助 pubsub-js 之类的库实现,首先执行 npm i pubsub-js 命令进行安装,而后依次进行消息的订阅,消息的发布以及消息的解绑即可,示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { PubSub } from "pubsub-js" ;mounted ( ) { this .wxgId =PubSub .subscribe ("wxg" , (msg,data ) => { console .log ("wxg msg is published" ,msg,data); }); }, beforeDestroy ( ) { PubSub .unsubscribe (this .wxgId ); }, methods : { sendMsgToSchool ( ) { PubSub .publish ("wxg" , this .msg ); } },
nextTick
this.$nextTick(回调函数),主要作用是在下一次 DOM 更新结束后执行其指定的回调,用于:当改变数据后,要基于更新后的新 DOM 进行某些操作时,要在 nextTick/所指定的回调函数中执行。
过渡动画
在插入、更新或移除 DOM 元素时,在合适的时候给元素添加样式类名,其大致属性如下
准备好样式:
元素进入的样式:
v-enter: 进入的起点
v-enter-active: 进入过程中
v-enter-to: 进入的终点
元素离开的样式:
v-leave: 离开的起点
v-leave-active: 离开过程中
v-leave-to: 离开的终点
使用 <transition> 包裹要配置过渡动画的元素,并配置 name 属性:
1 2 3 <transition name ="hello" > <h1 v-show ="isShow" > 你好啊!</h1 > </transition >
note: >1. 对于 <transition> 如果制定了 name 属性,则对应的 v-enter-active 应改为 fade-enter-active
2. 若有多个元素需要过度,则需要使用:<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 name ="fade" :appear ="true" > <h1 v-show ="isShow" class ="v-enter-active" > 你好呀</h1 > </transition > </div > </template > <script > export default { name : 'Animo-item' , data ( ) { return { isShow : true } } } </script > <style scoped > h1 { background-color : orange; } .fade-enter-active { animation : wxg 1s ; } .fade-leave-active { animation : wxg 1s reverse; } @keyframes wxg { from { transform : translateX (-100% ); } to { transform : translateX (0px ); } } </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 <template > <div > <button @click ="isShow = !isShow" > 显示/隐藏</button > <transition-group name ="fade" :appear ="true" > <h1 v-show ="isShow" :key ="1" > 你好呀</h1 > <h1 v-show ="isShow" :key ="2" > 牧星</h1 > </transition-group > </div > </template > <script > export default { name : 'Animo2-item' , data ( ) { return { isShow : true } } } </script > <style scoped > h1 { background-color : orange; } .fade-enter ,.fade-leave-to { transform : translateX (-100% ); } .fade-enter-active ,.fade-leave-active { transition : 1s ; } .fade-enter-to ,.fade-leave { transform : translateX (0px ); } </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 <template > <div > <button @click ="isShow = !isShow" > 显示/隐藏</button > <transition-group appear name ="animate__animated animate__bounce" enter-active-class ="animate__swing" leave-active-class ="animate__backOutUp" > <h1 v-show ="isShow" :key ="1" > 你好呀</h1 > <h1 v-show ="isShow" :key ="2" > 牧星</h1 > </transition-group > </div > </template > <script > import 'animate.css' export default { name : 'Animo3-item' , data ( ) { return { isShow : true } } } </script > <style scoped > h1 { background-color : orange; } </style >
Ajax
脚手架配置代理
配置代理的主要目的是避免 跨域 问题,参照下述代码在 Vue.config.js 中添加配置项以设定基础代理和更专业的代理:
基础代理的特点:
优点:配置简单请求资源时直接发给前端 (8080) 即可。
缺点:不能配置多个代理,不能灵活的控制请求是否走代理
工作方式:若按照上述配置代理,当请求了前端不存在的资源时,那么该请求会转发给服务器(优先匹配前端资源)
专项代理的特点:
优点:可以配置多个代理,且可以灵活的控制请求是否走代理。
缺点:配置略微繁琐,请求资源时必须加前缀。
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 const { defineConfig } = require ('@vue/cli-service' )module .exports = defineConfig ({ transpileDependencies : true , lintOnSave : 'warning' , devServer :{ proxy :"http://localhost:5000" }, devServer :{ proxy :{ '/car' :{ target : "http://localhost:5000" , changeOrigin : true , ws : true , pathRewrite : { '^/car' : '' } }, '/user' : { target : "http://localhost:5000" , changeOrigin : true , ws : true , pathRewrite : { '^/user' : '' } } } } })
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 lang ="" > <div class ="app" > <h1 > {{msg}}</h1 > <button @click ="fetchData()" > 获取学生信息</button > </div > </template > <script > import axios from "axios" ;export default { name : 'App' , components : { }, data ( ) { return { msg : 'Hello Vue' , } }, methods : { fetchData ( ) { axios.get ("http://127.0.0.1:8080/car/car" ).then ( response => { console .log ("请求成功" ,response.data ); }, error => { console .log ("请求失败" ,error.message ); } ) } }, } </script >
Vue-resource
原本是 Vue 官方维护的一个 Vue 插件,用于处理网络请求,在 1.0 版本中使用较多,现在交给了其他团队维护,使用较少,引入插件之后只需要将 axios 换为 this.$http 即可
1 2 3 4 5 6 7 8 9 10 11 12 import VueResource from 'vue-resource' Vue .use (VueResource )this .$http .get ("http://127.0.0.1:8080/car/car" ).then ( response => { console .log ("请求成功" ,response.data ); }, error => { console .log ("请求失败" ,error.message ); } )
插槽
父组件以向子组件指定位置插入 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 <template > <div class ="category" > <h3 > {{title}}</h3 > <slot > 默认值,未传值时会显示</slot > </div > </template > <script > export default { name : 'Category-item' , props :['listData' ,'title' ], data ( ) { return { } }, } </script > <style > .category { background-color : skyblue; width : 200px ; height : 300px ; } h3 { text-align : center; background-color : orange; } </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 <template > <div class ="container" > <Category title ="美食" > <img src ="https://wanstarge.github.io/images/mx.jpg" alt ="" > </Category > <Category title ="游戏" :listData ="games" > <ul > <li v-for ="(item, index) in games" :key ="index" > {{ item }}</li > </ul > </Category > <Category title ="电影" > <video controls src ="./assets/videos/yar.mp4" > </video > </Category > </div > </template > <script > import Category from "./components/Category" ;export default { name : 'App' , components : { Category }, data ( ) { return { foods : ['火锅' , '烧烤' , '小龙虾' , '牛排' ], games : ['红色警戒' , '穿越火线' , '劲舞团' , '超级玛丽' ], films : ['《教父》' , '《拆弹专家》' , '《你好,李焕英》' , '《万星阁》' ] } }, } </script > <style > .container { display : flex; justify-content : space-around; } img { width : 100% ; } video { width : 100% ; } </style >
具名插槽
和上述默认插槽类似,指定名称即可,示例如下:
1 2 3 4 5 6 7 8 <template > <div class ="category" > <h3 > {{title}}</h3 > <slot name ="center" > 默认值,未传值时会显示</slot > <slot name ="footer" > 默认值,未传值时会显示</slot > </div > </template >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <Category title ="游戏" :listData ="games" > <ul slot ="center" > <li v-for ="(item, index) in games" :key ="index" > {{ item }}</li > </ul > <a slot ="footer" href ="http://www.baidu.com" > 百度搜一下</a > </Category > <Category title ="电影" > <video slot ="center" controls src ="./assets/videos/yar.mp4" > </video > <template v-slot:footer > <div class ="foot" > <a href ="https://btnull.net" > 来片库看看</a > <a href ="https://iqiyi.com" > 推荐</a > </div > </template > </Category > </template >
作用域插槽
数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定,(games 数据在 Category 组件中,但使用数据所遍历出来的结构由 App 组件决定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template > <div class ="category" > <h3 > {{title}}</h3 > <slot :games ="games" > 我是默认的一些内容</slot > </div > </template > <script > export default { name : 'Category-item' , props :['listData' ,'title' ], data ( ) { return { games : ['红色警戒' , '穿越火线' , '劲舞团' , '超级玛丽' ], } }, } </script >
1 2 3 4 5 6 7 8 9 10 11 <template > <div class ="container" > <Category :games ="games" title ="游戏" > <template slot-scope ="wxg" > <ul > <li v-for ="(game, index) in wxg.games" :key ="index" > {{ game }}</li > </ul > </template > </Category > </div > </template >
vuex
在 Vue 中实现集中式状态(数据)管理的一个 vue 插件,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。可以用于实现多组件共享数据,前面说到的 全局事件总线 同样可以实现,如下图所示:
使用 vuex 实现如下图所示:
原理分析
首先 Actions,Mutations,State 都是一个结构体,流程图以顺时针方向直行:
首先由组件分发一个消息,到 Actions 中去查询对应的函数(注:如果函数的参数要从其他渠道获得,可以在此处执行代码,如果参数已知可以跳过改步骤)
Actions 将查询到的函数递交给 Mutations 中去查询,Mutations 中如果查村到了对应的函数,就执行该函数并更新 State
State 中的数据会渲染到组件中
note: Actions 和 Mutations,State 都被 Store 所管理
搭建环境
创建文件: src/store/index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import Vuex from 'vuex' import Vue from "vue" ;const actions = {}const mutations = {}const state = {}Vue .use (Vuex )export default new Vuex .Store ({ actions, mutations, state })
在 main.js 中创建 vm 是传入 store 配置项
1 2 3 4 5 6 7 8 9 import Vue from 'vue' import App from './App.vue' import store from "./store/index" ;Vue .config .productionTip = false new Vue ({ render : h => h (App ), store }).$mount('#app' )
基础使用
在 index.js 中准备好将被调用的任何函数,在组件中调用对应的函数
组件中读取 vuex 中的数据:$store.state.num
组件中修改 vuex 中的数据:$store.dispatch('actiont中的方法名',数据) 或
$store.commit('mutations中的方法名',数据)
note: 若没有网络请求或其他业务逻辑,组件中也可以越过 actions,即不写 dispatch,直接编写 commit
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 import Vuex from 'vuex' import Vue from "vue" ;const actions = { jiaOdd (context, value ){ if (context.state .sum % 2 ) { context.commit ('JIA' , value) } }, jiaWait (context,value ){ setTimeout (() => { context.commit ('JIA' , value) }, 500 ); } } const mutations = { JIA (state,value ) { state.sum += value }, JIAN (state, value ) { state.sum -= value } } const state = { sum : 0 , } Vue .use (Vuex )export default new Vuex .Store ({ actions, mutations, state })
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 > export default { name : 'Count-item' , data ( ) { return { n : 1 , } }, methods : { increment ( ) { this .$store .commit ('JIA' ,this .n ) }, decrement ( ) { this .$store .commit ('JIAN' , this .n ) }, incrementOdd ( ) { this .$store .dispatch ('jiaOdd' , this .n ) }, incrementWait ( ) { this .$store .dispatch ('jiaWait' , this .n ) } }, } </script >
getters
当 state 中的数据需要经过加工后再使用时,可以使用 getters 加工
在 store.js 中追加 getters 配置
1 2 3 4 5 6 7 8 9 const getters={ bigSum (state ){ return state.sum * 10 } } export default new Vuex .Store ({ ...... getters })
组件中读取对应的数据:$store.getters.bigSum
note: 类似于 vm 中的 data 和 computed
map 方法
为了避免复杂的 $store.state 或者 this.$store 写法
mapState 方法: 用于帮助映射 state 中的数据为计算属性
1 2 3 4 5 6 computed : { ...mapState ({'sum' :'sum' , 'school' :'school' , 'subject' :'subject' }), ...mapState (['sum' , 'school' , 'subject' ]), }
mapGetters 方法: 用于帮助我们映射 getters 中的数据为计算属性
1 2 3 4 5 6 computed : { ...mapGetters ({'sum' :'sum' , 'school' :'school' , 'subject' :'subject' }), ...mapGetters (['sum' , 'school' , 'subject' ]), }
mapActions 方法: 用于帮助我们生成与 actions 对话的方法,即:包含 $store.dispatch(xxx) 的函数
note: 注意此时默认传递的参数为 value 也就是 event,在组件调用时应当指明参数,比如 @click="inecermentOdd(n)"
1 2 3 4 5 6 methods : { ...mapActions ({'incrementOdd' :'jiaOdd' , 'incrementWait' :'jiaWait' }), ...mapActions (['jiaOdd' , 'jiaWait' ]), }
mapMutations 方法: 用于帮助我们生成与 mutations 对话的方法,即:包含 $store.commit(xxx) 的函数
1 2 3 4 5 6 methods : { ...mapActions ({'increment' :'JIA' , 'decrement' :'JIAN' }), ...mapActions (['JIA' , 'JIAN' ]), }
模块化
在 store 对应的 index.js 中以模块化的形式创建对应的配置项,并在其中写入对应的函数及变量,而后使用 mmodules 属性进行挂载
在 Vue 文件中使用上述提到的 map 方法时,可以直接使用 ...mapState(['cntOptions','perOptions']) 来读取,而后在组件中使用 cntOptions.变量名 来插值绑定数据,也可以在创建配置项时指定成员属性 namespaced:true 来启用命名空间,这样就可以使用 ...mapState('perOptions', ['personList']), 来读取,从而直接使用变量名来绑定插值数据
直接通过 this.$store 访问
使用 this.$store.commit("方法名",...) 来触发事件的可以改为 this.$store.commit("模块名/方法名",...) 来进行触发
对于使用 this.$store.state.变量名 来读取变量的方式应当修改为 this.$store.模块名/变量名 来读取数据
对于通过 this.$store.getters.属性名 访问数据的可以修改为 this.$store.getters["模块名/属性名"] 来进行访问
note: 模块化编程的好处,可以将各个数据和对应的操作分开保存在不同的文件中
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 const cntOptions={ namespaced :true , actions :{ jiaOdd (context, value ) { if (context.state .sum % 2 ) { context.commit ('JIA' , value) } }, jiaWait (context, value ) { setTimeout (() => { context.commit ('JIA' , value) }, 500 ); } }, mutations :{ JIA (state, value ) { state.sum += value }, JIAN (state, value ) { state.sum -= value } }, state :{ sum : 0 , school : '万星阁' , subject : '全栈' , }, getters :{ bigSum (state ) { return state.sum * 10 } } } const perOptions={ namespaced : true , actions :{}, mutations :{}, state :{ personList : [ { id : '001' , name : '张三' } ] }, getters :{} } export default new Vuex .Store ({ modules : { cntOptions, perOptions } })
1 2 3 4 5 6 7 8 9 methods : { ...mapMutations ('cntOptions' ,{ 'increment' : 'JIA' , 'decrement' : 'JIAN' }), ...mapActions ('cntOptions' ,{ 'incrementOdd' : 'jiaOdd' , 'incrementWait' : 'jiaWait' }), }, computed : { ...mapState ('cntOptions' ,['sum' , 'school' , 'subject' ,'personList' ]), ...mapState ('perOptions' , ['personList' ]), ...mapGetters ('cntOptions' ,['bigSum' ]) }
路由
路由就是一组 key-value 的(key 为路径,value 可能是 function 或 component)对应关系,多个路由,需要经过路由器的管理,编程中的路由往往是为了为实现单页 Web 应用 (SPA, single page web application) :
整个应用只有一个完整的页面。
点击页面中的导航链接不会刷新页面,只会做页面的局部更新。
数据需要通过 ajax 请求获取。
vue 中路由的实现依靠 vue-router 这个插件(Vue2 使用 3 版本)来实现,路由又分为前端路由和后端路由:
后端路由
理解:value 是 function,用于处理客户端提交的请求
工作过程:服务器接收到一个请求时,根据请求路径找到匹配的函数来处理请求,返回响应数据。
前端路由
理解:value 是 component,用于展示页面内容
工作过程:当浏览器的路径改变时,对应的组件就会显示
基础使用
安装 vue-router: npm install vue-router@3
在 src/router/index.js 文件中编写 router 配置项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import Vue from 'vue' import VueRouter from "vue-router" ;import Home from "../components/Home" ;import About from '../components/About' ;Vue .use (VueRouter )export default new VueRouter ({ routes : [ { path : '/about' , component : About , }, { path : '/home' , component : Home , } ] })
实现切换 (active-class 可配置高亮样式){html}<router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
指定显示位置:{html}<router-view></router-view>
attention: >5. 路由组件通常存放在 pages 文件夹,一般组件通常存放在 components 文件夹。
6. 通过切换,“隐藏了的路由组件,默认是被销毁掉的,需要的时候再去挂载。
7. 每个组件都有自己的 $route 属性,里面存储着自己的路由信息。
8. 整个应用只有一个 router,可以通过组件的 $router 属性获取到。
嵌套(多级)路由
配置多级子路由规则
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 export default new VueRouter ({ routes : [ { path : '/about' , component : About , }, { path : '/home' , component : Home , children : [ { path : 'news' , component : News }, { path : 'message' , component : Message } ] } ] })
在对应父组件 (此处为 Home.vue) 中进行调用:{html}<router-link class="list-group-item" active-class="active" to="/home/news">新闻</router-link>
同样使用 {html}<router-view></router-view> 标签进行展示
attention: >4. 子路由配置时不要写 /
5. 标签跳转使用时,要带上父路径
路由传参
query 参数
传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <router-link :to ="`/home/message/detail?id=${message.id}&title=${message.title}`" > {{message.title}}</router-link > <router-link :to ="{ path:'/home/message/detail', query:{ id:message.id, title:message.title } }" > {{ message.title }}</router-link >
接收参数
1 2 $route.query .id $route.query .title
命名路由
可以简化路由的跳转
给路由命名:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 { path : '/home' , component : Home , children : [ { path : 'news' , component : News }, { path : 'message' , component : Message , children : [ { name :'detail' , path : 'detail' , component : () => import ('../pages/Detail' ) }, ] } ] }
简化跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 <router-link :to ="{ // 简化后通过名字直接跳转 name:'detail', // 简化前通过路径跳转 path:'/home/message/detail', query:{ id:message.id, title:message.title } }" > {{ message.title }}</router-link >
params 参数
配置路由,声明接收 params 参数
1 2 3 4 5 6 7 8 9 10 11 { path : 'message' , component : Message , children : [ { name :'detail' , path : 'detail/:id/:title' , component : () => import ('../pages/Detail' ) }, ] }
传递参数
1 2 3 4 5 6 7 8 9 10 11 12 13 <router-link :to ="`/home/message/detail/${message.id}/${message.title}`" > {{message.title}}</router-link > <router-link :to ="{ name: 'detail', params: { id: message.id, title: message.title } }" > {{ message.title }}</router-link >
attention: 此时若使用对象写法,则必须要求使用 name 属性,也就是上方提到的命名路由方法,而不能使用 path 属性
props
可以让路由组件更方便地接收参数,有对象写法,布尔写法,函数写法,最为常用的是函数写法,示例配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { name :'detail' , path : 'detail' , component : () => import ('../pages/Detail' ), props : { test : 'test' }, props : true , props ($route ){ return { id : $route.query .id , title : $route.query .title } } },
在组件中使用 props:['id','title'] 等来接收数据
replace 模式
和浏览器的网址切换模式有关,基础的模式是栈模式 (push) ,即每路由到一个新的地址,向栈中压入一条现有的网址信息;可以通过在 {html}<route-link> 标签中指定 replace 属性为真来切换为替换模式 ,即直接将栈顶的网址替换
1 2 3 <router-link :replace =true class ="list-group-item" active-class ="active" to ="/about" > About</router-link > <router-link replace class ="list-group-item" active-class ="active" to ="/about" > About</router-link >
编程式路由
也就是不使用指定的 {html}<route-link> 标签,可以通过自指定函数实现跳转的路由方法
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 <template > <button @click ="pushShow(message)" > push查看</button > <button @click ="replaceShow(message)" > replace查看</button > <button @click ="back" > 后退</button > <button @click ="forward" > 前进</button > </template > <script > methods : { pushShow (message ){ this .$router .push ({ name : 'detail' , query : { id : message.id , title : message.title } }, () => { }) }, replaceShow (message ){ this .$router .replace ({ name : 'detail' , query : { id : message.id , title : message.title } }, () => { }) }, back ( ){ this .$router .back () }, forward ( ){ this .$router .forward () } }, </script >
还有一个 this.$router.go() 方法在其中传入正数表示前进多少步,负数则是后退多少步
缓存路由
防止切换时组件被销毁,输入的信息需要重新输入,这时可以使用缓存路由,在需要缓存的组件展示区外包裹 <keepalive></keepalive> 标签,include 属性中指定缓存哪些组件(注:此时要求为组件名,即在编写组件时 name 属性指定的名称)
1 2 3 4 5 6 7 <keep-alive include ="News-item" > <router-view > </router-view > </keep-alive > <keep-alive include ="News-item,Message-item" > <router-view > </router-view > </keep-alive >
生命周期
作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。
activated : 路由组件被激活时发。
deactivated : 路由组件失活时发。
路由守卫
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 router.beforeEach ((to, from , next ) => { if (to.meta .isAuth ) { if (localStorage .getItem ('school' )==='wxg' ) { next () }else { alert ('无权限' ) } }else { next () } }) router.afterEach ((to, from ) => { if (from .path === '/home/news' ) { console .log ('全局后置守卫' ) } }) { name : 'news' , path : 'news' , component : News , beforeEnter : (to, from , next ) => { if (localStorage .getItem ('school' ) === 'wxg' ) { next () } else { alert ('无权限' ) } } },
组件内路由守卫
1 2 3 4 5 6 7 8 beforeRouteEnter (to, from , next) { next () }, beforeRouteLeave (to, from , next) { next () }
History 模式
对于一个 url 来说,#及其后面的内容就是 hash 值,hash 值不会包含在 HTTP 请求中,即:hash 值不会带给服务器
在路由的配置文件中与 routes 并列设置一个 mode 属性,在其中可以指定下列两种模式:
hash 模式:
网址路径中会包含 /#/,不美观
若以后将地址通过第三方手机 app 分享,若 app 校验严格,则地址会被标 i 记为不合法
兼容性较好
history 模式:
网址路径中不包含 /#/,干净美观
兼容性和 hash 模式相比略差
应用部署上线时需要后端人员支持,解决刷新页面服务端 404 的问题,在 nodejs 中参考 history模式解决
1 2 3 4 const router = new VueRouter ({ mode : history, routes : [...], })
UI
ElementUI
elementui 默认是全部引入,为了按需引入需要使用 babel-plugin-component 插件来实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 module .exports = { presets : [ '@vue/cli-plugin-babel/preset' , ["@babel/preset-env" , { "modules" : false }] ], "plugins" : [ [ "component" , { "libraryName" : "element-ui" , "styleLibraryName" : "theme-chalk" } ] ] }
1 2 3 import { Button , Row } from 'element-ui' ;Vue .component (Button .name , Button );Vue .component (Row .name , Row );