一. Vue
1.1. 官方文档
1). 一位华裔前Google工程师(尤雨溪)开发的前端js库
2). 作用: 动态构建用户界面
3). 特点:
* 遵循MVVM模式
* 编码简洁, 体积小, 运行效率高, 移动/PC端开发
* 它本身只关注UI, 可以轻松引入vue插件和其它第三库开发项目
4). 与其它框架的关联:
* 借鉴angular的模板和数据绑定技术
* 借鉴react的组件化和虚拟DOM技术
5). vue包含一系列的扩展插件(库):
* vue-cli: vue脚手架
* vue-resource(axios): ajax请求
* vue-router: 路由
* vuex: 状态管理
* vue-lazyload: 图片懒加载
* vue-scroller: 页面滑动相关
* mint-ui: 基于vue的组件库(移动端)
* element-ui: 基于vue的组件库(PC端)
基本使用:
1). 引入vue.js
2). 创建Vue实例对象(vm), 指定选项(配置)对象
el : 指定dom标签容器的选择器
data : 指定初始化状态数据的对象/函数(返回一个对象)
3). 在页面模板中使用{{}}或vue指令
复制代码
1.2. 编程范式
命令式编程 => 声明式编程
1.3. Vue的MVVM
View ViewModel(Vue) Model
- View —> DOM层,视图,模板页面
- ViewModel —> 视图模型(Vue实例),数据绑定和DOM监听
- Model —> 模型,数据层
1.4. Mustache
Mustache语法(双大括号):支持变量也支持表达式
1.5. Vue对象的选项
1.51. el
指定dom标签容器的选择器
Vue就会管理对应的标签及其子标签
复制代码
1.52. data
对象或函数类型
指定初始化状态属性数据的对象
vm也会自动拥有data中所有属性
页面中可以直接访问使用
数据代理: 由vm对象来代理对data中所有属性的操作(读/写)
复制代码
1.53. methods
包含多个方法的对象
供页面中的事件指令来绑定回调
回调函数默认有event参数, 但也可以指定自己的参数
所有的方法由vue对象来调用, 访问data中的属性直接使用this.xxx
复制代码
1.54. computed
包含多个方法的对象
对状态属性进行计算返回一个新的数据, 供页面获取显示
一般情况下是相当于是一个只读的属性
利用set/get方法来实现属性数据的计算读取, 同时监视属性数据的变化
如何给对象定义get/set属性
在创建对象时指定: get name () {return xxx} / set name (value) {}
对象创建之后指定: Object.defineProperty(obj, age, {get(){}, set(value){}})
实例的 computed 中,包含 get 方法和 set 方法(不常用)。
计算属性会进行缓存,如果多次使用时,计算属性只调用一次。(watch 监视单个,cumputed 监视多个)
复制代码
1.55. watch
包含多个属性监视的对象
分为一般监视和深度监视
xxx: function(value){}
xxx : {
deep : true,
handler : fun(value)
}
另一种添加监视方式: vm.$watch('xxx', function(value){})
复制代码
1.6. Vue内置指令
指令 (Directives) 是带有 v-
前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式(v-for
除外),指令的职责是,当表达式的值改变时,将其产生的连带影响,响应式地作用于 DOM。
v-text/v-html: 指定标签体
* v-text : 当作纯文本
* v-html : 将value作为html标签来解析
v-if v-else v-show: 显示/隐藏元素
* v-if : 如果vlaue为true, 当前标签会输出在页面中
* v-else-if : 表示 v-if 的“else if 块”。可以链式调用。
* v-else : 与v-if一起使用, 如果value为false, 将当前标签输出到页面中
* v-show: 就会在标签中添加display样式, 如果vlaue为true, display=block, 否则是none
v-for : 遍历
* 遍历数组 : v-for="(person, index) in persons"
* 遍历对象 : v-for="value in person" $key
v-on : 绑定事件监听
* v-on:事件名, 可以缩写为: @事件名
* 监视具体的按键: @keyup.keyCode @keyup.enter
* 停止事件的冒泡和阻止事件默认行为: @click.stop @click.prevent
* 隐含对象: $event
v-bind : 强制绑定解析表达式
* html标签属性是不支持表达式的, 就可以使用v-bind
* 可以缩写为: :id='name'
* :class
* :class="a"
* :class="{classA : isA, classB : isB}"
* :class="[classA, classB]"
* :class="classes()"
* :style
:style="{key(属性名) : value(属性值)''(防止解析成变量)}"
v-model
* 双向数据绑定
* 自动收集用户输入数据
* 原理v-bind,v-on:input
* 修饰符lazy,number,trim。event.target.value
v-slot
* 提供具名插槽或需要接收 prop 的插槽
ref : 标识某个标签
* ref='xxx'
* 读取得到标签对象: this.$refs.xxx
v-once :
* 元素和组件只渲染一次,不会随着数据的改变而改变。
v-pre :
* 跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
v-cloak :
* [v-cloak] { display: none; } 占位
复制代码
1.7. 自定义指令
1.71. 注册全局指令
Vue.directive('my-directive', function(el, binding){
el.innerHTML = binding.value.toUpperCase() // 转换为大写
})
复制代码
1.72. 注册局部指令
// 放在Vue实例中
directives : {
'my-directive' : function(el, binding) {
el.innerHTML = binding.value.toUpperCase() // 转换为大写
}
}
复制代码
1.73. 使用指令
<div v-my-directive='xxx'>
复制代码
1.8. 数组中哪些方法是响应式的
- push() 添加元素到数组最后,可以是多个
- pop() 删除数组中最后一个元素
- shift() 删除数组中第一个元素
- unshift() 在数组最前面添加元素,可以是多个
- splice() 作用:删除/插入/替换元素
- sort()
- reverse()
- 通过索引值改变数组,不能响应式改变界面
1.9. Vue 生命周期和响应式原理
1.91. 生命周期
主要的生命周期函数(钩子)
created() / mounted(): 启动异步任务(启动定时器,发送ajax请求, 绑定监听)
beforeDestroy(): 做一些收尾的工作
复制代码
1.92. 响应式原理
2.0. 过渡动画
利用vue去操控css的transition/animation动画
模板: 使用<transition name='xxx'>包含带动画的标签
css样式
.fade-enter-active: 进入过程, 指定进入的transition
.fade-leave-active: 离开过程, 指定离开的transition
.xxx-enter, .xxx-leave-to: 指定隐藏的样式
编码例子
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.xxx-enter-active, .xxx-leave-active
transition: opacity .5s
}
.xxx-enter, .xxx-leave-to {
opacity: 0
}
<button @click="isShow=!isShow">toggle</button>
<transition name="xxx">
<p v-if="show">hello</p>
</transition>
CSS 动画用法同 CSS 过渡,区别是在动画中 v-enter 类名在节点插入 DOM 后不会立即删除,而是在 animationend 事件触发时删除。
.bounce-enter-active {
animation: bounce-in .5s;
}
.bounce-leave-active {
animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
0% {
transform: scale(0);
}
50% {
transform: scale(1.5);
}
100% {
transform: scale(1);
}
}
复制代码
2.1. 过滤器
1). 定义过滤器
Vue.filter(filterName, function(value[,arg1,arg2,...]){
// 进行一定的数据处理
return newValue
})
2). 使用过滤器
<div>{{myDate | filterName}}</div>
<div>{{myDate | filterName(arg)}}</div>
复制代码
2.2. 插件
2.21. 自定义插件
/**自定义 Vue 插件 */
(function () {
const MyPlugin = {}
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或属性
Vue.myGlobalMethod = function () {
alert('Vue 函数对象方法执行') }
// 2. 添加全局资源
Vue.directive('my-directive', function (el, binding) {
el.innerHTML = "MyPlugin my-directive " + binding.value })
// 3. 添加实例方法
Vue.prototype.$myMethod = function () {
alert('vue 实例对象方法执行')
}
}
window.MyPlugin = MyPlugin
})()
复制代码
2.22. 页面使用插件
<div id="demo">
<!--使用自定义指令-->
<p v-my-directive="msg"></p>
</div>
<script type="text/javascript" src="../js/vue.js"></script>
<script type="text/javascript" src="vue-myPlugin.js"></script>
<script type="text/javascript">
//使用自定义插件
Vue.use(MyPlugin)
let vm = new Vue({
el: '#demo',
data: {
msg: 'atguigu'
}
})
//调用自定义的静态方法
Vue.myGlobalMethod()
//调用自定义的对象方法
vm.$myMethod()
</script>
复制代码
2.3. 数据代理(MVVM.js)
1.通过一个对象代理对另一个对象中属性的操作(读/写)
2.通过vm对象来代理data对象中所有属性的操作
3.好处: 更方便的操作data中的数据
4.基本实现流程
1). 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
2). 所有添加的属性都包含getter/setter
3). 在getter/setter内部去操作data中对应的属性数据
复制代码
2.4. 模板解析(compile.js)
1.模板解析的关键对象: compile对象
2.模板解析的基本流程:
1). 将el的所有子节点取出, 添加到一个新建的文档fragment对象中
2). 对fragment中的所有层次子节点递归进行编译解析处理
* 对表达式文本节点进行解析
* 对元素节点的指令属性进行解析
* 事件指令解析
* 一般指令解析
3). 将解析后的fragment添加到el中显示
3.解析表达式文本节点: textNode.textContent = value
1). 根据正则对象得到匹配出的表达式字符串: 子匹配/RegExp.$1
2). 从data中取出表达式对应的属性值
3). 将属性值设置为文本节点的textContent
4.事件指令解析: elementNode.addEventListener(事件名, 回调函数.bind(vm))
v-on:click="test"
1). 从指令名中取出事件名
2). 根据指令的值(表达式)从methods中得到对应的事件处理函数对象
3). 给当前元素节点绑定指定事件名和回调函数的dom事件监听
4). 指令解析完后, 移除此指令属性
5.一般指令解析: elementNode.xxx = value
1). 得到指令名和指令值(表达式)
2). 从data中根据表达式得到对应的值
3). 根据指令名确定需要操作元素节点的什么属性
* v-text---textContent属性
* v-html---innerHTML属性
* v-class--className属性
4). 将得到的表达式的值设置到对应的属性上
5). 移除元素的指令属性
复制代码
2.5. 数据劫持–>数据绑定
1.数据绑定(model==>View):
1). 一旦更新了data中的某个属性数据, 所有界面上直接使用或间接使用了此属性的节点都会更新(更新)
2.数据劫持
1). 数据劫持是vue中用来实现数据绑定的一种技术
2). 基本思想: 通过defineProperty()来监视data中所有属性(任意层次)数据的变化, 一旦变化就去更新界面
3.四个重要对象
1). Observer
* 用来对data所有属性数据进行劫持的构造函数
* 给data中所有属性重新定义属性描述(get/set)
* 为data中的每个属性创建对应的dep对象
2). Dep(Depend)
* data中的每个属性(所有层次)都对应一个dep对象
* 创建的时机:
* 在初始化define data中各个属性时创建对应的dep对象
* 在data中的某个属性值被设置为新的对象时
* 对象的结构
{
id, // 每个dep都有一个唯一的id
subs //包含n个对应watcher的数组(subscribes的简写)
}
* subs属性说明
* 当一个watcher被创建时, 内部会将当前watcher对象添加到对应的dep对象的subs中
* 当此data属性的值发生改变时, 所有subs中的watcher都会收到更新的通知, 从而最终更新对应的界面
3). Compile
* 用来解析模板页面的对象的构造函数(一个实例)
* 利用compile对象解析模板页面
* 每解析一个表达式(非事件指令)都会创建一个对应的watcher对象, 并建立watcher与dep的关系
* complie与watcher关系: 一对多的关系
4). Watcher
* 模板中每个非事件指令或表达式都对应一个watcher对象
* 监视当前表达式数据的变化
* 创建的时机: 在初始化编译模板时
* 对象的组成
{
vm, //vm对象
exp, //对应指令的表达式
cb, //当表达式所对应的数据发生改变的回调函数
value, //表达式当前的值
depIds //表达式中各级属性所对应的dep对象的集合对象
//属性名为dep的id, 属性值为dep
}
5). 总结: dep与watcher的关系: 多对多
* 一个data中的属性对应对应一个dep, 一个dep中可能包含多个watcher(模板中有几个表达式使用到了属性)
* 模板中一个非事件表达式对应一个watcher, 一个watcher中可能包含多个dep(表达式中包含了几个data属性)
* 数据绑定使用到2个核心技术
* defineProperty()
* 消息订阅与发布
4.双向数据绑定
1). 双向数据绑定是建立在单向数据绑定(model==>View)的基础之上的
2). 双向数据绑定的实现流程:
* 在解析v-model指令时, 给当前元素添加input监听
* 当input的value发生改变时, 将最新的值赋值给当前表达式所对应的data属性
复制代码
二. 组件化
1.1. 组件化开发
- 提供一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
- 任何的应用都会被抽象成一颗组件树。
1.11. 注册组件基本步骤
- 创建组件构造器 Vue.extend()方法
- 注册组件 Vue.component()
- 使用组件 Vue实例的作用范围内使用组件
<div id="app">
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 创建组件构造器对象
let cpn = Vue.extend({
template: `
<div>
<h2>zs</h2>
<p>历史</p>
</div>
`
})
// 2. 注册组件
Vue.component('cpnc', cpn)
const app = new Vue({
el: "#app",
data: {
}
})
</script>
复制代码
1.12. 全局组件,局部组件
<div id="app">
<cpnc></cpnc>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 1. 创建组件构造器对象
let cpn = Vue.extend({
template: `
<div>
<h2>zs</h2>
<p>历史</p>
</div>
`
})
// 2. 注册组件 (全局组件)
// Vue.component('cpnc', cpn)
const app = new Vue({
el: "#app",
data: {
// 局部组件
components: {
// cpnc 使用组件的标签名
cpnc: cpn
}
}
})
</script>
// 组件中的数据为函数,返回一个对象,增强复用性
复制代码
1.13. 父组件,子组件
1.14. 父子组件的通信
- 通过props向子组件传递数据(驼峰式的prop作为特性时,v-bind:后的属性需要转为 message-prop (短横线隔开))
<!-- 向子组件传递数据为 数组和对象时,default值为函数 -->
<!-- 子组件 -->
<template>
<div id="container">
{{msg}}
</div>
</template>
<script>
export default {
data() {
return {};
},
props:{
msg: String
}
};
</script>
<style scoped>
#container{
color: red;
margin-top: 50px;
}
</style>
<!-- 父组件 -->
<template>
<div id="container">
<input type="text" v-model="text" @change="dataChange">
<Child :msg="text"></Child>
</div>
</template>
<script>
import Child from "@/components/Child";
export default {
data() {
return {
text: "父组件的值"
};
},
methods: {
dataChange(data){
this.msg = data
}
},
components: {
Child
}
};
</script>
<style scoped>
</style>
复制代码
-
通过事件向父组件发送消息(this.$emit(“事件名”),参数)
<!-- 子组件 --> <template> <div id="container"> <input type="text" v-model="msg"> <button @click="setData">传递到父组件</button> </div> </template> <script> export default { data() { return { msg: "传递给父组件的值" }; }, methods: { setData() { this.$emit("getData", this.msg); } } }; </script> <style scoped> #container { color: red; margin-top: 50px; } </style> <!-- 父组件 --> <template> <div id="container"> <Child @getData="getData"></Child> <p>{{msg}}</p> </div> </template> <script> import Child from "@/components/Child"; export default { data() { return { msg: "父组件默认值" }; }, methods: { getData(data) { this.msg = data; } }, components: { Child } }; </script> <style scoped> </style> 复制代码
1.15. 父子组件的访问方式
- 父组件访问子组件:使用refs(reference引用) (对象类型,默认空对象,添加ref属性使用)
- 子组件访问父组件 $parent
1.16. 注册组件语法糖
// 全局组件注册语法糖
Vue.component('cpn1',{
template: `
<div>
<h2>zs</h2>
<p>历史</p>
</div>
`
})
// 局部组件注册语法糖
const app = new Vue({
el: "#app",
data: {
// 局部组件
components: {
// cpnc 使用组件的标签名
cpnc: {
template: `
<div>
<h2>zs</h2>
<p>历史</p>
</div>
`
}
}
}
})
复制代码
1.17. 模板组件的分离写法
- script标签,类型必须是text/x-template
- template标签
// 模板的分离写法
<script type="text/x-template" id="cpn"></script>
<template id="cpn">
<div>
<h2>zs</h2>
<p>历史</p>
</div>
</template>
<script>
const app = new Vue({
el: "#app",
data: {},
components: {
cpnc: {
template: "#cpn"
}
}
})
</script>
复制代码
1.2. 组件化高级
1.21. 插槽
slot 让封装的组件更具有扩展性
1.22. 具名插槽
<cpn>
<h3 slot="button">张三</h3>
</cpn>
<slot name="button"><button>按钮</button></slot>
复制代码
1.23. 编译的作用域
1.24. 作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供。
<div id="app">
<cpn>
<div slot-scope ="slot">
<span v-for="item in slot.data">{{item}}</span>
</div>
</cpn>
</div>
<template id="cpn">
<div>
<slot :data="num"></slot>
</div>
</template>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: "#app",
data: {},
components: {
cpn: {
template: "#cpn",
data() {
return {
num: [1, 2, 3, 4]
}
}
}
}
})
</script>
复制代码
1.3. 模块化开发
1.31. 模块化规范
最基础的封装:在匿名函数内部,定义一个对象。
给对象添加各种需要暴露到外面的属性和方法(不需要暴露的直接定义即可)。
最后将这个对象返回,并且在外面使用了一个MoudleA接受。
------------------------------------------------------
模块化所解决的问题:
1.模块作用域: 安全的包装一个模块的代码--不污染模块外的任何代码
2.模块唯一性: 唯一标识一个模块--避免重复引入
3.模块的导出: 优雅的把模块的API暴漏给外部--不增加全局变量
4.模块的引入: 方便的使用所依赖的模块
目前模块化的解决方案
1.CommonJS -- Node.js
2.AMD -- RequireJS
3.CMD -- SeaJS
4.UMD
5.ES6 Module
------------------------------------------------------
CommonJS
一个单独的JS文件就是一个模块,每一个模块都是一个单独的作用域
定义全局函数require,通过传入模块标识来引入其他模块,执行的结果即为别的模块暴漏出来的API
如果被require函数引入的模块中也包含依赖,那么依次加载这些依赖
如果引入模块失败,那么require函数应该报一个异常
模块通过变量exports来向外暴漏API,exports只能是一个对象,暴漏的API须作为此对象的属性。
CommonJS 例子
//a.js
exports.foo = function() {
console.log('foo')
}
//b.js
var a = require('./a.js')
a.foo()
CommonJS 缺点
只能在服务端(Node.js)使用, 不能在浏览器直接使用
模块是同步加载的, 如果加载过慢会阻塞进程
------------------------------------------------------
ES6 Module 基本用法
定义模块
//变量, module.js
export var bar = 'bar'
// 函数, module.js
export function foo(){}
// 统一导出&重命名, module.js
var bar = 'bar'
function foo(){}
export { bar as myBar, foo }
// 默认导出, module.js
function foo(){}
export default foo
------------------------------------------------------
ES6 Module 基本用法
引用模块
// 从模块中导入指定对象, 支持重命名, main.js
import { foo, bar as myBar } from './module.js'
// 从模块中导入默认对象(名称可跟原名称不一样)
import myFoo from './module.js'
// 执行模块, 但不导入任何值
import './module.js'
// 整体导入
import * as myModule from './module.js'
注意: ES6 import导入的模块都是原模块的引用
ES6 Module 基本用法
浏览器使用: 在入口JS文件加上type="module"就可以在该文件内使用ES6 Module 语法
<script src="https://juejin.cn/post/scripts/main.js" type="module"></script>
复制代码
三. Vue CLI
CLI:Command-Line-Interface 翻译为命令行界面, 但是俗称脚手架.
Vue CLI是一个官方发布 vue.js 项目脚手架.
使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
1.1. 安装Vue脚手架
安装:
npm install -g @vue/cli
# OR
yarn global add @vue/cli
升级:
npm update -g @vue/cli
# 或者
yarn global upgrade --latest @vue/cli
复制代码
Vue CLI >= 3 和旧版使用了相同的 vue 命令,所以 Vue CLI 2 (vue-cli) 被覆盖了。如果你仍然需要使用旧版本的 vue init 功能,你可以全局安装一个桥接工具:
Vue CLI3的版本
npm install -g @vue/cli-init
# `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同
# CLI2初始化项目
vue init webpack my-project
# CLI3初始化项目
vue create my-project
复制代码
1.2. Vue CLI2详解
1.21. 目录结构详解
1.3. runtime-only和runtime-compiler
1.31. render和template
1.32. Vue程序运行过程
- runtime-compiler:template -> ast(抽象语法树) -> render ->vdom -> UI
- runtime-only:render ->vdom -> UI
1.4. build和dev
1.5. 修改配置:webpack.base.conf.js起别名
1.6. Vue CLI3
- vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
- vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目
- vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
- 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
1.61. 项目配置
1.62. 目录结构详解
1.63. 自定义配置:起别名
四. Vue-Router
路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动. — 维基百科
1.1. 后端路由和前端路由
- 后端渲染/后端路由
- 前后端分离
- SPA/前端路由
后端渲染: 服务器直接生产渲染好对应的HTML页面, 返回给客户端进行展示。比如:jsp页面
前端渲染: 浏览器中显示的网页中的大部分内容,都是由前端写的 js 代码在浏览器中执行,最终渲染出来的网页。(后端返回JSON数据,前端利用预先写的html模板,循环读取JSON数据,拼接字符串,并插入页面。)
后端路由: 浏览器在地址栏中切换不同的 url 时,每次都向后台服务器发出请求,服务器响应请求,服务器渲染好整个页面, 并且将页面返回给客户端。
前端路由: 核心-改变URL,但是页面不进行整体的刷新。
1.2. SPA页面
SPA:单页面富应用,整个网页只有一个html页面。
SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
前端路由的核心是改变URL,但是页面不进行整体的刷新。
1.3. URL的hash
URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
我们可以通过直接赋值location.hash来改变href, 但是页面不发生刷新.
location.href
"http://192.168.1.101:80/"
location.hash = '/foo'
"http://192.168.1.101:80/#/foo"
复制代码
1.4. HTML5的history模式
- pushState
- back
- forward
- replaceState
- go
history.pushState({},'','/foo')
location.href
"http://192.168.1.101:80/foo"
history.replaceState({},'','/foo')
location.href
"http://192.168.1.101:80/foo"
"http://192.168.1.101:80/foo"
history.go(-1)
location.href
"http://192.168.1.101:80/"
history.go(1)
"http://192.168.1.101:80/foo"
复制代码
1.5. Vue Router
vue-router是基于路由和组件的
- 路由用于设定访问路径, 将路径和组件映射起来.
- 在vue-router的单页面应用中, 页面的路径的改变就是组件的切换.
1.6. 安装和使用路由
第一步:导入路由对象,并且调用 Vue.use(VueRouter)
第二步:创建路由实例,并且传入路由映射配置
第三步:在Vue实例中挂载创建的路由实例
import Vue from 'vue'
import VueRouter from 'vue-router'
路由懒加载
const Home = () => import('../components/Home.vue')
const About = () => import('../components/About.vue')
const Message = () => import('../components/Message.vue')
const news = () => import('../components/news.vue')
Vue.use(VueRouter)
使用vue-router的步骤:
第一步: 创建路由组件
第二步: 配置路由映射: 组件和路径映射关系
第三步: 使用路由: 通过<router-link>和<router-view>
// 定义路由
const routers = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home,
// 嵌套路由
children: [
{
path: 'message',
component: Message,
},
{
path: 'news',
component: News,
},
{
path: '',
redirect: 'message'
}
]
},
{
path: '/about',
component: About
}
]
// 创建router实例
const router = new VueRouter({
routers,
// 使用HTML5的history模式
mode: 'history'
// 修改active-class属性的默认类名(router-link-active)
LinkActiveClass: 'active'
})
// 导出router实例
export default router
// 挂载到vue实例
new Vue({
el: '#app',
router,
render: h => h(APP)
})
<router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.
<router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件.
网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级.
在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.
复制代码
1.7. router-link 其他属性
- to:, 属性: to, 用于指定跳转的路径.
- tag:tag可以指定之后渲染成什么组件, 比如上面的代码会被渲染成一个
- 元素, 而不是
- replace:replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中
- active-class:当对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class, 设置LinkActiveClass可以修改默认的名称.
- 在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类.
- 但是通常不会修改类的属性, 会直接使用默认的router-link-active即可.
1.8. 页面路由跳转
<button @click="linkToHome">首页</button>
<script>
export default {
name: 'App',
methods: {
linkToHome() {
this.$router.push('/home')
}
}
}
</script>
复制代码
1.9. 动态路由
1.91. params的类型
- 配置路由格式: /router/:id
- 传递的方式: 在path后面跟上对应的值
- 传递后形成的路径: /router/123, /router/abc
//拿到用户名(userId是自己router中定义的路径)
{
path: '/user/:userId',
component: User
}
<router-link to="/user/123">用户</router-link>
<div>
<h3>{{$route.params.userId}}</h3>
</div>
{{this.$route.params.userId}}
复制代码
1.92. query的类型
- 配置路由格式: /router, 也就是普通配置
- 传递的方式: 对象中使用query的key作为传递方式
- 传递后形成的路径: /router?id=123, /router?id=abc
获取参数
{{$route.params}}
{{$route.query}}
复制代码
1.93. 两种传递参数的方式
传递参数方式一:
传递参数方式二: JavaScript代码
1.94. router是有区别的
-
router.push方法
-
$route为当前router跳转对象里面可以获取name、path、query、params等
2.0. 路由的懒加载
作用:将路由对应的组件打包成一个个的js代码块,只有在这个路由被访问到的时候,才加载对应的组件。
方式一: 结合Vue的异步组件和Webpack的代码分析.
const Home = resolve => { require.ensure(['../components/Home.vue'], () => { resolve(require('../components/Home.vue')) })};
方式二: AMD写法
const About = resolve => require(['../components/About.vue'], resolve);
方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
const Home = () => import('../components/Home.vue')
复制代码
2.1. 嵌套路由
2.2. 导航守卫
- vue-router提供的导航守卫主要用来监听监听路由的进入和离开的.
- vue-router提供了beforeEach和afterEach的钩子函数, 它们会在路由即将改变前和改变后触发.
首先, 我们可以在钩子当中定义一些标题, 可以利用meta来定义
其次, 利用导航守卫,修改我们的标题.
const routers = [
{
path: '/',
redirect: '/home'
},
{
path: '/home',
component: Home,
meta: {
title: '首页'
}
},
{
path: '/about',
component: About,
title: '关于'
}
]
const router = new VueRouter({
routers,
})
router.beforeEach((to, from, next) => {
window.document.title = to.meta.title;
next()
})
export default router
导航钩子的三个参数解析:
to: 即将要进入的目标的路由对象.
from: 当前导航即将要离开的路由对象.
next: 调用该方法后, 才能进入下一个钩子.
复制代码
2.3. keep-alive
keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
- 两个属性:
- include – 字符串或正则表达,只有匹配的组件会被缓存
- exclude – 字符串或正则表达式,任何匹配的组件都不会被缓存
router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存。
通过create声明周期函数来验证。
2.4. TabBar案例
-
如果在下方有一个单独的TabBar组件,你如何封装
- 自定义TabBar组件,在APP中使用
- 让TabBar出于底部,并且设置相关的样式
-
TabBar中显示的内容由外界决定
- 定义插槽
- flex布局平分TabBar
-
自定义TabBarItem,可以传入 图片和文字
- 定义TabBarItem,并且定义两个插槽:图片、文字。
- 给两个插槽外层包装div,用于设置样式。
- 填充插槽,实现底部TabBar的效果
-
传入 高亮图片
- 定义另外一个插槽,插入active-icon的数据
- 定义一个变量isActive,通过v-show来决定是否显示对应的icon
-
TabBarItem绑定路由数据
- 安装路由:npm install vue-router —save
- 完成router/index.js的内容,以及创建对应的组件
- main.js中注册router
- APP中加入组件
-
点击item跳转到对应路由,并且动态决定isActive
- 监听item的点击,通过this.$router.replace()替换路由路径
- 通过this.$route.path.indexOf(this.link) !== -1来判断是否是active
-
动态计算active样式
- 封装新的计算属性:this.isActive ? {‘color’: ‘red’} : {}
代码实现
五. Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。简单的讲就是把需要多个组件共享的变量全部存储在一个对象里面。
- 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
- Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
1.1. 单界面的状态管理
1.2. Vuex状态管理图例
1.3. Vuex核心的概念:
-
State
State单一状态树 State: { count: 0 } 复制代码
-
Getters
/* 就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。 getters默认是不能传递参数的, 如果希望传递参数, 那么只能让getters本身返回另一个函数 */ const store = new Vuex.Store({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } } }) 复制代码
-
Mutation
Vuex的store状态的更新唯一方式:提交Mutation Mutation主要包括两部分: 1.字符串的事件类型(type) 2.一个回调函数(handler),该回调函数的第一个参数就是state。 mutation的定义方式 mutations: { increment(state){ state.count++ } } 通过mutation更新 increment(){ this.$store.commit('increment') } 在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数 参数被称为是mutation的载荷(Payload) Mutation响应规则 当给state中的对象添加新属性时, 使用下面的方式更新数据: 方式一: 使用Vue.set(obj, 'newProp', 123) 方式二: 用心对象给旧对象重新赋值 Mutation常量类型 – 概念 使用常量替代Mutation事件的类型. 通常情况下, 不要再mutation中进行异步的操作 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照. 复制代码
-
Action
/* Action类似于Mutation, 但是是用来代替Mutation进行异步操作的. 在Vue组件中, 如果我们调用action中的方法, 那么就需要使用dispatch,同样的, 也是支持传递payload */ mutations: { increment(state){ state.count++ } }, actions: { increment(context,payload) { setTimeout(()=>{ context.commit('increment', payload) }, 3000) } } 复制代码
-
Module
/* 由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割: */ const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... } } const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态 store.state.b // -> moduleB 的状态 复制代码
1.4. 项目结构
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
复制代码