目录
1.简介:
Vue是一套用于构建用户界面的渐进式框架。与其他大型框架不同的是,Vue被设计为可以自底向上逐层应用。Vue的核心库只关注视图层,不仅容易上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue也完全能够为复杂的单页应用提供驱动。用来做单页面应用--index.html,页面跳转用的vue插件路由来实现跳转。
【vue】Vue.js - 渐进式 JavaScript 框架 | Vue.js
【gitee封面人物:尤雨溪谈Vue.js】尤雨溪 - Gitee 封面人物
【采访 Vue 作者尤雨溪】比较详细的采访 Vue 作者尤雨溪 - 简书
【Object.defineProperty()详解 拓展理解vue源码】Object.defineProperty()详解 - 天天向上吧 - 博客园 vue早期源码学习系列之一:如何监听一个对象的变化 - 知乎
1-Vue基本渲染-插值 ### js代码 window.onload = function () { // 创建vue实例,vue实例要与模板(DOM)绑定 let vm= new Vue({ el: "#app", data: {// 数据模型 msg:'hello world' }, // 函数 methods: { changeData(){ if(this.msg==='hello world'){ // 更改数据模型中的数据 // 获取数据模型中的数据 // 在vue实例内访问vue实例身上的属性和方法 this.msg='你好我是修改后的值' }else{ this.msg='hello world'; } } }, }) } ### html代码 <!-- 以前把msg作为元素文本的,获取dom节点,绑定事件,dom.innerText=msg--> <div id="app"> <!-- 在模板内访问vue实例中数据模型中的数据 插值 --> <!-- 基本渲染 插值 --> {{msg}} <p>{{msg}}</p> <!-- 点击按钮 改变数据模型中的数据msg变量 --> <button @click='changeData'>点我修改数据模型msg数据</button> </div> 数据模型更改,引发了vm驱动
2.MVVM
Model 数据模型
View 视图
VM ViewModel 连接视图和数据模型的纽带,数据模型发生了变化,vm通知视图修改;视图发生了变化,vm通知数据模型进行相对应的修改.
视图模型 帮我们把数据模型中的数据渲染到视图中 相当于一个纽带
优化渲染:当前数据模型发生改变只改变对应的视图,只渲染对应的视图层
mvvm介绍: m指model服务器上的业务逻辑操作,v指view视图(页面),vm指ViewModel模型跟视图间的核心枢纽,比如vue.js
可以将上图中的
DOM Listeners
和Data Bindings
看作两个工具,它们是实现双向绑定的关键。从View侧看,ViewModel中的
DOM Listeners
工具会帮我们监测页面上DOM元素的变化,如果有变化,则更改Model中的数据;从Model侧看,当我们更新Model中的数据时,
Data Bindings
工具会帮我们更新页面中的DOM元素。
### 2.数据模型 let vm=new Vue({ el:'#app', data:{ msg:'科大优质男', time:new Date() }, methods:{ sayName(){ console.log(this.name) }, } }) // 每隔一秒重新查询一次时间 setInterval(()=>{ vm.time=new Date() },1000) // 查看vue实例对象 console.log(vm)
** 3.生命周期(钩子函数)
从vue实例创建到虚拟dom产生再到数据绑定监听数据渲染以及销毁的整个过程
生命周期的第一步首先是创建vue实例,并且进行初始化。 ### vue实例初始化阶段 beforeCreate 在初始化的时候调用了beforeCreate,完成了vue实例的生命周期相关属性的初始化以及事件的初始化。这个时候还不能访问数据模型中的data和methods中的方法。 created 在初始化完毕之后,完成vue的数据注入以及数据监听操作,该构造的执行意味着vue实例创建完毕,可以进行data数据模型和和methods方法的访问 ### vue实例挂载阶段 beforeMount 在created之后,vue会判断实例中是否含有el属性,如果没有vm.$mount(el),接着会判断是否含有template属性,如果有将其解析为一个render function,如果没有将el指定的外部html进行解析。这里只完成了模板的解析但是数据并没有绑定到模板中。 mounted 创建vm.$el替换el,实际上完成的是数据绑定操作,在其间执行了render函数,将模板进行了解析,将数据进行了动态绑定 ### vue实例更新阶段 beforeUpdate 更新虚拟dom节点 updated 完成了页面的重新渲染 ### vue实例销毁阶段 beforeDestroy 销毁之前调用,此时可以访问vue实例 destroyed 完成了监听器,子组件,事件监听等移除,销毁vue实例对象。
### js代码 <script> let vm=new Vue({ el:"#app", data:{ msg:'hello world' }, beforeCreate(){ console.log('vue实例初始化之前') }, created() { console.log('vue实例初始化好了,可以访问到数据模型中的数据以及methods方法') }, beforeMount() { console.log('实例挂载之前') }, mounted(){ console.log('实例挂载完毕,可以获取dom节点,一般不操作dom') }, beforeUpdate() { console.log('实例发生改变之前触发该生命周期') }, updated() { console.log('实例发生改变后页面视图发生变化,触发该生命周期') }, beforeDestroy() { // console.log('实例销毁之前,仍然可以访问到数据模型中的数据和方法') }, destroyed() { // console.log('实例销毁完毕,销毁了监听器和事件监听以及子组件'); }, methods:{ sayHello(){ console.log(this.msg) }, } }); console.log(vm) setTimeout(()=>{ vm.$destroy() },3000) </script> ### html代码 <div id="app"> {{msg}} </div>
4.模板语法
介绍:
vue使用了基于HTML的模板语法,允许开发者声明式地将DOM绑定至底层Vue实例的数据。所有 Vue的模板都是合法的 HTML,所以能被遵循规范的浏览器和HTML解析器解析。在底层的实现上,Vue 将模板编译成虚拟 DOM 渲染函数。结合响应系统,Vue 能够智能地计算出最少需要重新渲染多少组件,并把 DOM 操作次数减到最少。如果你熟悉虚拟 DOM 并且偏爱 JavaScript 的原始力量,你也可以不用模板,直接编写渲染函数及render,使用可选的 JSX 语法(react采用的就是jsx语法)。
1.插值: 1.基本渲染 {{}}
当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。
渲染最常见的形式就是使用“Mustache”语法 (双大括号) 的文本插值。
2.原始html(v-html,解析标签中的文本内容)
3.属性v-bind(可以使用:简写 表示绑定的是一个变量)
4.事件v-on:click (可以使用@简写)
5.javascript表达式(可以进行js逻辑运算)
```Mustache 标签```将会被替代为对应数据对象上 ```msg property```的值。无论何时,绑定的数据对象上 ``` msg property ``` 发生了改变,插值处的内容都会更新。当这些数据改变时,视图会进行重渲染。 1.文本渲染 ### js代码 let vm=new Vue({ el:'#app', data:{ msg:'hello world', animal:['大笨象','小猴子','狐狸'] }, methods:{ } }); ### html代码 <div id="app"> <div>{{animal}}</div> <div>{{msg}}</div> </div> 也可以使用v-once指令,执行一次性地插值,当数据发生改变,插值处的内容不会更新 ### js代码 let vm=new Vue({ el:'#app', data:{ msg:'hello world' } }) setTimeout(()=>{ vm.msg='hello vue' },2000) ### html代码 <div id="app" > <!-- {{ msg }} 不会再更改,一直是hello world --> <div v-once>{{msg}}</div> </div>
2.如果我们向后台请求的数据是一段HTML代码,如果我们直接通过{{}}来输出,会将HTML代码也一起输出。双大括号会将数据解析为普通文本,而非 HTML 代码。但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。如果我们希望解析出HTML展示 可以使用**v-html**指令:该指令后面往往会跟上一个string类型,会将string的html解析出来并且进行渲染。 <div id="app"> <div v-html='url'></div> </div> new Vue({ el:'#app', data(){ return { list:"All the changan flowers in one day", url:'<a href="https://www.baidu.com">百度一下</a>' } } })
3.属性渲染 双大括号语法不能作用在元素属性上,遇到这种情况应该使用**v-bind**指令 : ### js代码 new Vue({ el:"#app", data(){ return { msg:'我是数据模型中的msg', title:'我是鼠标悬浮就会展示的title' } } }) ### html代码 <div id="app"> <!-- <div v-bind:title='title'>{{msg}}</div> --> <!-- 简写为:属性名="变量名" --> <div :title='title'>{{msg}}</div> </div>
4.事件渲染 可以使用v-on给当前元素绑定事件,也可以使用简写形式@click ###js代码 new Vue({ el:'#app', data:{ }, methods:{ test(){ alert(1) } } }) ### html代码 <div id="app"> <button v-on:click='test'>点我点我</button> <button @click='test'>点我点我</button> </div>
5.javaScript表达式 ###js代码 new Vue({ el:'#app', data(){ return { animal:['大笨象','小猴子','狐狸'], count:1001, firstname:'ren', lastname:'terry' } } }) ###html代码 <div id="app"> <ul> <li v-for='(item,index) in animal'> {{index+1}}-{{item}}-{{index}} </li> </ul> <div>{{Number(animal)}}</div> <div>{{Boolean(animal)}}</div> <div>{{count*100}}</div> <div>{{firstname}}--{{lastname}}</div> </div>
条件渲染
1.v-if(可以单独使用),表达式为true的时候渲染使用v-if属性的元素,否则使用v-else渲染
2.v-show(切换css样式中display属性),频繁切换css样式,使用v-show**
不推荐同时使用 v-if 和 v-for。当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级。
****** `v-show 与 v-if 的区别 : v-show 不支持 v-else v-show 不管初始条件是什么,元素总是会被渲染,并且只是简单地基于 CSS 进行切换。 v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。 v-if 也是惰性的:如果在初始渲染时条件为假,则什么也不做;直到条件第一次变为真时,才会开始渲染条件块。 v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好。 ` v-if对应的是元素/标签的添加或者删除 满足条件添加元素 不满足条件删除元素 v-show对应的是元素的CSS样式中的display属性 满足条件对应的是元素的显示 display:block 不满足条件对应的是元素的隐藏 display:none v-show用于频繁的切换css样式
### javascript代码 new Vue({ el:"#app", data:{ type:'email', animal:['老虎','大象','狮子'], isShow:false } }) ### html代码 <button @click="type='email'">点我获取email</button> <button @click="type='telephone'">点我获取telephone</button> <div v-if="type=='email'"> <form> <input type="text" placeholder="请输入email"> </form> </div> <div v-else-if="type==='telephone'"> <form > <input type="text" placeholder="请输入telephone"> </form> </div> <div v-else>错误</div> <ul v-show=""> <li v-for="item in animal" :key="item">{{item}}</li> </ul> </div>
列表渲染 v-for
用于将列表数据进行渲染。v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 则是被迭代的数组元素的别名。
*** key
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染。这么做可以使 Vue 变得非常快。但是有些时候,我们却不希望vue复用,这时候Vue 为你提供了一种方式来表达“这两个元素是完全独立的,不要复用它们”。只需添加一个具有唯一值的 key
### html代码 <ul> <li v-for='(item,index) in animal' :key='item'> {{index+1}}-{{item}}-{{index}} </li> </ul> ### js代码 new Vue({ el:'#app', data(){ return { animal:['大笨象','小猴子','狐狸'], } } })
style绑定
操作元素的class列表和内联样式是数据绑定的一个常见需求,因为它们都是attribute,所以我们可以用v-bind处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将v-bind用于class和style时,Vue做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
### html代码 <div :style="styleObj">Hello</div> <div :style="{color:currentColor}">World</div> <div :style="[styleObj2,styleObj]"> hello world</div> ### css代码 new Vue({ el: '#app', data: { currentColor: 'blue', styleObj: { color: 'red', "font-size": '30px', }, styleObj2: { background: 'pink', color: 'blue' } }, })
class绑定
操作元素的class列表和内联样式是数据绑定的一个常见需求,因为它们都是attribute,所以我们可以用v-bind处理它们:只需要通过表达式计算出字符串结果即可。不过,字符串拼接麻烦且易错。因此,在将v-bind用于class和style时,Vue做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组。
### css样式 .red { color: red; } .size { font-size: 30px; } ### html代码 <div id="app"> <div class="red" :class="{size:true}">Hello</div> <div :class="{red:false,size:true}">World</div> <div :class="[{red:true},{size:false}]">hello World</div> </div> ### js代码 new Vue({ el: '#app', data: { }, })
***** 【面试题:为什么在大型项目中data是一个函数而不是一个对象】
组件是一个可复用的实例,当你引用一个组件的时候,组件里的data是一个普通的对象,所有用到这个组件的都引用的同一个data,就会造成数据污染。 不使用return包裹的数据会在项目的全局可见,会造成变量污染; 使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。 当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
//1、在简单的vue实例中看到的Vue实例中data属性,如下所示: <script> let vm=new Vue({ el:'', data:{},//数据可以直接挂在vue实例上 methods:{} }) </script> //2、在使用组件化的项目中,如下所示: //每一个.vue文件中都对应一个vue组件实例,该vue组件实例的构造函数参数由当前页面的export default提供 <script> export default{ data(){ return { } }, methods:{} } } </script>
官方推荐我们在使用v-for时,给对应的元素或组件添加上一个:key属性。
为什么需要这个key属性呢(了解)?
这个其实和Vue的虚拟DOM的Diff算法有关系。
当某一层有很多相同的节点时,也就是列表节点时,我们希望插入一个新的节点
-
我们希望可以在B和C之间加一个F,Diff算 法默认执行起来是这样的。
-
即把C更新成F,D更新成C,E更新成D,最 后再插入E,是不是很没有效率?
所以我们需要使用key来给每个节点做一个唯一标识
-
Diff算法就可以正确的识别此节点
-
找到正确的位置区插入新的节点。
所以一句话,key的作用主要是为了高效的更新虚拟DOM。
事件机制
概述 在dom阶段,我们已经讲述了事件机制的特点:
事件三要素
事件绑定
事件流
事件对象
事件代理
事件类型 这些概念在vue中依旧存在,但是vue中的事件机制要更加的简单便捷一
1.事件绑定 可以用 v-on 指令监听DOM 事件,并在触发时运行一些 JavaScript 代码。v-on 还可以接收一个需要调用的方法名称。
<button v-on:click="handler">good</button>
methods: { handler: function (event) { if (event) { alert(event.target.tagName) } //event
是原生 DOM 事件 } } 除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法,通过$
event传递原生事件对象:<button v-on:click="say('hi',$event)">Say hi</button>
methods: { say: function (message,event) { alert(message) } } 由于事件绑定在vue中使用概率较大,所以这里提供了简写形式 <button @click="say('hi',$event)">Say hi</button>
事件参数 在事件调用时,可以进行参数的传递 : ### html代码 <div id="app"> <el-button @click="toAdd" type="primary" size="small">新增</el-button> <div> <el-table type="index" size="small" :data="tableData" style="width: 50%"> <el-table-column prop="date" label="日期" width="180"> </el-table-column> <el-table-column prop="name" label="姓名" width="180"> </el-table-column> <el-table-column prop="address" label="地址"> </el-table-column> <el-table-column label="操作"> <template slot-scope="scope"> <el-button size="small" @click="toEdit(scope.row)" type="primary">编辑</el-button> <el-button size="small" type="danger" @click="toDelete(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> </div> <el-dialog :title="title" :visible.sync="dialogFormVisible"> <el-form :model="form"> <el-form-item label="时间" :label-width="formLabelWidth"> <el-input v-model="form.date" autocomplete="off"></el-input> </el-form-item> <el-form-item label="姓名" :label-width="formLabelWidth"> <el-input v-model="form.name" autocomplete="off"></el-input> </el-form-item> <el-form-item label="地址" :label-width="formLabelWidth"> <el-input v-model="form.address" autocomplete="off"></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button @click="dialogFormVisible = false">取 消</el-button> <el-button type="primary" @click="dialogFormVisible = false">确 定</el-button> </div> </el-dialog> </div> ### js代码 <script> new Vue({ el: "#app", data: { dialogFormVisible: false, formLabelWidth: "120px", form: {}, title: '', tableData: [{ id: 1, date: '2016-05-02', name: '王小虎', address: '上海市普陀区金沙江路 1518 弄' }, { id: 2, date: '2016-05-04', name: '王小虎', address: '上海市普陀区金沙江路 1517 弄' }, { id: 3, date: '2016-05-01', name: '王小虎', address: '上海市普陀区金沙江路 1519 弄' }, { id: 4, date: '2016-05-03', name: '王小虎', address: '上海市普陀区金沙江路 1516 弄' }] }, methods: { toAdd() { this.dialogFormVisible = true; this.form = {}; this.title = '新增数据' }, toEdit(row) { this.form = { ...row }; this.dialogFormVisible = true; this.title = '修改数据'; }, toDelete(id) { this.$confirm('此操作将永久删除该文件, 是否继续?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(() => { this.tableData.splice(id,1) this.$message({ type: 'success', message: '删除成功!' }); }).catch(() => { this.$message({ type: 'info', message: '已取消删除' }); }); } } }) </script>
进行事件绑定时,可以将v-on:事件名缩写为@事件名,此方式经常使用 :
事件修饰符
事件修饰符 在事件处理程序中调用 event.preventDefault() 或 event.stopPropagation() 是非常常见的需求。Vue提供了更好的方式:事件处理函数只有纯粹的数据逻辑,而不是去处理 DOM 事件细节,通过事件修饰符来完成这些细节。
<button v-on:click.prevent="handler">点我点我</button>
常见修饰符如下 .stop 停止事件冒泡 .prevent 阻止事件默认行为 .capture 在事件捕获阶段执行事件处理函数 .self 只当在 event.target 是当前元素自身时触发处理函数 .once 事件处理函数执行一次后解绑 .passive 滚动事件的默认行为 (即滚动行为) 将会立即触发 ,一般与scroll连用,能够提升移动端的性能 按键修饰符 一般与keyup事件类型配合使用 .enter、.tab、.delete、.esc、.space、.up、.down、.left、.right .ctrl、.alt、.shift、.meta 鼠标修饰符mouseup事件 .left、.right、.middle
### js代码 new Vue({ el: "#app", data: { msg: '事件修饰符' }, methods: { keyupHandle() { console.log('按下某些特殊键'); }, toJump() { console.log('跳转'); alert(1); }, outer(e) { // e.target是触发事件的源头元素,目标元素 // e.currentTarget 当前执行事件处理程序的元素,当前目标元素 // console.log('outer', e.target, e.currentTarget); console.log('outer') for (let i = 0; i < 100; i++) { console.log(i); } }, inner(e) { // e.stopPropagation(); // console.log('inner', e.target, e.currentTarget); console.log('inner'); } } }) ### html代码 <div id="app"> <!-- <input type="text" @keyup.enter="keyupHandle"> --> <input type="text" @keyup.13="keyupHandle"> <!-- <input type="text" @mouseup.left="keyupHandle"> --> {{msg}} <a @click.prevent="toJump" href="http://www.baidu.com">百度一下</a> <!-- 点击inner event.target --> <!-- <div class="outer" @click.self.once="outer"> --> <!-- <div class="outer" @click.self="outer"> --> <!-- <div class="outer" @click.capture="outer"> --> <div class="outer" @scroll.passive="outer"> outer <div class="inner" @click="inner"> <!-- <div class="inner" @click.stop="inner"> --> inner </div> </div> </div>
表单
可以用 v-model 指令在表单
<input>
、<textarea>
及<select>
元素上创建双向数据绑定。 它会根据控件类型自动选取正确的方法来更新元素。尽管有些神奇,但 v-model 本质上不过是语法糖。它负责监听用户的输入事件以更新数据,并对一些极端场景进行一些特殊处理。使用v-model绑定了值, 那么name属性就可以不用写了。
### html代码 <div id="app"> {{msg}} <br> {{stu}} <br> <!-- 用户名:<input type="text" v-model.lazy="stu.username"> --> 用户名:<input type="text" v-model.trim="stu.username"> <br> <!-- .number修饰符,可以将采集到的数据转为number类型,然后再存储到数据模型中 --> 年龄:<input type="text" v-model.number="stu.age"> <br> <!-- 性别 --> 性别:<input type="radio" value="male" v-model="stu.gender">男 <input type="radio" value="female" v-model="stu.gender">女 <br> <!-- 爱好 --> 爱好:<input type="checkbox" value="basketball" v-model="stu.hobby">篮球 <input type="checkbox" value="swimming" v-model="stu.hobby">游泳 <input type="checkbox" value="dancing" v-model="stu.hobby">跳舞 <br> <!-- 城市 --> 城市: <!-- <select multiple v-model="stu.city"> --> <select v-model="stu.city"> <option value="shanghai">上海</option> <option value="beijing">北京</option> <option value="guangzhou">广州</option> </select> <br> <!-- 简介 --> 简介:<textarea v-model="stu.info" cols="30" rows="10"></textarea> </div> ### js代码 new Vue({ el: '#app', data: { msg: 'hello', stu: { // 复选框 hobby: [] } }, methods: {} })
watch(监听器 监听属性)
当需要在数据变化时执行异步或开销较大的操作时,使用监听器是最有用的
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的 。
### html代码 <div id="app"> {{msg}} <br> a:<input type="text" v-model.number="a"> <br> + <br> b:<input type="text" v-model.number="b"> <br> = <br> <output>{{total}}</output> </div> ### js代码 new Vue({ el: '#app', data: { msg: 'hello', a: 0, b: 0, total: 0 }, methods: {}, // 监听 侦听 watch: { a(newValue, oldValue) { this.total = this.a + this.b; }, b(newValue, oldValue) { this.total = this.b + this.a; } } })
### 深度监听 ### js代码 new Vue({ el: '#app', data: { msg: 'hello', a: 1, obj: { name: 'zhangsan', age: 12 }, }, watch: { a(newValue, oldValue) { console.log('a数据发生变化...'); }, /* obj(newValue, oldValue) { console.log('obj数据发生变化...'); } */ // 深度监听 obj: { handler(newValue, oldValue) { console.log('obj数据发生变化...'); }, // 深度监听 deep: true } }, methods: { changeObj() { // 更改内部数据 this.obj.name = 'lisi'; } } }) ### html代码 <div id="app"> {{msg}} <br> {{obj}} <button @click="changeObj">更改obj</button> </div>
computed(计算属性)
计算属性 有依赖关系的数据
我们希望一个变量是经过某种计算然后输出而不是直接输出的时候可以使用到计算属性 计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。而每次调用函数都会导致函数的重新执行。
### html代码 <div id="app"> {{msg}} <br> a:<input type="text" v-model.number="a"> <br> + <br> b:<input type="text" v-model.number="b"> <br> = <br> {{total}} </div> ### js代码 new Vue({ el: '#app', data: { msg: 'hello', a: 0, b: 0, // total: 0 }, // 计算属性 computed: { total(){ console.log('计算属性'); // return 111 return this.a+this.b } }, methods: {} })
面试题 *** watch和computed的区别 computed 1. 具有缓存性,页面重新渲染值不变化,计算属性会立即返回之前的计算结果,而不必再次执行函数 2.计算属性计算某一个属性得变化,如果某一个值改变了,计算属性会见监听到进行返回 watch 1. 监听值的变化,执行异步操作【axios请求】 ’$route.query‘:{ this.loadArticle() } 2. 无缓存性,只有当当前值发生变化时才会执行/响应
1.组件机制
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用is特性进行了扩展的原生 HTML 元素。组件注册的时候需要为该组件指定各种参数。
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
### 组件的特点 * 组件可以进行任意次数的复用。 * 组件的data必须是一个函数,确保每个实例可以维护一份被返回对象的独立的拷贝,也就是任何一个组件的改变不会影响到其他组件。 let component = { data () { return { count: 0 } }, template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>' } 一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝
2.组件注册
要想进行组件使用得先进行组件注册
全局注册 可以使用Vue.component(tagName, options) 注册一个全局组件, 注册之后可以用在任何新创建的 Vue 根实例的模板中 Vue.component('my-component-name',component)
局部注册
全局注册往往是不够理想的。比如,如果你使用一个像 webpack 这样的构建系统,全局注册所有的组件意味着即便你已经不再使用一个组件了,它仍然会被包含在你最终的构建结果中。这造成了用户下载的 JavaScript 的无谓的增加。局部注册的组件只能在当前组件中使用
全局组件-->全局变量
局部组件-->局部变量
全局注册 ### js代码 // 1.创建组件 let mycom = { data() { return { Commsg: '组件数据' } }, methods:{ changeObj(){ if(this.Commsg==='组件数据'){ this.Commsg='修改后的组件数据' }else{ this.Commsg='组件数据' } } }, // 模板 template: ` <div> <br> 组件使用 <br> ------------------------- <br> <span>{{Commsg}}</span> <span>{{Commsg}}</span> <button @click='changeObj'>更改数据模型中的数据</button> </div> ` }; // 全局注册 Vue.component('my-com',mycom) new Vue({ el: '#app', data: { } }) ### html代码 <div id="app"> <my-com></my-com> <my-com></my-com> <my-com></my-com> </div>
### 局部注册 ### html代码 <div id="app"> <my-com-b></my-com-b> -------------------- <my-com-a></my-com-a> </div> ### js代码 // 创建组件 let myComA={ // 模板 template:` <div>A组件</div> ` }; let myComB={ components:{ 'my-com-a':myComA }, // 模板 template:` <div>B组件 <my-com-a></my-com-a> </div> ` }; //全局注册 // Vue.component('my-com-a',myComA) Vue.component('my-com-b',myComB) new Vue({ components:{ 'my-com-a':myComA }, el:'#app', data:{ } })
3.组件交互/通信/传值
组件 A 在它的模板中使用了组件 B。它们之间必然需要相互通信:父组件可能要给子组件下发数据,子组件则可能要将它内部发生的事情告知父组件。在 Vue 中,父子组件的关系可以总结为 prop 向下传递,事件向上传递。父组件通过 prop 给子组件下发数据,子组件通过事件给父组件发送消息。
1.子组件内使用父组件数据 父-子
1.父组件传递数据给子组件 (可以传递静态属性 动态属性 )
<my-com title='' :msg='msg' attr-a=''>
2.子组件接收并处理数据
{
props:['title','msg','attrA],
template:``
}
2.父组件内使用子组件的数据
事件发射 自定义事件
在子组件内发射 在父组件内接受
子组件发射时机
1.手动按钮发送
2.监听子组件内数据变化,然后发射
在子组件内监听comMsg的变化,this.$emit('',this.comMsg) 父组件的事件处理程序调用,可以更改父组件数据模型中的数据,同步反映到父组件视图中
### 父组件传递数据给子组件 ### html代码 <div id="app"> <!-- 父组件给子组件传递数据 --> <my-com :msg='msg' title='父组件向子组件传值' attr-a='父组件给自组件的静态数据'></my-com> </div> ### js代码 // 创建组件 let myCom={ // 接受父组件传递的数据 props:['title','msg','attrA'], data(){ return { } }, template:` <div>组件通信 <br> <span>{{msg}}--{{title}}--{{attrA}}</span> </div> ` } new Vue({ components:{ "my-com":myCom }, el:"#app", data:{ msg:'我是父组件' } })
### 子组件向父组件传递数据 ### html代码 <div id="app"> {{msg}} <my-a title='父组件静态数据' :msg='msg' data-attr='父组件静态数据' @my-event='handler'> </my-a> </div> </div> ### js代码 <script> // 创建组件 let myA={ props:['title','msg','dataAttr'], data(){ return { subMsg:'子组件数据' } }, methods:{ toSend(){ // 参数 第一个参数自定义事件名称 第二个参数传递的数据 this.$emit('my-event',this.subMsg,100) } }, template:` <div> {{subMsg}}-- {{msg}}-- {{dataAttr}}-- <button @click='toSend'>发射数据</button> </div> }; // 全局注册 Vue.component('my-a',myA) new Vue({ el:"#app", data:{ msg:'父组件数据' }, methods: { handler(a,b){ console.log(a,b) } }, })
4.组件传值
父组件通过属性绑定的方式将参数传递给子组件,子组件通过props声明期望从父组件那里获取的参数。camelCase (驼峰命名法) 的 prop 名需要使用其等价的 kebab-case (短横线分隔命名) 命名:
希望每个prop都有指定的值类型。这时,你可以以对象形式列出prop,这些property的名称和值分别是prop各自的名称和类型
父组件给子组件数据的时候,子组件可以定义数据类型 静态传参:不加冒号,传字符串 动态传参:加冒号,传number、boolean、object、数组类型的值,变量
props: { title: String, likes: Number, isPublished: Boolean, commentIds: Array, author: Object, callback: Function, contactsPromise: Promise // or any other constructor }
prop验证
我们可以为组件的 prop 指定验证要求,例如你知道的这些类型。如果有一个需求没有被满足,则 Vue 会在浏览器控制台中警告你。这在开发一个会被别人用到的组件时尤其有帮助。
Vue.component('my-component', { props: { // 基础的类型检查 (
null
和undefined
会通过任何类型验证) propA: Number, // 多个可能的类型 propB: [String, Number], // 必填的字符串 propC: { type: String, required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 } })
### js代码 // 创建组件 let myA={ props:['title','msg'], data(){ return { subMsg:'子组件' } }, template:` <div> {{title}}-{{msg}} </div> ` } Vue.component( 'my-a',myA ) new Vue({ el:'#app', data:{ msg:'父组件' } }) ### html代码 <div id="app"> {{msg}} <my-a title='hello' :msg='msg' ></my-a> </div>
### html代码 <div id="app"> {{msg}} <!-- <my-a sub-msg="父给子" :stus="[1,2,3]" :is-active="undefined"></my-a> --> <!-- <my-a sub-msg="父给子" :stus="[1,2,3]" :is-active="true"></my-a> --> <my-a :msg="msg" sub-msg="父给子" :is-active="true" :age="80"></my-a> </div> ### js代码 let myA = { props: { msg: String, subMsg: String, // stus: Array, stus: { type: Array, // 错误 // default: [6, 7, 8] // 正确 Object/Array要一个工厂函数返回默认值 default() { return [6, 7, 8]; } }, // isActive: Boolean isActive: [String, Boolean], name: { type: String, // 必填项 // required: true default: "lisi" }, // 自定义校验规则 age: { type: Number, // 校验器 // value 是形参,实参是将来父组件给子组件传递的数据 validator(value) { /* if(value>50){ return true; }else{ return false; } */ return value > 50; } } }, template: ` <div> {{subMsg}} {{stus}} {{isActive}} {{name}} {{age}} {{msg}} </div> ` }; Vue.component('my-a', myA); let vm = new Vue({ el: '#app', data: { msg: 'hello' }, methods: {} })
单向数据流(数据更改的方向)
父组件可以改给子组件的数据 父改子可以 子组件不可以改父组件的数据 子改父不行
修改父组件的msg 子组件的msg跟着修改但是反过来不行
vm.msg='hello vue' 观察子组件的变化
1.插槽
普通插槽,具名插槽,作用域插槽
插槽允许我们在调用子组件的时候为子组件传递模板。
<slot> 元素作为承载分发内容的出口。 一个不带 name 的 <slot> 出口会带有隐含的名字“default”。
父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
### 1.默认插槽 没有名字的插槽就是默认插槽 <slot>123</slot> <slot name="default">123</slot> ### 2. 具名插槽 具有名字的插槽 <slot name="slot2">123</slot> ### 3.插槽填充 <my-com>hello</my-com> 内容填充到模板的哪个插槽中? 指定插槽 <my-com> <template v-slot:default> hello </template> <template v-slot:slot> 123 </template> <template #slot> 123 </template> </my-com> ### 插槽声明 {template:`<slot>插槽默认模板</slot>`} 插槽模板 ### 1.默认模板 <slot>插槽默认模板</slot> ### 2.自定义模板 <my-com> <template #name> 插槽自定义模板 </template> </my-com> 在插槽默认模板中,可以直接访问子组件的数据,可以通过props间接访问父组件的数据 在插槽自定义模板中,可以直接访问父组件的数据,可以通过插槽属性间接访问子组件的数据(插槽作用域)
<!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://cdn.jsdelivr.net/npm/vue@2.7.10"></script> </head> <body> <div id="app"> <my-a :arr="arr"> <div>我是子组件</div> <template v-slot:header> 开始的内容</template> <template v-slot:center> 中间的内容</template> <template v-slot:footer> 结束的内容</template> <template slot-scope="scope"> {{scope.row}}</template> </my-a> </div> <script> let myA = { props: ['arr'], data() { return { msg: '222' } }, template: ` <div> A组件 <slot name='default'></slot> <div class="header"><slot name='header'></slot></div> <div class="center" > <slot name='center'></slot> </div> <div class="footer"><slot name='footer'></slot></div> <ul> <li v-for="item in arr"> <slot v-bind:row='item'></slot> </li> </ul> </div> ` }; Vue.component('my-a', myA) new Vue({ el: "#app", data: { arr: [1, 2, 3, 4] }, methods: {} }) </script> </body> </html>
2.自定义指令
directive
Vue中多有的指令都以 v- 来调用。但是,有时候Vue提供给我们的指令并不能满足我们的需求,这个时候 我们就需要自定义指令。
指令允许我们对普通 DOM 元素进行底层操作。可以全局注册也可以局部注册
全局注册
使用Vue.directive
局部注册
在Vue实例或组件中添加新的选项directives
钩子函数
钩子函数可以在指令的生命周期内的关键时刻加入代码
<!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://cdn.jsdelivr.net/npm/vue@2.7.10"></script> </head> <body> <div id="app"> <input v-focus="msg" type="text" > {{msg}} <input v-myshow="msg" type="text" > </div> <script> Vue.directive('focus',{ inserted(el){ el.focus() }, bind(el,binding,vnode){ el.style.backgroundColor=binding.value } }) new Vue({ directives:{ 'myshow':{ inserted(el){ }, bind(el,binding,vnode){ el.value=binding.value; } } }, el:"#app", data:{ msg:'red' }, methods:{} }) </script> </body> </html>
3.render渲染函数
Vue 推荐在绝大多数情况下使用模板来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力。这时你可以用渲染函数,它比模板更接近编译器。
<!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://cdn.jsdelivr.net/npm/vue@2.7.10/dist/vue.js"></script> </head> <body> <div id="app"> <my-a :friuts="friuts"> 列表 </my-a> </div> <script> let myA={ props:{ friuts:{ type:Array, } }, beforeMount(){ alert('beforeMount') }, mounted(){ alert('mounted') }, render(h){ alert('2222') let lis=this.friuts.map(item=>{ return h('li',{},item) }) return h('ul',{},[this.$slots.default,...lis]) }, // template:` // <div> // <ul> // <li v-for='item in friuts'>{{item}}</li> // </ul> // </div> // `, data(){ return { } } } new Vue({ components:{ 'my-a':myA }, el:"#app", data:{ friuts:['苹果','香蕉','菠萝'] }, methods:{} }) </script> </body> </html>
4.过滤器
Vue.js 允许自定义过滤器,可被用于一些常见的文本格式化。过滤器可以用在两个地方:双花括号插值和 v-bind表达式 (后者从 2.1.0+ 开始支持)。过滤器应该被添加在 JavaScript 表达式的尾部,由“管道”
|
符号指示:<!-- 在双花括号中 --> {{ message | filterMethod }} <!-- 在 `v-bind` 中 -->
首先引入 `moment`第三方库,再进行接下来的操作。引入moment仅供实现功能,与过滤器没有关系。 <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/locale/af.js"></script> <script> // 全局注册 Vue.filter("fmtDate_global", function (date) { return moment(date).format("YYYY-MM-DD HH:mm:ss"); // 或者return自己编写的时间处理函数 }) new Vue({...}) </script>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>hello world</title> <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script> </head> <body> <div id="app"> <!-- 使用过滤器 --> <div>{{ new Date() | fmtDate_global}}</div> <div :title="new Date() | fmtDate_global">鼠标悬停查看时间</div> </div> <script> // 全局注册过滤器 Vue.filter("fmtDate_global", function (date) { return moment(date).format("YYYY-MM-DD HH:mm:ss"); }) new Vue({ el: '#app', }) </script> </body> </html>
5.插件
plugin
插件通常用来为 Vue 添加全局功能。Vue.js 的插件应该暴露一个 install 方法。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:
MyPlugin.install = function (Vue, options) { // 1. 添加全局方法或 property Vue.myGlobalMethod = function () { // 逻辑... } // 2. 添加全局资源 Vue.directive('my-directive', { bind (el, binding, vnode, oldVnode) { // 逻辑... } ... }) // 3. 注入组件选项 Vue.mixin({ created: function () { // 逻辑... } ... }) // 4. 添加实例方法 Vue.prototype.$myMethod = function (methodOptions) { // 逻辑... } }通过全局方法
Vue.use()
使用插件。它需要在你调用new Vue()
启动应用之前完成:// 调用 `MyPlugin.install(Vue)` Vue.use(MyPlugin) new Vue({ // ...组件选项 })
<!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://cdn.jsdelivr.net/npm/vue@2.7.10"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.29.1/moment.min.js"></script> </head> <body> <div id="app"> {{time | fmtTime}} <input type="text" v-focus> </div> <script> let MyPlugin = { install(Vue, options) { Vue.filter('fmtTime', (val) => { return moment(val).format('YYYY--MM-DD') }), Vue.prototype.$message=function(val){ alert(val) }, Vue.directive('focus', { inserted(el) { el.focus() }, bind(el, binding, vnode) { el.style.backgroundColor = binding.value } }) }, }; Vue.use(MyPlugin) new Vue({ el: "#app", data: { time: new Date().getTime() }, created(){ this.$message('请求成功') }, methods: {} }) </script> </body> </html>
1.动态组件
动态渲染组件
默认情况下,当组件在切换的时候都会重新创建组件,但是有些时候我们希望组件实例能够被在它们第一次被创建的时候缓存下来。为了解决这个问题,我们可以用一个 <keep-alive> 元素将其动态组件包裹起来。
<keep-alive> <component v-bind:is="currentTabComponent"></component> </keep-alive>
### js代码 let myA = { template: ` <div> A组件内容 </div> `, created() { console.log('A组件created'); }, activated(){ alert('进入A组件了'); }, deactivated(){ alert('退出a组件了'); }, errorCaptured(){ console.log('子组件异常') } }; let myB = { template: ` <div> B组件内容 </div> `, created() { console.log('B组件created'); } }; let myC = { template: ` <div> C组件内容 </div> `, created() { console.log('C组件created'); } }; Vue.component('my-a', myA) Vue.component('my-b', myB) Vue.component('my-c', myC) new Vue({ el: '#app', data: { msg: 'hello', current:myA, }, methods: {} }) ### html代码 <div id="app"> {{msg}} <button @click="current='my-a'">A组件</button> <button @click="current='my-b'">B组件</button> <button @click="current='my-c'">C组件</button> <!-- 静态is值为注册的组件的名称 --> <keep-alive> <component :is="current"></component> </keep-alive> </div>
3.混入
混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。
1.全局混入
Tip: 不建议使用全局注册混入对象,一旦使用全局混入,它将会影响以后创建的每一个vue实例。
混入规则
当组件和混入对象有同名选项时,这些选项会以恰当的方式合并
数据 data 数据对象在混入时,会进行合并,发生冲突时,保留组件的数据
值为对象 methods computed等 在混入时,methods会合并成为一个对象,如果对象的键名发生冲突,则保留组件对象的键值对
生命周期钩子函数 同名的钩子函数会被合并为一个数组,依次都会被调用,但是混入对象的钩子函数先被调用
### js代码 // 混入(对象) let mixin = { // 数据 data() { return { mixinData: '混入的数据', obj: { name: 'zhangsan', gender: '男' } } }, // 生命周期 created() { console.log('混入的created'); }, // 方法 methods: { mixinMethod() { console.log('mixinMethod方法'); }, test() { console.log('test 混入'); } } }; // 全局混入 Vue.mixin(mixin) let vm = new Vue({ el: '#app', // 局部混入 // mixins: [mixin], data: { msg: 'hello', obj: { age: 12, name: 'lisi' } }, created() { console.log('created...'); }, methods: { test() { console.log("test"); } } }) ### html代码 <div id="app"> {{msg}} <my-a></my-a> </div>
3.Vue3
每个 Vue 应用都是通过 createApp 函数创建一个新的 应用实例:
import { createApp } from 'vue' const app = createApp({ /* 根组件选项 */ }) 挂载应用
<div id="app"></div> app.mount('#app')
响应式代理 vs. 原始值
在 Vue 3 中,数据是基于 JavaScript Proxy(代理) 实现响应式的。使用过 Vue 2 的用户可能需要注意下面这样的边界情况:
export default { data() { return { someObject: {} } }, mounted() { const newObject = {} this.someObject = newObject console.log(newObject === this.someObject) // false } }
当你在赋值后再访问 this.someObject
,此值已经是原来的 newObject
的一个响应式代理。与 Vue 2 不同的是,这里原始的 newObject
不会变为响应式:请确保始终通过 this
来访问响应式状态。
v-for
与 v-if
同时使用 v-if
和 v-for
是不推荐的,因为这样二者的优先级不明显。
当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名: <!-- 这会抛出一个错误,因为属性 todo 此时 没有在该实例上定义 --> <li v-for="todo in todos" v-if="!todo.isComplete"> {{ todo.name }} </li>
什么是“组合式函数”? 在 Vue 应用的概念中,“组合式函数”(Composables) 是一个利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。
生命周期
组合式API
<!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> <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.27.2/axios.js"></script> </head> <body> <div id="app"> {{msg}} <div v-for="item in article"> {{item.name}}-{{item.introduce}} <img :src="item.url" alt=""> </div> </div> <script> let ref=Vue.ref; let reactive=Vue.reactive; let onMounted=Vue.onMounted; let app=Vue.createApp({ data(){ return { msg:'hello Vue3', } }, // 组合式api setup(props){ let article=ref([]); let loadArticle=async ()=>{ let res = await axios.get('http://121.199.0.35:8888/index/carousel/findAll'); console.log(res); article.value=res.data.data } onMounted(loadArticle()); return { article } }, }); app.mount('#app'); </script> </body> </html>
路由机制
vue-router是vue的一个插件,用来提供路由功能。通过路由的改变可以动态加载组件,达到开发单页面程序的目的。
安装
方式一 : CDN引入
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.10"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vue-router/3.5.4/vue-router.min.js" integrity="sha512-zxWuRslhjyZyRAYInXTNl4vC/vBBwLQuodFnPmvYAEvlv9XCCfi1R3KqBCsT6f9rk56OetG/7KS9dOfINL1KCQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
方式二:本地引入
<script src='vue.js'></script> <script src='vue-router.js'></script>
方式三 : cnpm下载
cnpm install vue-router
使用
在html文件中使用时需要引入vueRouter(先引入vue.js,在引入vueRouter)
### js代码 <script> // 声明组件 let myA = { template: ` <div>A组件</div> ` }; let myB = { template: ` <div>B组件</div> ` }; // 1.定义路由对象数组 route路由对象 router路由器对象 routes路由对象组 let routes = [{ path: '/a', component: myA }, { path: '/b', component: myB }]; // 2.创建路由器对象 声明路由器实例对象 let router = new VueRouter({ // 声明配置路由对象 routes: routes }) // 3.注册路由器对象 new Vue({ el: '#app', // 将路由实例对象导入vue实例,相当于router:router,在此使用简写形式 router, components: { 'my-a': myA, 'my-b': myB }, data: { msg: 'hello' }, }) </script> ### html代码 <div id="app"> {{msg}} <!-- 4.实现路由切换,router-link的本质是创建a标签 --> <div> <router-link to="/a">去A路由</router-link> <router-link to="/b">去B路由</router-link> <a href="#/a">a标签路由去A</a> </div> <div> <!-- 路由组件显示的位置 路由出口,将匹配到的路由组件,渲染到此--> <router-view></router-view> </div> </div>
let routes=[ { path:"/", //路由名称 name:"aRoute", //别名 重命名 alias:'/aa', //重定向 // redirect:'/a' redirect:{name:'aRoute'} } ]
3.动态路由匹配
需要把某种模式匹配到的所有路由,全部映射到同一个组件。
例如 : 有一个user组件,对于所有id不同的用户,都要使用这个组件来渲染,那么可以在vue-router的路由路径中使用动态路径参数来达到这个效果。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch $route 对象,或者使用组件内部的导航守卫
<div id="app"> {{msg}} <!-- 4.使用路由 --> <div> <router-link to="/user/id/1/username/zhangsan">去A路由</router-link> </div> <div> <!-- 路由组件显示的位置 --> <router-view></router-view> </div> </div> // 声明组件 let myA = { data() { return { id: null, username: '' } }, template: ` <div>A组件{{id}}--{{username}}</div> `, /* created() { alert(1); // console.log(this.$route); this.id = this.$route.params.id; this.username = this.$route.params.username; } */ //监听 /* watch: { msg(newValue, oldValue) { }, $route:{ handler(){ // to是新路由 from是旧路由 // console.log(to, from); this.id = this.$route.params.id; this.username = this.$route.params.username; }, deep:true } } */ // 监听路由发生变化 路由守卫 beforeRouteUpdate(to, from, next) { // console.log(to.params); this.id = to.params.id; this.username = to.params.username; // 继续 // next(); // 阻断 // next(false) } }; let myB = { template: ` <div>B组件</div> ` }; // 1.定义路由对象数组 route路由对象 router路由器对象 routes路由对象组 let routes = [{ // 动态路由 // /user/id/1/username/zhangsan // /user/id/2/username/lisi path: '/user/id/:id/username/:username', component: myA }]; // 2.创建路由器对象 let router = new VueRouter({ routes: routes }) // 3.注册路由器对象 new Vue({ el: '#app', router: router, components: { 'my-a': myA, 'my-b': myB }, data: { msg: 'hello' }, methods: {} })
4.嵌套路由
实际生活中的应用界面,通常由多层嵌套的组件组合而成。同样地,URL 中各段动态路径也按某种结构对应嵌套的各层组件 :
### js代码 <script> // 创建一个组件 let user={ template:` <div> <router-link to='/userChild1'>子组件1</router-link> <router-link to='/userChild2'>子组件2</router-link> <router-view ></router-view> </div> ` } let manager={ template:` <div>管理员</div> ` } let userChild1={ template:` <div>子组件1</div> ` } let userChild2={ template:` <div>子组件2</div> ` } let routes=[{ path:'/manager', component:manager },{ path:'/user', component:user, redirect:'/userChild1', children:[{ path:"/userChild1", component:userChild1 },{ path:"/userChild2", component:userChild2 }] }]; let router=new VueRouter({ routes }) new Vue({ el:"#app", router, data:{ } }) ### html代码 <div id="app"> <router-link to='user'>去用户</router-link> <router-link to='manager'>去管理员</router-link> <router-view></router-view> </div>
5.编程式导航
除了使用
<router-link>
创建 a 标签来定义导航链接,还可以借助 router 的实例方法,通过编写代码来实现。this.$router.push()跳转到指定路由,会向history栈添加一个新的记录,当用户点击浏览器回退按钮的时候,可以回到跳转前的url。
### js代码 <script> // 创建A B组件 let myA={ data(){ return { } }, template:` <div> A组件 </div> `, created(){ console.log(this.$route) }, } let myB={ data(){ return { } }, template:` <div> B组件 </div> ` }; // 创建路由组件对象数组 routes router路由器对象 route路由对象 let routes=[ { path:"/a", component:myA, name:'mya' }, { path:"/b", component:myB, } ]; // 创建路由器对象实例 let router=new VueRouter({ // 配置路由对象组 routes }) new Vue({ el:'#app', data:{ }, // 3.注册路由 router, components:{ 'my-a':myA, 'my-b':myB }, methods:{ toPath(){ this.$router.push({ path:"/a", // 有效 // query:{id:1}, }) }, toPath1(){ this.$router.push({ name:'mya', query:{id:1}, // 参数是一次性携带刷新页面 params失效 params:{ name:"terry" } }) } } }) </script> ### html代码 <div id="app"> <router-link to='/a'>去a路由</router-link> <router-link to='/b'>去b路由</router-link> <a href="#/a/2">去a路由</a> <button @click="$router.push('/a')">跳转到A路由</button> <button @click="$router.push('a')">跳转到A2路由</button> <button @click="toPath">跳转到A3路由</button> <button @click="toPath1">跳转到A4路由</button> <router-view></router-view> </div>
this.$router.replace({ path:"/column", query:{id:1} }) //跳转到指定页面 可传递参数 前进一步记录 router.go(1) // 后退一步记录,等同于 history.back() router.go(-1) // 前进 3 步记录 router.go(3) // 如果 history 记录不够用,则会报错 router.go(-100) router.go(100)
6.路由组件传参
路由传递是指,从A页面跳转到B页面时,将A页面中的变量传递给B页面使用,传递参数的方式有两种 :
path-query传参
使用path与query结合的方式传递参数时,参数会被拼接在浏览器地址栏中,并且刷新页面后数据也不会丢失。
name-params传参
### js代码 <script> let user = { template:` <div @click="userHandler">普通用户,点击此处可跳转至管理员页面</div> `, data() { return { list:"hello", obj:{ name:'tom', age:3 } } }, methods: { userHandler() { // 跳转 this.$router.push({ path:'/manager', query:{ list:this.list, obj:JSON.stringify(this.obj) } }) } }, } let manager = { template:` <div>管理员 {{$route.query.list}} {{$route.query.obj}}</div> ` } let router = new VueRouter({ routes:[{ path:'/user', component:user },{ path:'/manager', component:manager }] }) new Vue ({ el:"#app", router }) </script> ### html代码 <div id="app"> <router-link to="/user">user</router-link> <router-link to="/manager">manager</router-link> <router-view></router-view> </div>
name-params传参 ### js代码 <script> let user = { template:` <div @click="userHandler">普通用户,点击此处可跳转至管理员页面</div> `, data() { return { list:"hello", obj:{ name:'tom', age:3 } } }, methods: { userHandler() { // 跳转 this.$router.push({ name:'manager', params:{ list:this.list, obj:JSON.stringify(this.obj) } }) } }, } let manager = { template:` <div>管理员 {{$route.params.list}} {{$route.params.obj}}</div> ` } let router = new VueRouter({ routes:[{ path:'/user', component:user, name:'user' },{ path:'/manager', component:manager, name:'manager' }] }) new Vue ({ el:"#app", router }) </script> ### html代码 <div id="app"> <router-link to="/user">user</router-link> <router-link to="/manager">manager</router-link> <router-view></router-view> </div>
路由模式
hash模式
hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在url后面随便添加一个#xx触发这个事件。
window.onhashchange = function(event){ console.log(event); // 打印出一个HashChangeEvent事件对象,在该对象内有newURL和oldURL // location.hash中也有相关的信息 // 假设hash值是个颜色值,通过location.hash来获取到对应的hash值,然后设置页面中的某个元素的背景颜色来改变页面 }
history模式
把window.history对象打印出来可以看到里边提供的方法和记录长度 history对象内有back(),forword(),go()等方法 前进,后退,跳转操作方法:
history.go(-3);//后退3次 history.go(2);//前进2次 history.go(0);//刷新当前页面 history.back(); //后退 history.forward(); //前进
vue-router使用的模式
vue-router 默认 hash 模式 —— 使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。如果不想要很丑的 hash,可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。不过这种模式要玩好,还需要后台配置支持。
hash路由和history路由的区别: 1.hash路由在地址栏URL上有#,而history路由没有会好看一点 2.进行回车刷新操作,hash路由会加载到地址栏对应的页面,而history路由一般就404报错了(刷新是网络请求,没有后端准备时会报错)。 3.hash路由支持低版本的浏览器,而history路由是HTML5新增的API。 4.hash的特点在于它虽然出现在了URL中,但是不包括在http请求中,所以对于后端是没有一点影响的,所以改变hash不会重新加载页面,所以这也是单页面应用的必备。 5.history运用了浏览器的历史记录栈,之前有back,forward,go方法,之后在HTML5中新增了pushState()和replaceState()方法(需要特定浏览器的支持),它们提供了对历史记录进行修改的功能,不过在进行修改时,虽然改变了当前的URL,但是浏览器不会马上向后端发送请求。
导航守卫 --路由的改变会触发导航守卫
全局守卫
全局守卫有全局前置守卫、全局后置守卫。
const router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... })
const router = new VueRouter({ ... }) router.afterEach((to, from) => { // ... })
路由独享守卫
组件内守卫
beforeRouteEnter: (to, from, next) => { //不!能!获取组件实例
this
} beforeRouteUpdate (to, from, next) {//在当前路由改变,但是该组件被复用时调用,可访问this} beforeRouteLeave (to, from, next) {//导航离开该组件的对应路由时调用,可访问this}
### 全局前置守卫 let router = new VueRouter({ ... }) router.beforeEach((to, from, next) => { // ... }) ### 全局后置钩子 let router = new VueRouter({ ... }) router.afterEach((to, from) => { // ... }) 全局守卫参数说明: * to: Route : 即将要进入的目标路有对象 * from: Route : 当前导航正要离开的路由 * next: Function : 一定要调用该方法来**resolve**这个钩子。执行效果依赖 next 方法的调用参数
路由独享守卫 可以在路由配置上直接定义 beforeEnter 守卫,改守卫与全局前置守卫的方法参数是一样的 : let router = new VueRouter({ routes: [ { path: '/home', component: Home, beforeEnter: (to, from, next) => { // ... } } ] }) 组件内的守卫 // 组件内的守卫 beforeRouteEnter(to,from,next){ // this--window console.log(this,'beforeRouteEnter'); next() }, beforeRouteUpdate(to,from,next){ //this--组件实例 console.log(this,'beforeRouteUpdate'); next() }, beforeRouteLeave(to,from,next){ //this--组件实例 console.log(this,'beforERouteLeave') }
Less
介绍
Less 是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性,使 CSS 更
易维护和扩展。Less 可以运行在 Node 或浏览器端。less中具有如下功能:
变量
混合
嵌套
运算
转义
函数
命名空间和访问符
映射
作用域
注释
导入
在浏览器环境中使用 Less :
<link rel="stylesheet/less" type="text/css" href="styles.less" /> <script src="//cdnjs.cloudflare.com/ajax/libs/less.js/3.11.1/less.min.js" ></script>
概览
Less (Leaner Style Sheets 的缩写) 是一门向后兼容的 CSS 扩展语言。这里呈现的是 Less 的官方文档(中文版),包含了 Less 语言以及利用 JavaScript 开发的用于将 Less 样式转换成 CSS 样式的 Less.js 工具。
因为 Less 和 CSS 非常像,因此很容易学习。而且 Less 仅对 CSS 语言增加了少许方便的扩展,这就是 Less 如此易学的原因之一。
-
有关 Less 语言特性的详细文档,请参阅 Less 语言特性 章节
-
有关 Less 内置函数的列表,请参阅 Less 函数手册 章节
-
有关详细的使用说明,请参阅 Less.js 用法 章节
-
有关第三方工具的详细信息,请参阅 工具 章节
Less 到底为 CSS 添加了什么功能?以下就是这些新加功能的概览。
变量(Variables)
无需多说,看代码一目了然:
@width: 10px; @height: @width + 10px; #header { width: @width; height: @height; }
编译为:
#header { width: 10px; height: 20px; }
混合(Mixins)
混合(Mixin)是一种将一组属性从一个规则集包含(或混入)到另一个规则集的方法。假设我们定义了一个类(class)如下:
.bordered { border-top: dotted 1px black; border-bottom: solid 2px black; }
如果我们希望在其它规则集中使用这些属性呢?没问题,我们只需像下面这样输入所需属性的类(class)名称即可,如下所示:
#menu a { color: #111; .bordered(); } .post a { color: red; .bordered(); }
.bordered
类所包含的属性就将同时出现在 #menu a
和 .post a
中了。(注意,你也可以使用 #ids
作为 mixin 使用。)
嵌套(Nesting)
Less 提供了使用嵌套(nesting)代替层叠或与层叠结合使用的能力。假设我们有以下 CSS 代码:
#header { color: black; } #header .navigation { font-size: 12px; } #header .logo { width: 300px; }
用 Less 语言我们可以这样书写代码:
#header { color: black; .navigation { font-size: 12px; } .logo { width: 300px; } }
用 Less 书写的代码更加简洁,并且模仿了 HTML 的组织结构。
你还可以使用此方法将伪选择器(pseudo-selectors)与混合(mixins)一同使用。下面是一个经典的 clearfix 技巧,重写为一个混合(mixin) (&
表示当前选择器的父级):
.clearfix { display: block; zoom: 1; &:after { content: " "; display: block; font-size: 0; height: 0; clear: both; visibility: hidden; } }
@规则嵌套和冒泡
@ 规则(例如 @media
或 @supports
)可以与选择器以相同的方式进行嵌套。@ 规则会被放在前面,同一规则集中的其它元素的相对顺序保持不变。这叫做冒泡(bubbling)。
.component { width: 300px; @media (min-width: 768px) { width: 600px; @media (min-resolution: 192dpi) { background-image: url(/img/retina2x.png); } } @media (min-width: 1280px) { width: 800px; } }
编译为:
.component { width: 300px; } @media (min-width: 768px) { .component { width: 600px; } } @media (min-width: 768px) and (min-resolution: 192dpi) { .component { background-image: url(/img/retina2x.png); } } @media (min-width: 1280px) { .component { width: 800px; } }
运算(Operations)
算术运算符 +
、-
、*
、/
可以对任何数字、颜色或变量进行运算。如果可能的话,算术运算符在加、减或比较之前会进行单位换算。计算的结果以最左侧操作数的单位类型为准。如果单位换算无效或失去意义,则忽略单位。无效的单位换算例如:px 到 cm 或 rad 到 % 的转换。
// 所有操作数被转换成相同的单位 @conversion-1: 5cm + 10mm; // 结果是 6cm @conversion-2: 2 - 3cm - 5mm; // 结果是 -1.5cm // conversion is impossible @incompatible-units: 2 + 5px - 3cm; // 结果是 4px // example with variables @base: 5%; @filler: @base * 2; // 结果是 10% @other: @base + @filler; // 结果是 15%
乘法和除法不作转换。因为这两种运算在大多数情况下都没有意义,一个长度乘以一个长度就得到一个区域,而 CSS 是不支持指定区域的。Less 将按数字的原样进行操作,并将为计算结果指定明确的单位类型。
@base: 2cm * 3mm; // 结果是 6cm
你还可以对颜色进行算术运算:
@color: #224488 / 2; //结果是 #112244 background-color: #112244 + #111; // 结果是 #223355
不过,Less 提供的 色彩函数 更有使用价值。
calc() 特例
Released v3.0.0
为了与 CSS 保持兼容,calc()
并不对数学表达式进行计算,但是在嵌套函数中会计算变量和数学公式的值。
@var: 50vh/2; width: calc(50% + (@var - 20px)); // 结果是 calc(50% + (25vh - 20px))
转义(Escaping)
转义(Escaping)允许你使用任意字符串作为属性或变量值。任何 ~"anything"
或 ~'anything'
形式的内容都将按原样输出,除非 interpolation。
@min768: ~"(min-width: 768px)"; .element { @media @min768 { font-size: 1.2rem; } }
编译为:
@media (min-width: 768px) { .element { font-size: 1.2rem; } }
注意,从 Less 3.5 开始,可以简写为:
@min768: (min-width: 768px); .element { @media @min768 { font-size: 1.2rem; } }
在 Less 3.5+ 版本中,许多以前需要“引号转义”的情况就不再需要了。
函数(Functions)
Less 内置了多种函数用于转换颜色、处理字符串、算术运算等。这些函数在Less 函数手册中有详细介绍。
函数的用法非常简单。下面这个例子将介绍如何利用 percentage 函数将 0.5 转换为 50%,将颜色饱和度增加 5%,以及颜色亮度降低 25% 并且色相值增加 8 等用法:
@base: #f04615; @width: 0.5; .class { width: percentage(@width); // returns `50%` color: saturate(@base, 5%); background-color: spin(lighten(@base, 25%), 8); }
命名空间和访问符
(不要和 CSS @namespace 或 namespace selectors 混淆了)。
有时,出于组织结构或仅仅是为了提供一些封装的目的,你希望对混合(mixins)进行分组。你可以用 Less 更直观地实现这一需求。假设你希望将一些混合(mixins)和变量置于 #bundle
之下,为了以后方便重用或分发:
#bundle() { .button { display: block; border: 1px solid black; background-color: grey; &:hover { background-color: white; } } .tab { ... } .citation { ... } }
现在,如果我们希望把 .button
类混合到 #header a
中,我们可以这样做:
#header a { color: orange; #bundle.button(); // 还可以书写为 #bundle > .button 形式 }
注意:如果不希望它们出现在输出的 CSS 中,例如 #bundle .tab
,请将 ()
附加到命名空间(例如 #bundle()
)后面。
映射(Maps)
从 Less 3.5 版本开始,你还可以将混合(mixins)和规则集(rulesets)作为一组值的映射(map)使用。
#colors() { primary: blue; secondary: green; } .button { color: #colors[primary]; border: 1px solid #colors[secondary]; }
输出符合预期:
.button { color: blue; border: 1px solid green; }
作用域(Scope)
Less 中的作用域与 CSS 中的作用域非常类似。首先在本地查找变量和混合(mixins),如果找不到,则从“父”级作用域继承。
@var: red; #page { @var: white; #header { color: @var; // white } }
与 CSS 自定义属性一样,混合(mixin)和变量的定义不必在引用之前事先定义。因此,下面的 Less 代码示例和上面的代码示例是相同的:
@var: red; #page { #header { color: @var; // white } @var: white; }
注释(Comments)
块注释和行注释都可以使用:
/* 一个块注释 * style comment! */ @var: red; // 这一行被注释掉了! @var: white;
导入(Importing)
“导入”的工作方式和你预期的一样。你可以导入一个 .less
文件,此文件中的所有变量就可以全部使用了。如果导入的文件是 .less
扩展名,则可以将扩展名省略掉:
@import "library"; // library.less @import "typo.css";