暗号: 男枪吃大油条
Vue的优点:
a) MVVM的开发模式,从dom中解脱出来,双向数据绑定;
b) 数据更新采用异步事件机制;
c) 采用单向数据流;
d) 组件式开发;
e) 采用虚拟dom
f) 支持模板和jsx两种开发模式;
g) 可以进行服务端渲染;
h) 扩展性强,既可以向上又可以向下扩展
缺点:
a) 不兼容IE8以下版本
二、 安装与使用:
1、 直接使用CDN引入:
<script src="https://unpkg.com/vue"></script>
2、 下载到本地:
https://vuejs.org/js/vue.js
3、 自动化构建工具:
Vue.js 有一款官方的脚手架生成工具 vue-cli:
λ 全局安装
npm install –g vue-cli
λ 创建项目
vue init webpack myproject
λ 安装依赖包
npm install
λ 目录如下:
build: 用于存放 webpack 相关配置和脚本。
config: 主要存放配置文件,用于区分开发环境、测试环境、线上环境的不同。
src: 项目源码及需要引用的资源文件。
static: 不需要 webpack 处理的静态资源。
test: 用于存放测试文件;
λ 启动服务:
正常开发时,就会运行命令npm run dev,启动一个小型的express 服务。在这个 express 服务中,会使用webpack-dev-middleware 和 webpack-hot-middleware 这两个 中间件,来进行项目的热部署,即每次修改 src 中的文件后,不需要再按浏览器的刷新来更新 代码,启动的 server 服务会自动监听文件的变化并编译,通知浏览器自动刷新。
λ 分析packjson文件:
"scripts": { "dev": "node build/dev-server.js", "start": "node build/dev-server.js --open", "build": "node build/build.js", "e2e": "node test/e2e/runner.js", "test": "npm run e2e", "lint": "eslint --ext .js,.vue src test/e2e/specs" },
dev:为本地开发的启动项;
start:和dev一样;
build:打包线上部署代码
e2e:这个代表端对端测试,确切的来说就是前端的一个自动化测试;这里使用的是java的nightwatch自动化测试工具,使用测试需要进行java的全局安装
三、 实例:
1、一个 Vue 实例其实正是一个 MVVM 模式中所描述的 ViewModel - 因此在文档中经常会使用 vm 这个变量名。
2、在实例化 Vue 时,需要传入一个选项对象,它可以包含数据、模板、挂载元素、方法、生命周期钩子等选项 ;
(1)模板 template:
<template> <div class="root"> <!--在template上使用for循环--> <template v-for="item,index in 5"> <div>{{index}}---{{item}}</div> </template> </div> </template>
(2)数据 data:
在new Vue()的时候,是可以给data直接赋值为一个对象的。
但是在组件Components中,data必须是一个函数返回数据,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。
因为可能在多处调用同一组件,所以为了不让多处的组件共享同一data对象,只能返回函数。
常见问题为何在大型项目中data需要使用return返回数据呢?
原因:不使用return包裹的数据会在项目的全局可见,会造成变量污染
使用return包裹后数据中变量只在当前组件中生效,不会影响其他组件。
(3)方法:
Methods对象来定义方法,并且使用v-on指令来监听DOM事件;
自定义事件:$emit、$on、($broadcast、$dispatch在2.0中已经删除)
(4)生命周期:
在vue实例创建时一系列的初始化步骤:
beforeCreate、created、beforeMount、mounted、beforeDestroy、destroyed 、beforeUpdate、updated、activated、deactivated
(5)数据绑定:
传统的web项目,往往是通过后端模板引擎来进行数据和视图的渲染,例如:php的smarty、java的velocity、node的express、也就是传统的服务端渲染,导致前后端语言混杂在一起,造成渲染后期进行视图更改是,只能通过dom;
a、文本插值
使用{{}}大括号,单次赋值:{{*}} (2.0中使用v-once=””)
b、 属性插值{{}} (2.0 v-bind:id=””)
c、表达式{{}}
(6)指令:
a、参数:v-bind:src=”attr”,给属性绑定值;
或者可以这样写:src=”{{attr}}”
或者可以这样写 :src=“attr”
b、v-on
四、 计算属性:
####三种对于数据变化监听机制:
1、 computed:一个属性依赖于多个属性时,推荐使用
2、 watch():多个属性依赖一个属性是,推荐使用
3、 Set、get:set对一个属性设置值时,会自动的调用相应的回掉函数,get的回调函数会根据,函数内部依赖的属性的改变而自动改变
五、 条件渲染:
1、 v-if
2、 template v-if
如果在一个判断中要对多个元素进行渲染,则需要配合template标签;
3、 v-else
4、 v-else-if
多次进行链式的使用
5、 key管理可复用的元素:
Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染
在这里用key进行元素的唯一标识赋值,降低元素的复用性;
6、 v-if与v-show
v-if 是“真正的”条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销
7、 v-if与v-for一起使用
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级;如果想让v-if优先级更高,则需要在外套一层标签;进行v-if的渲染
六、 列表渲染:
A、数组:
1、 常用写法:
v-for=”item in items”
v-for=”(item,index) in items”
2、 结合template使用:
带有 v-for 的 <template> 标签来渲染多个元素块
B、对象:
1、 常用写法:
v-for="value in object"
v-for="(value, key) in object"
v-for="(value, key, index) in object"
C、整数迭代:
1、 常用写法:
v-for="n in 10"
D、组件:
1、 常用写法:
<my-component v-for="item in items" :key="item.id"></my-component>
E、key:
当 Vue.js 用 v-for 正在更新已渲染过的元素列表时,它默认用 “就地复用” 策略,默认的模式是高效的,但是只适用于不依赖子组件状态或临时 DOM 状态。为了给 Vue 一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一 key 属性。
注:如果加key值,列表重新渲染的元素,是从key变化的第一个元素起到key值变化的最后一个元素结束,中间所有的元素都是重新渲染,不管中间元素的key值变化与否;
F、变异方法,触发视图更新:
Push、pop、shift、unshift、splice、sort、reverse
G、重塑数组、显示过滤/排序结果
对于非变异的数组方法,在操作数组时,一般是采用重新赋值的方式,操作做完原数组,再将结果赋给原属性;如:filter、concat、slice
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
七、 方法与事件:
1、 方法处理器:
v-on 指令监听 DOM 事件
2、 内联语句处理器:
方法的调用方式,可以直接出入参数,或$event当前事件传入
3、 事件修饰符:
.stop:等同于调用event.stopPropagetion()
.prevent:等同于调用event.preventDefault()
.capture:使用capture模式添加事件监听器,使用事件捕获模式
.self:只当事件是从监听元素本身时才会触发回调;
.once:事件将只会触发一次;
4、 按键修饰符:
KeyCode: (键值)
<input v-on:keyup.13="submit">
按键别名:
enter、tab、delete、esc、space、up、down、left、right
八、 表单控件绑定:
1、 v-model 会忽略所有表单元素的 value、checked、selected 特性的初始值。
2、 text、textarea
3、 复选框:
单选:(值为true/false)
<input type="checkbox" id="checkbox" v-model="checked">
多选:(值为数组格式,数组中每一项为input的value值)
<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<input type="checkbox" id="john" value="John" v-model="checkedNames">
4、 单选按钮
值为字符串,当前选中元素的value值;
<input type="radio" id="one" value="One" v-model="picked">
<input type="radio" id="two" value="Two" v-model="picked">
5、 选择列表
单选列表:(值为字符串)
<select v-model="selected">
<option disabled value="">请选择</option> <option>A</option> <option>B</option> <option>C</option>
</select>
注:如果 v-model 表达初始的值不匹配任何的选项,<select> 元素就会以”未选中”的状态渲染。
多选列表:
a) 值为一个数组格式
b) 多选时,需要ctrl配合
c) 无实际意义
d) 比单选列表多一个multiple属性
<select v-model="selected" multiple style=" 50px">
<option>A</option> <option>B</option> <option>C</option>
</select>
6、 绑定value:
(当单选或复选框选中时,此时的model值一般为boolen值,但是根据业务需求,有时我们需要动态绑定值,用法如下)
a) 复选框:
<input type="checkbox" v-model="toggle" v-bind:true-value="a" v-bind:false-value="b">
// 当选中时
vm.toggle === vm.a
// 当没有选中时
vm.toggle === vm.b
b) 单选框:
<input type="radio" v-model="pick" v-bind:value="a">
// 当选中时
vm.pick === vm.a
7、 修饰符
lazy:在 change 事件中同步
number:自动将用户的输入值转为 Number 类型
trim:自动过滤用户输入的首尾空格
九、 组件
组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。
(一) 组件注册:
组件构造器:Vue.extend({ }),2.0中可以不使用构造器进行构造,直接写入对象;
规则:对于自定义标签名,Vue.js 不强制要求遵循 W3C 规则 (小写,并且包含一个短杠),尽管遵循这个规则比较好
1、 全局注册:
全局注册需要在根实例初始化前注册,这样才能使组件在任意实例中被使用,方式:Vue.component(“组件名”,组件)
2、 局部注册:
组件只能在被注册的组件中使用,而无法在其他组件中使用.
3、 注册语法糖:
全局注册和局部注册(使用components属性,值为对象,组件名和组件定义对象是键值对的关系);
(二) 组件选项:
1、 template:注意分析模板,is使用;
2、 el和data两个属性,不可以直接赋值,比如data如果直接赋值,组件使用多次的时候,多个组件将公用一个对象;如果一个改变,则所有都会改变;所以使用function方式,每次return出一个新的对象;el类似;由于el很少直接在组件中挂载,所以可以忽略;
3、 props:
组件间的作用域是孤立的,这里跟angular的指令作用域有所不同,angular指令的默认作用域是公用的,不管是在子指令还是父指令中定义的;在vue中,props起到了父组件向下与子组件间的桥梁作用;子组件给父组件发送消息,需要events;
ν 驼峰命名:html的属性不区分大小写;
ν 动态props:
通过v-bind进行绑定;
如果要进行数值的传递,必须通过动态绑定,否则直接传递过去的是个字符串;
ν props的接受:
在子组件中使用props属性进行接受,props的值可以为数组(不可做校验),可以为对象(可以做校验)
ν props数据的处理:
不应该在子组件中进行prop值的改变,因为prop的值是随父组件进行更变,如果在组件中直接对prop进行改变,vue则会抛出警告
正确的处理方式:
a) 定义一个局部变量,并用 prop 的值初始化它:(只会在初始化时,进行一次赋值,后期不会动态改变)
props: ['count'],
data: function () { return { counter: this.count } }
b) 定义一个计算属性,处理prop的值并返回:(进行属性值的动态跟踪)
props: ['size'],
computed: { stringRevese: function () { return string.split('').reverse().join('') } }
ν 绑定类型:
1) 默认时一直跟随父组件值得变化而变化,
2) .sync:双向绑定(取消);在2.3.0以后,再次以事件监听实现双向数据流;如:
父组件:
<comp :foo="bar" @updata="val => bar = val"></comp>
子组件:
this.$emit('update:foo', newValue)
3) .once:单次绑定,子组件接受一次父组件传递的数据后,单独对这份数据进行维护;(2.0版本后取消此修饰符)
注:如果prop传递的是一个引用类型的数据,如对象或数组,即使单向绑定,子组件也会影响父组件;(传递为单向传递)
ν Props验证:
1) 基础类型检测:prop:Number,接受的参数为原生的构造器:String、Number、Boolen、Function、Object、Aarry、Symbol、Null(意味着任何类型都行)
2) 可以为多种类型:props:[Number,String]
3) 参数必须:props:{type:Number,required:true} 对象的写法
4) 参数默认:props {type:Number,default:0} 对象的写法
5) 绑定类型:props:{twoWay:true} 校验数据绑定类型,如果非双向绑定会抛出一条警告;(这个不用了)
6) 自定义验证函数:props:{validator:function(val){ return val>1},这样就可以验证一个数值是否大于1;
(三) 非Prop属性:
1、 在组件上使用自定义属性,如果不在子组件中使用props进行接受,则此属性会被默认添加到组件的根元素上;
2、 在组件上使用html元素已有的属性,如class、style等,则组件上的属性值,会和组件的根节点的相应属性值进行合并等特性;如type等属性值,会进行覆盖或替换;如:组件中的input的type为number,使用组件时传入的type值为text,则组件内部的input的type值将被替换为text;
(四) 组件间通信:
1、 直接访问:
1) $parent:父组件实例
2) $children:包含所有子组件实例
3) $root:组件所在的根实例;
这三个属性都挂载在组件的this上,不推荐这样做,会造成父子组件间的强耦合;
2、 自定义事件监听:
1) 组件实例化时,在组件的标签上使用@进行事件的监听;
3、 自定义事件触发机制:
1) $emit:在实例本身上触发事件在子组件的钩子函数中去触发事件;
4、 子组件索引:
通过ref指令,绑定一个子组件,可以在父组件中使用this.$refs.属性名 的方式获取子组件;
5、 非父子通信:
思想:建立一个空的vue实例作为中央事件总线,即全局的vue实例;
代码如下:
var bus = new Vue() // 触发组件 A 中的事件 bus.$emit('id-selected', 1) // 在组件 B 创建的钩子中监听事件 bus.$on('id-selected', function (id) { // ... })
五) 内容分发:
1、 基础用法:
提供一种混合父组件内容与子组件自己模板的方式叫做内容分发;
Slot标签;
2、 使用需求:
当子组件中的dom结构以及展示的内容由父组件决定时
3、 编译作用域:
如果在子组件的模板中使用{{}}解析数据,则模板会解析自己作用域中的数据,而不是解析父组件作用中的数据;
4、 默认slot:
<slot>标签允许有一个匿名slot,不需要name值,作为找不到匹配的内容片段的回退插槽,如果没有默认的slot,这些找不到匹配的内容片段将被忽略;
5、 作用域插槽:
在父组件的slot模板中使用子组件中数据;如:
//子组件 <div class="child"> <slot text="hello from child"></slot> </div> //父组件 <div class="parent"> <child> <template scope="props"> <span>hello from parent</span> <span>{{ props.text }}</span> </template> </child> </div>
在父级中,具有特殊属性 scope 的 <template> 元素必须存在,表示它是作用域插槽的模板。scope 的值对应一个临时变量名,此变量接收从子组件中传递的 props 对象;
在slot中可以动态绑定属性值,绑定的属性需要在父组件的scope中进行显示声明;
(六) 动态组件:
1、基础用法:
component 标签上 is 属性决定了当前采用的子组件,:is 是 v-bind:is 的缩写,绑定了父 组件中 data 的 currentView 属性。顶部的 ul 则起到导航的作用,点击即可修改 currentView 值,也就修改 component 标签中使用的子组件类型,需要注意的事,currentView 的值需要 和父组件实例中的 components 属性的 key 相对应。(起到路由的作用)
2、keep-alive
在component 标签外层包裹一个 keep-alive 标签,可以将切换出去的组件保留在内存中,避免重新 渲染。
十、 指令
1、 内置指令:
1) V-bind
2) V-model
3) V-if/ v-else-if/v-else/v-show
4) V-for
5) V-on
6) V-text
7) V-html
8) v-pre 指令相对简单,就是跳过编译这个元素和子元素,显示原始的 {{}}Mustache 标 签,用来减少编译时间。
9) v-cloak 指令相当于在元素上添加了一个 [v-cloak] 的属性,直到关联的实例结束编译。 官方推荐可以和 css 规则 [v-cloak]{ display :none } 一起使用,可以隐藏未编译的 Mustache 标签直到实例准备完毕
10) v-once 指令是 Vue.js 2.0 中新增的内置指令,用于标明元素或组件只渲染一次,即使随 后发生绑定数据的变化或更新,该元素或组件及包含的子元素都不会再次被编译和渲染。
2、 自定义指令基础:
1) 注册:通过 Vue.directive(id, definition) 方法注册一个全局自定义指令,接收参数 id 和定义对象。id 是指令的唯一标识,定义对象则是指令的相关属性及钩子函数。
也可以通过在组件的 directives 选项注册一个局部的自定 义指令
2) 指令的定义对象:
主要包含钩子函数:
bind: 只被调用一次,在指令第一次绑定到元素上时调用。用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
inserted: 被绑定元素插入父节点时调用(父节点存在即可调用,不必存在于 document 中)。
update : 所在组件的 VNode 更新时调用,但是可能发生在其孩子的 VNode 更新之前。指令的值可能发生了改变也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新
componentUpdated: 所在组件的 VNode 及其孩子的 VNode 全部更新时调用。
unbind :指令从元素上解绑时调用,只调用一次。
注:如果我们只需要使用 update 函数时,可以直接传入一个函数代替定义对象;
指令绑定的值:
a) data 中的属性,使用<div v-my-directive="constant string"/></div>
b) 绑定字符串需要加单引号,<div v-my-direcitve="'constant string'"></div>
c) 利用字面修饰符后无需使用单引号
<div v-my-directive.literal="constant string"></div>
d) 受对象字面量或任意合法的 JavaScript 表达式:
<div v-my-directive="{ title : 'Vue.js', author : 'You'}" ></div> <div v-my-directive="isExist ? 'yes' : 'no'" ></div>
3) 指令的实例属性:
a) el: 指令所绑定的元素,可以用来直接操作 DOM 。
b) binding: 一个对象,包含以下属性:
name: 指令名,不包括 v- 前缀。
value: 指令的绑定值, 例如: v-my-directive="1 + 1", value 的值是 2。
oldValue: 指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression: 绑定值的表达式形式。 例如 v-my-directive="1 + 1" , expression 的值是 "1 + 1"。
arg: 传给指令的参数。例如 v-my-directive:foo, arg 的值是 "foo"。
modifiers: 一个包含修饰符的对象。 例如: v-my-directive.foo.bar, 修饰符对象 modifiers 的值是 { foo: true, bar: true }。
c) vnode: Vue 编译生成的虚拟节点,查阅 VNode API 了解更多详情。
d) oldVnode: 上一个虚拟节点,仅在 update 和 componentUpdated 钩子中可用。
3、 指令与组件的区别:
组件一般是指一个独立实体,组件之间的关系通常都是树状。
指令是一种Decorator模式,用以改写某个组件的默认行为,或者增强使其获得额外功能,一般来说可以在同一个组件上叠加若干个指令,使其获得多种功能。
十一、 vue-route
(一) 基本用法:
1、 router-link:
a) <router-link> 组件支持用户在具有路由功能的应用中(点击)导航。 通过 to 属性指定目标地址,默认渲染成带有正确链接的 标签,可以通过配置 tag 属性生成别的标签,<router-link> 比起写死的 会好一些;
b) props:路由参数:
to:格式为字符串或者是对象;
写法:
<router-link to="home">Home</router-link> <router-link :to="'home'">Home</router-link> <router-link :to="{ path: 'home' }">Home</router-link> <router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link> <router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link> replace: 设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录
append: 在当前(相对)路径前添加基路径。例如,我们从 /a 导航到一个相对路径 b,如果没有配置 append,则路径为 /b,如果配了,则为 /a/b
注:另一个路径不允许加/;
tag:将touter-link转换为另外一种标签;
active-class:设置 链接激活时使用的 CSS 类名。默认值可以通过路由的构造选项 linkActiveClass 来全局配置。
2、 router-view
渲染对应的路由配置中 components 下的相应组件;可以和<transition> 、 <keep-alive>等配合使用,两个结合一起用,要确保在内层使用 <keep-alive>:
<transition>
<keep-alive>
<router-view></router-view>
</keep-alive>
</transition>
3、 路由信息对象:
路由信息对象表示当前激活的路由的状态信息,是不可变得,每次成功的导航都会产生一个新的对象;
出现的地方:
λ 组件内部的钩子函数中;
λ 导航的钩子函数中
λ watch监听的$route对象中的(to,from)参数:
例:
watch: { '$route' (to, from) { } }
scrollBehavior 方法的参数
路由信息对象的属性:
$route.path:字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
$route.params:包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
$route.query: 一个key/value对象,表示URL查询参数,例如对路径/foo?user=1,则有route.query.user == 1,如果没有查询参数,则是个空对象。
$route.matched:一个数组,包含当前路由的所有嵌套路径片段的 路由记录 。路由记录就是 routes 配置数组中的对象副本(还有在 children 数组)
$route.name:当前路由的名称
4、 router构造配置
mode: 配置路由模式:hash | history | abstract
base:应用的基路径。例如,如果整个单页应用服务在 /app/ 下,然后 base 就应该设为 "/app/"。(这里有坑)
linkActiveClass: 全局配置 <router-link> 的默认『激活 class 类名
5、 router实例:
属性:
router.app: 配置了 router 的 Vue 根实例。
router.mode: 路由使用的 模式。
router.currentRoute:当前路由信息对象;
方法:
router.push(location) :进行路由的跳转,并且保存到浏览器的history中
router.replace(location):进行路由的替换,不会将上次的路由保存到history中
router.go(n):执行浏览器的前进动作,n代表前进的步数,负数代表后退
router.back(): 执行浏览器的后退动作
router.forward()
router.addRoutes(routes): 动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组
6、 动态路由匹配:
书写方式:
{ path: '/user/:id', component: User }
{ path: '/user/:username/post/:post_id', component: User }
上述的动态值通过 $route.params去接受
路由参数的变化:
使用路由参数时,例如从 /user/foo 导航到 user/bar,原来的组件实例会被复用。因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用;
解决办法:
watch(监测变化) $route 对象:
7、 嵌套路由:
在 VueRouter 的参数中使用 children 配置,属性值为路由对象数组;
注意:在子路由的path属性中设置路由地址时,不可以加“/”,否则将会以根路由进行添加;
8、 多视图多组件渲染:
多视图对应多组件,使用的路由对象属性为:components;
例:
html:
<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>
js:
{ path: '/', components: { default: Foo, a: Bar, b: Baz } }
9、 重定向:
属性为redirect,属性值可以为{name:“foo”}或“foo”或者为一个方法的返回值
例:
{ path: '/a', redirect: { name: 'foo' }}
10、 别名:
例:{ path: '/a', component: A, alias: '/b' }
当用户访问/b时,路由显示的是/b,其实规则为/a
11、 路由的钩子函数:
1) 全局钩子:(一般不使用)
使用router的实例对象进行注册;当一个导航触发时,全局的 beforeEach钩子按照创建顺序调用。钩子是异步解析执行,此时导航在所有钩子 resolve 完之前一直处于 等待中
三个参数:
next(): 进行管道中的下一个钩子
next(false): 中断当前的导航。重置到 from 路由对应的地址
next('/') 或next({ path: '/' }):当前导航被中断,重定向到另一个新的导航:
例:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {})
2) 路由的独享钩子:
在路由的配置项中定义:beforeEnter属性;
{ path: '/foo', component: Foo, beforeEnter: (to, from, next) => { } }
3) 组件内的路由钩子:
beforeRouteEnter:路由跳转之前执行,不能获取this,组件实例还没有创建;
beforeRouteUpdate:路由重新跳转之前,该方法在组件复用时调用,该钩子函数中可以访问组件的实例this
beforeRouteLeave:离开该组件的对应路由时调用,可以访问实例this,一般在用户未保存信息时,不允许跳转相应的路由,next(false)阻止;
关于beforeRouteEnter钩子不能访问this,可以通过一个回调给next来访问组件实例,例:
beforeRouteEnter (to, from, next) { next(vm => { // 通过 `vm` 访问组件实例 }) }
12、 数据的获取
1) 导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示『加载中』之类的指示。
2) 导航完成之前获取:导航完成前,在路由的 enter 钩子中获取数据,在数据获取成功后执行导航。
注:
导航完成之后获取需要借助组件的watch属性,在动态路由中组件复用时,进行数据的重新请求;
导航完成之前获取借助beforeRouteUpdate, 在动态路由中组件复用时,进行数据的重新请求;
13、 组件的懒加载
1、 vue的异步组件和webpack的require.ensure()相结合,例:
let index = resolve => require.ensure(['@/components/index'], () =>
resolve(require('@/components/index')))
2、 采用amd风格的require,例:
var Hello = resolve => require(['@/components/Hello'], resolve)
3、 把组件按组分块:
提供 require.ensure 第三个参数作为 chunk 的名称,Webpack 将相同 chunk 下的所有异步模块打包到一个异步块里面 —— 这也意味着我们无须明确列出 require.ensure 的依赖(传空数组就行)
例:
const Foo = resolve => require.ensure([], () => resolve (require('./Foo.vue')), 'group-foo') const Bar = resolve => require.ensure([], () => resolve (require('./Bar.vue')), 'group-foo') const Baz = resolve => require.ensure([], () => resolve (require('./Baz.vue')), 'group-foo')
十三、 状态管理:(vuex)
1、 简介:
a) 在一些大型应用中,有时我们会遇到单页面中包含着大量的组件及复杂的数据结构,而 且可能各组件还会互相影响各自的状态,在这种情况下组件树中的事件流会很快变得非常复 杂,也使调试变得异常困难。为了解决这种情况,我们往往会引入状态管理这种设计模式,来 降低这种情况下事件的复杂程度并且使调试变得可以追踪。
b) 每个组件都会拥有自己的状态,也可以理解成自身实例中的 data 对 象。用户在操作的过程中,会通过一些交互行为,例如点击、输入、拖动等修改组件的 状态,而此时往往需要将用户引起的状态变化通知到其他相关组件,让他们也进行对应 的修改。由于 Vue.js 本身的事件流是依赖于 DOM 结构的,组件修改状态后需要经过 一系列冒泡才能达到顶部的组件,而且如果需要修改兄弟组件的状态还需要共同的父组 件再进行一次广播。这种方式无疑是低效而且不易维护的,我们也很难去追踪事件流的 走向。
c) vuex 和 redux 间的差异表示关注,redux 是 React 生态环境中最流行的 Flux 实现。Redux 事实上无法感知视图层,所以它能够轻松的通过一些简单绑定和Vue一起使用。vuex区别在于它是一个专门为 vue 应用所设计。这使得它能够更好地和vue进行整合,同时提供简洁的API和改善过的开发体验。
2、 配置使用:
安装vuex,在main.js中引入vuex进行使用;
import vuex from “vuex”,
Vue.use(vuex)
实例化Vuex:
const store = new Vuex.Store({ state: { count: 0 }, mutations: { increment (state) { state.count++ } } })
将vuex挂载到Vue实例中:
const app = new Vue({ el: '#app', // 把 store 对象提供给 “store” 选项,这可以把 store 的实例注入所有的子组件 store, components: { Counter }, template: ` <div class="app"> <counter></counter> </div> ` })
现在,你可以通过 store.state 来获取状态对象,以及通过 store.commit 方法触发状态变更,我们通过提交 mutation 的方式,而非直接改变 this.$store.state.count,是因为我们想要更明确地追踪到状态的变化
3、 核心概念:
a) state:
ν 数据的定义以及存放数据的属性,类似于组件内部的data属性;所有在vuex使用的数据都必须在此属性中进行定义与声明;不建议将应用所有的数据都放入state,交互性低,比较单一使用的数据,建议放在组件内部定义;
ν mapState辅助函数
写法:
对象写法:
computed:mapState({ //写法一:箭头函数可使代码更简洁 count:state=> state.count //写法二:传字符串参数 'count' 等同于 `state => state.count` count:state=> state.count //写法三:为了能够使用 `this` 获取局部状态,必须使用常规函数 count(state){ return state.count+this.a } })
数组写法:
当映射的计算属性的名称与 state 的子节点名称相同时
computed:mapState([
“count”
])
ν 对象展开运算符:
注意运用此方法时,因为对象扩展运算符是在es提案的stage-3阶段,由于babel进行转码时,在webpack中默认配置的是es-2015,所以需要借助babel-plugin-transform-object-rest-spread插件进行编译;
在babel的配置文件中如下配置:
"plugins": ["transform-runtime", "transform-object-rest-spread"],
在computed属性中的写法如下:
...mapState({...})
上面的mapState的参数可以是对象,也可以是数组,跟mapState辅助函数中的配置一样
b) Getters:
应用场景:
有时候,我们需要对state数据进行一些操作后再在组件中进行使用,如果多个组件都需要操作后的这个数据,我们可能会在多个组件computed中都对state进行操作一次;
解决办法:
使用getters:
getters: { doneTodos: state => { return state.todos.filter(todo => todo.done) } }
组件中使用,跟state的使用一样,可以使用辅助函数,或者对象展开运算符
c) Mutations:
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutations 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数;
定义的方式跟getters一样,例:
mutations: { increment (state) { // 变更状态 state.count++ } }
回调函数的第一个参数为state,第二个参数可以动态传入值,值的类型,可以为基本类型或对象;
提交载荷:
可以使用store.commit(‘increment’,10)
或store.commit({type:’increment’,amount:10})
遵循vue的数据相应规则:
在对象上添加新属性时,使用 Vue.set(obj, 'newProp', 123)或者
state.obj = { ...state.obj, newProp: 123 }
mutation必须是同步函数:
如果为异步,则无法跟踪数据
在组件中直接提交(不推荐)
methods: { ...mapMutations([ 'increment' // 映射 this.increment() 为 this.$store.commit('increment') ]), ...mapMutations({ add: 'increment' // 映射 this.add() 为 this.$store.commit('increment') }) }
d) actions:
action提交的是mutation,而不是直接更变状态。
action可以包含任意异步操作。
注册:
actions: { increment (context) { context.commit('increment') } }
i. 在actions的内部可以获取以一个context参数。这个参数相当于一个store实例,但不是当前应用的store实例本身,因为如果采用modules方式进行数据的管理时,store实例是一个数组;
ii. 可以通过context参数获取state或者getters等等,一般可以通过此参数来提交commit;
分发action:
应用场景:当一次action对应的数据的更改,必须依赖另外一个action中的数据更改时,必须在本此action中先去触发另外一个action操作;另外一个action可能为同步,也可能为异步,如果为异步需要借助promise或者async函数;
// 以载荷形式分发
store.dispatch('incrementAsync', { amount: 10 }) // 以对象形式分发 store.dispatch({ type: 'incrementAsync', amount: 10 })
在组件中分发action:
methods: { ...mapActions([ 'increment' // 映射 this.increment() 为 this.$store.dispatch('increment') ]), ...mapActions({ add: 'increment' // 映射 this.add() 为 this.$store.dispatch('increment') }) }
通过dispatch触发异步action:
使用promise:
actionA ({ commit }) { return new Promise((resolve, reject) => { setTimeout(() => { commit('someMutation') resolve() }, 1000) }) } actionB ({ dispatch, commit }) { return dispatch('actionA').then(() => { commit('someOtherMutation') }) }
使用async函数:
getData、getOtherData返回的是promise,或者是axiso的数据请求函数
async actionA ({ commit }) { commit('gotData', await getData()) }, async actionB ({ dispatch, commit }) { await dispatch('actionA') // 等待 actionA 完成 commit('gotOtherData', await getOtherData()) }
e) modules:
应用场景:
- 当一个项目比较大时,所有的数据与数据管理都放在同一个store中时,此时的store对象就会变得比较臃肿;
- 当某个大型项目,几个开发团队一起开发时,同时操作一个store,可能会出现冲突等问题,每个团队负责的产品链的数据管理相对独立时,可以采用module的方式管理;
定义:
将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割
如:
const moduleA = { state: { ... }, mutations: { ... }, actions: { ... }, getters: { ... } } const store = new Vuex.Store({ modules: { a: moduleA, b: moduleB } }) store.state.a // -> moduleA 的状态
模块的局部状态:
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象
对于模块内部的 action,局部状态通过 context.state 暴露出来, 根节点状态则为 context.rootState
在getter中根节点状态会作为第三个参数暴露出来
持续更行ing...
上面的mapState的参数可以是对象,也可以是数组,跟mapState辅助函数中的配置一样
b) Getters:
应用场景:
有时候,我们需要对state数据进行一些操作后再在组件中进行使用,如果多个组件都需要操作后的这个数据,我们可能会在多个组件computed中都对state进行操作一次;
解决办法:
使用getters: