irpas技术客

vue基础用法&基础原理整理_格雷狐思_vue &

网络投稿 3798

vue基础用法&基础原理整理

md文件地址:https://gitee.com/gaohan888/note

1. vue基础知识和原理 1.1 初识Vue 想让Vue工作,就必须创建一个Vue实例,且要传入一个配置对象demo容器里的代码依然符合html规范,只不过混入了一些特殊的Vue语法demo容器里的代码被称为【Vue模板】Vue实例和容器是一一对应的真实开发中只有一个Vue实例,并且会配合着组件一起使用{{xxx}}是Vue的语法:插值表达式,{{xxx}}可以读取到data中的所有属性一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新(Vue实现的响应式)

初始示例代码

<!-- 准备好一个容器 --> <div id="demo"> <h1>Hello,{{name.toUpperCase()}},{{address}}</h1> </div> <script type="text/javascript" > Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 //创建Vue实例 new Vue({ el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。 data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。 name:'hello,world', address:'北京' } }); </script> 1.2 模板语法

Vue模板语法有2大类:

插值语法:

功能:用于解析标签体内容

写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性

指令语法:

功能:用于解析标签(包括:标签属性、标签体内容、绑定事件…)

举例:v-bind:href=“xxx” 或 简写为 :href=“xxx”,xxx同样要写js表达式,且可以直接读取到data中的所有属性

代码

