Vue3.x——选项式API 一、Vue3简介 1、概述 作者:尤雨溪
官网:https://cn.vuejs.org
Vue.js是一套构建用户界面的渐进式 框架。声明式渲染和组件系统是Vue的核心库所包含内容。
构建用户界面: 通过数据渲染成页面模板,展示给前端用户
渐进式 :从vue
模板-指令-组件-路由- vuex
等由简单=>复杂开发学习应用过程
框架 :半成品的应用,不断在维护更新的开源框架(之前学的bootstrap,jQuery也是一个框架)
声明 式渲染:(如同js基础一样,要使用变量则必须先声明变量,这种称之为声明式, 数据驱动视图渲染)Vue.js的核心是一个允许采用简洁的模板语法来声明式的将数据渲染进DOM的系统。也就是咱们后边数据驱动页面渲染。
组件系统是Vue的另一个重要概念,因为它是一种抽象的允许我们使用小型、独立 和通常可复用 的“小积木”构建大型应用。几乎任意类型的应用界面都可以抽象为一个组件树。
2、回顾js-dom渲染数据 特点: 需要开发者频繁操作dom data数据;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <body> <div id ="test" > </div > <p > <button onclick ="editmsgFn()" > 修改msg</button > </p > </body> <script > let msg = 'hello world' let oDom = document .querySelector ('#test' ) oDom.innerHTML = msg const editmsgFn = ( ) => { msg = '嘻嘻嘻' ; oDom.innerHTML = msg } </script >
二、Vue入门 1、vue2-API 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js" ></script> <div id ="app" > {{ message }} </div > const app = new Vue ({ data : { msg : 'hello Vue 2' , count : 10 , } }) app.$mount('#app' )
2、vue3-API 风格 2种 Vue 的组件可以按两种不同的风格书写:选项式 API 和组合式 API 。 使用选项式 API,我们可以用包含多个选项的对象来描述组件的逻辑,例如 data
、methods
和 mounted
。选项所定义的属性都会暴露在函数内部的 this
上,它会指向当前的组件实例。
对应的data数据只能显示在对应的vue实例绑定的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 25 26 27 28 29 30 31 32 33 34 <body > <div id ="app" > <div id ="test" > {{ msg }}</div > <div id ="count" @click ='addCount' > {{ count }}</div > </div > </body > <script type ="module" > import { createApp } from 'https://unpkg.com/vue@3/dist/vue.esm-browser.js' const { createApp } = Vue const app = createApp ({ data () { return { msg : 'Hello Vue!' , count : 10 , } }, methods :{ addCount ( ){ this .count ++ } } }) app.mount ('#app' ) </script > </html >
组合式 API (Composition API) 通过组合式 API,我们可以使用导入的 API 函数来描述组件逻辑。在单文件组件中,组合式 API 通常会与 setup 搭配使用。这个 setup
attribute 是一个标识,告诉 Vue 需要在编译时进行一些处理,让我们可以更简洁地使用组合式 API。比如,<script></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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="./node_modules/vue/dist/vue.global.js" > </script > </head > <body > <div id ="app" > <p > {{ message }}</p > <p > {{count}}</p > <button @click ="count--" > -</button > <button @click ="handleIncrement" > +</button > </div > </body > </html > <script > const { createApp, ref } = Vue ; createApp ({ setup ( ) { const message = ref ('hello world' ) const count = ref (1 ) const handleIncrement = ( ) => count.value ++ return { count, message, handleIncrement } } }).mount ('#app' ) </script >
3、数据渲染 新旧虚拟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 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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > </head > <body > <div id ="app" > <div > <p > count: {{ count }}</p > </div > <p > <button @click ="count++" > +</button > </p > <br /> <p > msg: {{ msg }}</p > </div > </body > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script > const { createApp } = Vue const vdom = { tag : 'div' , attrs : { id : 'app' }, children : [ { tag : 'p' , children : `count: {{ count }}` }, { tag : 'br' }, { tag : 'p' , children : `msg: {{ msg }}` }, ] } const vdom2 = { tag : 'div' , attrs : { id : 'app' }, children : [ { tag : 'p' , children : `count: 1` }, { tag : 'br' }, { tag : 'p' , children : `msg: Hello World` }, ] } const app = createApp ({ data ( ) { return { msg : 'Hello World' , count : 1 } } }).mount ('#app' ) console .log (app); </script > </html >
4、Vue的开发模式 M-V-VMM:(model)普通的javascript数据对象(其实就是一个对象,对象里放了数据) V:(view)前端展示页面(可以理解成html内容) VM:(ViewModel)用于双向绑定数据 与页面,对于我们的课程来说,就是vue的实例vm MVVM 模式中的 ViewModel,它采用双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然。这种模式下,页面输入改变数据,数据改变影响页面数据展示与渲染
vue使用MVVM响应式编程模型,避免直接操作(真实)DOM , 降低DOM操作的复杂性。(虚拟DOM)
通过chrome中的谷歌插件商店安装Vue Devtools工具,此工具帮助我们进行vue数据调试所用,一定要安装。Vue工具在谷歌商店的地址是:https://chrome.google.com/webstore?utm_source=chrome-ntp-icon 请注意:打开chrome应用商店,需要科学上网 才能访问到,至于怎么科学上网请各位自行解决。
安装好后打开Chrome的开发者工具(F12或Ctrl+Shift+I)
即可使用:
补充:如果自己解决不了科学上网问题,但是又需要用Vue开发工具那该怎么办? 如果实在解决不了科学上网难题,Vue官方也提供了插件源码允许我们自己编译/构建Google Chrom插件,步骤如下(构建插件流程稍微麻烦一些<**不要求掌握如何构建**>,此处已为同学们构建好,可以直接使用)。
安装chrome Vue Devtools调试工具详细教程: https://blog.csdn.net/li22356/article/details/113092495?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control
5、Vue模板语法 5.1、插值表达式 => Mustache 语法 插值表达式: 是vue框架提供的一种在HTML模板中绑定数据的方式,也叫 “Mustache”语法, 使用{{变量名}}
方式绑定Vue实例中data中的数据变量,会将绑定的数据实时的在视图中显示出来。
插值表达式的写法支持使用:
变量名 部分 JavaScript表达式注:{{ }}
括起来的区域,就是一个就是js语法区域,在里面可以写部份的js语法。不能写 var a = 10;分支语句;循环语句 三元运算符 方法调用(方法必须需要先声明) … 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 <body > <div id ="app" > <p > msg: {{ msg }}</p > <p > count: {{ count }}</p > <p > fn: {{ fn }}</p > <p > bool: {{ bool }}</p > <p > udf: {{ udf }}</p > <p > null: {{ null }}</p > <p > arr: {{ arr }}</p > <p > obj: {{ person }}</p > <hr /> <p > sum: {{ count * 2 + 3 }}</p > <p > random: {{ randomNum() }}</p > <p > 是否成年: {{ age >= 18 ? '成年' : '未成年' }}</p > <p > 昵称: {{ nickname || '新用户asydiasbd' }}</p > <p > 昵称: {{ nickname ?? '新用户asydiasbd' }}</p > <p > 昵称: {{ nickname && '新用户asydiasbd' }}</p > <h5 > {{title.substr(0,6)}}</h5 > </div > </body > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script > const { createApp } = Vue ; createApp ({ data ( ) { return { msg : 'Hello World' , count : 1 , bool : true , udf : undefined , null : null , fn : () => { }, arr : [1 , 2 , 3 ], person : { name : 'Allen' , age : 18 }, age : 18 , nickname : undefined , title : "我是一个标题,你们看到没有" , name : "张三" , } }, methods : { randomNum ( ) { return Math .random () } } }).mount ('#app' ) </script > </html >
指令 组件 路由 vuex ui库和插件
5.2、指令 问1:什么是指令?
指令的含义:在vue的html中,以v-
开头的自定义属性就是指令。
详见官网对指令的说明:https://cn.vuejs.org/v2/api/#%E6%8C%87%E4%BB%A4
问2:指令有什么作用?
正如插值表达式的效果,插值表达式只能用于标签之间的数据输出;在标签属性上,插值表达式无用武之地,但是有需要在属性中使用可变数据的情况,此时指令就能帮助我们解决这个问题。
语法糖:复杂操作的简化形式
当表达式的值改变时,将其产生的连带影响,响应式地作用于页面(DOM)。(简化操作)
小试牛刀 :v-text指令与v-html指令【相当于innertHTML和innerText】
官网
v-text:https://cn.vuejs.org/v2/api/#v-text
v-html:https://cn.vuejs.org/v2/api/#v-html
三、常用指令 (v-model v-bind v-if v-show v-text v-html v-on(@) v-for(数组(item,inex) 对象 (value,key,index) 数字(1) 字符串)
1、v-cloak 作用: 解决浏览器在加载页面时因存在时间差而产生的闪动
问题
原理: 先隐藏元素挂载位置,处理好渲染后再显示最终的结果
注意:**通过属性选择器设置该元素的 display: none;
文档地址: https://cn.vuejs.org/v2/api/#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 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <style > [v-cloak] { display : none; } </style > </head > <body > <div id ="app" v-cloak > <p > {{ message }}</p > <p > {{count}}</p > <button @click ="count--" > -</button > <button @click ="handleIncrement" > +</button > </div > </body > </html > <script type ="module" > const { createApp } = Vue setTimeout (() => { const app = createApp ({ data ( ) { return { message : 'hello world' , count : 1 } }, methods : { handleIncrement ( ) { this .count ++ } } }) app.mount ('#app' ) }, 1000 ) </script >
2、v-text 数据绑定 v-text 填充纯文本, 可以将文本数据渲染在绑定指令的dom内部(innerText)相当于之前原生的innerText
相比插值表达式更加简洁 不存在插值表达式的闪动问题 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <div id ='app' > <span > {{msg1}}</span > <span v-text ="msg1" > </span > <span > {{msg2}}</span > <span v-text ="msg2" > </span > </div > <script > new Vue ({ el : '#app' , data : { msg1 : '今晚da老虎' , msg2 :'<a href="http://www.baidu.com/">百度一下</a>' } }) </script >
3、v-html 数据绑定 v-html 填充HTML片段, 可以将html格式的字符串转译为真正的html标签来渲染(利用文章数据/商品详情数据很可能会是html格式的字符串数据) ,往往这种标签结构的数据都是前端通过富文本编辑器提交给后端的.相当于原生innerHTML
存在安全问题 本网站内部数据可以使用,来自第三方的数据不可使用只有一个场景会使用:后台会用,比如有一个企业站,会发不企业的动态的新闻,这个时候会使用富文本编辑器,由于内容是自己人加的,所以可以放心使用。 自己攻击自己(自攻) 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 <div id ='app' > <div > {{html1}}</div > <div v-html ="html1" > </div > <div v-html ="html2" > </div > </div > <script > const { createApp } = Vue ; const app = createApp ({ data ( ) { return { html1 :'<a href="http://www.baidu.com/">百度一下</a>' , html2 :'<span onclick="console.log(document.cookie)">测试</span>' } }, methods : { handleIncrement ( ) { this .count ++ } } }) app.mount ('#app' ) </script >
有些时候我们不想指令中的表达式进行运行,只需要给表达式加个引号 。例如:
1 2 <div v-html ='"msg"' > </div > <div v-html ="'msg'" > </div >
针对后续想让指令属性值不解析的操作都可以这么去做。
4、v-once(了解) 作用: 只渲染元素或组件 一次,之后元素或组件将失去 响应式(数据层面)功能(对于数据的一锤子买卖)
使用vm.message = ‘hello world’ ,页面不会重新渲染 示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <div id ="app" > <h3 > {{message}}</h3 > <div v-once > {{message}}</div > </div > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > <script type ='javascript' > const { createApp } = Vue ; const app = createApp ({ data ( ) { return { message :"只有一次哦" } } }) app.mount ('#app' ) </script >
5、v-pre(了解) 元素内具有 v-pre
,所有 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 <body > <div id ="app" > <div v-html ="msg" > </div > <div v-text ="msg" > </div > <div > {{ msg }}</div > <div v-pre > {{ msg }}</div > </div > </body > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > <script > const { createApp } = Vue ; const app = createApp ({ data () { return { msg : '<h1>hello vue</h1>' } } }) app.mount ('#app' ) </script > </html >
6、v-bind(重点) 作用: 动态地绑定一个或多个attribute
【实现了可以允许我们在html内置的属性值中使用变量】
场景:复用某个数据的时候会使用。例如:飞猪官网
1 2 3 4 5 <a v-bind:href ="url" v-bind:target ="target" > {{alt}}</a > <a :href ="url" :target ="target" > {{alt}}</a >
示例代码 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <body > <div id ="app" > <p v-bind:title ="tit" > 北科千锋前端培训扛把子</p > <p v-bind:class ="active" > 北科千锋前端培训扛把子</p > <p :class ="active" > 北科千锋前端培训扛把子</p > <img :src ='imgUrl' /> <img v-bind ="imgInfo" /> </div > </body > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > <script > Vue .createApp ({ data ( ) { return { imgurl : 'http://www.mobiletrain.org/page/images/%E9%83%AD%E7%BF%94.png' , width : 200 , disabled : false , imgInfo : { width : 200 , src : 'http://www.mobiletrain.org/page/images/%E9%83%AD%E7%BF%94.png' } } }, }).mount ('#app' ) </script >
7、v-on(重点) 7.1、基本使用 作用: 绑定事件监听器(事件绑定)
语法: v-on:事件名= “事件的逻辑处理”, 注意: 事件的处理逻辑可以是简单的逻辑运算比如赋值, 也可以是一个处理函数
示例代码:
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 <button v-on:click ="num++" > </button > <button @click ="num++" > </button > <button @click ="alert(123)" > alert</button > <button @click ="say1" > 点击事件不传参</button > <button @click ="say2(100,$event)" > 点击事件传参数</button > <p > <button v-for ="(item,index) in numArr" :key ="index" @click ="()=>{count+=item}" > + {{item}}</button > </p > <script src ="https://unpkg.com/vue@3/dist/vue.global.js" > </script > `<script > Vue .createApp ({ data ( ) { return { num:10 } }, methods : { say1 : function (event ) { console .log (event) }, say2 : function (num, event ) { console .log (event) console .log ('你点了' + num); } } }).mount ('#app' ) </script >
在不传递自定义参数的时候,上述两种用法均可以使用;但是如果需要传递自定义参数的话,则需要使用第2种方式。
事件对象的传递与接收注意点
如果事件直接使用函数名并且不写小括号,那么默认 会将事件对象作为唯一参数进行传递,可以在定义函数的位置直接定义一个形参,并且在函数内可以使用该形参 如果使用常规的自定义函数调用(只要写了小括号),那么如果需要使用事件对象则必须作为参数进行传递 ,且事件对象的名称必须是“$event” 7.2、事件修饰符 含义:用来处理事件的特定行为(也是vue提供一些语法糖)
使用示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <button @click.once ="doThis" > 抽奖</button > <div @click.self ='fun1' > <p @click ='fun2' > 子元素 </p > </div > <button @click.stop ="doThis" > </button > <button @click.prevent ="doThis" > </button > <div @click.capture ="doThis" > ...</div > <a @click.once ="doThis" > </a > <button @click.stop.prevent ="doThis" > </button >
更多事件修饰符请参考官方文档:https://cn.vuejs.org/v2/api/#v-on
7.3、按键修饰符 按键修饰符:注意必须是键盘事件
.enter
.tab
.delete
(捕获“Delete”和“Backspace”两个按键).esc
.space
.up
.down
.left
.right
在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on
在监听键盘事件时添加按键修饰符。
1 2 3 4 5 <input @keyup.enter ="submit" > <input v-on:keyup.delete ="handle" >
更多按键修饰符请参考官方文档:https://cn.vuejs.org/v2/guide/events.html#%E6%8C%89%E9%94%AE%E4%BF%AE%E9%A5%B0%E7%AC%A6
8、循环渲染指令 作用: 根据一组数组 或对象的选项列表进行渲染。
指令: v-for
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 <body > <div id ="app" > <ul > <li v-for ="(item,index) in list" > {{index}}--{{item.name}} </li > </ul > <ul > <li v-for ="(item,index) in list" :key ="item.id" > {{index}}--{{item.name}} </li > </ul > <ul > <li v-for ="(value, key, index) in myObject" > {{ index }}. {{ key }}: {{ value }} </li > </ul > <span v-for ="(str, index) in message" :style ="{ color: index % 2 === 0 ? 'red' : 'green' }" > {{str}}</span > <ul > <li v-for ="(item,index) in list1" :key ="item.id" > <div > <p > {{item.name}}</p > <span v-for ="child in item.girlfriend" :key ="index" > {{child}}</span > </div > </li > </ul > </div > </body > <script > Vue .createApp ({ data ( ) { return { message : 'Hello' , list : [ { id : 1 , name : '张三' , age : 16 }, { id : 2 , name : '李四' , age : 8 }, { id : 3 , name : '王五' , age : 1.5 }, ], userinfo : { name : "李云龙" , age : 20 , sex : '男' }, list1 : [ { id : 1 , name : '张三' , girlfriend : ['小红' , '小丽' ] }, { id : 2 , name : '李四' , girlfriend : ['小白' , '小黑' ] }, ] } }, }).mount ('#app' ) </script >
细节:key的作用,提高性能,不影响显示效果(如果没有id,可以考虑使用索引替代或单元项本身
),切记key
的值不能重复,只要遵循不重复的原则即可,值是什么无所谓。
key的作用, 就是帮助vue在数据变化而生成新的虚拟dom之后, 做虚拟dom对比的时候判断的依据. 判断节点没变化就复用, 如果变化了,就根据虚拟dom,删除旧的真实的dom节点,然后创建新的真实的dom 节点.
演示案例 - 设置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 <body > <div id ="app" > <ul > <li v-for ="(item,index) in listName" :key ='item.id' > <input type ="checkbox" /> {{index}}--{{item.name}} </li > <input type ="text" v-model ='a' /> </ul > <button @click ='add' > add</button > </div > </body > <script > Vue .createApp ({ data ( ) { return { a : '' , "listName" : [ { "id" : 1 , name : '德玛西亚' }, { "id" : 2 , name : '德邦' }, { "id" : 3 , name : '剑圣' } ] } }, methods : { add ( ) { this .listName .unshift ({ id : this .listName .length + 1 , name : this .a }) } } }).mount ('#app' ) </script >
9、分支渲染指令 作用: 根据表达式的布尔值(true/false)进行判断是否渲染 /显示 该元素
上述三个指令是分支中最常见的。根据需求,v-if可以单独使用,也可以配合v-else一起使用,也可以配合v-else-if和v-else一起使用。
使用示例:
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 ="app" > <div v-if ="score >= 90" > 优秀 </div > <div v-else-if ="score >= 80 && score < 90" > 良好 </div > <div v-else-if ="score >= 70 && score < 80" > 一般 </div > <div v-else > 不及格 </div > <hr /> <div v-if ="flag" > 控制元素是否渲染</div > <button @click ="flag=!flag" > 是否渲染</button > </div > </body > <script > Vue .createApp ({ data ( ) { return { score : 88 , flag : false } }, }).mount ('#app' ) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <p v-show ='isflag' > 是否显示内容</p > <p v-if ='isflag' > 是否显示内容</p > <button @click ='isflag=!isflag' > 切换</button > <template v-show ="isflag" > <h1 > Title</h1 > <p > Paragraph 1</p > <p > Paragraph 2</p > </template > <script > Vue .createApp ({ data ( ) { return { isflag : false } }, }).mount ('#app' ) </script >
思考:v-if系列与v-show的区别是什么?
v-if:控制元素是否渲染
v-show:控制元素是否显示(已经渲染 ,display:none;)
v-if系列指令、v-show指令可以与v-for指令结合起来使用(循环+分支)。例如
1 2 3 <ul > <li v-for ='(v,k,i) in obj' v-show ='v==25' > {{v}}</li > </ul >
面试题:v-for与v-if谁的优先级高,能否一起使用?
面试题 v-for 与v-if 比较 优先级谁高?
解释:
在vue3中, 当它们同时存在于一个节点上时,v-if
比 v-for
的优先级更高
在vue2中, 当它们同时存在于一个节点上时,v-for 比
v-if` 的优先级更高
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <ul> <!--第一种情况报错,由于v- for 优先级高于v-if 在v-if 的作用域中获取不到item--> <!-- <li v-for="(item,index) in list" :key="item.id" v-if="item.age>8"> {{index}}--{{item.name}} </li> --> <!--解决方法1: 使用v-show 代替v-if--> <!-- <li v-for="(item,index) in list" :key="item.id" v-show="item.age>8"> {{index}}--{{item.name}} </li> --> <!--解决方法2: 使用v-show 代替v-if--> <template v-for="(item,index) in list"> <li v-if="item.age>8"> {{ item.name }} </li> </template> </ul>
面试题:v-if 和 v-show的 使用区别?
答: 如果元素频繁要切换显示隐藏 则使用v-show 更加合适,
如果元素被创建,可能不会进行状态得切换,使用v-if
9、综合案例:简易购物车 案例效果
细节:
展示基本的商品信息 计算每个商品的小计 商品数量的加、减操作+:增加商品数量,同时更新小计 -:减少商品数量,同时更新小计,如果本身为“1”,再点-号则需要移除商品 如果需要在Vue实例中访问自身data属性中的数据,可以使用以下方式:
this.xxxxx this.$data.xxxxx this._data.xxxxx 实例代码
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 <!DOCTYPE html > <html > <head > <meta charset ="utf-8" /> <meta http-equiv ="X-UA-Compatible" content ="IE=edge,chrome=1" /> <title > 综合案例:简易购物车</title > <meta name ="viewport" content ="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /> <meta name ="description" content ="" /> <meta name ="keywords" content ="" /> <link href ="" rel ="stylesheet" /> <script src ="./js/vue.js" > </script > </head > <body > <div id ="app" > <ul > <li v-for ="(item,index) in cartData" :key ="item.id" > 商品id:{{item.id}}    商品名称:{{item.name}}    商品单价:{{item.price}}    购买数量:<button @click ="jian(item,index)" > -</button > {{item.num}}<button @click ="item.num++" > +</button >     商品小计:{{item.price * item.num}} </li > </ul > </div > <script > Vue .createApp ({ data : { cartData : [ { id : 1 , name : '小米' , price : 100 , num : 1 }, { id : 2 , name : '华为' , price : 200 , num : 1 }, { id : 3 , name : '联想' , price : 300 , num : 1 } ] }, methods : { jian (item, index ) { if (item.num === 1 ) { if (confirm ("确认不买一件吗?" )) { this .cartData .splice (index, 1 ) } } else { item.num -- } }, } }).mount ('#app' ) </script > </body > </html >
 
表示tab
,一个顶四个
10、样式绑定 10.1、class样式绑定 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 <style > .on { color : red; } .big { font-size : 40px ; } </style > <div v-bind:class ="{on: isflag}" > class样式</div > <div v-bind:class ="isflag?'on2':'on3'" > 三目运算符</div > <div v-bind:class ="[activeClass]" > 数组写法</div > <p :class ="['on',isflag?'big':'']" > {{msg}}</p > <p :class ="['on',{big:isflag}]" > {{msg}}</p > <p :class ="{on:false,big:isflag}" > {{msg}}</p > <script type ='text/javascript' > data : { isflag : true , activeClass :'on' }f </script >
10.2、style样式处理 1 2 3 4 5 <p style ="color:red" > {{msg}}</p > <p :style ="{color:'red','font-size':'20px'}" > {{msg}}</p >
10.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 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script> <style> * { margin: 0; padding: 0; } ul { display: flex; } ul li { list-style: none; margin: 10px 20px; } .on { background-color: red; } </style> </head> <body> <div id="app"> <div class="tabs is-centered"> <ul> <li v-for="(item,index) in tabs" :class="activeTabId==item.id?'on':''" :key="index" @click="activeTabId=item.id"> {{item.title}} </li> </ul> </div> <div class="box"> <div v-for="(item,index) in contentArr" v-show="item.id==activeTabId"> {{item.content}} </div> </div> </div> <script> const { createApp } = Vue createApp({ data() { return { tabs: [ { id: 1, title: '图片' }, { id: 2, title: '音乐' }, { id: 3, title: '视频' } ], activeTabId: 1, contentArr: [ { id: 1, content: '我是图片div' }, { id: 2, content: '我是音乐div' }, { id: 3, content: '我是视频div' } ] } }, methods: { handleChangeTab(id) { this.activeTabId = id } } }).mount('#app') </script> </body> </html>
9、v-model 作用:表单元素的绑定,实现了 双向数据绑定 ,通过表单项可以更改数据。
v-model会忽略所有表单元素的value、checked、selected特性的初始值,而总是将Vue实例的数据作为数据来源,应该在data选项中声明初始值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <div id ='app' > <p > {{message}}</p > <input type ='text' :value ='msg' @input ='msg=$event.target.value' /> <input type ='text' v-model ='msg' > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data : { msg : 'message默认值' } }) </script >
1 2 3 4 5 6 7 8 9 10 11 12 <div id ='app' > <textarea v-model ="message" > </textarea > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data : { message : '我是多行文本内容' } }) </script >
注意:在多行文本框中使用插值表达式无效(此时,其只能接受数据,不能改变数据)
1 2 3 4 5 6 7 8 9 10 11 12 <div id ='app' > <input type ="checkbox" v-model ="checked" > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data :{ checked :true } }) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <div id ='app' > <input type ="checkbox" value ="html" v-model ="checkedNames" > <input type ="checkbox" value ="css" v-model ="checkedNames" > <input type ="checkbox" value ="js" v-model ="checkedNames" > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data :{ checkedNames :['html' ] } }) </script >
注意:此种用法需要input
标签提供value
属性,并且需要注意属性的大小写要与数组元素的大小写一致
1 2 3 4 5 6 7 8 9 10 11 12 13 <div id ='app' > 男<input type ="radio" name ="sex" value ="男" v-model ="sex" > 女<input type ="radio" name ="sex" value ="女" v-model ="sex" > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data : { sex : '女' } }) </script >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <div id ='app' > <select v-model ="selected" > <option > 请选择</option > <option value ='HTML' > HTML</option > <option value ='CSS' > CSS</option > <option value ='JS' > JS</option > </select > </div > <script type ='text/javascript' > new Vue ({ el : '#app' , data : { selected : 'JS' } }) </script >
1. 表单修饰符 表单修饰符:用来处理表单的一些特定行为
.lazy:默认情况下Vue的数据同步采用input
事件,使用.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 <body > <div id ='app' > <input type ="text" v-model.lazy ="msg" @input ="fun" /> <input type ="text" v-model.number ="num" /> <input type ="text" v-model.trim ="str" /> </div > </body > const app = Vue.createApp({ el: '#app', data() { return{ msg:'', num:'', str:'' } }, methods:{ fun(){ console.log(this.msg) } } })
2.Vue的双向数据绑定原理 1.原理说明:
当把一个普通的JavaScript对象传给Vue实例的data选项,Vue将遍历此对象所有的属性,使用Object.defineProperty 把这些属性全部转为getter/setter(数据劫持/数据映射)。在属性被访问和修改时通知变化。每个组件实例都有相应的 watcher 实例对象,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
2. Object.defineProperty(obj, prop, descriptor)
obj: 要定义属性的对象。 prop:要定义或修改的属性的名称 。 descriptor: 描述选项。 3.1 value:默认值(给对象属性赋值)
3.2 writable: 是否能够写/修改 true | false(默认)
3.3 configurable: 是否能够删除 true | false(默认)
3.4 enumerable:是否可枚举(遍历) true | false(默认)
3.5 get:获取属性值
3.6 set:设置属性值
**注意: 不能同时设置(writable,value) 和 get,set方法,否则浏览器会报错。 **
4.代码演示vue 双向数据绑定原理 let result = {};
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 <body> <div > <input type ="text" id ="inpt" oninput ="changeVal(this.value)" /> </div > <div id ="content" > </div > </body> <script > let obj = { name : '郭麒麟' , age : 20 }; function observe (target, fn ) { let result = {}; Object .defineProperty (result, 'name' , { get ( ) { console .log ('访问了name' ); return target.name }, set (val ) { console .log ('修改了name' ); target.name = val; fn (result) } }) fn (result) return result } const res = observe (obj, (result ) => { document .querySelector ('p' ).innerHTML = `${result.name} ` }) function changeVal (val ) { res.name = val } </script >
vue3双向数据绑定原理 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> <p id ="p1" > </p > <input type ="text" oninput ="changeFn(this.value)" > </body > </html> <script> let obj = { name : '小翠' , age : 15 }; let p = new Proxy (obj, { get (target, prop ) { console .log (`访问了p的${prop} 属性` ); return Reflect .get (target, prop) }, set (target, prop, val ) { Reflect .set (target, prop, val) }, deleteProperty (target, prop ) { console .log (`删除课p 上的${prop} 属性` ); return Reflect .deleteProperty (target, prop) } }) console .log (p); document .querySelector ('#p1' ).innerHTML = p.name ; document .querySelector ('input' ).value = p.name function changeFn (value ) { console .log (value); document .querySelector ('#p1' ).innerHTML = value; document .querySelector ('input' ).value = value }
10、综合案例:全选/全不选 实现步骤:
添加input框,使得页面具备相同的效果 给全选/全不选
的框添加==合适==的事件 指定事件处理程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 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 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > </title > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <style > li { list-style : none; display : flex; justify-content : space-around; align-items : center; } img { width : 100px ; height : 50px ; } #box { padding : 30px 0 ; } </style > </head > <body > <div id ="app" > <p > <input type ="checkbox" name ="" v-model ="checkFlag" @change ="allcheck" /> 全选/全不选</p > <ul > <li v-for ="item in shopcar" :key = 'item.id' > <input type ="checkbox" name ="" v-model ="checkGroup" :value ="item" @change ="danxuan" /> <img :src ="item.pic" > <div > <p > 商品名称:{{item.name}}</p > <p > 商品价格:{{item.price}}</p > </div > <p > <button @click ='item.num--' :disabled ="item.num==1" > -</button > {{item.num}}<button @click ="item.num++" :disabled ="item.num==item.limit" > +</button > </p > <button @click ="del(item.id)" > 删除</button > </li > </ul > <div id ="box" > 总价:{{sum()}} </div > </div > </body > </html > <script type ="text/javascript" > const app = Vue .createApp ({ data ( ){ checkFlag :false , checkGroup :[], shopcar :[ { "id" :0 , "name" :"商品1" , "price" :10 , "num" :1 , "limit" :5 , "pic" :"https://static.maizuo.com/pc/v5/usr/movie/44dc08914d508fc47c8267c6ca73f2d8.jpg" }, { "id" :1 , "name" :"商品2" , "price" :20 , "num" :2 , "limit" :5 , "pic" :"https://static.maizuo.com/pc/v5/usr/movie/44dc08914d508fc47c8267c6ca73f2d8.jpg" }, { "id" :3 , "name" :"商品3" , "price" :30 , "num" :3 , "limit" :5 , "pic" :"https://static.maizuo.com/pc/v5/usr/movie/44dc08914d508fc47c8267c6ca73f2d8.jpg" } ] }, methods : { sum ( ){ let total = 0 ; this .checkGroup .forEach ((item )=> { total+=item.price *item.num }) return total }, del (id ){ this .shopcar .forEach ((item,i )=> { if (item.id == id){ this .shopcar .splice (i,1 ) } }) this .checkGroup = this .checkGroup .filter ((item )=> { return item.id !=id }) this .danxuan () }, allcheck ( ){ if (this .checkFlag ){ this .checkGroup = this .shopcar }else { this .checkGroup = [] } }, danxuan ( ){ if (this .checkGroup .length == this .shopcar .length ){ this .checkFlag =true }else { this .checkFlag =false } } } }) app.mount ('#app' ); </script >
11、v-memo (不常用) 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 <div id="app" v-cloak> // 案例1: <p>{{ memoData }}</p> // 当v-memo 为空数组时, 没有依赖的值, 当前节点数据更新后,不重新渲染 <div v-memo="[]" @click="changeData">{{ memoData }}</div> // 当依赖的项为memoData时, memoData发生变化, 重新渲染当前节点 <div v-memo="[memoData]" @click="changeData">{{ memoData }}</div> // 案例2: // item.id === selected 当该值发生变化,当前所在节点才会重新渲染 <div v-for="item in list" :key="item.id" v-memo="[item.id === selected]"> <p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p> <p>...more child nodes</p> </div> </div> <script> <script> const { createApp } = Vue; const vm = createApp({ data() { return { memoData: 99, selected: 1, list: [ { id: 1, name: 'item 1' }, { id: 2, name: 'item 2' }, { id: 3, name: 'item 3' } ] } }, methods: { changeData() { this.memoData = 1000 }, } }).mount('#app') </script>
四、Vue常用属性 1、自定义指令 - directive 除了核心功能默认内置的指令,Vue也允许开发者注册自定义指令。有的情况下,对普通DOM元素进行底层操作,这时候就会用到自定义指令绑定到元素上执行相关操作。
自定义指令分为:全局指令和局部指令 ,当全局指令和局部指令同名时以局部指令为准 (局部指令的优先级高于全局的)。
问题:全局与局部有什么区别?
在当前(非工程化,每一个文件都是一个html文件)的时候是没区别的 vue工程化的时候是有区别的全局的适用于整个项目的(常用) 局部的适用于当前组件的 自定义指令常用 钩子函数(或生命周期函数)有:
created (el, binding, vnode, prevVnode) { // 在绑定元素的 attribute 前 调用 } beforeMount(el, binding, vnode, prevVnode) {// 在元素被插入到 DOM 前调用 }, mounted(el, binding, vnode, prevVnode) {// 在绑定元素的父组件 , 及他自己的所有子节点都挂载完成后调用 }, beforeUpdate(el, binding, vnode, prevVnode) { // 绑定元素的父组件更新前调用 }, updated(el, binding, vnode, prevVnode) {// 在绑定元素的父组件 , 及他自己的所有子节点都更新后调用 }, beforeUnmount(el, binding, vnode, prevVnode) {// 绑定元素的父组件卸载前调用 }, unmounted(el, binding, vnode, prevVnode) {// 绑定元素的父组件卸载后调用 } 请注意:不管在定义全局还是局部自定义指令时,所提及的指令名均是不带v-
前缀的名称 。
全局指令语法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 app.directive ('指令名' ,{ 钩子函数名: function (el ){ } } app.directive ('指令名' ,{ 钩子函数名: function (el,binding ){ let param = binding.value }, .... }
请务必注意,作为全局配置,不能将其写在指定的Vue实例里,后续其它全局配置亦是如此
局部自定义指令定义
可以在new Vue
的时候添加directives
以注册局部自定义指令,局部自定义指令只能在当前组件实例中使用:
1 2 3 4 5 6 7 8 directives : { 指令名: { 钩子函数名: function (el,binding ) { } } }
函数简写(了解,使用机会很少)
部分时候,我们可能想在 bind
和 update
时触发 相同 行为(如果只是其一,则还是单独分开声明),而不关心其它的钩子。那么这样写:
1 2 3 4 5 6 7 8 9 10 11 app.directive ('指令名' , function (el,binding ) { }) directives : { 指令名(el,binding) { } }
在自定义指令的方法中,不能像以前的methods
中的方法一样使用关键词this
,此时this
关键词指向的是Window
对象。
案例:使用自定义指令实现以下效果
使用全局指令定义自定义的v-red(不传参)
和v-color(传参)
,在元素被插入时设置内容颜色 使用局部自定义指令实现v-mobile(不传参)
验证用户输入的是否是合法的手机号,不合法手机号为红色,合法为黑色 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 <div id ="app" > <input type ="text" v-focus > <p v-color v-if ="flag" > {{msg}}</p > <button @click ="flag=!flag" > 修改flag</button > <p v-blue ="'blue'" > 自定义指令传递参数</p > 手机号: <input type ="text" v-model ="mobile" v-mobile /> </div > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script > const { createApp } = Vue ; const app = createApp ({ data ( ) { return { msg : "自定义指令" , flag : true , mobile : '' } }, methods : { }, directives : { focus : { mounted : (el ) => el.focus () }, color : { created (el, binding, vnode ) { }, beforeMount (el, binding, vnode, prevVnode ) { }, mounted (el, binding, vnode, prevVnode ) { }, beforeUpdate (el, binding, vnode, prevVnode ) { }, updated (el, binding, vnode, prevVnode ) { }, beforeUnmount (el, binding, vnode, prevVnode ) { }, unmounted (el, binding, vnode, prevVnode ) { } }, mobile : { updated (el ) { console .log (el); let mobile = el.value if (/^1[3-9]\d{9}$/ .test (mobile)) { el.style .color = "black" } else { el.style .color = "red" } } } } }) app.directive ('blue' , { mounted (el, binding, vnode, prevVnode ) { el.style .background = binding.value } }) app.mount ('#app' ) </script >
2、计算属性 - computed 01: 为什么使用计算属性
模板中放入太多的逻辑(方法)会让模板过重且难以维护,使用计算属性可以让模板变得简洁易于维护。计算属性是基于它们的响应式依赖进行缓存 的
02: 计算属性语法特点
计算属性定义在Vue对象中,通过关键词computed
属性对象中定义一个个函数,并返回一个值,该返回的值就是计算属性的值, 使用计算属性时和data
中的数据使用方式一致。
计算可以根据一个或者多个现有的数据 来生成一个新的数据, 并且新的数据依赖于现有的数据,当依赖的数据发生变化, 计算属性会重新计算
核心点:
计算属性其在代码的表现也是方法,但是与methods不同 在某些场景下,计算属性的效率要比methods效率高计算属性支持数据的缓存操作(在依赖数据不变的情况下),而methods不行 只要依赖的数据源不发生改变,计算属性里的对应方法就只被调用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 31 32 <div id ="app" > <div > {{ cfn }}</div > <div > {{ cfn }}</div > <div > {{ fn() }}</div > <div > {{ fn() }}</div > </div > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script type ="text/javascript" > Vue .createApp ({ data ( ) { return { num : 'abc' , } }, methods : { fn ( ) { console .log ("methods" ); return this .num .toUpperCase (); }, }, computed : { cfn ( ) { console .log ("computed" ); return this .num .toUpperCase (); } }, }).mount ('#app' ) </script >
03: 计算属性默认只有 getter,不过在需要时你也可以提供一个 setter:现在再运行 vm.fullName = 'John
Doe'
时,setter 会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
课堂案例:
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 <div id="app" > <p> <input type="text" name="" id="" v-model="firstname"> </p> <p> <input type="text" name="" v-model="lastname"> </p> <p> <input type="text" v-model="fullname"> </p> </div> <script> Vue.createApp({ data() { return { firstname: '', lastname: '' } }, // 计算属性 computed: { fullname: { get() { return this.firstname + this.lastname }, set(val) { console.log(val); this.firstname = val.substr(0, 1); this.lastname = val.substr(1) } } } }).mount('#app') </script>
课堂案例: 全选/反选/单选
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 <script src ="./node_modules/vue/dist/vue.global.js" > </script > <body > <div id ="app" > <table > <tr v-for ='item in goodlist' > <td > <input type ="checkbox" v-model ='item.checked' > </td > <td > 商品名称:{{item.name}} </td > <td > 商品价格:{{item.price}} </td > <td > 数量:{{item.num}} </td > </tr > </table > <div > <input type ="checkbox" name ="" v-model ='checkall' > 全选 </div > </div > </body > <script > Vue .createApp ({ data ( ) { return { goodlist : [ { id : 0 , name : "苹果" , checked : true , price : 100 , num : 1 }, { id : 1 , name : "梨" , checked : false , price : 200 , num : 2 }, { id : 2 , name : "香蕉" , checked : true , price : 300 , num : 3 } ] } }, computed : { checkall : { get ( ) { return this .goodlist .every ((item ) => { return item.checked == true }) }, set (val ) { this .goodlist .forEach (item => { item.checked = val }); } } } }).mount ('#app' ) </script >
3、监听器 - watch 使用watch来侦听data 中数据的变化,watch中的属性(watch是对象格式)一定是data 中已经存在的数据 。(特殊情况除外 如: 监听路由的变化)
使用场景: 数据变化时执行异步或开销比较大的操作 。
典型应用: http://www.pinyinzi.cn/
案例: 给定三个输入框,第一个为姓输入框,第二个为名输入框,第三个为姓名组合结果框;要求当用户更新姓或名后,第三个输入框自动生成完整的姓名结果。
语法
1 2 3 4 5 6 7 new Vue({ ..... watch: { data中数据的名称: fn方法, .... } })
参考代码:
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 <div id ="app" > <p > <input type ="text" v-model ='firstName' placeholder ="姓" /> </p > <p > <input type ="text" v-model ='lastName' placeholder ="名" /> </p > <p > <input type ="text" v-model ='fullName' placeholder ="全名" /> </p > </div > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script type ="text/javascript" > Vue .createApp ({ data ( ) { return { firstName : '' , lastName : '' , fullName : '' } }, methods : { }, watch : { firstName : function (newvalue, oldvalue ) { this .fullName = newvalue + ' ' + this .lastName }, lastName : function (newvalue, oldvalue ) { this .fullName = this .firstName + ' ' + newvalue } } }).mount ('#app' ) </script >
注意点:
声明监听器,使用的关键词是watch
每个监听器的方法,可以接受2个参数,第一个参数是新的值,第二个参数是之前的值 注意: 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,此时就需要deep属性对对象进行深度监听 。 监听的时候可以配置参数
使用对象的数据形式改写上述案例参考代码:
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 <div id ="app" > <p > <input type ="text" v-model ='userinfo.firstName' placeholder ="姓" /> </p > <p > <input type ="text" v-model ='userinfo.lastName' placeholder ="名" /> </p > <p > <input type ="text" v-model ='userinfo.fullName' placeholder ="全名" /> </p > </div > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <script type ="text/javascript" > Vue .createApp ({ data ( ) { return { userinfo : { firstName : '' , lastName : '' , fullName : '' } } }, watch : { userinfo : { handler (val ) { console .log (val); this .userinfo .fullName = val.firstName + ' ' + val.lastName }, deep : true , immediate : true , } } }).mount ('#app' ) </script >
案例3: watch监听器中查看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 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 <div id="app" > <div id ="box" v-if ="flag" class ="box echart" > box</div > <button class ="button" @click ="flag = !flag" > toggle</button > </div> Vue .createApp ({ data ( ) { return { flag : false } }, methods : { }, watch : { async flag ( ) { console .log (1 , document .querySelector ('#box' )) await nextTick () console .log (2 , document .querySelector ('#box' )) }, flag : { handler ( ) { console .log (document .querySelector ('#box' )) }, flush : 'post' } } }).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 35 36 37 38 39 40 41 42 43 44 45 46 47 <div id="app" > <div id ="box" v-if ="flag" class ="box echart" > box</div > <button class ="button" @click ="flag = !flag" > toggle</button > </div> </body> <script > const { nextTick } = Vue ; console .log (Vue ); Vue .createApp ({ data ( ) { return { flag : false } }, methods : { }, watch : { async flag ( ) { console .log (1 , document .querySelector ('#box' )) await nextTick () console .log (2 , document .querySelector ('#box' )) }, flag : { handler ( ) { console .log (document .querySelector ('#box' )) }, flush : 'post' } } }).mount ('#app' ) </script >
面试题:vue中计算属性与监听器有什么区别??
4、watch与computed的区别: 1、watch监控现有的属性,computed通过现有的属性计算出一个新的属性
2、watch不会缓存数据,每次打开页面都会重新加载一次, 但是computed如果之前进行过计算他会将计算的结果缓存,如果再次请求会从缓存中,得到数据(所以computed的性能比watch更好一些)
3、watch 一般用于在数据变化时执行异步或开销较大的操作,computed 一般用于简单运算的操作。
4、计算属性支持深度深度数据是否变化的监听的(默认的),watch监听器默认不支持深度响应,仅支持字面量处理,但是其支持通过代码的改动来支持深度监听
6、生命周期(重点) 生命周期:从vue实例产生开始到vue实例被销毁这段时间所经历的过程。
每个 Vue 实例在被创建之前都要经过一系列的初始化过程。例如需要设置数据监听、编译模板、挂载实例到 DOM,在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做生命周期钩子 的函数,目的是给予用户在一些特定的场景下添加他们自己代码的机会。
所有生命周期钩子函数的 this
上下文都会自动指向当前调用它的组件实例。注意:避免用箭头函数来定义生命周期钩子,因为如果这样的话你将无法在函数中通过 this
获取组件实例。
Vue生命周期的主要阶段 :
挂载(初始化相关属性)beforeCreate注意点 :vue实例创建前阶段,在此时不能获取data中的数据,也就是说this.msg
得到的是undefined
created注意点 :vue实例已经创建完成 ,在此时可以获取data中的数据和methods 中的方法,以及计算属性可以在该阶段调用接口,请求数据 beforeMount注意点: 组件被挂载之前, 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点 mounted【页面加载完毕的时候就是此时】注意点 :默认情况下,在组件的生命周期中只会触发一次可以进行数据的请求 更新(元素或组件的变更操作)beforeUpdate 在组件即将因为一个响应式状态变更而更新其 DOM 树之前调用。 updated 在组件因为一个响应式状态变更而更新其 DOM 树之后调用。 销毁(销毁相关属性) ($destroy v-if 路由切换 动态组件切换切换)beforeDestroy 在一个组件实例被卸载之前调用。 一般用于清除副作用 destroyed 在一个组件实例被卸载之后调用。 一般用于清除副作用 示例代码:
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 <body > <div id ="app" > <input type ="text" placeholder ="用户名" v-model ='inpvalue' /> <button @click ='add' > 添加</button > <p id ="myp" > {{msg}}</p > <button @click ='edit' > 修改数据</button > </div > </body > </html > <script type ="text/javascript" > const app = Vue .createApp ({ el : '#app' , data : { "msg" : "我是定义的数据" , "inpvalue" : '' }, methods : { add ( ) { console .log (this .inpvalue ) }, edit ( ) { this .msg = '我被修改了' } }, beforeCreate ( ) { console .log (this .msg ) console .log (this .add ) }, created ( ) { console .log (this .msg ) console .log (this .add ) }, beforeMount ( ) { console .log (document .querySelector ('p' )) }, mounted ( ) { console .log (document .querySelector ('p' )) document .onclick = function ( ) { console .log ('document点击事件' ) } }, beforeUpdate ( ) { console .log (this .msg ) console .log (document .getElementById ('myp' ).innerHTML ) }, updated ( ) { console .log (this .msg ) console .log (document .getElementById ('myp' ).innerHTML ) }, beforeDestroy ( ) { }, destroyed ( ) { document .onclick = null } }) </script >
关于8个生命周期涉及到的方法,可以参考Vue官网API:https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F%E9%92%A9%E5%AD%90
7、了解虚拟DOM与diff算法概念 什么是虚拟DOM?
定义:指将真实的dom按照特定的语法转化(抽象)成一个js对象,这个js对象称之为虚拟dom 。
什么是diff(different)算法?
差异比较算法的一种,把树形结构按照层级分解,只比较同级 元素。不同层级的节点只有添加和删除操作
上图为新旧虚拟dom同层比较
虚拟DOM+diff算法
的方式与传统DOM操作
相比,有什么好处?
传统DOM操作 :在一次操作中,往往会伴随多次个DOM节点更新,浏览器收到第一个DOM请求后并不知道还有若干次更新操作,因此会马上执行流程,最终执行若干次次。而且操作DOM频繁还会出现页面卡顿,影响用户体验。
虚拟DOM+diff算法 :若一次操作中有若干次更新DOM的动作,此时 首先 会产生新的虚拟dom结构,新的虚拟dom结构会和上一次的虚拟dom结构作比较, 此时比较会采用diff算法同层比较,比较的内容有(虚拟dom节点的增加/删除/修改/属性的改变,内容的改变等), 然后将这若干次更新的diff内容记录到本地一个JS对象中,虚拟DOM不会立即操作DOM,等到新旧虚拟dom对比完后, 最后将这个本地记录差异变化的JS对象一次性 应用到DOM树上,该js对象中哪里有变化,就局部更新页面对应的真实dom位置部分,这样就实现了性能的最大优化,避免了重复的渲染.
建议:面试之前一定要去找下比较正规的理论性的东西。
五、网络请求 1、fetch 优点:
浏览器内置的,相比jq而言,省去了导入js包的麻烦 可以看做是xhr的升级版 支持promise 支持多种请求类型,但是默认为get请求类型 官网地址:https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
语法1:
1 2 3 4 5 fetch (url).then (res => res.json ()).then (res => console .log (res))res.json ()
语法2:
1 2 3 4 5 6 7 8 fetch (url, { method : 'POST' , body : JSON .stringify ({key :value}), headers : new Headers ({ 'Content-Type' : 'application/json' }) }).then (res => res.json ()).then (res => console .log (res))
语法3:
1 2 3 4 5 6 7 8 fetch (url, { method : 'POST' , body : "username=zhangsan&age=11" , headers : new Headers ({ 'Content-Type' : 'application/x-www-form-urlencoded' }) }).then (res => res.json ()).then (res => console .log (res))
案例:使用fetch获取接口地址https://api.i-lynn.cn/college
,获取到数据之后,将数据中的list
展示在页面上即可
注意:网络请求需要在created周期中写
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="js/vue.js" > </script > </head > <body > <div id ="app" > </div > </body > </html > <script > const vm = new Vue ({ el : "#app" , data : { list : '' }, methods : { getData ( ) { fetch ("https://api.i-lynn.cn/college" ) .then (res => res.json ()).then (res => { console .log (res) }) }, getData1 ( ) { fetch ("https://api.i-lynn.cn/college" , { method : 'POST' , body : "username=zhangsan&age=11" , headers : new Headers ({ 'Content-Type' : 'application/x-www-form-urlencoded' }) }).then (res => res.json ()).then (res => console .log (res)) }, getData2 ( ) { fetch ("https://api.i-lynn.cn/college" , { method : 'POST' , body : JSON .stringify ({ username : 123 , age : 456 }), headers : new Headers ({ 'Content-Type' : 'application/json' }) }).then (res => res.json ()).then (res => console .log (res)) } }, created ( ) { this .getData () this .getData1 () this .getData2 () } }) </script >
作业:自己写一个表单,表单可以输入任意一个合法的ipv4地址,点击查询按钮查询接口:https://api.i-lynn.cn/ip?query=获取ip地址对应的信息,将信息展示在页面上即可。
2、axios axios 是一个基于 promise 的 HTTP 库 ,可以用在浏览器和node.js中。axios是vue作者推荐使用的网络请求库 ,它具有以下特性:
a.支持浏览器和node.js(降低学习成本)
b.支持promise
c.能够拦截请求和响应
(拦截器)
d.自动转换json数据
2.在使用axios之前需要在对应的模板文件中引入axios的js库文件,随后按照以下用法使用axios: 1 2 <script src="https://unpkg.com/axios/dist/axios.min.js" ></script>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 const app = Vue .createApp ({ el : "#app" , data : { list : '' }, methods : { getData0 ( ) { axios.get ('https://api.i-lynn.cn/college?id=100' ).then (ret => console .log (11 , ret.data )) }, getData ( ) { axios.get ('https://api.i-lynn.cn/college' , { params : { id : 10010 , name : 'zhangsan' , age : 26 } }).then (ret => console .log (11 , ret.data )) }, getData1 ( ) { axios.post ('https://api.i-lynn.cn/college' , { firstName : 'zhang' , lastName : 'san' }).then (res => { console .log (res) }) }, getData2 ( ) { axios.post ('https://api.i-lynn.cn/college' , "firstName=zhang&lastName=san" ).then (res => { console .log (res) }) }, getData3 ( ) { axios ({ method : 'post' , url : 'https://api.i-lynn.cn/college' , timeout : 1000 , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, data : "username=zhangsan&type=2" , }).then (res => { console .log (res) }) }, getData4 ( ) { axios ({ method : 'get' , url : 'https://api.i-lynn.cn/college' , timeout : 1000 , params : { ID : 12345 }, }).then (res => { console .log (22 , res) }) } }, created ( ) { this .getData0 () } })
axios 请求总结:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1. axios:是vue作者推荐在vue中使用的网络请求库2. 简写方式有: ①get请求: axios.get (url).then (res => 处理程序) ②post请求 axios.post (url,请求体,{options}).then (res => 处理程序) 3. 常规写法有: axios ({ url, method, headers, params, data, .... }).then () 4. 与fetch 对比: 与fetch不一样,fetch最终的res就是我们的返回值,而axios这里最后的res并不是我们的返回值,而是axios的请求 响应对象,我们的返回数据在res.data 中
4.当然axios除了支持传统的GET
和POST
方式以外,常见的请求方式还支持: 需要注意,针对POST请求,此处的参数提交格式以参数形式为准:
1 2 3 4 axios.post (url,{js对象}).then () axios.post (url,"a=1&b=2" ).then ()
以上方的axios请求示例为例,接口响应结果(ret
)的主要属性有:
a. data:实际响应回来的数据(最常用)
b. status:响应状态码
c. statusText:响应状态信息
在使用axios发送请求之前它允许我们通过全局配置 做一些设置,这样可以方便后续的请求操作,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 axios.defaults .baseURL = 'http://localhost/app' ; axios.defaults .timeout = 3000 ; axios.defaults .headers ['_token' ] = '123123123' ; axios.defaults .headers .common ['_token' ] = '123123' ; const instance = axios.create ({ baseURL : 'http://kumanxuan1.f3322.net:8001' , timeout : 1000 , headers : { 'Content-Type' : 'application/x-www-form-urlencoded' }, });
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 instance.interceptors .request .use (function (config ) { config.headers .token = 123 console .log ('请求拦截' , config) return config; }, function (error ) { return Promise .reject (error); }); instance.interceptors .response .use (function (response ) { console .log ('响应拦截' , response) alert ('请求成功' ) return response; }, function (error ) { return Promise .reject (error); });
综合案例:
自己写一个表单,表单可以输入任意一个合法的ipv4地址,点击查询按钮查询接口:https://api.i-lynn.cn/ip?query=获取ip地址对应的信息,将信息展示在页面上即可。
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 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta http-equiv ="X-UA-Compatible" content ="IE=edge" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > Document</title > <script src ="./js/vue.js" > </script > <script src ="https://unpkg.com/axios/dist/axios.min.js" > </script > </head > <body > <div id ="app" > <div > <input type ="text" v-model ="ip" placeholder ="请输入合法的ipv4地址" /> <button @click ="clickHandler" > 查询</button > </div > <div v-show ="isShow" > <div > 查询的ip地址:{{res.ip}}</div > <div > 国家/地区:{{res.country}}</div > <div > 运营商:{{res.area}}</div > </div > </div > </body > </html > <script > new Vue ({ el : "#app" , data : { ip : "" , res : {}, isShow : false }, methods : { clickHandler ( ) { let ip = this .ip axios.get ("https://api.i-lynn.cn/ip?query=" + ip).then ((res ) => { console .log (res) if (res.status == 200 ) { this .res = res.data this .isShow = true } }) axios.post ("https://api.i-lynn.cn/ip" , "query=" + ip).then ((res ) => { if (res.status == 200 ) { this .res = res.data this .isShow = true } }); axios.post ("https://api.i-lynn.cn/ip" , { query : ip }).then ((res ) => { if (res.status == 200 ) { this .res = res.data this .isShow = true } }); } } }) </script >
3、axios 与fetch的区别(面试题) 不同点:
axios 相当于 ajax 的promise 封装, 底层还是对XMLHttpRequest的封装 axios 可以设置请求拦截和响应拦截 fetch 是ECMAScript 的一种新 的请求方式,语法简单,不需要额外引入,直接使用 fetch 不能自动自动转换json,axios 可以. 相同点:
都是异步的请求方式
他们返回都是promise 对象,
六、组件 1、什么是组件 组件允许我们将 UI 划分为独立的、可重用的部分,并且可以对每个部分进行单独的思考。在实际应用中,组件常常被组织成层层嵌套的树状结构:
组件 (Component)是 Vue.js 最强大的功能之一,组件是一个自定义HTML元素(标签) 或称为一个模块,包括所需的模板(HTML)、逻辑(JavaScript)和样式(CSS)。
组件化开发的特点:
组件也是有全局(component)
与局部(components)
之分。
2、组件的注册及使用 在使用组件时需要注意以下几点:
组件模板template
必须是单个根元素
<!-- 单个根元素 div -->
<div>
<ul>
<li></li>
</ul>
<ul>
<li></li>
</ul>
</div>
<!-- 没有包裹的根元素 -->
<p></p>
<p></p>
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 - 支持模板字符串形式 - 组件名称命名方式 - 短横线方式(推荐) - my-component - 大驼峰方式(只能在其他组件模板字符串中使用,不能在HTML模板中**直接**使用) - MyComponent > 大驼峰式组件名不能在HTML模板中直接使用,如果需要在HTML模板中使用,需要将其进行特定规则转化: > > - 首字母从大写转为小写 > - 后续每遇到大写字母都要转化成小写并且在转化后的小写字母前加`-` > > 例如,`WoDeZuJian`这个大驼峰组件名在HTML中使用的时候需要写成`wo-de-zu-jian` ### 2.1、全局组件 全局组件注册形式如下: ~~~javascript const { createApp } = Vue // 声明全局组件 app.component(componentName,{ // 用于定义组件的视图内容,必须有一个跟标签 template: '组件模版内容' // 存放该组件需要使用的数据 data: function(){ return { } }, })
上述示例中,component()
的第一个参数是组件名
(实则可以看作是HTML标签名称 ),第二个参数是一个对象形式的选项,里面存放组件的声明信息。全局组件注册后,任何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 <body> <div id ="app" > <global-child > </global-child > </div > <div id ="app1" > <global-child > </global-child > </div > </body> app.component ("globalChild" , { template : `<div><p>我是全局组件----{{globalname}}</p><p>我是第二个p</p></div>` , data ( ) { return { globalname : "我是全局数据" } } }) const app = Vue .createApp ({ data ( ){ return { } } })
2.2、局部组件 局部组件是在父组件的实例中注册子组件,通过某个 Vue 实例/组件的实例选项 components 注册。 注册完只能在当前的父组件中使用. 例如,有以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body> <div id ="app" > <partchild > </partchild > </div > </body> const app = Vue .createApp ({ data ( ){ return { } }, components : { partchild : { template : `<p>我是局部组件---{{partname}}</p>` , data ( ) { return { partname : "我是局部组件数据" } } } } }).mount ('#app' )
2.3、组件的使用 在HTML模板中,组件以一个自定义标签的形式存在 ,起到占位符的功能。通过Vue.js的声明式渲染后,占位符将会被替换为实际的内容,下面是一个最简单的模块示例: 1 2 3 <div id ="app" > <my-component > </my-component > </div >
组件的嵌套: 也可以在一个组件的组件模板中去使用其他已经注册的组件(一般是指全局组件) 的组件,例如:
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 ="app" > <departchild > </departchild > </div > </body > <script type ="text/javascript" > const app = Vue.createApp({ components: { // 注册局部组件departchild departchild: { template: `<div > <div > 我是局部组件departchild</div > <globalchild > </globalchild > </div > ` } } }) // 注册全局组件globalchild app.component('globalchild', { template: `<div > 我是全局组件globalchild</div > ` }) app.mount('#app') </script >
2.4 补充 destroyed 生命周期 加分项, destroyed生命周期的另一个应用. 将电脑的网络设置为低速3G 就可以测出来.
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 <body> <div id ="app" > <global-child1 v-if ="flag" > </global-child1 > <global-child2 v-else > </global-child2 > <button @click ="flag=!flag" > 切换组件1/2</button > </div > </body> const app = Vue .createApp ({ data ( ) { return { msg : '123' , flag : true } }, methods : { }, components : { globalChild1 : { template : `<div>我是全局组件1</div>` , data ( ) { return { controller : new AbortController () } }, mounted ( ) { axios.get ('https://api.i-lynn.cn/college' , { signal : this .controller .signal }).then (function (response ) { }).catch (e => { console .log (e); }) }, unmounted ( ) { } }, globalChild2 : { template : `<div>我是全局组件2</div>` , data ( ) { return { controller : new AbortController () } }, mounted ( ) { axios.get ('https://api.i-lynn.cn/college' , { signal : this .controller .signal }).then (function (response ) { }).catch (e => { console .log (e); }) }, unmounted ( ) { } } }, }) app.mount ('#app' )
3、组件间传值(重点) 3.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 ="app" > <child :day ='day' > </child > </div > </body > <script src ="./js/vue.js" > </script > <script type ="text/javascript" > var child = { props : ['day' ], props : { day : { default : '日' , type : String } }, template : '<p>星期{{day}}</p>' } const vm = new Vue ({ el : '#app' , data : { day : '五' }, components : { child } }) </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 <div id="app" > <!-- 使用局部组件 --> <part-child :username ="username" > </part-child > </div> const app = Vue .createApp ({ data ( ) { return { username : '成龙' } }, methods : { }, components : { partChild : { template : `<div> <p>我是局部组件--{{username}}</p> </div>` , data ( ) { return { msg : '456' , count : 1000 } }, props : ['username' ], mounted ( ) { } } } })
第二种方式: 子组件传递一个方法给父组件,然后该方法在父组件中调用将父组件中的数据传递给子组件, 子组件
中的该方法的形参就是父组件中的数据
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 <div id="app" > <!-- 使用局部组件 --> <part-child @myevent ="getvalue" > </part-child > </div> const app = Vue .createApp ({ data ( ) { return { msg : '123' } }, methods : { getvalue (fn ) { fn (this .msg ) } }, components : { partChild : { template : `<div> <p @click='transfer'>我是局部组件</p> </div>` , data ( ) { return { msg : '456' , count : 1000 } }, methods : { partchildMethod (mydata ) { console .log (1 , mydata); }, transfer ( ) { this .$emit('myevent' , this .partchildMethod ) } } } } })
第三种方式: 通过 $parent 获取父组件的实例对象,然后进而获取父组件中的数据和和方法实现父传子
// 但是 不建议使用, 后期代码维护更新有风险
1 2 在vue中,有一个实例属性也可以实现“父传子”的效果:$parent;
3.2、子传父 子组件模版内容中用$emit()
触发自定义事件
,$emit()
方法至少 有2个参数第一个参数为自定义的事件名称(不要和内置的事件重名,例如click、change等) 第二个参数为需要传递的数据(可选,可以是任何格式的数据) 父组件模板内容中的子组件占位标签上用v-on(或@)绑定子组件定义的自定义事件名,监听子组件的事件,实现通信 第一种方式:
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 <div id ="app" > <part-child @myevent ="getvalue" > </part-child > </div > const app = Vue.createApp({ data() { return { msg: '123' } }, methods: { getvalue(val){ // val 就为获取参数 console.log(val) } }, components: { partChild: { template: `<div > <p @click ='transfer' > 我是局部组件</p > </div > `, data() { return { msg: '456', count: 1000 } }, methods: { transfer() { this.$emit('myevent', this.count) } } } } })
第二种方式 : 父组件传递一个方法给子组件,子组件接收该方法,并且调用该方法, 然后将子组件的数据作为实参传递, 在父组件中执行传递的函数,函数的参数就是子组件中的数据.
第三种方法: 使用ref , 通过在父组件中给子组件设置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 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 <!DOCTYPE html > <html > <head > <meta charset ="UTF-8" > <title > </title > <script type ="text/javascript" src ="js/vue.js" > </script > </head > <body > <div id ="app" > <publish @transfer ='pushitem' > </publish > <ul > <child v-for ='(item,index) in list' :data ='item' > </child > </ul > </div > </body > </html > <script type ="text/javascript" > const vm = new Vue ({ el : '#app' , data : { msg : '我是父组件数据' , list : [ { name : "张三" , content : '1111111' }, { name : "李四" , content : '222222' } ] }, methods : { pushitem (m ) { console .log (m) this .list .push (m) } }, components : { child : { props : ['data' ], template : `<li> <h3>评论人:{{data.name}}</h3> <p>评论内容:{{data.content}}</p> </li>` , }, publish : { template : `<p> <input type="text" placeholder="评论人" v-model='name'/> <input type="text" placeholder="评论内容" v-model='content'/> <button @click='pub'>发表</button> </p>` , data : function ( ) { return { name : '' , content : '' } }, methods : { pub ( ) { let obj = { name : this .name , content : this .content } this .$emit('transfer' , obj) this .name = '' this .content = '' } } } } }) </script >
3.3、非父子组件之间的通信 使用状态提升,也就是通过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 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 <body > <div id ="app" > <part-child > </part-child > </div > </body > </html > <script > const app = Vue .createApp ({ data ( ) { return { msg : '123' } }, methods : { }, components : { partChild : { template : `<div> <globalChild1 ref='ref1'></globalChild1> <globalChild2 ref='ref2'></globalChild2> </div>` , data ( ) { return { username : 'partChild' } } }, } }) app.component ('globalChild1' , { template : `<div>全局组件1</div>` , data ( ) { return { nameArr : ['张三' , '李四' ] } } }) app.component ('globalChild2' , { template : `<div> <p>全局组件2</p> </div>` , data ( ) { return { } }, mounted ( ) { this .$parent .$refs .ref1 .nameArr .push ('王五' ) console .log (this .$parent .$refs .ref1 ); } }) app.mount ('#app' )
3.4、兄弟组件间传值 EventBus又被称之为中央事件总线
在vue2中可以提供我们去创建中央事件总线 ,使用 new Vue(),然后再利用this.$emit 和 this.$on 来发送和接收参数.实现兄弟组件之间的传参.
在vue3中移除了中央事件总线,我们不可以再这么用了,,,但是官方给我们推荐了外部第三方的库来帮我们完成事件总线,官方推荐了两个: mitt 或者 tiny-emitter
核心步骤
建立事件中心
// 需要借助第三方的插件
// npm i mitt
// 引入到页面中 <script src="./node_modules//mitt/dist/mitt.umd.js"></script>
const emitter = mitt()
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 - 传递数据 - ~~~javascript emitter.emit('自定义事件', 传递的数据) ~~~ - 接收数据 - ~~~javascript emitter.on('transfer', data => {console.log(data) // data 就是接收到的数据 }) ~~~ - 销毁事件中心 - ~~~javascript emitter.off('自定义事件') // ~~~ 使用案例: ```js <head> <script src="./node_modules/vue/dist/vue.global.js"></script> <script src="./node_modules//mitt/dist/mitt.umd.js"></script> </head> <body> <div id="app"> <global-child1></global-child1> <global-child2></global-child2> </div> </body> <script> //01:创建一个中央事件总线对象 const emitter = mitt() const app = Vue.createApp({ data() { return { } }, components: { globalChild1: { template: `<div @click='emitFn()'>我是全局组件1--{{msg1}}</div>`, data() { return { msg1: '八戒' } }, methods: { // 使用中央事件总线去发送数据 emitFn() { emitter.emit('transfer', { name: this.msg1 }) } } }, globalChild2: { template: `<div>我是全局组件2</div>`, data() { return { msg2: '悟空' } }, mounted() { // 使用中央事件总线去接收数据 emitter.on('transfer', data => { // data 参数就是数据 console.log(data); }) }, unmounted() { emitter.off('transfer') //关闭 } } } }).mount('#app') </script>
3.5、跨级组件传参 provide
和 inject
通常成对一起使用,使一个祖先组件作为其后代组件的依赖注入方,无论这个组件的层级有多深都可以注入成功,只要他们处于同一条组件链上。
provide
选项应当是一个对象或是返回一个对象的函数。这个对象包含了可注入其后代组件的属性。
注意: 默认提供的数据不是响应式,为了实现响应式 可以使用计算属性包裹 提供的数据.
inject 用于声明要通过从上层提供方匹配并注入进当前组件的属性。
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 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script src="./node_modules/vue/dist/vue.global.js"></script> </head> <body> <div id="app"> <global-child1></global-child1> <button @click="msg='观世音大士'">修改msg</button> </div> </body> </html> <script> // 注意:不使用计算属性的话,那么提供的数据就不是响应的数据了. const { computed } = Vue; const app = Vue.createApp({ data() { return { msg: '唐僧' } }, // 为后代组件提供数据 provide() { return { msg: computed(() => this.msg) //适应计算属性包裹 } }, components: { globalChild1: { template: `<div> <div>我是全局组件1--{{msg1}}</div> <global-child2></global-child2> </div>`, data() { return { msg1: '八戒' } }, components: { globalChild2: { template: `<div>我是全局组件2-{{msg2}}---{{msg}}</div>`, data() { return { msg2: '悟空' } }, inject: ['msg'], //provider 组件提供数据注入到这里 mounted() { } } } } } }) // 如下config 配置就是为了调试时出现报警警告,使用此方法可以解决该警告 app.config.unwrapInjectedRef = true; app.mount('#app') </script>
3.6、ref 父取子的数据信息。(方向:子-父,但是区别于之前的子传父,之前是主动,现在是被动)
ref
属性被用来给元素或子组件注册引用信息,引用信息将会注册在父组件的 $refs
对象上。如果在普通的 DOM 元素上使用ref
属性,则引用指向的就是 DOM 元素;如果ref
属性用在子组件上,引用就指向子组件实例 。
ref
放在标签上,拿到的是原生节点。ref
放在组件上 拿到的是组件实例原理:在父组件中通过ref
属性(会被注册到父组件的$refs
对象上)拿到组件/DOM对象,从而得到组件/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 25 26 27 28 29 30 31 32 33 <p ref ="p" @click ='handleClick' > hello</p > <child-comp ref ="child" > </child-comp > <script > new Vue ({ el : '#app' , data : { }, mounted : function ( ){ console .log (this .$refs .p ); console .log (this .$refs .child ); this .$refs .comp .msg = '123' }, methods :{ handleClick ( ){ console .log (this .$refs .child ) } }, omponents : { childComp : { template : `我是子组件` , data ( ) { return { mydata : '我是子组件' } } } } }) </script >
注意:
ref
属性这种获取子元素/组件的方式虽然写法简单,容易上手,但是其由于权限过于开放,不推荐使用,有安全问题。(不仅可以获取值,还可以获取其他所有的元素/组件的数据,甚至可以修改这些数据。)
4、动态组件 通过使用保留的 <component>
元素,动态地绑定到它的 is
特性,==我们让多个组件可以使用同一个挂载点(位置),并动态切换。==
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 <body > <div id ="app" > <keep-alive > <component :is ="currentView" > </component > </keep-alive > </div > </body > <script src ="./js/vue.js" > </script > <script > var home = {} var posts = {} var vm = new Vue ({ el : "#app" , data : { currentView : "home" , }, components : { home, posts, }, }); </script >
keep-alive 的作用:
keep-alive
可以将已经切换出去的非活跃组件保留在内存中。如果把切换出去的组件保留在内存中,可以保留它的状态,避免重新渲染。
案例:使用动态组件实现简易的步骤向导效果
案例参考代码
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 <head > <title > Document</title > <script src ="./node_modules/vue/dist/vue.global.js" > </script > <style > </style > </head > <body > <div id ="app" > <button @click ='change("step1")' > 第一步</button > <button @click ='change("step2")' > 第二步</button > <button @click ='change("step3")' > 第三步</button > <keep-alive > <component :is ="name" > </component > </keep-alive > </div > </body > </html > <script > const { createApp } = Vue ; const app = createApp ({ data ( ) { return { name : 'step1' } }, components : { step1 : { template : '<div>这是第一步的操作</div>' , activated ( ) { console .log ('step1-1' ); }, created ( ) { console .log ('created' ); }, deactivated ( ) { console .log ('step1-2' ); }, unmounted ( ) { console .log ('unmounted' ); } }, step2 : { template : '<div>这是第二步的操作</div>' , activated ( ) { console .log ('step2-1' ); }, deactivated ( ) { console .log ('step2-2' ); } }, step3 : { template : '<div>这是第三步的操作</div>' , activated ( ) { console .log ('step3-1' ); }, deactivated ( ) { console .log ('step3-2' ); } } }, methods : { change : function (name ) { this .name = name } } }) app.mount ('#app' ) </script >
在动态组件中存在2个生命周期函数(需要配合keep-alive标签):
activated:激活缓存组件的时候被触发
deactivated:离开缓存组件的时候被触发
当使用了keepalive组件后,组件在切换的时候就不会被销毁,而是被缓存起来了。【此处需要注意生命周期相关的执行情况】
上述2个周期函数与销毁的2个周期函数如果都存在,则只会激活其中的一对(要么激活系列,要么销毁系列,可以看作激活系列是销毁系列的替代)。
如果使用了keepalive,则只有第一次渲染的时候会走前4个生命周期函数,后续再激活组件的时候,前四个周期就不会再产生触发效果。(因为不会再重新创建组件了,组件被缓存了)
5、插槽 插槽也是组件传值的一种方式。
就是在使用组件的时候,将一些内容写在组件的标签内部, 但是因为使用组件的部分不是html而是组件或者应用的模板vue会编译模板为虚拟dom之后,再次渲染到页面中, 在编译的过程中不知道该讲这样的内容渲染到哪里,索性就不会渲染,总之, 如果我们不作处理的话, 组件标签内部写的内容是看不到的.
这时候, 需要组件配合, 需要指定是否要渲染这部分的内容, 还有就是要渲染到哪里, 这个时候就要用到slot插槽 也就是在组件的模板中利用slot标签才指定要将组件使用时标签内写入的内容插入到哪个位置. 也就是在组件的模板中利用slot标签才指定要将组件使用时标签内写入的内容插入到哪个位置.
组件的最大特性就是重用
,而用好插槽能大大提高组件的可重用能力。
插槽的作用: 父组件(卡)向子组件(游戏机)传递内容。【插槽应该在子组件上】
通俗的来讲,插槽无非就是在子组件
中挖个坑,坑里面放什么东西由父组件
决定。 (父-子)
插槽类型有:
5.1、匿名插槽 (2.6.0后更新) 匿名插槽一般就是使用单个插槽
示例代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <body > <div id ="app" > <alert-box > Something bad happened.</alert-box > </div > </body > <script src ="./js/vue.js" > </script > <script type ="text/javascript" > Vue.component("alert-box", { template: ` <div class ='demo-alert-box' > <strong > Error:</strong > <slot > </slot > </div > ` }); const vm = new Vue({ el: "#app", }); </script >
注意:子组件的slot
标签中允许书写内容,当父组件不往子组件传递内容时,slot
中的内容才会被展示出来。
5.2、具名插槽 (2.6.0后更新) slot
元素可以用一个特殊的特性 name
来进一步配置如何分发内容。多个插槽可以有不同的名字,具名插槽将匹配内容模板中有对应 v-slot
指令名对应的内容
v-slot
有对应的简写 #
,因此 可以简写为 <template #header>
上中下
形式网页布局示例代码
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 ="app" > <app-layout > <template v-slot:header > <h1 > 头部</h1 > </template > <template v-slot:default > <p > 主体1</p > <p > 主体2</p > </template > <template v-slot:footer > <p > 尾部</p > </template > </app-layout > </div > </body > </html > <script > const app = Vue.createApp({ data() { return { } }, methods: { }, components: { appLayout: { template: `<div class ="container" > <header > <slot name ="header" > </slot > </header > <main > <slot > </slot > </main > <footer > <slot name ="footer" > </slot > </footer > </div > `, data() { return { count: 100 } } } } }).mount('#app')
具名插槽存在的意义就是为了解决在单个页面中同时使用多个插槽。
5.3、作用域插槽 (2.6.0后更新) 应用场景: 父组件对子组件的内容进行加工处理
作用域插槽是一种特殊类型 的插槽,作用域插槽会绑定了一套数据,父组件可以拿这些数据来用 ,于是,情况就变成了这样:样式父组件说了算,但父组件中内容可以显示子组件插槽绑定的数据。
示例代码
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 <body > <div id ="app" > <child > <template v-slot ='slotProps' > <div id ='1' > {{slotProps.userinfo.name}}--{{slotProps.userinfo.age}} </div > </template > </child > </div > </body > </html > <script > const app = Vue .createApp ({ data ( ) { return { } }, methods : { }, components : { child : { template : ` <div> <p>我是作用域插槽</p> <slot :userinfo='userinfo'></slot> </div>` , data ( ) { return { userinfo : { name : "旺财" , age : 10 } } } } } }).mount ('#app' ) </script >
6、全局方法 Vue.use() 用法 :安装 Vue.js 插件。如果插件是一个对象,必须提供 install
方法。如果插件是一个函数,它会被作为 install 方法。install 方法调用时,会将 Vue 作为参数传入。该方法需要在调用 new Vue()
之前被调用。
模拟实现一下Element-ui 实现原理
1 2 3 4 import ElementUI from 'element-ui' ;import 'element-ui/lib/theme-chalk/index.css' ;Vue .use (ElementUI );
第一步:在components 目录下创建两个组件 Ubutton.vue 和 Uinput.vue 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!--Ubutton.vue --> <template> <button>按钮</button> </template> <script> export default { name: "Ubutton", data() { return {}; }, }; </script> <style scoped> /* @import url(); 引入css类 */ </style>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <!-- Uinput.vue --> <template> <input type="text" name="" id="" placeholder="搜索框" /> </template> <script> export default { name: "Uinput", data() { return {}; }, }; </script> <style scoped> /* @import url(); 引入css类 */ </style>
第二步:在utils 目录创建一个index.js文件,代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import Ubutton from '@/components/Ubutton.vue' ;import Uinput from '@/components/Uinput.vue' ;const components = [Ubutton , Uinput ];const ElementUI = { install (Vue ) { components.forEach (item => { Vue .component (item.name , item) }) } } export default ElementUI
第三步:在main.js 中使用,这样就可以在全局使用这两个组件了 而不需要引入
1 2 3 4 5 6 7 8 import ElementUI from './utils' Vue .use (ElementUI )new Vue ({ router, render : h => h (App ), }).$mount('#app' )
七、vue 工程化开发 vue command line tool,简单的来讲,就是一个基于命令行的vue开发工具。
Vue-CLI ≠ Vue ,Vue-CLI就是一个Vue工具。
vue脚手架工具
1、单文件组件 在很多 Vue 项目中,我们使用 app.component
来定义全局组件,紧接着用 new Vue({ el: '#container '})
在每个页面内指定一个容器元素。这种方式在很多中小规模的项目中运作的很好,在这些项目里 JS 只被用来加强特定的视图。但当在更复杂的项目中,或者你的前端完全由JS驱动的时候,下面这些缺点将变得非常明显:
所有的组件都放同一个html文件中 没有构建步骤(build操作),不能使用npm来管理项目 不能进行代码的打包压缩 没有针对单个组件的css样式支持 不能使用less/sass 这些css 预编译工具对css 进行高效编写 不方便使用大量的模块化导入导出语句,提升开发效率 创建一个本地的服务器, 可以使代码在开发阶段在本地服务器上运行 针对于上述的问题,vue框架发布了vue-cli
项目生成
工具,Vue-cli是一个基于 Vue.js 进行快速开发的完整系统, 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。
单文件组件的要求:
后缀必须是“.vue” 需要使用三个标签将整个文件分成3部分template标签:包裹的是html部分(视图部分)【必须要有的】 script标签:包裹的是JavaScript部分(逻辑部分)【必须要有的】css-in-js:在JavaScript中写样式 style标签:包裹的css/scss/less等样式部分(样式部分)【可以没有】样式存在范围的问题的有“scoped”属性则表示该组件的样式代码只在当前组件生效 如果没有“scoped”属性则表示该组件的样式会影响自己及后代,一般在工程化开发的模式中,只有根组件“App.vue”不写“scoped”属性(全局样式) 其他的语法与之前的一致 单文件组件只是工程化中的一个文件,无法单独运行,必须在项目中运行 2、工具安装 网址:http://npmjs.com
1 2 3 4 5 6 7 8 9 10 11 # # -g:全局安装 npm init vue@latest # vue --version vue -V # Vue和VueCLI是两回事 # npm uninstall -g @vue/cli
如果需要安装其他版本,可以使用npm install -g @vue/cli@版本号
的方式进行指定版本。
如果最新版安装不成功,可以尝试以下几种方式去解决:
断网,使用热点共享流量去执行安装命令 安装其他版本 切换一下npm镜像源,切换成淘宝镜像 卸载nodejs重安装 重装系统/换电脑 3、vue2与vue3脚手架的区别 vue2 使用的脚手架是webpack, vue3使用的脚手架是 vite, vite 是vue 团队出的 相比而言, vite 更新更快比webpack 4、目录结构介绍 如何很好的划分功能组件与视图组件呢?
小技巧:可以被复用的就算它功能组件,不能被复用的就算它是视图组件。
补充:(readme.md文件中的内容)后续入职的时候项目给到的代码可能不不包含node_modules目录,需要自己执行npm i
,随后项目才完整。
5、项目的运行及注意事项 5.1、项目的启停
如上图所示,在创建项目完成后有提示我们后续的操作:
在命令行中进入项目目录 运行npm run serve
命令来启动项目 按照上述命令执行后,我们会见到如下的效果,即表示项目运行成功:
注意:默认端口号会从8080开始,如果再次启动其他项目后续会以8081、8082……进行监听。
如果需要停止正在运行的项目,可以选择以下两种方式任一:
关闭终端 在终端中按下组合键Ctrl + C
(Cancel),随后选择Y
并键入回车
(如下图) 也可以按下两次Ctrl + C
部分同学的机器在启动vue项目的时候可能会出现卡在“40%”的进度并且长时间不动,如果这样,则直接Ctrl + C
停止本次启动,重新再去尝试启动。
==关于项目运行时,如果修改了项目代码是否需要重启的说明:==
是否需要重启取决于我们修改了什么内容,如果只是修改了代码部分(js、css、vue文件等)是不需要开发者手动重启项目的,系统会自动重新编译(有点nodemon感觉);但是如果修改的是配置文件,则必须需要自己先去停止项目,然后再去启动项目(手动实现重启)。
5.2、关于ESlint ESlint用于规范项目的编码,大型项目一般多人开发,为了避免一些个人编程恶习坑自己坑别人
,项目中使用了ESlint会起到紧箍咒
的作用,强制开发人员注意代码规范。例如,在不使用ESlint的情况下,JS允许我们声明一个不变量但不使用。如果使用了ESlint,在上述情况下会报错如下:
关于ESlint的报错,有一份错误参照,可以访问以下地址查看:https://cn.eslint.org/docs/rules/
在前期学习阶段不建议去使用ESlint,所以待会会重新创建一个不带有eslint
的项目来学习路由的使用。但是,以后企业中开发项目的时候都会启用eslint。
2.开发阶段关闭eslint
在项目根目录创建vue.config.js 文件,配置如下:
module.exports = {
lintOnSave: false// eslint-loader 是否在保存的时候检查
}
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 ### 5.3、环境变量 环境变量=配置文件 问题:此处的配置文件与之前模块化拆分出来的config.js文件有啥区别? 答:虽然都能实现配置,但是还是有区别的。区别在于,config.js文件是模块化产物,在使用的时候需要导入;而此处的环境变量的配置文件是属于系统性质的文件,不需要(我们自己)导入,系统在运行的时候自动引入。 步骤: ①在vue中是支持环境变量的使用的,但是其默认没有给我们提供环境变量配置文件,需要自己创建: a. 环境变量配置文件创建的位置在:**项目的根目录下** b. 环境变量的配置文件名称固定: 1. `.env`:该文件里放的是全局的环境变量的配置(该文件中的配置项在任何的时候都会被使用) 2. `.env.development`:该文件里放的是开发环境下才使用的配置(该文件中的配置只有在运行`npm run serve`的时候会被加载) 3. `.env.production`:该文件里放的是生产环境下才使用的配置(该文件中的配置只有在运行`npm run build`的时候才会被加载) c. 三个文件的名称千万不能写错,我们也不用自己去考虑应该导入哪个,全是系统自动判断 4. 在上述三个配置文件中,配置项的名字必须以`VUE_APP_`开头,如果不是,则该配置项不会生效 5. 如果有一个配置项,例如`VUE_APP_NAME`同时存在于上述三个配置文件中,优先级是怎么样的? 如果是开发模式(npm run serve):.env.development > .env 如果是生产模式(npm run build): .env.production > .env ②在代码中去获取配置项的值采用以下语法: ~~~js process.env.VUE_APP_XXXX_YYYY ~~~ ③如果配置文件中的某配置项暂时性的不想要,则可以采用注释的方式临时取消,注释符是`#` # 八、路由 ## 1、路由的概念 路由的本质就是一种`对应关系`(此处的路由含义同之前nodejs的路由),根据不同的URL请求,返回对应不同的资源。那么url地址和真实的资源之间就有一种对应的关系,就是路由。 路由分为:`后端路由`和`前端路由` - 后端路由:由服务器端进行实现并实现资源映射分发(nodejs、jsp、php等) - 概念:根据不同的用户URL请求,返回不同的内容(**地址与资源**产生对应关系) - 本质:URL请求地址与服务器资源之间的对应关系(映射)  - 前端路由:根据不同的**事件**来显示不同的页面内容,是事件与事件处理函数之间的对应关系 - 概念:根据不同的用户事件,显示不同的页面内容(**地址与事件**产生对应关系) - 本质:用户事件与事件处理函数之间的对应关系  记住一句话:有请求就应该有响应,只不过区别在于,之前node是响应资源,现在在前端中通过事件来进行响应。 ## 2、前端路由实现 > 面试题:请你说出前端路由是怎么实现的?或者有哪几种实现方式? > > 答:前端路由模式有两种实现方式:hash方式、history方式。 核心思想:通过**监听**地址栏的变化**事件**来实现资源的动态显示 前端路由有2种模式: - hash模式 > hash路由模式是这样的:http://xxx.abc.com/#/abx。 有带#号,hash值为 #/abc,它不会向服务器发出请求,因此也就不会刷新页面。并且每次hash值发生改变的时候,会触发hashchange事件。因此我们可以通过监听该事件,来知道hash值发生了哪些变化。 ~~~javascript window.addEventListener('hashchange', ()=>{ // 通过 location.hash 获取到最新的 hash 值 console.log(location.hash); }); ~~~ **hash路由原理:** ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> <script type="text/javascript" src="js/vue.js"></script> </head> <body> <a href="#/a">去a页面</a> <a href="#/b">去b页面</a> <a href="#/c">去c页面</a> <a href="#/d">去d页面</a> <!-- 存放对应的页面 --> <div id="route-view"></div> </body> <script type="text/javascript"> // hash 是指 url 中锚的部分,即(锚点链接)也就是从#开始的部分 // 如: http://www.runoob.com/test.htm#part2 输出 #part2 即为hash值 // 获取元内容素 var ctn = document.getElementById('route-view') // 默认渲染 render('#/a') // 监听hashchange事件 window.addEventListener('hashchange', function () { render(location.hash) }) // 分支 function render(router) { switch (router) { case '#/a': ctn.innerHTML = '这是a页面' break; case '#/b': ctn.innerHTML = '这是b页面' break; case '#/c': ctn.innerHTML = '这是c页面' break; case '#/d': ctn.innerHTML = '这是d页面' break; default: ctn.innerHTML = '404页面' break; } } </script>
history模式
形如:http://xxx.abc.com/xx/yy/zz。HTML5的History API为浏览器的全局history对象增加了该扩展方法。它是一个浏览器(bom)的一个接口,在window对象中提供了popstate事件来监听历史栈的改变,只要历史栈有信息发生改变的话,就会触发该事件。
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 <ul> <li onclick ='goto({name:"home"},"","home")' > 首页</li > <li onclick ='goto({name:"category"},"","category")' > 分类</li > </ul> <router-view > </router-view > function goto (a, b, c ) { history.pushState (a, b, c) if (a.name == 'home' ) { document .querySelector ('router-view' ).innerHTML = '首页' } if (a.name == 'category' ) { document .querySelector ('router-view' ).innerHTML = '分类' } } window .addEventListener ('popstate' , function (e ) { if (e.state .name == 'home' ) { document .querySelector ('router-view' ).innerHTML = '首页' } if (e.state .name == 'category' ) { document .querySelector ('router-view' ).innerHTML = '分类' } })
注:浏览器地址没有#, 比如(http://localhost:3001/a ); 它也一样不会刷新页面的。但是url地址会改变。但它在服务器没有配置的情况下,不能手动刷新,否则返回404页面
3、Vue Router 网址:https://router.vuejs.org/zh/,vuerouter是vue全家桶之一。
此处建议创建一个不带ESlint
、带Router的vue项目。
3.1、介绍 Vue Router 是 Vue.js 官方的路由管理器 。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。包含的功能有:
3.2、安装 如果在vue-cli创建项目时没有勾选上vue-router
选项,此时就需要手动的来安装它(切记,要进入项目中再去运行这个指令 ):
1 npm install vue-router@4
查看是否安装成功,查看此文件/package.json
3.3、Vue Router基本使用(了解) Vue Router的基本使用步骤:
在src/创建路由文件的归档目录“router” 引入相关库文件 VueRouter引入到Vue类中 定义路由组件规则 创建路由实例 把路由挂载到Vue根实例中 添加路由组件渲染容器(router-view,组件)到对应组件中(占坑) 情况1:放在根组件中 情况2:放在嵌套路由中的父组件中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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 import { createRouter, createWebHistory } from 'vue-router' const router = createRouter ({ history : createWebHistory (), routes : [ { path : '/' , redirect : '/home' }, { path : '/home' , name : 'home' , component : HomeView }, { path : '/category' , name : 'category' , component : () => import ('../views/CategoryView.vue' ) }, { path : '/car' , name : 'car' , component : () => import ('../views/CarView.vue' ) }, { path : '/mine' , name : 'mine' , component : () => import ('../views/MineView.vue' ) } ] }) export default routerexport default routerconst app = createApp (App )app.use (router) app.mount ('#app' ) <!-- html,添加路由组件渲染容器 --> <div id ="app" > <template > <header > <p > <router-link to ="/home" > 首页</router-link > <router-link to ="/category" > 分类</router-link > <router-link to ="/car" > 购物车</router-link > <router-link to ="/mine" > 我的</router-link > </p > </header > <router-view > </router-view > </template > </div >
后期在项目中以index
命名的文件在引入时,可以省去文件名不写。 在import
的时候如果涉及到了路径,建议写@
符号开头的路径(@表示src目录
,这个操作是打包工具已经帮我们定义好了,vue-cli的功劳,后续webpack再去说明) 名称的规范:在创建路由实例
的时候要去其属性名必须是routes
在挂载路由实例到根实例
的时候要求属性名必须是router
请注意大小写 示例代码:
src/views/Hello.vue代码
1 2 3 <template> <div>这是hello页面</div> </template>
src/views/News.vue代码
1 2 3 <template> <div>这是新闻页面</div> </template>
实现效果:
3.4、路由模式切换 vue-router中默认使用的是hash模式的路由,也就是前面介绍的地址栏中URL带有“#”的形式,如果需要切换成history模式,则可以在创建路由实例时进行指定,指定的配置项为mode
,可选值:
hash:默认 ,设置路由模式为hash路由 history:设置路由模式为history路由 例如,如果我们想设置路由模式从hash
改变为history
则可以配置路由入口文件
3.5、导航方式 含义:从一个组件/地址去往另一个组件/地址的方式。
在页面中,导航实现有2种方式:
声明式导航:通过点击链接实现的导航方式,例如HTML中的“<a>
”标签,Vue中的“<router-link>
”所实现的。(其性质与a标签的性质类似) 编程式导航:通过调用JavaScript形式API实现的导航方式,例如location.href实现的跳转效果 3.5.1、声明式导航 它就是先在页面中定义好跳转的路由规则,vueRouter中通过router-link组件来完成
1 2 3 <router-link to ="path" > xxx</router-link > <router-link :to ="{path:'path'}" > xxx</router-link > <router-link :to ="{name:'name'}" > xxx</router-link >
3.5.2、编程式导航 简单来说,编程式导航就是通过JavaScript
来实现路由跳转
1 2 3 4 5 6 7 8 this .$router .push ("/login" );this .$router .push ({ path :"/login" });this .$router .push ({ path :"/login" ,query :{username :"jack" } });this .$router .push ({ name :'user' , params : {id :123 } }); this .$router .go ( n );this .$router .back ();
注意点: 编程式导航在跳转到与当前地址一致的URL时会报错,但这个报错不影响功能:
如果患有强迫症,可以在路由入口文件index.js
中添加如下代码解决该问题:
1 2 3 4 5 const originalPush = VueRouter .prototype .push ;VueRouter .prototype .push = function push (location ) { return originalPush.call (this , location).catch ((err ) => err); };
面试题问题:this.$router
与this.$route
有什么区别?
答:
$router
是整个路由实例对象,即(new VueRouter());$route
是用户获取当前路由信息的。
页面跳转:使用$router
,获取当前路由信息 使用$route
3.6、路由重定向 概念:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示特定的组件页面 实现: 通过路由规则的redirect
属性,指定一个新的路由地址,可以很方便地设置路由的重定向 代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 <script type ="javascript" > var router = new VueRouter ({ routes : [ { path : '/' , redirect : '/user' }, { path : '/user' , component : User }, { path : '/register' , component : Register } ] }) </script >
component属性是可选属性,因此在写的时候需要注意,写错了也不会报错的。
3.7、嵌套路由(重点) 路由前缀: /admin/user /add /admin/user /del /admin/user /mod
相同部分可以提取 出来,减少重复劳动。
————————————以上为nodejs中的概念————————————————
上述概念在vue中被称之为叫做嵌套路由。
套娃,可以按照之前的nodejs处的路由前缀去理解。(当有些路由有公共部分的前缀时,在vue中就可以使用嵌套路由)
嵌套路由最关键在于理解子级路由的概念:
比如我们有一个/users
的路由,那么/users
下面还可以添加子级路由,如:/users/index
、/users/add
等等,这样的路由情形称之为嵌套路由。
核心思想:在父路由组件 的模板内容中添加子路由链接和子路由填充位(占坑) ,同时在路由规则处为父路由配置children属性 指定子路由规则
注意:一级路由需要使用path+query方式跳转,这样才能正常显示该一级路由默认对应的二级路由,使用 name+params 方式不展示二级路由对应的组件
1 2 3 4 5 6 7 8 9 10 11 12 13 routes : [ { path : "/user" , component : User , children :[ { path : "" , component : Index }, { path : "/index" , component : Index }, { path : "add" , component : Add }, ] } ]
1 2 <router-view > </router-view >
3.8、404路由 作用:用于处理当路由匹配不上的时候页面的展示(不做404路由,则页面显示白板页面)
由于Vue路由是从上到下执行 的,只要在路由配置规则中最后面放个*号(通配符)路由就可以了 ,例如:
1 2 3 4 5 6 7 8 9 const routes = [ { path : "/hello" , redirect : Hello }, { path : "/about" , component : About }, { path : "/news" , component : News }, { path : "*" , component : NotFound }, { path : '/:pathMatch(.*)*' , name : 'not-found' , component : NotFound }, ];
3.9、动态路由参数(重点) 本节知识点就是为了restful服务的,看如果在vue中使用restful形式进行参数传递 。
所谓动态路由就是路由规则中有部分规则是动态变化的,不是固定的值,需要去匹配取出数据(即路由参数
)。
如何传递在声明路由的时候,将可变部分通过“:变量名
”的形式进行替代 如何获取 1 2 3 4 5 6 7 8 9 10 11 12 13 14 var router = new VueRouter ({ routes : [ { path : '/user/:id' , component : User }, ] }) const User = { template : '<div>User ID is {{$route.params.id}}</div>' }
1 2 3 4 5 6 <template> <div> <!-- 单文件形式的组件, 可以在视图中直接接收路由参数,但是一般不这么用 --> 这是news组件{{$route.params.id}} </div> </template>
路由规则中的“:”只是在声明的时候写,在使用的时候不需要写“:”,例如如下代码:
问题:如上代码,如果路由规则里声明需要传递参数,但是实际使用的时候没传递参数会怎么样?
答:如果声明需要传递参数,但是实际不传的话则会影响落地页的显示,显示成白板(但是不报错)。但是如果有404路由在规则的最后,则匹配404路由。
注意:在实际开发的时候会有可能需要传参也可能不需要传参的情况,这个时候需要用到可选路由参数
点。
定义可选路由参数的方式很简单,只需要在原有的路由参数声明位置后面加上个?
即可。例如:
1 { path : "showdetail/:id?" , component : ShowDetail },
3.10、路由跳转传参的几种方式 声明式导航传参:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <router-link to="/about/1" >About </router-link> <router-link :to ="'/about/' + id" > About</router-link > <router-link :to ="{ path: '/test', query: { id: id, name: '张三' } }" > About</router-link > <router-link :to ="{ name: 'About', params: { id: id, name: '李四' } }" > About</router-link > { path : '/about/:id/:name' , name : 'About' , component : () => import ( '../views/About.vue' ) } this .$route .params 或 this .$route .query (使用query方式传参)
编程式导航传参:
1 2 3 4 5 6 7 this .$router .push ({ name : "Test" , params : { id : 123 } });this .$router .push ({ path : "/test" , query : { id : 123 , name : "李四" } });this .$route .params 或 this .$route .query (使用query方式传参)
3.11、命名路由(可选) 命名路由:路由别名,顾名思义就是给路由起名字(外号)。
例如:阿列克赛·马克西莫维奇·彼什科夫(高尔基)
通过一个名称来标识一个路由显得更方便一些,特别是在链接一个路由,或者是执行一些跳转的时候。
1 2 3 4 5 6 7 8 9 10 const router = new VueRouter ({ routes : [ { path : '/user/:id' , name : 'user' , component : User } ] })
1 2 <router-link :to ="{ name: 'user', params: { id: 123 }}" > User</router-link >
问:一般什么使用用命名路由?
答:当路由本身的path写法比较长的时候,建议写命名的方式。而且需要注意,如果使用的是path写法,则当path发生变化后,其对应的导航地址也需要跟着变化。但是如果使用了别名则不用理会path内容的变化(只要名不变就没事)。
九、路由守卫 1. 全局前置守卫 概念: 顾名思义就是在路由跳转前执行一些操作。
你可以使用 router.beforeEach
注册一个全局前置守卫:
1 2 3 4 5 const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
to: Route
: 即将要进入的目标 路由对象 from: Route
: 当前导航正要离开的路由next: Function
: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next
方法的调用参数。next()
: 进行管道中的下一个钩子,通俗得讲就是对该路由进行放行。next(false)
: 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from
路由对应的地址。next('/')
或者 next({ path: '/' })
: 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next
传递任意位置对象,且允许设置诸如 replace: true
、name: 'home'
之类的选项以及任何用在 router-link
的 to
prop 或 router.push
中的选项。next(error)
: (2.4.0+) 如果传入 next
的参数是一个 Error
实例,则导航会被终止且该错误会被传递给 router.onError()
注册过的回调。案例: 做一个登录守卫的判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 router.beforeEach ((to,from )=> { if (to.path !='/login' ){ if (!localStorage .getItem ('userinfo' )){ return {path :'/login' } } }else { if (localStorage .getItem ('userinfo' )){ alert ('已经登录,不需要重复登录' ) return {path :from .path } } } })
2. 路由独享的守卫 定义: 顾名思义就是对应得某个路由路径独享得守卫。
1 2 3 4 5 6 7 8 9 10 11 12 const router = new VueRouter({ routes: [ { path: '/foo', component: Foo, beforeEnter: (to, from, next) => { // 这些守卫与全局前置守卫的方法参数是一样的。 // ... } } ] })
3. 组件内的守卫 你可以在路由组件内直接定义以下路由导航守卫:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const Foo = { template : `...` , beforeRouteEnter (to, from , next ) { }, beforeRouteUpdate (to, from , next ) { }, beforeRouteLeave (to, from , next ) { } }
4. 全局后置钩子 后置和守卫不同的是,这些钩子不会接受 next
函数也不会改变导航本身,因为触发时间是已经发生了跳转了,跳转完成了
案例: 修改页面标题
由于vue项目是单页应用,所以所有的页面组件展示的标题都一样,有时候咱们需要根据不同得页面组件显示不同的标题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 { path : '/home' , name : 'home' , meta :{ title :'首页' }, } router.afterEach ((to,from )=> { document .title = to.meta .title })
十、Vuex 全局状态管理 1. 为什么使用vuex 1.1、介绍 如果遇到组件间嵌套层次较多,比较复杂得化,多个组件之间共有一个数据,使用组件传值处理起来得话,就比较麻烦 vuex 是vue 配套的数据管理工具,我们可以将组件共享数据保存到vuex 中,方便整个程序中得任何组件都可以获取和修改 vuex 中保存得公共数据 2.安装vuex 3.使用vuex 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 import { createStore } from 'vuex' ;const store = createStore ({ state () { return { count : 0 } }, mutations : { add (state) { state.count ++ } } }) export default store
4. vuex 核心概念 4.1、模块介绍 state 概念: 提供全局唯一得公共数据源,所有的共享数据都要放在store 中得state 进行存储。 可以理解相当于 组件中的data
mutations 概念:同步 修改state中的数据, 通过mutations 修改数据虽然繁琐一些,但是可以集中监控所有数据得变化
注意: 只能通过mutations 修改store中得数据,不能直接修改store得数据
1 2 3 4 5 6 7 8 9 10 11 12 13 const store = new Vuex .Store ({ state :{ count :0 }, mutations :{ add (state ){ state.count ++ }, addStep (state,step ){ state.count +=step } } })
组件中修改state中的数据—-第一种方式:使用常规语法 1 2 this .$store .commit (“add”) this .$store .commit (“addStep”,10 )
组件中修改state中的数据—-第二种方式:使用mapMutations 辅助函数 1 2 3 4 5 6 7 8 9 <button @click="add" >+1 </button> <button @click ="addStep(10)" > +10</button > >import {mapMutations} from 'vuex' methods :{ ...mapMutations (['add' ,'addStep' ]), }
actions 概念:可以异步 修改state 中得数据,
注意: action 不能直接修改state中的数据,需要间接通过触发mutation中得方法修改数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 const store = new Vuex .Store ({ state :{ count :0 }, mutations :{ ... } actions :{ addAsync (context ){ setTimeout (()=> { context.commit ('add' ) },1000 ) }, addStepAsync (context,step ){ setTimeout (()=> { context.commit ('addStep' ,step) },1000 ) } }, })
组件中异步修改state中的数据—-第一种方式:使用常规语法 1 2 this .$store .dispatch (“addAsync”) this .$store .dispatch (“addStepAsync”,10 )
组件中修改state中的数据—-第二种方式:使用mapActions辅助函数 1 2 3 4 5 6 7 8 <button @click="addAsync" >+1 异步</button> <button @click ="addAsyncStep(10)" > +10异步传参</button > import {mapActions} from ‘vuex’methods :{ ...mapActions (['addAsync' ,'addAsyncStep' ]) }
getters 概念:用于对store中得数据进行加工处理成新的数据。类似与vue 得计算属性
注意:store中得数据发生变化时,则getter 中对应的数据也发生变化。getter不会修改store 中得数据,只是包装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 const store = new Vuex .Store ({ state :{ count :0 }, mutations :{ }, actions :{ }, getters :{ formatCount (state ){ return `包装处理后的${state.count} ` } } })
1 2 3 4 5 6 <p>{{$store.getters .方法名 }}</p> this .$store .getters .方法名
组件中第二种使用方式:使用mapGetters辅助函数 1 2 3 4 5 6 7 8 <p>getters处理过的count : {{ formatCount }}</p> import {mapGetters} from 'vuex' computed :{ ...mapGetters (['formatCount' ]) }
mudules: 概念: 如果项目中使用单一的模块,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿,vuex 允许我们将 store 分割成多个模块(module)。每个模块拥有自己的 state、mutation、action、getter
注意: 分模块开发时,必须给每个模块设置命名空间 namespaced:true
,
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 export default { namespaced :true , state :{ num :0 }, mutations :{ addNum (state ){ state.num ++ }, addNumStep (state,step ){ state.num +=step } }, actions :{ addNumAsync (context ){ setTimeout (()=> { context.commit ('addNum' ) },1000 ) }, addNumStepAsync (context,step ){ setTimeout (()=> { context.commit ('addNumStep' ,step) },1000 ) } }, getters : { fiveNumTimes (state ) { return state.num * 5 } } }
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 <!-- 调用模块A的方法 --> <h3 > num:{{$store.state.moduleA.num}}</h3 > <button @click ="addNum" > +1</button > <button @click ="addNumStep(10)" > +10 传参</button > <button @click ="addNumAsync" > +1 异步</button > <button @click ="addNumStepAsync(10)" > +10 异步传参</button > methods :{ addNum ( ){ this .$store .commit ('moduleA/addNum' ) }, addNumStep (step ){ this .$store .commit ('moduleA/addNumStep' ,step) }, addNumAsync ( ){ this .$store .dispatch ('moduleA/addNumAsync' ) }, addNumStepAsync (step ){ this .$store .dispatch ('moduleA/addNumStepAsync' ,step) } }
组件中第二种使用方式: 使用mapMutations 和mapActions 辅助函数映射。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <!-- 调用模块A的方法 --> <h3 > num:{{num}}</h3 > <h3 > getters处理过的num:{{ fiveNumTimes }}</h3 > <button @click ="addNum" > 模块++</button > <button @click ="addNumStep(10)" > 模块++</button > <button @click ="addNumAsync" > +1 异步</button > <button @click ="addNumStepAsync(10)" > +10 异步 传参</button > computed : { ...mapState ("moduleA" , ["num" ]), ...mapGetters ("moduleA" , ["fiveNumTimes" ]), }, methods :{ ...mapMutations ('moduleA' ,['addNum' ,'addNumStep' ]), ...mapActions ('moduleA' ,['addNumAsync' ,'addNumStepAsync' ]) }
4.2、vuex数据持久化 vuex 中的数据刷新页面数据会自动消失,这是我们不愿意看到的,那么怎么可以将vuex 中的数据持久化呢?
解决方案: 使用vuex-persistedstate
插件
第一步:在项目中安装插件
1 npm install vuex-persistedstate --save
第二步:使用vuex-persistedstate默认存储到localStorage中
1 2 3 4 5 6 7 import createPersistedState from "vuex-persistedstate" ;const store = newVuex.Store ({ state : {}, mutations : {}, actions : {}, plugins : [createPersistedState ()] })
第三步:也可以指定存储到sessionStorage中
1 2 3 4 5 6 7 8 9 import createPersistedState from "vuex-persistedstate" const store = new Vuex .Store ({ state : {}, mutations : {}, actions : {}, plugins : [createPersistedState ({ storage :window .sessionStorage })] })
第四步:也可以指定需要持久存储的state中对应某条数据;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import createPersistedState from "vuex-persistedstate" ;const store = newVuex.Store ({ state : {}, mutations : {}, actions : {}, plugins : [createPersistedState ({ storage :window .sessionStorage , reducer (val ) { return { assessmentData : val.token } } })] })
十一、Pinia 全局状态管理工具使用 注意: pinia 中没有mutations, 只有actions , 不区分同异步.
首先需要下载Pinia
第一步: 在main.js文件中引入
1 2 3 import { createPinia } from 'pinia' const pinia = createPinia () app.use (pinia)
第二步: 在src/store/ 新建userStore.js,每一个业务模块都新建一个对应的 store.js文件,如 goodsStore.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { defineStore } from 'pinia' export const userStore = defineStore ('user' , { state : () => ({ count : 0 }), getters : { double : (state ) => state.count * 2 , }, actions : { increment ( ) { this .count ++ }, }, })
第三步: 在页面中使用:
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> <div class ="home" > <p > store中的数据 {{ myOwnName }}--{{ double }}</p > <p > <button @click ="myOwnName++" > myOwnName++</button > <button @click ="add" > add++</button > </p > </div > </template> <script > import { mapState } from 'pinia' ;import { userStore } from '../store' ;import { mapWritableState } from 'pinia' export default { name : '' , data ( ) { return { show : true }; }, computed : { ...mapState (userStore, ['count' , 'double' ]), ...mapWritableState (userStore, ['count' ]), ...mapWritableState (userStore, { myOwnName : 'count' , }), }, mounted ( ) { console .log ('home' , this ); }, methods : { add ( ) { setTimeout (() => { useStore ().$patch({ count : useStore ().count + 1 , }) }, 1000 ) } } } </script >
十二、 vue3中需要注意的问题 5、过滤器 - filter(vue3中已经废弃) 作用: 格式化数据,比如将字符串格式化为首字母大写、将日期格式化为指定的格式等。
过滤器可以定义成全局过滤器和局部过滤器。 过滤器的本质就是一个方法 ,使用过滤器实际上就相当于方法调用,仅是书写形式上的差异(使用的时候需要用“|
”(shift + \
),其也可以被称之为 管道修饰符 )语法:{{待修饰的数据|过滤器方法名}}
{{待修饰的数据|过滤器方法名(参数1,参数2....)}}
v-bind:动态属性="待修饰的数据|过滤器方法名"
这玩意在vue3中已经废弃了
声明语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 Vue .filter ('过滤器名称' ,function (value ){ return .... }) el : '#app' ,data : {},filters : { 过滤器名称1 : function (value,a,b ){ return something }, 过滤器名称2 : function (value,a,b ){ return something }, } Vue .filter ('formDate' , function (data ) { return moment (data).format ('YYYY-MM-DD' ) })
过滤器的处理函数中的第一个参数固定 是绑定的待处理数据
,后续可以根据需要添加自定义参数
使用语法:
1 2 3 4 5 6 7 8 9 10 11 <div > {{msg | upper}}</div > <div > {{msg | upper | lower}}</div > <div v-bind:id ='id | formatId' > </div > <div > {{msg | mysub(1,2)}}</div >
案例:声明转字母为大写的全局过滤器和转字母为小写的局部过滤器并使用
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 ="app" > <h4 > {{msg | toUpper}}</h4 > <h4 > {{msg | toLower}}</h4 > </div > </body > <script src ="./js/vue.js" > </script > <script type ="text/javascript" > Vue .filter ('toUpper' ,(val ) => { return val.toUpperCase () }) const vm = new Vue ({ el : '#app' , data : { msg : 'HeLLo WoRld' }, filters : { toLower : (val ) => { return val.toLowerCase () } } }) </script >
十三、vue中的内置组件 1、内置组件 1、transition组件: <transition>
元素作为单个 元素/组件的过渡效果。<transition>
只会把过渡效果应用到其包裹的内容上,而本身不会渲染成一个 DOM 元素,也不会出现在可被检查的组件层级中。
transition特点 :
内部只能包裹一个元素,如果内部包裹多个元素则使用<transition-group></transition-group>
transition 属性:
appear 属性初始化是否执行动画 值为boolean true/false name 属性 修改动态类名 如:name='abc'
修改前是 v-enter-active
修改后为 abc-enter-active
transition为包裹元素添加的动态类名有如下几个:
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 14 15 16 17 18 19 20 21 22 23 // html部分 <router-view v-slot ="{ Component }" > <transition name ="slide-fade" > <component :is ="Component" /> </transition > </router-view > // css <style scope > .slide-fade-enter-active { transition : all 0.3s ease-in-out; } .slide-fade-leave-active { transition : all 0.3s cubic-bezier (1 , 0.5 , 0.8 , 1 ); } .slide-fade-enter-from ,.slide-fade-leave-to { transform : translateX (100% ); opacity : 0 ; } </style >
2、keep-alive 组件: <keep-alive>
包裹动态组件时,保留组件的状态,而不需要重新创建。避免多次重复渲染,降低性能,所以可以使用keep-alive 提升页面性能
和 <transition>
相似,<keep-alive>
是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。
代码演示:切换路由A/B/C 每个组件中的input 输入的内容都能缓存
1 2 3 4 5 6 7 8 9 // App.vue中代码 <template> <router-link to="/a">A</router-link> <router-link to="/b">B</router-link> <router-link to="/c">C</router-link> <keep-alive> <router-view></router-view> </keep-alive> </template>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // A组件 <template> <div> A页面 <input type="text" name="" id="" /> </div> </template> <script> export default { name: "item_A", data() { return {}; }, }; </script>
keep-alive组件属性
1 2 3 4 5 6 <router-link to="/a">A</router-link> <router-link to="/b">B</router-link> <router-link to="/c">C</router-link> <keep-alive include="item_A"> <router-view></router-view> </keep-alive>
exclude: 名称匹配的组件不缓存
1 2 3 4 5 6 <router-link to="/a">A</router-link> <router-link to="/b">B</router-link> <router-link to="/c">C</router-link> <keep-alive exclude="item_A"> <router-view></router-view> </keep-alive>
结合router,缓存部分页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 const router = new VueRouter ({ routes : [ { path : '/a' , component : () => import ('@/views/a.vue' ), meta : { keepAlive : true } }, { path : '/b' , component : () => import ('@/views/b.vue' ), meta : { keepAlive : false } }, { path : '/c' , component : () => import ('@/views/c.vue' ), meta : { keepAlive : 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 // app.vue <router-link to="/a">A</router-link> <router-link to="/b">B</router-link> <router-link to="/c">C</router-link> <router-view v-slot="{ Component }"> <keep-alive :exclude="arr"> <component :is="Component" /> </keep-alive> </router-view> <script> export default { data() { return { } }, computed: { arr() { let newArr = []; // 不缓存的组件名组成的数组 if (this.$route.meta.iskeepalive == false) { // 当路由切换的时候,此时依赖的项发生变化,计算属性重新计算, newArr.push(this.$route.name) } return newArr } } } </script>
十四、sass 和less 预编器的使用 sass 和less 都是一门css预编译处理语言,说白了就是方便大家在开发项目时设置样式. 可以使用sass 或less 写样式
01. sass语法: 第一步: 在项目中下载sass
第二步: 将项目中的style 设置lang 属性为scss
1 <style lang='scss'></style>
第三步: 语法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 1 . 嵌套语法:.box { p { background-color : red; } } 2 . & 语法:ul { li { &:hover { color : green; } } } 3 . 变量 $ (Variables: $)$w: 100px ; $red: red; $green: green; $border2: 2px solid black; .box { width : $w; height : 100px ; background-color : $red; border : $border2; p { background-color : $green; } } 4 . @import 导入其他样式文件 @import "foo.scss" ; @import "foo.css" ; 5 . 定义minxin@mixin wrap { width : 200px ; height : 200px ; background-color : deeppink; } 6 . 带参数的minxin@mixin wrap2($w,$h) { width :$w; height : $h; background-color : deeppink; } .bigbox { @include wrap; // 使用minxin 函数 margin : 10px ; } .bigbox1 { @include wrap2(100px ,100px ); // 使用minxin 函数 margin : 10px ; }
02: less 语法: 需要下载 less 到项目中;
修改style 属性
1 2 3 <style lang='less'> // less 语法 </style>