1. 传统的 MVP 开发模式与 MVVM 开发模式对比
- 对于传统的
mvp
开发模式,m
也就是model
一般是通过发送ajax
请求获取到的数据,v
也就是视图,p
就是Presenter
相当于控制器,
Presenter
作为View和Model
之间的“中间人”,除了基本的业务逻辑外,还有大量代码需要对从View
到Model
和从Model
到View
的数据进行“手动同步”,这样Presenter
显得很重,维护起来会比较困难。而且由于没有数据绑定,如果Presenter
对视图渲染的需求增多,它不得不过多关注特定的视图,一旦视图需求发生改变,Presenter
也需要改动,我们大部分的关注点是在视图与数据,以及通过控制器进行操作。
- 对于
vue
的开发模式mvvm
,他把View
和Model
的同步逻辑自动化了,与MVP
不同,没有了View
为Presenter
提供的接口,之前由Presenter
负责的View
和Model
之间的数据同步交给了ViewModel
中的数据绑定进行处理,当Model
发生变化,ViewModel
就会自动更新;ViewModel
变化,Model
也会更新。我们的关注点主要是在model
与view
之间,而model
发生变化,view
进行同步更新,这些都交给了viewmodel
,mvp
的模式我们大部分的关注点是在操作了dom
,提高了开发效率。
2. 一些指令以及使用技巧
2.1. Vue 中计算属性的使用技巧
get、set
;如果是获取数值,通过get
获取到值,也可以通过set
函数设置值,注意如果你为一个计算属性使用了箭头函数,则 this 不会指向这个组件的实例,不过可以通过其实例作为函数的第一个参数来访问:
computed: {
aDouble: vm => vm.a * 2
}
get、set
用法:
var vm = new Vue({
data: { a: 1 },
computed: {
// 仅读取
aDouble: function () {
return this.a * 2
},
// 读取和设置
aPlus: {
get: function () {
return this.a + 1
},
set: function (v) {
this.a = v - 1
}
}
}
})
vm.aPlus // => 2
vm.aPlus = 3
vm.a // => 2
vm.aDouble // => 4
计算属性的结果会被缓存,除非依赖的变量变化才会重新计算。注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的。 我们一般如果处理数据显示,如果声明函数、计算属性、侦听器这三者都可以实现的话,一般建议使用计算数据,因为存在缓存机制。
计算属性设置值的时候直接使用=
,如上面的aPlus
数值,而不是与函数类似进行赋值。
aPlus = 10
2.2. v-bind 绑定 class 与 内联样式
2.2.1. v-bind 绑定 class
在v-bind
中,绑定class
,使用:class="{active:isActive}"
,前面的active如果没有在data中定义是不会报错的,他是一个对象表达式,意思就是active
这个类的显示与否都在于isActive
这个变量,该变量为布尔类型,为true
为显示,为false
是不显示。
<div
class="static"
v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>
data
:
data: {
isActive: true,
hasError: false
}
渲染的结果:如果hasError
的值为true
,class
列表将变为"static active text-danger"
<div class="static active"></div>
而如果使用:class="[chextType, active ]"
,这样chextType
这个必须在data
中定义。div
显示的类名就是显示chextType、active
变量中存储的类名。另外他也可以与普通的 class
共存
<div v-bind:class="[activeClass, errorClass]"></div>
data
:
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
渲染的结果:如果hasError
的值为true
,class
列表将变为"static active text-danger"
<div class="active text-danger"></div>
这样写将始终添加errorClass
,但是只有在isActive
是true
时才添加 activeClass
。不过,当有多个条件 class
时这样写有些繁琐。所以在数组语法中也可以使用对象语法:
<div v-bind:class="[{ active: isActive }, errorClass]"></div>
当在一个自定义组件上使用 class
属性时,这些类将被添加到该组件的根元素上面。这个元素上已经存在的类不会被覆盖。
例如,如果你声明了这个组件:
Vue.component('my-component', {
template: '<p class="foo bar">Hi</p>'
})
然后在使用它的时候添加一些 class
:
<my-component class="baz boo"></my-component>
HTML
将被渲染为:
<p class="foo bar baz boo">Hi</p>
对于带数据绑定的class
跟前面是一样的。
2.2.2. 绑定内联样式
对象语法:
v-bind:style
的对象语法十分直观——看着非常像CSS
,但其实是一个 JavaScript
对象。CSS
属性名可以用驼峰式 或短横线分隔 (记得用引号括起来) 来命名:
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
data: {
activeColor: 'red',
fontSize: 30
}
直接绑定到一个样式对象通常更好,这会让模板更清晰:同样的,对象语法常常结合返回对象的计算属性使用。
<div v-bind:style="styleObject"></div>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
数组语法:
v-bind:style
的数组语法可以将多个样式对象应用到同一个元素上:
<div v-bind:style="[baseStyles, overridingStyles]"></div>
自动添加前缀:
当 v-bind:style
使用需要添加浏览器引擎前缀的 CSS
属性时,如transform
,Vue.js
会自动侦测并添加相应的前缀。
2.3. v-if 与 v-else 标签必须要连在一起使用,不然会抛出错误。
2.4. key 值
Vue
在重新渲染页面的时候,会尝试复用页面里面的dom
元素,如果页面有两个相同的标签可以添加一个key
,这样,vue
会区分,不会复用。
2.5. v-for 循环
一般在v-for
循环的时候,一般建议加一个:key
值,绑定一个唯一的标识,不建议直接绑定循环的index
,会消耗性能,建议绑定后台传入的数据的主键。
2.6. Vue 中操作数组
Vue
中,不能直接通过数组下标的方法,进行添加数据,这样页面不会渲染的,需要通过数据的操作函数进行增删改查:push、pop、shift、unshift、splice、sort、reverse
2.7. template 模板占位符
template
模板占位符,比如我们使用v-for要循环两个标签,可以在两个标签外层加一个div
,但是这个div
会在页面显示出来,我们可以把外层的div
换成template
,不会显示在页面。
2.8. Vue 中遍历对象进行渲染以及对根级别响应式对象添加属性
对象的循环:key
是,键;index
是位置信息,item
是值;
<div v-for="(value, name, index) in object">
{{ index }}. {{ name }}: {{ value }}
</div>
渲染结果:
<div id="v-for-object-value-name-index" class="demo"><div>
0. title: How to do lists in Vue
</div><div>
1. author: Jane Doe
</div><div>
2. publishedAt: 2016-04-10
</div></div>
给对象直接修改属性是可以再次进行渲染。
给对象直接添加值,是不变的,可以直接改变引用,换成一个全新的对象,也可以使用set
方法,Vue.set(vm.userInfo,"address","wuhan")
这样,userInfo
对象会增加数据,页面也会变动,重新渲染。也可以使用实例的$set方法
,vm.$set(vm.userInfo,"address","wuhan")
对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value)
方法向嵌套对象添加响应式属性。
2.9. 对于数组的 set 方法
对于数组的set
方法。Vue.set(vm.userInfo,4,5)
,将第四个位置的数据改成5,也可以用实例vm.$set(vm.userInfo,4,5)
,所以改变数组的值有两种方法,第一个是使用js的数组操作函数,另一个是使用vue
的set
方法、
2.10 v-text 与 v-html
这两个指令旨在显示数值,跟我们直接在html
中使用插值表达式类似:
<div>{{message}}</div>
v-text
显示的结果与插值表达式一致的,而v-html
会展示为html
,如果字符串是一个html
的字符串,他会进行渲染显示。
3. 组件的一些知识
3.1. is属性
有些 HTML
元素,诸如 <ul>
、<ol>
、<table>
和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>
、<tr>
和 <option>
,只能出现在其它某些特定的元素内部。比如tbody
里只能显示tr
,我们希望在tr里放其他的内容,可以借助is
属性:
<table>
<blog-post-row></blog-post-row>
</table>
这个自定义组件<blog-post-row>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。幸好这个特殊的 is 特性给了我们一个变通的办法:
<table>
<tr is="blog-post-row"></tr>
</table>
这句代码就是说,我们在tbody
是显示tr
,其实是is
里面的组件;遇到组件上的小bug
,可以使用is
进行解决;比如ol、select
等等。
3.2 . 子组件中 data
子组件中data
为函数;是为了保证每一个组件中的数据互不干扰;
3.3. 组件中操作 DOM
ref
:引用 ,被用来给元素或子组件注册引用信息。引用信息将会注册在父组件的 $refs
对象上。如果在普通的DOM
元素上使用,引用指向的就是 DOM
元素;如果用在子组件上,引用就指向组件实例:
<!-- `vm.$refs.p` will be the DOM node -->
<p ref="p">hello</p>
<!-- `vm.$refs.child` will be the child component instance -->
<child-component ref="child"></child-component>
获取dom
节点,通过this.$refs.ref
的值这样获取dom
节点;比如上面的结构,如果需要获取this.$refs.child
就会获取到对应的dom
信息。
当 v-for 用于元素或组件的时候,引用信息将是包含 DOM 节点或组件实例的数组。
关于 ref 注册时间的重要说明:因为 ref 本身是作为渲染结果被创建的,在初始渲染的时候你不能访问它们 - 它们还不存在!$refs 也不是响应式的,因此你不应该试图用它在模板中做数据绑定。
3.4. 组件之间的通讯
3.4.1 子组件向父组件派发事件
使用this.$emit('change')
;在父组件触发change事件。@change="handleChange";
父组件的change
事件执行handleChange
方法。
3.4.2 父组件向子组件传递
父组件向子组件传递是通过属性,用v-bind
进行绑定,子组件尽量不要修改父组件传进来的参数,可以使用data
复制一份传入的值;
对于组件参数的校验,直接在props
,接收的时候,为一个对象,type
为类型,default
为默认值,required
为设置参数是否必须,validator(value){return (value.length>5)}
传入的值必须大于五;
Vue.component('my-component', {
props: {
// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
propA: Number,
// 多个可能的类型
propB: [String, Number],
// 必填的字符串
propC: {
type: String,
required: true
},
// 带有默认值的数字
propD: {
type: Number,
default: 100
},
// 带有默认值的对象
propE: {
type: Object,
// 对象或数组默认值必须从一个工厂函数获取
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
// 这个值必须匹配下列字符串中的一个
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
})
还是需要注意的是v-bind绑定的属性后面是一个对象,直接跟class
一样绑定属性,为字符串;props
传递的属性,在dom
渲染出来的HTML
上不会显示出来。
3.5 组件绑定原生事件
给父组件绑定事件,其实是一个自定义事件,想要给组件绑定事件,需要在template
里面进行绑定。子组件想要触发自定义事件,需要使用this.$emit('chandleClick');
如果想在父组件添加事件,需要添加native
事件修饰符:@click.native="handleClick";
这里在子组件向父组件传值定义事件名的时候需要注意,不同于组件和
prop
,事件名不存在任何自动化的大小写转换。而是触发的事件名需要完全匹配监听这个事件所用的名称。所以如果我们定义了一个this.$emit('myEvent')
,然后在父组件监听的时候:<my-component v-on:my-event="doSomething"></my-component>
使用短横行的方式去监听,是监听不到的,因为不同于组件和prop
,事件名不会被用作一个JavaScript
变量名或属性名,所以就没有理由使用camelCase
或PascalCase
了。并且v-on
事件监听器在DOM
模板中会被自动转换为全小写 (因为HTML
是大小写不敏感的),所以v-on:myEvent
将会变成v-on:myevent
——导致myEvent
不可能被监听到。
所以推荐我们一般使用短横线的方式去命名。
3.6. 非父子组件传值( Bus 总线/发布订阅模式/观察者模式)
Vue.prototype.bus = new Vue();
//子组件触发事件
this.bus.$emit('change',this.value)//来触发事件;然后组件进行监听:
//在父组件的mounted中去监听子组件触发的事件
mounted(){
this.bus.$on('change',function(msg)
{
//...
}
);
3.7. 插槽相关知识
在slot
插槽中,如果在父组件中不进行插入dom
,在子组件的<slot>
默认内容</slot>
,里面的字会显示出来,自定义的内容放在<slot>中间
,如果父组件有数据,则不会显示;具名插槽也可以有默认内容。
具名插槽:
有时我们需要多个插槽。例如对于一个带有如下模板的
<div class="container">
<header>
<!-- 我们希望把页头放这里 -->
</header>
<main>
<!-- 我们希望把主要内容放这里 -->
</main>
<footer>
<!-- 我们希望把页脚放这里 -->
</footer>
</div>
对于这样的情况,<slot>
元素有一个特殊的特性:name
。这个特性可以用来定义额外的插槽:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
一个不带name
的 <slot>
出口会带有隐含的名字“default”
。
在向具名插槽提供内容的时候,我们可以在一个 <template>
元素上使用v-slot
指令,并以v-slot
的参数的形式提供其名称:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
现在 <template>
元素中的所有内容都将会被传入相应的插槽。任何没有被包裹在带有v-slot
的<template>
中的内容都会被视为默认插槽的内容。
然而,如果你希望更明确一些,仍然可以在一个 <template>
中包裹默认插槽的内容:
<base-layout>
<template v-slot:header>
<h1>Here might be a page title</h1>
</template>
<template v-slot:default>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</template>
<template v-slot:footer>
<p>Here's some contact info</p>
</template>
</base-layout>
最终上面的代码渲染的结果为:
<div class="container">
<header>
<h1>Here might be a page title</h1>
</header>
<main>
<p>A paragraph for the main content.</p>
<p>And another one.</p>
</main>
<footer>
<p>Here's some contact info</p>
</footer>
</div>
注意 v-slot 只能添加在一个
<template>
上
作用域插槽:在父组件向子组件传入内容时候,需要使用template
标签进行包裹,这里的template
是必须的
<child>
<template slot-scope="props">
<h1>{{props.item}}</h1>
</template>
</child>
在子组件中:
<div>
<ul>
<slot
v-for="item of list"
:item=item
>
</slot>
</ul>
</div>
应用场景:子组件进行循环或者某一dom渲染的样式需要外部传入,进行不同的显示;作用域插槽也就相当于就是插槽之间的数据通讯。
3.8. 动态组件
动态组件:component
标签的is
属性来自动加载组件:<component :is="com-a"></component>,
显示com-a
组件;不像v-if
,它是将组件进行缓存在内存里面的。
3.9. vue 中的动画
vue
中的动画,需要使用transition
标签进行包裹需要动画显示的组件,他会给里面包裹的元素添加多个类名fade-enter、fade-enter-active、fade-enter-to
等, 前缀为fade
是因为我们添加的name
为fade,vue
默认为v-enter、v-enter-active
等等。div
标签外只要使用transition
包裹,div
不管使用v-show
还是v-if
过渡动画都是可以显示的。如果我们需要自定义类名,直接在transform
标签上添加 enter-active-class="active" leave-active-class="leave"
对应的active
以及leave
是自定义的类名;
vue
中使用animate.css
在transition
标签上直接使用:enter-active-class="animated swing" leave-active-class="animated shake"
;
为了让div
能在初次进去页面的时候有动画,添加一个自定义属性appear-active-class
,还需要加一个appear
,意思就是让组件第一次显示的时候也有一个动画效果,就是appear-active-class
;可以添加属性type=""
来指定动画播放时长;
3.11. 非 Props 特性
一个非prop
特性是指传向一个组件,但是该组件并没有相应 prop
定义的特性。
因为显式定义的 prop
适用于向一个子组件传入信息,然而组件库的作者并不总能预见组件会被用于怎样的场景。这也是为什么组件可以接受任意的特性,而这些特性会被添加到这个组件的根元素上。
例如,想象一下你通过一个 Bootstrap
插件使用了一个第三方的 <bootstrap-date-input>
组件,这个插件需要在其 <input>
上用到一个 data-date-picker
特性。我们可以将这个特性添加到你的组件实例上:
然后这个data-date-picker="activated"
特性就会自动添加到<bootstrap-date-input>
的根元素上。
3.12. 组件中替换/合并已有的特性
如果定义了一个组件
<input type="date" class="form-control">
为了给我们的日期选择器插件定制一个主题,我们可能需要像这样添加一个特别的类名:
<bootstrap-date-input
data-date-picker="activated"
class="date-picker-theme-dark"
></bootstrap-date-input>
在这种情况下,我们定义了两个不同的class
的值:
form-control
,这是在组件的模板内设置好的date-picker-theme-dark
,这是从组件的父级传入的
对于绝大多数特性来说,从外部提供给组件的值会替换掉组件内部设置好的值。所以如果传入 type="text"
就会替换掉 type="date"
并把它破坏!庆幸的是,class
和style
特性会稍微智能一些,即两边的值会被合并起来,从而得到最终的值:form-control date-picker-theme-dark
。
3.13. 组件的禁用特性继承
如果你不希望组件的根元素继承特性,也就是说你不希望你的组件的根元素取绑定你没有接受的属性值,你可以在组件的选项中设置 inheritAttrs: false。例如:
Vue.component('my-component', {
inheritAttrs: false,
// ...
})
这尤其适合配合实例的 $attrs
属性使用,该属性包含了传递给一个组件的特性名和特性值,例如:
{
required: true,
placeholder: 'Enter your username'
}
有了 inheritAttrs: false
和$attrs
,你就可以手动决定这些特性会被赋予哪个元素。在撰写基础组件的时候是常会用到的:
Vue.component('base-input', {
inheritAttrs: false,
props: ['label', 'value'],
template: `
<label>
{{ label }}
<input
v-bind="$attrs"
v-bind:value="value"
v-on:input="$emit('input', $event.target.value)"
>
</label>
`
})
注意
inheritAttrs: false
选项不会影响style
和class
的绑定。
这个模式允许你在使用基础组件的时候更像是使用原始的 HTML
元素,而不会担心哪个元素是真正的根元素:
<base-input
v-model="username"
required
placeholder="Enter your username"
></base-input>
可以看到我们的组件并没有接收required
以及placeholder
属性,因此,$attrs
的值就是:
{
required: true,
placeholder: 'Enter your username'
}
然后我们在组件中直接将这些属性进行绑定了:v-bind="$attrs"
,也就是说$attrs
存储非prop
特性,inheritAttrs
控制vue
对非prop
特性默认行为,在标签内添加$attrs
可以渲染上未注册的属性inheritAttrs:false
是允许组件绑定的未注册属性渲染到组件根节点上的。$attrs
是一个对象。
4. vue-cli 以及 vue-router
4.1. Javascript 中 Promise 对象与 callbacks 的区别
Javascript
中Promise
对象与callbacks
的区别进行比较,显著的优点就是,promise
对象减少了嵌套,有效的防止了进入回调地狱;并且可以一次触发多个promise
对象:
const eatMeal=Promise.all([firstPromise,burgerPromise,drinkPromise])
.then([fries,burger,drinks]=>{
console.log(`Chomp. Awesome ${burger}`);
console.log(`Chomp. Awesome ${fries}`);
console.log(`Chomp. Awesome ${drinks}`);
});
reject
与reslove
使用,在创建promise
对象的时候进行判断,如果符合条件,就执行resolve
,不符合就执行reject
;
然后就是.then
,不管是成功还是失败,都会执行,也就是执行reject
的时候会执行.catch
;.catch
是执行失败的时候会调用。
4.2. vue-cli 知识
.vue
文件是代表vue
的一个组件,在js
文件中我们创建一个组件,是通过Vue.component('com-a',{})
这样,在vue
工程化中,我们使用.vue
的文件进行创建组件。
对于vue-cli
脚手架的理解,首先我们在根目录的main.js
文件中,可以看到:引用了route
r以及app
文件。
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
// 局部组件
components: { App },
// 下面这句话意思是将app组件渲染在页面中,可以不在这里写,直接在index里面进行书写app标签。
template: '<App/>'
})
// 路由 <route-view>显示的是当前路由地址所对应的内容
在App.vue
文件中,我们可以看到:<route-view>
显示的是当前路由地址所对应的内容 ,
<template>
<div id="app">
[外链图片转存失败(img-cbxcO81D-1562120294364)(https://mp.csdn.net/mdeditor/assets/logo.png)]
<!-- <route-view>显示的是当前路由地址所对应的内容 -->
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
打开Router
夹子中的index.js
文件:它配置了当路由显示为home
,也就是http://localhost:8080/#/home
这样的时候,显示组件HelloWorld
,这样控制到不同的路由显示不同的内容。
import Vue from 'vue'
import Router from 'vue-router'
import HelloWorld from '@/components/HelloWorld'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/home',
name: 'HelloWorld',
component: HelloWorld
}
]
})
下面的代码注释:
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import List from '@/pages/list/List'
Vue.use(Router)
export default new Router({
routes: [
// 当用户访问根目录的时候,<router-view>显示home组件
{
path: '/',
// 路由名字
name: 'home',
component: Home
}, {
// 当用户访问根目录的时候,<router-view>显示home组件
path: '/list',
name: 'List',
component: List
}
]
})
有关.vue
中的页面跳转,以前我们使用a
标签进行跳转的,在.vue
中,我们使用<router-link>
,进行跳转,如下面代码:
<template>
<!-- 这里需要注意的是template只能包裹一个内容,如果有多个,需要在外层再加一个div -->
<div>
<div>home</div>
<!-- 页面跳转 to里面是跳转到根路径下的list页面,就是路由里面配置-->
<router-link to="/list">列表</router-link>
</div>
</template>
<script>
export default {
// 组件的名字
name: 'Home'
}
</script>
<style>
div {
font-size: 19px;
}
</style>
当然我们可以使用router
的函数式导航的方式进行跳转页面:可以看到跳转到了backCar
页面,传入的参数为id: carInfo.CARID
,在另一个组件接收传入的参数this.$router.query.id
, 对于页面传值的方式还有很多。
this.$router.push({ path: '/backCar', query: {ID: carInfo.CARID} });
还有一个需要注意的是:<template>标签只能向外暴露一个根标签,如果有多个,需要再到外层加一个div
;
单页应用:通过js进行控制页面的显示。
4.3. 项目中一些技巧
4.3.1. import 引入 css
通过import可以直接引用css,如下面代码,可以直接写在main.js
中:
import './assets/style/reset.css'
4.3.2. 移动端 Click 事件延迟执行
防止移动端中出现点击click
事件,延迟300毫秒执行的插件,在工程中输入下面命令进行安装:
cnpm install fastclick --save
然后在main.js
中进行设置:
// 使用attach方法,绑定到body中。
fastClick.attach(document.body)
4.3.3. vue-cl项目中配置路径别名
在vue-cli
项目中配置路径引用的标记,我们经常在项目中看到@符号,代表就是src
路径,~@
代表的是src
路径下的两层路径,有时候我们的层级特别多,不能使用@符号,我们可以进行配置我们的简写路径。打开配置文件,修改如下:
styles
就是代表我们的src/assets/style
这个路径。
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'styles': resolve('src/assets/style'),
}
},
所以我们在引用该目录下的文件的直接就直接这样写:
import 'styles/border.css'
import 'styles/iconfont.css'
4.3.4. vue-cl项目中使用 stylus
引入stylus
,他类似sass
或者less
这种,可以直接在css中进行定义变量;帮助我们工程化代码;输入下面的命令进行安装:
npm install stylus --save
npm install stylus-loader --save
然后我们可以在我们的项目中使用,如下面的`<style>`标签内,需要添加一个`lang="stylus"`属性,这样就直接可以使用`stylus`进行写css代码,
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header
display: flex
line-height: .86rem
background: $bgColor
color: #fff
.header-left
width: .64rem
float: left
.back-icon
text-align: center
font-size: .4rem
</style>
这里还有需要注意的是,我们可以使用stylus
,新建一个varibles.styl
文件,来进行定义变量,比如我们主题的颜色,在很多css文件中都要使用,如果后期需要更换主题颜色,就很麻烦;这时候,我们可以通过新建一个styl
后缀的文件,在里面进行定义我们的变量,这样后期维护就比较方便;varibles.styl
文件内如下:
$bgColor = #00bcd4
$darkTextColor = #333
$headerHeight = .86rem
```
然后我们引入这个文件,直接可以使用在里面定义的变量,代码如下:
```css
@import '~styles/varibles.styl'
.header
display: flex
line-height: .86rem
background: $bgColor
4.3.5. 移动端像素问题
首先需要明白rem
与px
像素单位之间的转化;1rem = html font-size = 50px
;经常为了方便处理转换rem
;我们会将html
的font-size
设置为50px
,这样我们直接在页面中写rem
的时候就方便计算了,直接是像素(px
)/100这样计算就可以了。比如我们要设置width=86px
,直接写成width=.86rem
就可以了。
需要注意的一个问题是,如果修改了
webpack
里面的配置项,需要重新启动服务才会有效。
5. 项目开发中的一些知识
5.1. 开发流程
开发项目中的流程:大多数公司在进行开发一个项目的时候,都会首先建立一个主分支(master
),然后将其他功能的编写,都放在其他分支上,最后将其他分支合并到主分支这样的开发流程。
在码云上新建分支,然后在项目文件夹下输入命名:
git push
他会提示提交到哪一个分支上,然后输入命令:git checkout index-swiprer
其中index-swiprer
是我们新建的一个分支。
将分支内容提交到主分支,还是通过git add . ; git commit -m '" ; git push
提交之后,然后输入命令git checkout master
切换到主分支,然后进行合并,输入命令:git merge origin/index-swiper
其中index-swiprer
是我们要合并的分支;最后再git push
5.2. vue 第三方轮播图插件 vue-awesome-swiper
vue
第三方轮播图插件vue-awesome-swiper
,安装如下:
输入命令npm install vue-awesome-swiper@2.6.7 --save
然后根据官网的操作步骤,就可以使用了。官网地址:https://github.com/surmon-china/vue-awesome-swiper
5.3. 浏览器的小技巧模拟网络
浏览器的小技巧:可以点击调试面试的network
右侧有一个offline
右侧的向下的箭头,可以模拟不同的网速,比如可以选择slow 3G
模拟3G
网络。
5.4. css 代码技巧
5.4.1. 宽高保持一定的百分比
高度根据宽度撑开百分比,设置宽高始终保持在31.25%
如下面的代码:
overflow: hidden
width: 100%
height: 0
/*高度会根据宽度自动撑开31.25% */
padding-bottom: 31.25%
或者直接写一个height:31.25vm
,把overflow
以及padding-bottom
删除 ,也是可以的。
5.4.2. 修改第三方插件样式
查找wrapper
下面的swiper-pagination-bullet-active
类都加一个背景色。应用场景是,有时候引用了第三方的插件,需要改插件的颜色,直接通过加! important
是不行的,可以通过下面的方法进行修改。
.wrapper >>> .swiper-pagination-bullet-active
background: #fff
另一个技巧:当页面显示的时候有可能显示的字体会很多,我们可以设置多余的显示为...
,可以通过css类控制:
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
我们可以借助stylus
进行封装css函数,如下面代码:
ellipsis()
overflow: hidden
white-space: nowrap
text-overflow: ellipsis
然后在css中进行引入:mixins.styl
文件就是我们写的代码,直接加一个ellipsis()
就可以了。
@import '~styles/mixins.styl'
.icon-desc
position: absolute
left: 0
right: 0
bottom: 0
height: .44rem
line-height: .44rem
text-align: center
color: $darkTextColor
ellipsis()
5.4.3. div 元素垂直居中
CSS
技巧:让给div
中的元素垂直居中:
display: flex
flex-direction: column
justify-content: center
然后里面的可以设置css
进行水平居中:
line-height: .44rem
text-align: center
5.4.4. 移动端获取元素离顶部元素真实高度
对于页面中获取离上面元素高度:
// 元素离顶部元素的高度
const startY = this.$refs['A'][0].offsetTop
// 获取到手离开屏幕的高度,他是获取到设备的最顶部到手指离开时的高度,这里高度需要减去顶部的header
//touch事件会传入一个手势参数,第一个存储着变量,跟点击事件一样,存储事件属性。
const touchY = e.touches[0].clientY - 79
5.5. vue-cli 项目中的静态资源访问以及代码提交配置
vue-cli
项目中,我们一般是将就静态文件放在static
文件夹中,因为整个项目,只能那个文件夹可以被外部访问到。访问其他文件夹中的内容会自动跳转到主页面。我们可以修改项目中的.gitignore
文件,进行配置在git提交代码的时候,提交哪些文件。如下面代码。设置将static
下面的mock
文件夹内的东西不进行提交:
.DS_Store
node_modules/
/dist/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
static/mock
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
5.6. 项目接口的转接
我们在写项目代码的时候,前端写的模拟数据,通过自己写的模拟接口,有可能是通过模拟的json
数据,但是当项目 上线的时候需要进行替换真的API
接口,如果在上线前进行替换,是有风险的,我们可以通过webpack
提供的配置,在config
文件夹下通过配置,将接口进行跳转,如下面的配置:
proxyTable: {
'/api': {
target: 'http://localhost:8080',
pathRewrite: {
'^/api': 'static/mock'
}
}
},
代码的意思是,将有/api
请求的路径替换成http://localhost:8080/static/mock
这样
5.7. Math 对象的一些方法
Math.round(),Math.ceil(),Math.floor(),Math.trunc
的区别:Math.floor()
首先是向下取整,Math.ceil()
是向上取整,Math.round()
就类似我们的四舍五入,Math.trunc()
方法会将数字的小数部分去掉,只保留整数部分。
5.8. CSS 的 rem 以及 vm
rem
是 CSS3
新增的一个相对单位(root em)
,即相对 HTML
根元素的字体大小的值。
em
也是一个相对单位,却是相对于当前对象内文本的字体大小。
一般建议在 line-height
使用em
。因为在需要调整字体大小的时候,只需修改font-size
的值,而line-height
已经设置成了相对行高了。
首行缩进两个字符:text-indent: 2em
视口单位 vw | vh
:
vw: 1vw = 视口宽度的 1%
vh: 1vh = 视口高度的 1%
5.9. Better-scroll 插件
Better-scroll
插件的用法:
首先进行安装,输入命令:cnpm install better-scroll --save
然后在页面就可以使用,如下面的代码:它主要的功能是做一个滚动,还有一个弹性的动画。
import Bscroll from 'better-scroll'
export default {
name: 'CityList',
mounted () {
// 传入dom元素
this.scroll = new Bscroll(this.$refs.wrapper)
}
}
可以使用该插件自带的方法,进行滚动到对应的DOM
元素,element
是对应要滚动到的元素DOM
节点,代码如下
this.scroll.scrollToElement(element)
在vue
中,我们获取组件,通常是在组件添加一个ref
属性,然后通过this.$refs.wrapper
获取到对应的组件,其中wrapper
是我们定义组件的ref
属性名字。
5.10. 防抖节流的例子
一个防抖节流的例子:这个是做了一个类似通讯录滑动右侧字母列表进行显示对应首字母的人员,我们会通过touch
事件来完成。思路是这样的:首先获取到A
到顶部的距离,然后获取到手指滑动结束后的位置,用手指滑动后的位置减去A
的位置,就是之间的距离差,然后根据距离差除以每一个字母的高度,就可以获取到到第几个位置,然后显示对应的数据。
但是当我们手指拖动的时候,会频繁的触发触摸事件,所以我们在这里就可以做一个节流的操作,定义一个计时器,每次移动完之后,首先清除上次的定时器,然后重新定义计时器,每隔16毫秒执行一次事件。这样避免了频繁调用事件,下面是实现的代码:
handleTouchMove (e) {
if (this.touchStatus) {
if (this.timer) {
clearTimeout(this.timer)
}
// 延迟16毫秒执行
this.timer = setTimeout(() => {
// 获取到手离开屏幕的高度,他是获取到设备的最顶部到手指离开时的高度,这里高度需要减去顶部的header
const touchY = e.touches[0].clientY - 79
const index = Math.floor((touchY - this.startY) / 20)
if (index >= 0 && index < this.letter.length) {
this.$emit('change', this.letter[index - 1])
}
}, 16)
}
},
5.11. touch 事件与 click 事件的冲突
有时候我们做的拖动页面,比如通讯录的右侧拖动字母表,显示对应的人,但是发现拖动的时候整个页面也会动,这个时候就需要阻止touchstart
事件的默认行为,需要在给组件绑定该事件的时候加一个事件修饰符,如下代码:
@click="handleLetterClick"
@touchstart.prevent="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
5.12. 解决手机不支持 es6 新特性
有些手机会不支持promise
对象,进行发送ajax
请求,这时候我们需要使用一个插件来解决;在项目中安装插件,输入下面的命令:
这个插件,会检测浏览器是否支持es6
的新特性,如果不支持,会进行修改。
npm install babel-polyfill --save
然后在main.js
文件中直接引入该插件就可以了import 'babel-polyfill'
6. vuex 知识
6.1. vuex 的使用
他是为了在多个组件共享数据的时候,方便我们管理共享状态;首先如果需要使用该插件,还是等进行安装。可以看下面的vuex
实现步骤:
每一个 Vuex
应用的核心就是 store
(仓库)。“store”
基本上就是一个容器,它包含着你的应用中大部分的状态 (state
);所以在使用的使用进行实例化Store
对象,如下面的代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '武汉'
},
// 组件可以直接调用commit进行修改数据,可以不进行事件的派发dispatch
// actions: {
// changeCity (ctx, city) {
// // 通过触发commit事件,来触发mutations来修改数据
// ctx.commit('changeCity', city)
// }
// },
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
从上面的示意图可以看到,如果需要改变状态里面的数据,首先得执行Dispatch
进行分发一个事件,去执行一个Actions
,然后通过commit
去触发Motations
里面的方法,去改变状态值;下面代码是Dispatch
进行分发一个事件:
// 触发changeCity这个Action
this.$store.dispatch('changeCity', city)
在Store
对象的Actions
里面我们这样写
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '武汉'
},
// 组件可以直接调用commit进行修改数据,可以不进行事件的派发dispatch
actions: {
changeCity (ctx, city) {
// 通过触发commit事件,来触发mutations来修改数据
ctx.commit('changeCity', city)
}
},
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
组件可以直接通过commit调用执行mutation里面的方法就行修改数据,在页面直接这样写
// 组件可以直接通过commit调用执行mutation里面的方法就行修改数据
this.$store.commit('changeCity', city)
然后在Store
对象的Mutations
直接写对应的处理函数changeCity
,代码:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '武汉'
},
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
在页面中,我们直接这样访问store
对象里面的数值:
<div class="header-right">
{{this.$store.state.city}}
<span class="iconfont arrow-icon"></span>
</div>
6.2. 页面之间跳转
对于页面跳转的,不仅可以使用<router-link to="/"></router-link>
这种方法跳转,也可以使用编程式导航这种。进行push要跳转的地址就可以,代码如下:
methods: {
handleCityClick (city) {
// 触发changeCity这个Action
// this.$store.dispatch('changeCity', city)
// 组件可以直接通过commit调用执行mutation里面的方法就行修改数据
this.$store.commit('changeCity', city)
// 不仅可以使用<router-link></router-link>这种方法跳转,也可以使用编程式导航这种。进行push要跳转的地址就可以
this.$router.push('/')
}
},
6.3. 访问 vuex 数据技巧
6.3.1. 组件中访问state数据
由于 Vuex
状态存储是响应式的,从store
实例中读取状态最简单的方法就是在计算属性中返回某个状态:
// 创建一个 Counter 组件
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
调用Vue.use(Vuex)
状态从根组件“注入”到每一个子组件中:
const app = new Vue({
el: '#app',
// 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
然后在组件中可以使用:
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count
}
}
}
6.3.2. mapState 辅助函数
访问vuex
数据技巧:前面写了可以通过this.$store.state.city
这样来获取到我们在Store
对象中定义的state
里面的数据,我们也可以这样写:需要注意的是,遍历命名只能使mapState
这样,下面的代码就是将store
里面的city
映射到计算属性里面的city
import { mapState } from 'vuex'
computed: {
// 展开运算符 把 store里面的city映射到计算属性里面的city
...mapState(['city'])
}
其实mapState
里面也可以包含一个对象,这样写,如下面的代码:代码意思是将vuex
的city
映射到计算属性的currentCity
import { mapState } from 'vuex'
computed: {
// 将vuex的city映射到计算属性的currentCity
...mapState({
currentCity: 'city'
})
},
同样对于通过commit
调用Mutations
中的方法,也可以进行改写,之前是这样的:
handleCityClick (city) {
// 触发changeCity这个Action
// this.$store.dispatch('changeCity', city)
// 组件可以直接通过commit调用执行mutation里面的方法就行修改数据
this.$store.commit('changeCity', city)
// 不仅可以使用<router-link></router-link>这种方法跳转,也可以使用编程式导航这种。进行push要跳转的地址就可以
this.$router.push('/')
},
可以借助 vuex
的简单方法,进行改写如下:需要注意的是changeCity
这个方法要对应你在store
对象的Mutations
中定义的一致。还有需要注意的是,需要进行引入import { mapState, mapMutations } from 'vuex'
import { mapState, mapMutations } from 'vuex'
methods: {
handleCityClick (city) {
// 触发changeCity这个Action
// this.$store.dispatch('changeCity', city)
// 组件可以直接通过commit调用执行mutation里面的方法就行修改数据
// this.$store.commit('changeCity', city)
// 可以用下面的方法这样写
this.changeCity(city)
// 不仅可以使用<router-link></router-link>这种方法跳转,也可以使用编程式导航这种。进行push要跳转的地址就可以
this.$router.push('/')
},
...mapMutations(['changeCity'])
},
6.3.3. 使用常量替代 Mutation 事件类型
使用常量替代 mutation
事件类型在各种 Flux
实现中是很常见的模式。这样可以使linter
之类的工具发挥作用,同时把这些常量放在单独的文件中可以让你的代码合作者对整个app
包含的mutation
一目了然:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// 我们可以使用 ES2015 风格的计算属性命名功能来使用一个常量作为函数名
[SOME_MUTATION] (state) {
// mutate state
}
}
})
6.4. vuex 中的 getters 属性
vuex
中的getters
属性:他类似vue
的计算属性一样,可以根据state
中的数据计算出其他数据,也就是从state
中派生出一些状态;比如下面的代码。我需要获取到我的城市名,出现两次,代码如下:下面是store
里面的代码
getters: {
doubleCity (state) {
return state.city + ' ' + state.city
}
}
然后在页面中,我们同样首先引入mapGetters
,代码如下:
import { mapGetters } from 'vuex'
computed: {
// 展开运算符 把 store里面的doubleCity映射到计算属性里面的doubleCity
...mapGetters(['doubleCity'])
}
6.5. 使用 keep-alive 优化性能
使用keep-alive
优化性能:我们的页面每次切换之后,都会进行重新的ajax
请求,这样会耗费性能,我们可以在<router-view/>
外层包裹一个<keep-alive>
,他是只要路由渲染过的页面,会存储在内存中,再一次加载的时候,不会进行重新渲染 相当于添加了一个缓存
<!-- keep-alive是只要路由渲染过的页面,会存储在内存中,再一次加载的时候,不会进行重新渲染 相当于添加了一个缓存-->
<keep-alive>
<router-view/>
</keep-alive>
但是有的时候,我们需要重新渲染页面,而不是读取缓存的内容,在使用keep-alive
会触发一个activated
方法,只要缓存的页面,再次显示,都会执行这个方法。我们可以在这里进行判断某些值是否改变,用不用重新发送请求。
activated () {
// 如果添加了keep-alive会有这个事件
if (this.lastCity !== this.city) {
this.lastCity = this.city
this.getHomeInfo()
}
},
6.6. router-link 一些知识
6.6.1. tag 属性
router-link
一些知识:对于router-link
,vue
会渲染成一个a
标签,当我们点击跳转到其他页面之后,会变颜色,当然我们可以通过css
进行控制跳转后链接的颜色,我们还可以直接将其他元素改为 router-link
然后添加一个tag
属性,等于替换的标签,如下面的代码:下面的代码。本身是一个li
标签,用router-link
替换之后,添加一个tag="li"
属性
<router-link tag="li" class="item border-bottom" v-for="item of list" :key="item.id" to="/detail">
![在这里插入图片描述]()
<div class="item-info">
<p class="item-title">{{item.title}}</p>
<p class="item-desc">{{item.desc}}</p>
<button class="item-button">查看详情</button>
</div>
</router-link>
6.6.2. 返回到前一页
router-link
中:如果想要返回到前一个页面,属性to='/'
这样就可以了,代码如下
<router-link tag="div" to="/" class="header-abs">
<div class="iconfont header-abs-back"></div>
</router-link>
6.7. <keep-alive> 一些知识点
<keep-alive>
一些知识点:对全局事件解析解绑;当我们在全局使用它进行包裹我们的组件的时候,前面也说了,会将我们的组件进行缓存,但是,他会因此产生两个生命周期函数,上面介绍了一个每次进入缓存页面的时候,执行的activated
钩子函数,还有一个与之对应的钩子函数就是deactivated
函数,就是离开这个页面的时候执行的方法。比如现在我们有一个页面,需要在windoes
对象绑定scroll
事件,但是如果在这个页面进行绑定了该事件之后,在其他的页面,还是会执行这个方法,所以我们需要对全局事件进行解绑。代码如下:activated
钩子函数是当进入到使用<keep-alive>
缓存的页面的时候执行的方法,我们在整理进行绑定了滚动事件,deactivated
是当要离开缓存的这个页面的时候,执行的钩子函数,我们在这里进行移除绑定的滚动事件。
activated () {
window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
window.removeEventListener('scroll', this.handleScroll)
}
6.8. vuex 中的插件
Vuex
的 store
接受 plugins
选项,这个选项暴露出每次mutation
的钩子。Vuex
插件就是一个函数,它接收store
作为唯一参数:
const myPlugin = store => {
// 当 store 初始化后调用
store.subscribe((mutation, state) => {
// 每次 mutation 之后调用
// mutation 的格式为 { type, payload }
})
}
使用:
const store = new Vuex.Store({
// ...
plugins: [myPlugin]
})
6.8. 严格模式
开启严格模式,仅需在创建store
的时候传入strict: true
:
const store = new Vuex.Store({
// ...
strict: true
})
7. vue-router 知识点
7.1. 路由中参数的传递
如果需要在路由带参数,直接在路由后面写上:id
这样,id
是参数名;如下面的代码:
{
// 动态路由 id为参数
path: '/detail/:id',
name: 'Detail',
component: Detail
}
如果需要在页面中需要获取路由中的参数,如下面的代码:使用this.$route.params.id
来进行获取该页面中的参数
getDetailInfo () {
axios.get('/api/detail.json?id=', {
params: {
id: this.$route.params.id
}
}).then(this.handleGetDataSucc)
}
一般我们发送
ajax
请求的时候,一般是在页面挂载之后执行,也就是mounted
钩子函数里面去执行发送请求。
还有一种传递参数的方式:
<router-link :to="{path:'/mailDetail',query:{id: item.id, type: 'rec'}}" :key="item.id">
<InfoList :info="item"></InfoList>
</router-link>
在组件中接收参数:
data () {
return {
form: {},
fjhref:'',
activeName: '1',
id: this.$route.query.id,
type: this.$route.query.type,
}
},
7.2 组件中 name 的用法总结
组件中name
的用法:组件的递归、去除缓存 、清除页面滚动。
7.2.1. 去除缓存
在指定页面去除缓存:在keep-alive
组件中添加一个属性exclude
,属性内容就是要去除缓存的页面的name
值,如下面的代码:
这里我的组件名叫Detail
,前面我们是通过缓存的两个钩子函数activated
和deactivated
配合清除缓存的,下面是另一种方法。
代码意思就是除了Detail
这个页面不被缓存,其他页面都是被缓存的。
<keep-alive exclude="Detail">
<router-view/>
</keep-alive>
7.2.2. 清除页面滚动
清除页面滚动行为:我们在前面时候写到一个请求滚动的也是通过缓存的两个钩子函数activated
和deactivated
配合清除滚动的,其实在vue-router
中,有一个配置项,是清除页面滚动行为的,使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router
能做到,而且更好,它让你可以自定义路由切换时页面如何滚动,在router
添加下面的代码,如下:scrollBehavior
选项意思就是每次进行路由切换,始终回到最顶部。
export default new Router({
routes: [
// 当用户访问根目录的时候,<router-view>显示home组件
{
path: '/',
// 路由名字
name: 'home',
component: Home
}, {
// 动态路由 id为参数
path: '/detail/:id',
name: 'Detail',
component: Detail
}
],
scrollBehavior (to, from, savedPosition) {
return { x: 0, y: 0 }
}
})
7.2.3. 组件的递归
递归组件:有时候我们在页面中,会有类似折叠菜单的这种,样式是一样的,使用同一个组件,这就需要使用递归组件,如下面数据:
categoryList": [{
"title": "成人票",
"children": [{
"title": "成人三馆联票",
"children": [{
"title": "成人三馆联票 - 某一连锁店销售"
}]
},{
"title": "成人五馆联票"
}]
递归组件就是在组件中调用他本身,上面的数据,只要有children
我们就进行调用它本身,代码如下:
<div class="item" v-for="(item,index) of list" :key="index">
<div class="item-title border-bottom">
<span class="item-title-icon"></span>
{{item.title}}
</div>
<div class="item-chilren" v-if="item.children">
<detail-list :list="item.children"></detail-list>
</div>
</div>
需要注意的是
detail-list
这个是改组件的name
值,这就是组件name
的其他用法
7.3. 响应路由参数的变化
当使用路由参数时,例如从 /user/foo
导航到/user/bar
,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。
复用组件时,想对路由参数的变化作出响应的话,你可以简单地 watch
(监测变化) $route
对象:
const User = {
template: '...',
watch: {
'$route' (to, from) {
// 对路由变化作出响应...-----可以再次请求数据
}
}
}
或者使用 2.2 中引入的beforeRouteUpdate
导航守卫:
const User = {
template: '...',
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
}
7.4. 路由中传递参数
函数式编程导航:
// 字符串
router.push('home')
// 对象
router.push({ path: 'home' })
// 命名的路由
router.push({ name: 'user', params: { userId: '123' }})
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
这里需要注意:如果提供了path
,params
会被忽略,上述例子中的 query
并不属于这种情况。取而代之的是下面例子的做法,你需要提供路由的name
或手写完整的带有参数的path
:
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
7.5. 命名路由
我们在写路由对象的时候可以给路由对象加一个name
属性,通过一个名称来表示一个路由会显得更方便一些:
const router = new VueRouter({
routes: [
{
path: '/user/:userId',
name: 'user',
component: User
}
]
})
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
或者使用编程式导航的方式:
router.push({ name: 'user', params: { userId: 123 }})
两种方式导航的地址:/user/123
7.6. 命名视图
如果我们的一个页面需要展示多个视图,就可以使用命名视图,如果 router-view
没有设置名字,那么默认为 default
。
<router-view class="view one"></router-view>
<router-view class="view two" name="a"></router-view>
<router-view class="view three" name="b"></router-view>
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components
配置 (带上 s
):
const router = new VueRouter({
routes: [
{
path: '/',
components: {
default: Foo,
a: Bar,
b: Baz
}
}
]
})
7.7. 组建内的守卫
组建内的守卫有三种:
beforeRouteEnter
beforeRouteUpdate
beforeRouteLeave
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
这个离开守卫通常用来禁止用户在还未保存修改前突然离开。该导航可以通过 next(false)
来取消。
beforeRouteLeave (to, from , next) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (answer) {
next()
} else {
next(false)
}
}
7.8.完整的导航解析流程
- 导航被触发。
- 在失活的组件里调用离开守卫。
- 调用全局的 beforeEach 守卫。
- 在重用的组件里调用 beforeRouteUpdate 守卫 (2.2+)。
- 在路由配置里调用 beforeEnter。
- 解析异步路由组件。
- 在被激活的组件里调用 beforeRouteEnter。
- 调用全局的 beforeResolve 守卫 (2.5+)。
- 导航被确认。
- 调用全局的 afterEach 钩子。
- 触发 DOM 更新。
- 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
8. 项目上线准备
8.1. API 接口的替换
在项目上线的时候,需要前后台的联调,需要使用真实的api
接口进行测试,这时候,我们需要在config
文件夹下的index
进行配置后台的接口,地址,配置如下面的代码:这里,我的后台的对应的地址是http://localhost:80
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:80',
pathRewrite: {
'^/api': 'static/mock'
}
}
如果后台跟前台写的api
地址是一样的,可以不进行配置pathRewrite
选项,如下:
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {
'/api': {
target: 'http://localhost:80'
}
8.2. 移动端项目真机调试
在vue
项目中,webpack
服务器默认是不能通过ip
地址进行访问我们的项目,比如运行cmd
输入ipconfig
查看我们的ip
地址,输入到网页,是看不到我们的项目,输入端口80是可以看到的,我们可以通过修改配置,只需要修改项目中的package.json
文件的配置项,代码如下:
"scripts": {
"dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js"
},
8.3. 项目打包
vue
项目打包上线:输入命令npm run build
将我们的代码进行打包编译,然后我们的项目会多出来一个dist
的文件夹。里面就是我们打包项目的代码。直接将dist
文件夹放在后台的站点就可以运行了,但是有时候我们的站点需要放在后台服务器的一个文件夹里面,比如说,我们需要将前台的代码放在一个project
的文件夹中,如果直接放进去,运行项目,你会发现报错,这个时候,我们需要修改config
文件夹中的inedx.js
里面的打包项:主要是assetsPublicPath: '/project',
这个
build: {
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/project',
}
9. vue 学习路线
vue
学习思路:边缘知识点进行查看,查看生态系统v-router
中的路由别名等等,查看vuex
中的一些核心概念,然后查看服务器端渲染,然后学习vue
的插件,查看官网的vue
资源。最后研究vue
源码,查看commit
,了解每次提交的的添加的功能的思路。
10. 其他
- 使用
JSX
,需要注意的是:如果使用JSX
,需要在webpack
中进行配置babel-plugin-transform-vue-jsx
, - 看了一篇文章,有关个人技术突破的,首先要了解技术门槛,认清自己当下局势。然后进行习惯养成与指定目标计划,
比如读源码,或者整理原理图,当完成目标可以给自己奖励,也可以不断测试自己的底线,调整目标。然后就是训练自己的
思维,善于对问题进行提问:问题是什么,当前真相是什么,为什么会发生。最后,,就是不怕吃亏。。 - Vue中的插件开发:使用一个公开的方法
install
;v-router
(实现原理跟使用is用来设置显示组件一样)、v-vuex
(非父子组件之间通讯的原理)等插件。 - 浏览器的基本工作原理从输入url到使用各种线程渲染页面。