<div id="root"> <h1>插值语法</h1> <h3>你好,{{name}}</h3> <hr/> <h1>指令语法</h1> <!-- 这里是展示被Vue指令绑定的属性,引号内写的是js表达式 --> <a :href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a> <a :href="school.url" x="hello">点我去{{school.name}}学习2</a> </div> <script> new Vue({ el:'#root', data:{ name:'jack', school:{ name:'百度', url:'http://·/post/6844903895467032589

webkit 渲染引擎工作流程图

中文版

所有的浏览器渲染引擎工作流程大致分为5步:创建 DOM 树 —> 创建 Style Rules -> 构建 Render 树 —> 布局 Layout -—> 绘制 Painting。

第一步,构建 DOM 树:当浏览器接收到来自服务器响应的HTML文档后,会遍历文档节点,生成DOM树。需要注意的是在DOM树生成的过程中有可能会被CSS和JS的加载执行阻塞,渲染阻塞下面会讲到。

第二步,生成样式表:用 CSS 分析器,分析 CSS 文件和元素上的 inline 样式,生成页面的样式表;

渲染阻塞:当浏览器遇到一个script标签时,DOM构建将暂停,直到脚本加载执行,然后继续构建DOM树。每次去执行Javascript脚本都会严重阻塞DOM树构建,如果JavaScript脚本还操作了CSSOM,而正好这个CSSOM没有下载和构建,那么浏览器甚至会延迟脚本执行和构建DOM,直到这个CSSOM的下载和构建。所以,script标签引入很重要,实际使用时可以遵循下面两个原则:

css优先:引入顺序上,css资源先于js资源

js后置:js代码放在底部,且js应尽量少影响DOM构建

还有一个小知识:当解析html时,会把新来的元素插入dom树里,同时去查找css,然后把对应的样式规则应用到元素上,查找样式表是按照从右到左的顺序匹配的例如:div p {…},会先寻找所有p标签并判断它的父标签是否为div之后才决定要不要采用这个样式渲染。所以平时写css尽量用class或者id,不要过度层叠

第三步,构建渲染树:通过DOM树和CSS规则我们可以构建渲染树。浏览器会从DOM树根节点开始遍历每个可见节点(注意是可见节点)对每个可见节点,找到其适配的CSS规则并应用。渲染树构建完后,每个节点都是可见节点并且都含有其内容和对应的规则的样式。这也是渲染树和DOM树最大的区别所在。渲染是用于显示,那些不可见的元素就不会在这棵树出现了。除此以外,display none的元素也不会被显示在这棵树里。visibility hidden的元素会出现在这棵树里。

第四步,渲染布局:布局阶段会从渲染树的根节点开始遍历,然后确定每个节点对象在页面上的确切大小与位置,布局阶段的输出是一个盒子模型,它会精确地捕获每个元素在屏幕内的确切位置与大小。

第五步,渲染树绘制:在绘制阶段,遍历渲染树,调用渲染器的paint()方法在屏幕上显示其内容。渲染树的绘制工作是由浏览器的UI后端组件完成的。

注意点:

1、DOM 树的构建是文档加载完成开始的? 构建 DOM 树是一个渐进过程,为达到更好的用户体验,渲染引擎会尽快将内容显示在屏幕上,它不必等到整个 HTML 文档解析完成之后才开始构建 render 树和布局。

2、Render 树是 DOM 树和 CSS 样式表构建完毕后才开始构建的? 这三个过程在实际进行的时候并不是完全独立的,而是会有交叉,会一边加载,一边解析,以及一边渲染。

3、CSS 的解析注意点? CSS 的解析是从右往左逆向解析的,嵌套标签越多,解析越慢。

**4、JS 操作真实 DOM 的代价?**传统DOM结构操作方式对性能的影响很大,原因是频繁操作DOM结构操作会引起页面的重排(reflow)和重绘(repaint),浏览器不得不频繁地计算布局,重新排列和绘制页面元素,导致浏览器产生巨大的性能开销。直接操作真实DOM的性能特别差,我们可以来演示一遍。

<div id="app"></div> <script> // 获取 DIV 元素 let box = document.querySelector('#app'); console.log(box); // 真实 DOM 操作 console.time('a'); for (let i = 0; i <= 10000; i++) { box.innerHTML = i; } console.timeEnd('a'); // 虚拟 DOM 操作 let num = 0; console.time('b'); for (let i = 0; i <= 10000; i++) { num = i; } box.innerHTML = num; console.timeEnd('b'); </script>

从结果中可以看出,操作真实 DOM 的性能是非常差的,所以我们要尽可能的复用,减少 DOM 操作。

虚拟 DOM 的好处

? 虚拟 DOM 就是为了解决浏览器性能问题而被设计出来的。如前,若一次操作中有 10 次更新 DOM 的动作,虚拟 DOM 不会立即操作 DOM,而是将这 10 次更新的 diff 内容保存到本地一个 JS 对象中,最终将这个 JS 对象一次性 attch 到 DOM 树上,再进行后续操作,避免大量无谓的计算量。所以,用 JS 对象模拟 DOM 节点的好处是,页面的更新可以先全部反映在 JS 对象(虚拟 DOM )上,操作内存中的 JS 对象的速度显然要更快,等更新完成后,再将最终的 JS 对象映射成真实的 DOM,交由浏览器去绘制。

? 虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。

? 回到最开始的问题,虚拟 DOM 到底是什么,说简单点,就是一个普通的 JavaScript 对象,包含了 tag、props、children 三个属性。

接下来我们手动实现下 虚拟 DOM。

分两种实现方式:

一种原生 js DOM 操作实现;

另一种主流虚拟 DOM 库(snabbdom、virtual-dom)的实现(用h函数渲染)(暂时还不理解)

算法实现

**(1)**用 JS 对象模拟 DOM 树:

<div id="virtual-dom"> <p>Virtual DOM</p> <ul id="list"> <li class="item">Item 1</li> <li class="item">Item 2</li> <li class="item">Item 3</li> </ul> <div>Hello World</div> </div>

我们用 JavaScript 对象来表示 DOM 节点,使用对象的属性记录节点的类型、属性、子节点等。

/** * Element virdual-dom 对象定义 * @param {String} tagName - dom 元素名称 * @param {Object} props - dom 属性 * @param {Array<Element|String>} - 子节点 */ function Element(tagName, props, children) { this.tagName = tagName; this.props = props; this.children = children; // dom 元素的 key 值,用作唯一标识符 if (props.key) { this.key = props.key } } function el(tagName, props, children) { return new Element(tagName, props, children); }

构建虚拟的 DOM ,用 javascript 对象来表示

let ul = el('div', { id: 'Virtual DOM' }, [ el('p', {}, ['Virtual DOM']), el('ul', { id: 'list' }, [ el('li', { class: 'item' }, ['Item 1']), el('li', { class: 'item' }, ['Item 2']), el('li', { class: 'item' }, ['Item 3']) ]), el('div', {}, ['Hello, World']) ])

现在 ul 就是我们用 JavaScript 对象表示的 DOM 结构,我们输出查看 ul 对应的数据结构如下:

**(2)**将用 js 对象表示的虚拟 DOM 转换成真实 DOM:需要用到 js 原生操作 DOM 的方法。

/** * render 将virdual-dom 对象渲染为实际 DOM 元素 */ Element.prototype.render = function () { // 创建节点 let el = document.createElement(this.tagName); let props = this.props; // 设置节点的 DOM 属性 for (let propName in props) { let propValue = props[propName]; el.setAttribute(propName, propValue) } let children = this.children || [] for (let child of children) { let childEl = (child instanceof Element) ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点 : document.createTextNode(child) // 如果是文本,就构建文本节点 el.appendChild(childEl); } return el; }

我们通过查看以上 render 方法,会根据 tagName 构建一个真正的 DOM 节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。

我们将构建好的 DOM 结构添加到页面 body 上面,如下:

let ulRoot = ul.render(); document.body.appendChild(ulRoot);

这样,页面 body 里面就有真正的 DOM 结构,效果如下图所示:

我们知道虚拟 DOM 的好处和虚拟 DOM 的实现后就要讲讲 key 的作用了。

贴一下上面实现地完整代码

<script> /** * Element virdual-dom 对象定义 * @param {String} tagName - dom 元素名称 * @param {Object} props - dom 属性 * @param {Array<Element|String>} - 子节点 */ function Element(tagName, props, children) { this.tagName = tagName; this.props = props; this.children = children; // dom 元素的 key 值,用作唯一标识符 if (props.key) { this.key = props.key } } function el(tagName, props, children) { return new Element(tagName, props, children); } let ul = el('div', { id: 'Virtual DOM' }, [ el('p', {}, ['Virtual DOM']), el('ul', { id: 'list' }, [ el('li', { class: 'item' }, ['Item 1']), el('li', { class: 'item' }, ['Item 2']), el('li', { class: 'item' }, ['Item 3']) ]), el('div', {}, ['Hello, World']) ]) /** * render 将virdual-dom 对象渲染为实际 DOM 元素 */ Element.prototype.render = function () { // 创建节点 let el = document.createElement(this.tagName); let props = this.props; // 设置节点的 DOM 属性 for (let propName in props) { let propValue = props[propName]; el.setAttribute(propName, propValue) } let children = this.children || [] for (let child of children) { let childEl = (child instanceof Element) ? child.render() // 如果子节点也是虚拟 DOM, 递归构建 DOM 节点 : document.createTextNode(child) // 如果是文本,就构建文本节点 el.appendChild(childEl); } return el; } let ulRoot = ul.render(); document.body.appendChild(ulRoot); console.log(ul); </script> 虚拟DOM中key的作用

key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】, 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:

旧虚拟DOM中找到了与新虚拟DOM相同的key: ①.若虚拟DOM中内容没变, 直接使用之前的真实DOM!②.若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。 旧虚拟DOM中未找到与新虚拟DOM相同的key 创建新的真实DOM,随后渲染到到页面。

好了,我们知道了最简单的key的原理,如果要继续研究下去就要涉及到vue的核心之一-Diff算法,后面会详细介绍。

用index作为key可能会引发的问题:

若对数据进行:逆序添加、逆序删除等破坏顺序操作:

会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。

案例

<!-- 准备好一个容器--> <div id="root"> <!-- 遍历数组 --> <h2>人员列表(遍历数组)</h2> <button @click.once="add">添加一个老刘</button> <ul> <li v-for="(p,index) of persons" :key="index"> {{p.name}}-{{p.age}} <input type="text"> </li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el: '#root', data: { persons: [ { id: '001', name: '张三', age: 18 }, { id: '002', name: '李四', age: 19 }, { id: '003', name: '王五', age: 20 } ] }, methods: { add() { const p = { id: '004', name: '老刘', age: 40 } this.persons.unshift(p) } }, }); </script>

解释:

初始数据

persons: [ { id: ‘001’, name: ‘张三’, age: 18 }, { id: ‘002’, name: ‘李四’, age: 19 }, { id: ‘003’, name: ‘王五’, age: 20 } ]

vue根据数据生成虚拟 DOM

初始虚拟 DOM

<li key='0'>张三-18<input type="text"></li> <li key='1'>李四-19<input type="text"></li> <li key='2'>王五-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM

this.persons.unshift({ id: '004', name: '老刘', age: 40 })

在 persons 数组最前面添加上 { id: ‘004’, name: ‘老刘’, age: 40 }

新数据:

persons: [

? { id: ‘004’, name: ‘老刘’, age: 40 },

? { id: ‘001’, name: ‘张三’, age: 18 }, ? { id: ‘002’, name: ‘李四’, age: 19 }, ? { id: ‘003’, name: ‘王五’, age: 20 } ]

vue根据数据生成虚拟 DOM

新虚拟 DOM

<li key='0'>老刘-30<input type="text"></li> <li key='1'>张三-18<input type="text"></li> <li key='3'>李四-19<input type="text"></li> <li key='4'>王五-20<input type="text"></li>

将虚拟 DOM 转为 真实 DOM

因为老刘被插到第一个,重刷了 key 的值,vue Diff 算法 根据 key 的值 判断 虚拟DOM 全部发生了改变,然后全部重新生成新的 真实 DOM。实际上,张三,李四,王五并没有发生更改,是可以直接复用之前的真实 DOM,而因为 key 的错乱,导致要全部重新生成,造成了性能的浪费。

来张尚硅谷的图

如果结构中还包含输入类的DOM:

会产生错误DOM更新 ==> 界面有问题。

这回造成的就不是性能浪费了,会直接导致页面的错误

结论: 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的

来张尚硅谷的图,正经使用 key

1.14 vue 监测data 中的 数据

先来个案例引入一下:

<!-- 准备好一个容器--> <div id="root"> <h2>人员列表</h2> <button @click="updateMei">更新马冬梅的信息</button> <ul> <li v-for="(p,index) of persons" :key="p.id"> {{p.name}}-{{p.age}}-{{p.sex}} </li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false const vm = new Vue({ el:'#root', data:{ persons:[ {id:'001',name:'马冬梅',age:30,sex:'女'}, {id:'002',name:'周冬雨',age:31,sex:'女'}, {id:'003',name:'周杰伦',age:18,sex:'男'}, {id:'004',name:'温兆伦',age:19,sex:'男'} ] }, methods: { updateMei(){ // this.persons[0].name = '马老师' //奏效 // this.persons[0].age = 50 //奏效 // this.persons[0].sex = '男' //奏效 this.persons[0] = {id:'001',name:'马老师',age:50,sex:'男'} //不奏效 // this.persons.splice(0,1,{id:'001',name:'马老师',age:50,sex:'男'}) } } }) </script>

点击更新马冬梅的信息,马冬梅的数据并没有发生改变。

我们来看看控制台:

控制台上的数据发生了改变,说明,这个更改的数据并没有被 vue 监测到。

所以我们来研究一下 Vue 监测的原理。

我们先研究 Vue 如何监测 对象里的数据

代码

<!-- 准备好一个容器--> <div id="root"> <h2>学校名称:{{name}}</h2> <h2>学校地址:{{address}}</h2> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el:'#root', data:{ name:'浙江师范大学', address:'金华', student:{ name:'tom', age:{ rAge:40, sAge:29, }, friends:[ {name:'jerry',age:35} ] } } }) </script>

讲一下解析模板后面的操作—》调用 set 方法时,就会去解析模板----->生成新的虚拟 DOM----->新旧DOM 对比 -----> 更新页面

模拟一下 vue 中的 数据监测

<script type="text/javascript" > let data = { name:'尚硅谷', address:'北京', } //创建一个监视的实例对象,用于监视data中属性的变化 const obs = new Observer(data) console.log(obs) //准备一个vm实例对象 let vm = {} vm._data = data = obs function Observer(obj){ //汇总对象中所有的属性形成一个数组 const keys = Object.keys(obj) //遍历 keys.forEach((k) => { Object.defineProperty(this, k, { get() { return obj[k] }, set(val) { console.log(`${k}被改了,我要去解析模板,生成虚拟DOM.....我要开始忙了`) obj[k] = val } }) }) } </script>

Vue.set 的使用

Vue.set(target,propertyName/index,value) 或

vm.$set(target,propertyName/index,value)

用法:

向响应式对象中添加一个 property,并确保这个新 property 同样是响应式的,且触发视图更新。它必须用于向响应式对象上添加新 property,因为 Vue 无法探测普通的新增 property (比如 vm.myObject.newProperty = 'hi')

代码

<!-- 准备好一个容器--> <div id="root"> <h1>学生信息</h1> <button @click="addSex">添加性别属性,默认值:男</button> <br/> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el:'#root', data:{ student:{ name:'tom', age:18, hobby:['抽烟','喝酒','烫头'], friends:[ {name:'jerry',age:35}, {name:'tony',age:36} ] } }, methods: { addSex(){ // Vue.set(this.student,'sex','男') this.$set(this.student,'sex','男') } } }) </script>

Vue.set() 或 vm.$set 有缺陷:

就是 vm 和 _data

看完了 vue 监测对象中的数据,再来看看 vue 如何监测 数组里的数据

先写个代码案例

<!-- 准备好一个容器--> <div id="root"> <h2>爱好</h2> <ul> <li v-for="(h,index) in student.hobby" :key="index"> {{h}} </li> </ul> <h2>朋友们</h2> <ul> <li v-for="(f,index) in student.friends" :key="index"> {{f.name}}--{{f.age}} </li> </ul> </div> <script type="text/javascript"> Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。 const vm = new Vue({ el:'#root', data: student:{ name:'tom', age:{ rAge:40, sAge:29, }, hobby:['抽烟','喝酒','烫头'], friends:[ {name:'jerry',age:35}, {name:'tony',age:36} ] } }, methods: { } }) </script>

所以我们通过 vm._data.student.hobby[0] = ‘aaa’ // 不奏效

vue 监测在数组那没有 getter 和 setter,所以监测不到数据的更改,也不会引起页面的更新

既然 vue 在对数组无法通过 getter 和 setter 进行数据监视,那 vue 到底如何监视数组数据的变化呢?

vue对数组的监测是通过 包装数组上常用的用于修改数组的方法来实现的。

vue官网的解释:

总结:

Vue监视数据的原理:

vue会监视data中所有层次的数据

如何监测对象中的数据?

通过setter实现监视,且要在new Vue时就传入要监测的数据。

对象中后追加的属性,Vue默认不做响应式处理

如需给后添加的属性做响应式,请使用如下API:

Vue.set(target,propertyName/index,value) 或

vm.$set(target,propertyName/index,value)

如何监测数组中的数据?

通过包裹数组更新元素的方法实现,本质就是做了两件事:

调用原生对应的方法对数组进行更新重新解析模板,进而更新页面

在Vue修改数组中的某个元素一定要用如下方法:

使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

1.15 收集表单数据

若:,则v-model收集的是value值,用户输入的就是value值。

<!-- 准备好一个容器--> <div id="root"> <form @submit.prevent="demo"> 账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/> 密码:<input type="password" v-model="userInfo.password"> <br/><br/> 年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/> <button>提交</button> </form> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el:'#root', data:{ userInfo:{ account:'', password:'', age:18, } }, methods: { demo(){ console.log(JSON.stringify(this.userInfo)) } } }) </script>

若:,则v-model收集的是value值,且要给标签配置value值。

<!-- 准备好一个容器--> <div id="root"> <form @submit.prevent="demo"> 性别: 男<input type="radio" name="sex" v-model="userInfo.sex" value="male"> 女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> </form> </div> <script type="text/javascript"> Vue.config.productionTip = false new Vue({ el:'#root', data:{ userInfo:{ sex:'female' } }, methods: { demo(){ console.log(JSON.stringify(this.userInfo)) } } }) </script>

若:

没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)配置input的value属性: v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)v-model的初始值是数组,那么收集的的就是value组成的数组 <!-- 准备好一个容器--> <div id="root"> <form @submit.prevent="demo"> 爱好: 学习<input type="checkbox" v-model="userInfo.hobby" value="study"> 打游戏<input type="checkbox" v-model="userInfo.hobby" value="game"> 吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat"> <br/><br/> 所属校区 <select v-model="userInfo.city"> <option value="">请选择校区</option> <option value="beijing">北京</option> <option value="shanghai">上海</option> <option value="shenzhen">深圳</option> <option value="wuhan">武汉</option> </select> <br/><br/> 其他信息: <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/> <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://·/hitokoto/get?type=social').then( response => { context.commit('ADD_PERSON',{id:nanoid(),name:response.data}) }, error => { alert(error.message) } ) } }, mutations:{ ADD_PERSON(state,value){ console.log('mutations中的ADD_PERSON被调用了') state.personList.unshift(value) } }, state:{ personList:[ {id:'001',name:'张三'} ] }, getters:{ firstPersonName(state){ return state.personList[0].name } }, }

index.js

//该文件用于创建Vuex中最为核心的store import Vue from 'vue' //引入Vuex import Vuex from 'vuex' import countOptions from './count' import personOptions from './person' //应用Vuex插件 Vue.use(Vuex) //创建并暴露store export default new Vuex.Store({ modules:{ countAbout:countOptions, personAbout:personOptions } })

count.vue

<template> <div> <h1>当前求和为:{{sum}}</h1> <h3>当前求和放大10倍为:{{bigSum}}</h3> <h3>我在{{school}},学习{{subject}}</h3> <h3 style="color:red">Person组件的总人数是:{{personList.length}}</h3> <select v-model.number="n"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> <button @click="increment(n)">+</button> <button @click="decrement(n)">-</button> <button @click="incrementOdd(n)">当前求和为奇数再加</button> <button @click="incrementWait(n)">等一等再加</button> </div> </template> <script> import {mapState,mapGetters,mapMutations,mapActions} from 'vuex' export default { name:'Count', data() { return { n:1, //用户选择的数字 } }, computed:{ //借助mapState生成计算属性,从state中读取数据。(数组写法) ...mapState('countAbout',['sum','school','subject']), ...mapState('personAbout',['personList']), //借助mapGetters生成计算属性,从getters中读取数据。(数组写法) ...mapGetters('countAbout',['bigSum']) }, methods: { //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法) ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}), //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法) ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'}) }, mounted() { console.log(this.$store) }, } </script> <style lang="css"> button{ margin-left: 5px; } </style>

person.vue

<template> <div> <h1>人员列表</h1> <h3 style="color:red">Count组件求和为:{{sum}}</h3> <h3>列表中第一个人的名字是:{{firstPersonName}}</h3> <input type="text" placeholder="请输入名字" v-model="name"> <button @click="add">添加</button> <button @click="addWang">添加一个姓王的人</button> <button @click="addPersonServer">添加一个人,名字随机</button> <ul> <li v-for="p in personList" :key="p.id">{{p.name}}</li> </ul> </div> </template> <script> import {nanoid} from 'nanoid' export default { name:'Person', data() { return { name:'' } }, computed:{ personList(){ return this.$store.state.personAbout.personList }, sum(){ return this.$store.state.countAbout.sum }, firstPersonName(){ return this.$store.getters['personAbout/firstPersonName'] } }, methods: { add(){ const personObj = {id:nanoid(),name:this.name} this.$store.commit('personAbout/ADD_PERSON',personObj) this.name = '' }, addWang(){ const personObj = {id:nanoid(),name:this.name} this.$store.dispatch('personAbout/addPersonWang',personObj) this.name = '' }, addPersonServer(){ this.$store.dispatch('personAbout/addPersonServer') } }, } </script> 4. 路由 理解: 一个路由(route)就是一组映射关系(key - value),多个路由需要路由器(router)进行管理。前端路由:key是路径,value是组件。 4.1 基本使用

安装vue-router,命令:npm i vue-router

应用插件:Vue.use(VueRouter)

编写router配置项:

//引入VueRouter import VueRouter from 'vue-router' //引入Luyou 组件 import About from '../components/About' import Home from '../components/Home' //创建router实例对象,去管理一组一组的路由规则 const router = new VueRouter({ routes:[ { path:'/about', component:About }, { path:'/home', component:Home } ] }) //暴露router export default router

实现切换(active-class可配置高亮样式)

<router-link active-class="active" to="/about">About</router-link>

指定展示位置

<router-view></router-view> 4.2 几个注意点 路由组件通常存放在pages文件夹,一般组件通常存放在components文件夹。通过切换,“隐藏”了的路由组件,默认是被销毁掉的,需要的时候再去挂载。每个组件都有自己的$route属性,里面存储着自己的路由信息。整个应用只有一个router,可以通过组件的$router属性获取到。 4.3 多级路由(多级路由)

配置路由规则,使用children配置项:

routes:[ { path:'/about', component:About, }, { path:'/home', component:Home, children:[ //通过children配置子级路由 { path:'news', //此处一定不要写:/news component:News }, { path:'message',//此处一定不要写:/message component:Message } ] } ]

跳转(要写完整路径):

<router-link to="/home/news">News</router-link>

指定展示位置

<router-view></router-view> 4.4 路由的query参数

传递参数

<!-- 跳转并携带query参数,to的字符串写法 --> <router-link :to="/home/message/detail?id=666&title=你好">跳转</router-link> <!-- 跳转并携带query参数,to的对象写法 --> <router-link :to="{ path:'/home/message/detail', query:{ id:666, title:'你好' } }" >跳转</router-link>

接收参数:

$route.query.id $route.query.title 4.5 命名路由

作用:可以简化路由的跳转。

如何使用

给路由命名:

{ path:'/demo', component:Demo, children:[ { path:'test', component:Test, children:[ { name:'hello' //给路由命名 path:'welcome', component:Hello, } ] } ] }

简化跳转:

<!--简化前,需要写完整的路径 --> <router-link to="/demo/test/welcome">跳转</router-link> <!--简化后,直接通过名字跳转 --> <router-link :to="{name:'hello'}">跳转</router-link> <!--简化写法配合传递参数 --> <router-link :to="{ name:'hello', query:{ id:666, title:'你好' } }" >跳转</router-link> 4.6 路由的params参数

配置路由,声明接收params参数

{ path:'/home', component:Home, children:[ { path:'news', component:News }, { component:Message, children:[ { name:'xiangqing', path:'detail/:id/:title', //使用占位符声明接收params参数 component:Detail } ] } ] }

传递参数

<!-- 跳转并携带params参数,to的字符串写法 --> <router-link :to="/home/message/detail/666/你好">跳转</router-link> <!-- 跳转并携带params参数,to的对象写法 --> <router-link :to="{ name:'xiangqing', params:{ id:666, title:'你好' } }" >跳转</router-link>

特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

接收参数:

$route.params.id $route.params.title 4.7 路由的props配置

作用:让路由组件更方便的收到参数

{ name:'xiangqing', path:'detail/:id', component:Detail, //第一种写法:props值为对象,该对象中所有的key-value的组合最终都会通过props传给Detail组件 // props:{a:900} //第二种写法:props值为布尔值,布尔值为true,则把路由收到的所有params参数通过props传给Detail组件 // props:true //第三种写法:props值为函数,该函数返回的对象中每一组key-value都会通过props传给Detail组件 props($route) { return { id: $route.query.id, title:$route.query.title, a: 1, b: 'hello' } } }

方便在要跳转去的组件里更简便的写法

跳转去组件的具体代码

<template> <ul> <h1>Detail</h1> <li>消息编号:{{id}}</li> <li>消息标题:{{title}}</li> <li>a:{{a}}</li> <li>b:{{b}}</li> </ul> </template> <script> export default { name: 'Detail', props: ['id', 'title', 'a', 'b'], mounted () { console.log(this.$route); } } </script> <style> </style> 4.8 <router-link>的replace属性 作用:控制路由跳转时操作浏览器历史记录的模式浏览器的历史记录有两种写入方式:分别为push和replace,push是追加历史记录,replace是替换当前记录。路由跳转时候默认为push如何开启replace模式:<router-link replace .......>News</router-link> 4.9 编程式路由导航

作用:不借助<router-link> 实现路由跳转,让路由跳转更加灵活

具体编码:

//$router的两个API this.$router.push({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.replace({ name:'xiangqing', params:{ id:xxx, title:xxx } }) this.$router.forward() //前进 this.$router.back() //后退 this.$router.go() //可前进也可后退 4.10 缓存路由组件

作用:让不展示的路由组件保持挂载,不被销毁。

具体编码:

这个 include 指的是组件名

<keep-alive include="News"> <router-view></router-view> </keep-alive> 4.11 两个新的生命周期钩子

作用:路由组件所独有的两个钩子,用于捕获路由组件的激活状态。 具体名字:

activated路由组件被激活时触发。deactivated路由组件失活时触发。

这两个生命周期钩子需要配合前面的缓存路由组件使用(没有缓存路由组件不起效果)

4.12 路由守卫

作用:对路由进行权限控制

分类:全局守卫、独享守卫、组件内守卫

全局守卫:

//全局前置守卫:初始化时执行、每次路由切换前执行 router.beforeEach((to,from,next)=>{ console.log('beforeEach',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'zhejiang'){ //权限控制的具体规则 next() //放行 }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() //放行 } }) //全局后置守卫:初始化时执行、每次路由切换后执行 router.afterEach((to,from)=>{ console.log('afterEach',to,from) if(to.meta.title){ document.title = to.meta.title //修改网页的title }else{ document.title = 'vue_test' } })

完整代码

// 这个文件专门用于创建整个应用的路由器 import VueRouter from 'vue-router' // 引入组件 import About from '../pages/About.vue' import Home from '../pages/Home.vue' import Message from '../pages/Message.vue' import News from '../pages/News.vue' import Detail from '../pages/Detail.vue' // 创建并暴露一个路由器 const router = new VueRouter({ routes: [ { path: '/home', component: Home, meta:{title:'主页'}, children: [ { path: 'news', component: News, meta:{isAuth:true,title:'新闻'} }, { path: 'message', name: 'mess', component: Message, meta:{isAuth:true,title:'消息'}, children: [ { path: 'detail/:id/:title', name: 'xiangqing', component: Detail, meta:{isAuth:true,title:'详情'}, props($route) { return { id: $route.query.id, title:$route.query.title, a: 1, b: 'hello' } } } ] } ] }, { path: '/about', component: About, meta:{ title: '关于' } } ] }) // 全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用 router.beforeEach((to, from, next) => { console.log('前置路由守卫', to, from); if(to.meta.isAuth) { if(localStorage.getItem('school') === 'zhejiang') { // 放行 next() } else { alert('学校名不对,无权查看') } } else { next() } }) // 全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用 router.afterEach((to, from) => { console.log('后置路由守卫', to, from) document.title = to.meta.title || '我的系统' }) export default router 独享守卫:

就是在 routes 子路由内写守卫

beforeEnter(to,from,next){ console.log('beforeEnter',to,from) if(to.meta.isAuth){ //判断当前路由是否需要进行权限控制 if(localStorage.getItem('school') === 'atguigu'){ next() }else{ alert('暂无权限查看') // next({name:'guanyu'}) } }else{ next() } }

组件内守卫:

在具体组件内写守卫

//进入守卫:通过路由规则,进入该组件时被调用 beforeRouteEnter (to, from, next) { }, //离开守卫:通过路由规则,离开该组件时被调用 beforeRouteLeave (to, from, next) { }

4.13 路由器的两种工作模式

对于一个url来说,什么是hash值?—— #及其后面的内容就是hash值。

hash值不会包含在 HTTP 请求中,即:hash值不会带给服务器。

hash模式:

地址中永远带着#号,不美观 。若以后将地址通过第三方手机app分享,若app校验严格,则地址会被标记为不合法。兼容性较好。

history模式:

地址干净,美观 。兼容性和hash模式相比略差。应用部署上线时需要后端人员支持,解决刷新页面服务端404的问题。

参考文章:

尚硅谷主讲的vue:https://·/post/6844903895467032589


1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,会注明原创字样,如未注明都非原创,如有侵权请联系删除!;3.作者投稿可能会经我们编辑修改或补充;4.本站不提供任何储存功能只提供收集或者投稿人的网盘链接。

标签: #Vue #amp #vue基础知识和原理11