重新对博客内容进行了修正,为了避免自己的文字引来不必要的歧义,所以删自己关于技术方面的,计划这一分类更多的采用引用为主。
本文转自简书《教你如何学好vue《文档》》,但对内容有所删减(Vue 历史、作者以及案例)以及补充,若想了解还请移步至原文。
ECMAScript 6.0(以下简称 ES6 )是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了也叫 ECMAScript 2015 。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言
ES6 新增了 let
命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在 let
命令所在的代码块内有效。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
for (let i = 0; i < 10; i++) {
// ...
}
console.log(i); // ReferenceError: i is not defined
const
声明一个只读的常量。一旦声明,常量的值就不能改变。
const PI = 3.1415;
PI // 3.1415
PI = 3; // TypeError: Assignment to constant variable.
const foo = {};
// 为 foo 添加一个属性,可以成功
foo.prop = 123;
foo.prop // 123
// 将 foo 指向另一个对象,就会报错
foo = {}; // TypeError: "foo" is read-only
const
和let
不存在变量提升
let a = 1;
let b = 2;
let c = 3;
// ES6 允许写成下面这样。
let [a, b, c] = [1, 2, 3];
// 对象的解构赋值
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
let place = "world"
// 变量place没有声明
let msg = `Hello, ${place}`;
属性的简洁表示法
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同于
const baz = {foo: foo};
function f(x, y) {
return {x, y};
}
// 等同于
function f(x, y) {
return {x: x, y: y};
}
f(1, 2) // Object {x: 1, y: 2}
函数的扩展 ES6 允许使用 =>
(箭头)定义函数。
var f = v => v;
// 等同于
var f = function (v) {
return v;
};
如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。
var sum = (num1, num2) => {
return num1 + num2;
}
箭头函数有几个使用注意点:
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise 对象是一个构造函数,用来生成Promise实例。下面代码创造了一个Promise实例。
const promise = new Promise(function(resolve, reject) {
if (/* 异步操作成功 */){
resolve(value); // 容器状态为成功
} else {
reject(error); // 容器状态为失败
}
});
promise.then(function() {
console.log('resolved.');
});
对于错误返回的捕获
// bad
promise.then(function(data) {
// success
}, function(err) {
// error
});
// good
promise.then(function(data) { //cb
// success
}).catch(function(err) {
// error
});
内容补充:为什么要用 Promise?
由于 Javascript 是单线程的,所以对于普通函数来讲执行顺序是 fun1 > fun2 > fun3 > fun4,但如是多个异步函数,则这种多线程操作就会导致执行顺序不固定,为此我们为了能保障执行顺序就需要在一个异步函数中再去运行另一个异步函数,这个行为称之为 回调地狱 。
Promise 本身是一个同步函数。它可以理解为一个容器,该容器用来监控其中异步函数的执行结果。
Promise 函数执行后,可以通过链式追加 .then() 决定函数成功或失败后需要操作的内容。
promise.all() 可以将多个 Promise 实例包装成一个新的 Promise 实例。成功的时候返回的是一个结果数组,而失败的时候则返回最先被 reject 失败状态的值。
promise.race() 顾名思义,就是赛跑的意思,Promise.race([p1, p2, p3]) 里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数是什么?一句话,它就是 Generator 函数的语法糖。
基本用法
async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。当函数执行的时候,一旦遇到 await 就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。
function timeout(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
async function asyncPrint(value, ms) {
await timeout(ms);
console.log(value);
}
asyncPrint('hello world', 50); // 上面代码指定 50 毫秒以后,输出 hello world。
返回 Promise 对象
async function f() {
return 'hello world';
}
f().then(v => console.log(v)) // "hello world"
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD(require.js 库,专门用于在浏览器中进行模块化开发,几乎已经淘汰了) 两种。前者用于服务器(Node.js),后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
为了学习方便,我们这里使用 Node.js 环境来举例说明 ES6 模块规则
这是一个 Node 文件,将其命名为 foo.js ,内容如下:
// export 也用于导出
// 但是可以多次使用
export const a = 1
export const b = 2
export const c = 3
export const d = 4
function add(x, y) {
return x + y
}
// 等价于 module.exports = add
// export default 只能使用一次
export default add
// 模块中有多个成员,一般都使用 export xxx
// 如果只有一个成员,那就 export default
补充内容:module.export 与 export 的区别
export 导出的源码逻辑如下
module = {export: {}}
var export = module.export
也就是说 export === module.export
当 module.export 内有多个属性的时候,也可以通过 export 导出
但若想单独导出方法或变量时,则只能使用 export default
因为当 export 重定义方法后就与 module.export 脱离了联系
将 foo.js 注入项目入口文件 main.js:
// 加载 export default 导出的成员
// import foo from './foo'
// console.log(foo)
// 按需加载 export 导出的成员
// import { a, b } from './foo.mjs'
// console.log(a, b)
// 一次性加载所有成员(包括 default 成员)
// import * as foo from './foo.mjs'
// console.log(foo)
// console.log(foo.a)
// console.log(foo.default(10, 3)) // 很少这样去访问 default 成员
// 为了方便,先加载默认 default 成员,然后加载其它 export 成员
// import abc, { a, b } from './foo'
// console.log(abc) // export default 成员
// console.log(a, b) //
// 可以使用 as 起别名
import { a as aa } from './foo'
console.log(aa)
// console.log(foo(1, 2))
补充内容:
import * as xxx from ‘xxx’ // 会将若干 export 导出的内容组合成一个对象返回
import xxx from ‘xxx’ // 只会导出这个默认的对象作为一个对象
它本身只是一个用于数据驱动视图更新的库,但随着衍生生态的发展,如今已经有了 Vue Router
、Vuex
、Vue CLI
、Vue Server Renderer
等功能库,所以当 Vue 和这些核心功能库结合到一起的时候,我们称之为一个框架(Vue 全家桶)。
Vue.js 不支持 IE8 及其以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器。最新稳定版本:2.6.10
直接下载
CDN
<script src="https://cdn.jsdelivr.net/npm/vue"></script> // 最新稳定版
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script> // 指定版本
使用 npm 下载(最好创建一个 package.json 文件,用来存储第三方包依赖信息)
npm install vue // 最新稳定版
npm install vue@版本号 // 指定版本
<meta charset="UTF-8">
<title>Document</title>
<div id="app">
{{ message }}
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
new Vue({
el: '#app', // 实例选项
data: {
message: 'Hello Vue.js!' // 声明式渲染
}
})
</script>
实例选项 - el
类型:string | Element:
详细: 提供一个在页面上已存在的 DOM 元素作为 Vue 实例的挂载目标。可以是 CSS 选择器,也可以是一个 HTMLElement 实例。在实例挂载之后,元素可以用 vm.$el 访问
注意:el 不能是 html、body 节点
实例选项 - data
new Vue({
el: '#app',
data(){
return {message:"demo"}
}
})
new Vue({
el: '#app',
data:{message:"demo"}
})
组件的定义只接受 function ,模板中访问的数据必须初始化到 data 中
// 文本
<p>{{ message }}</p>
<span>{{ message }}</span>
<strong>{{ message }}</strong>
// JavaScript 表达式
<p>{{ number + 1 }}</p>
<p>{{ number + 1 > 10 ? 'number大于10' : 'number小于10' }}</p>
<p>{{ arr }}</p>
<p>{{ message.split('').reverse().join('') }}</p>
指令 (Directives) 是带有 v- 前缀的特殊特性。指令特性的值预期是单个 JavaScript 表达式 (v-for 是例外情况,稍后我们再讨论)
v-if :指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回真值的时候被渲染。
<h1 v-if="awesome">Vue is awesome!</h1>
v-else :
<div v-if="Math.random() > 0.5">
Now you see me
</div>
<div v-else>
Now you don't
</div>
v-else-if :
<div v-if="type === 'A'">
A
</div>
<div v-else-if="type === 'B'">
B
</div>
<div v-else>
Not A/B
</div>
v-show :
<h1 v-show="ok">Hello!</h1>
一般来说,v-if 有更高的切换开销,而 v-show 有更高的初始渲染开销。因此,如果需要非常频繁地切换,则使用 v-show 较好;如果在运行时条件很少改变,则使用 v-if 较好
v-text :
<div v-text="text"></div> // 带标签的内容不会被解析
v-html :
<div v-text="text"></div> // 带标签的内容会被解析
v-model(表单输入绑定) :
<input v-model="test"/> //这个指令是一个语法糖,一般只用于input标签
v-for(列表渲染)
v-for="item in todos"
v-for="(item, index) in todos"
v-bind(绑定指令)
绑定指令能绑定元素上的任何属性,常用的比如 id, style, class, src, … 也能绑定自定义属性。
操作元素的 class 列表和内联样式是数据绑定的一个常见需求,因为它们都是属性,所以我们可以用 v-bind 处理它们,在将 v-bind 用于 class 和 style 时,Vue.js 做了专门的增强。表达式结果的类型除了字符串之外,还可以是对象或数组
Class 绑定
// 一般使用
// class 的值就是变量 className 的值
<div v-bind:class="className"></div>
<div v-bind:class="className?'a':'b'"></div>
// 对象语法
// 如果变量isActive 值为真则有active这个类,否则没有
<div v-bind:class="{ active: isActive }"></div>
<script>
data: {
isActive: true
}
</script>
// 渲染为
<div class="active"></div>
// 数组语法(使用偏少)
<div v-bind:class="[activeClass, errorClass]"></div>
<script>
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
</script>
// 渲染为
<div class="active text-danger"></div>
Style 绑定
// 对象语法
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
<script>
data: {
activeColor: 'red',
fontSize: 30
}
</script>
// 直接绑定到一个样式对象通常更好,这会让模板更清晰
<div v-bind:style="styleObject"></div>
<script>
data: {
styleObject: {
color: 'red',
fontSize: '13px'
}
}
</script>
缩写
<a v-bind:href="url">...</a> // 完整语法
<a :href="url">...</a> // 缩写
v-on(监听指令)
可以用 v-on 指令监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
<div id="example-1">
<button v-on:click="counter += 1">Add 1</button>
<p>The button above has been clicked {{ counter }} times.</p>
</div>
var example1 = new Vue({
el: '#example-1',
data: {
counter: 0
}
})
许多事件处理逻辑会更为复杂,所以直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称
<div id="example-2">
<!-- `greet` 是在下面定义的方法名 -->
<button v-on:click="greet">Greet</button>
<!-- DOM的原生事件,可以用特殊变量 $event 把它传入方法 -->
<button v-on:click="say('hi')">Say hi</button>
</div>
var example2 = new Vue({
el: '#example-2',
data: {
name: 'Vue.js'
},
methods: { // 在 `methods` 对象中定义方法
greet: function (event) {
alert('Hello ' + this.name + '!') // `this` 在方法里指向当前 Vue 实例
if (event) { // `event` 是原生 DOM 事件
alert(event.target.tagName)
}
},
say: function (message,ev) {
alert(message)
console.log(ev)
}
}
})
缩写
<a v-on:click="doSomething">...</a> // 完整语法
<a @click="doSomething">...</a> // 缩写
补充内容:修饰符
.stop 阻止事件冒泡
.self 当事件在该元素本身触发时才触发事件
.capture 添加事件侦听器是,使用事件捕获模式
.prevent 阻止默认事件
.once 事件只触发一次
补充内容:《vue中的slot(插槽)》
插槽(Slot)是 Vue 提出来的一个概念,正如名字一样,插槽用于决定将所携带的内容,插入到指定的某个位置,从而使模板分块,具有模块化的特质和更大的重用性。
插槽显不显示、怎样显示是由父组件来控制的,而插槽在哪里显示就由子组件来进行控制。
<template>
<div>
<slotOne1>
<p style="color:red">我是父组件插槽内容</p>
</slotOne1>
</div>
</template>
在父组件引用的子组件中写入想要显示的内容,
我是父组件插槽内容
为组件插槽显示的内容<template>
<div class="slotOne1">
<slot></slot>
</div>
</template>
在子组件中写入slot,slot所在的位置承接的就是父组件中
我是父组件插槽内容
具名插槽
<template>
<div class="slottwo">
<slot name="header"></slot>
<slot></slot>
<slot name="footer"></slot>
</div>
</template>
在子组件中定义了三个 slot 标签,其中有两个分别添加了 name 属性 header 和 footer
<template>
<div>
<slot-two>
<p>啦啦啦,啦啦啦,我是卖报的小行家</p>
<template slot="header">
<p>我是name为header的slot</p>
</template>
<p slot="footer">我是name为footer的slot</p>
</slot-two>
</div>
</template>
在父组件中使用 template 并写入对应的 slot 值来指定该内容在子组件中现实的位置(当然也不用必须写到 template ),没有对应值的其他内容会被放到子组件中没有添加 name 属性的 slot 中
// 基础用法
data() {
return {
str: 'string'
}
}
computed: {
beautifyStr() {
return this.str.split('');
}
}
对 data 属性的监听,说明属性是在 data 中声明过的属性更新时调用监听函数,可选参数分别为新值和旧值,对属性重新设置值只要跟原来的值相等就不会触发函数调用,这一点跟计算属性是相似的,监听某一个值,当被监听的值发生变化时,执行对应的操作,与 computed 的区别是,watch 更加适用于监听某一个值的变化并做对应的操作,比如请求后台接口等,而 computed 适用于计算已有的值并返回结果
// 基础用法
new Vue({
el: '#id',
data: {
firstName: 'Leo',
lastName: 'Alan',
obj1: {
a: 0
}
},
watch: {
// 监听firstName,当firstName发生变化时就会执行该函数
firstName(newName, oldName) {
// 执行需要的操作...
// 注:初始化不会执行,只有当被监听的值(firstName)发生变化时才会执行
},
// 监听lastName
lastName: {
handler(newName, oldName) {
// 执行需要的操作...
},
immediate: true // true: 初始化时就会先执行一遍该监听对应的操作
},
obj1: {
handler() {
// 执行需要的操作...
},
deep: true // 该属性默认值为 false.
// 当被监听的值是对象,只有 deep 为 true 时,对应属性的值( obj1.a )发生变化时才能触发监听事件,但是这样非常消耗性能
},
// 监听对象具体的属性,deep 就不需要设置为 true 了
'obj1.a': {
handler() {
// 执行需要的操作...
}
}
}
})
Vue CLI 是 Vue 的脚手架工具,它可以帮助我们快速生成 Vue 基础项目代码,提供开箱即用的功能特性。
// > 依赖要求:Vue CLI 需要 Node.js 8.9 或更高版本 (推荐 8.11.0+)。
npm install -g @vue/cli
在终端中输入 vue --version
若可以打印出版本号,则代表安装成功。
运行以下命令来创建一个新项目
vue create my-project
内容补充:输入命令后,会跳出如下问题
Project name (baoge) 项目名称(注意这里的名字不能有大写字母,如果有会报错)
Project description (A Vue.js project) 项目描述,也可直接点击回车,使用默认名字
Author () 作者
Runtime + Compiler: recommended for most users 运行加编译,既然已经说了推荐,就选它了
Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specificHTML) are ONLY allowed in .vue files - render functions are required elsewhere 仅运行时,已经有推荐了就选择第一个了
Install vue-router? (Y/n) 是否安装vue-router,这是官方的路由,大多数情况下都使用,这里就输入 y 后回车即可
Use ESLint to lint your code? (Y/n) 是否使用 ESLint 管理代码,ESLint 是个代码风格管理工具
Pick an ESLint preset (Use arrow keys) 选择一个 ESLint 预设,编写 vue 项目时的代码风格,直接 y 回车
Setup unit tests with Karma + Mocha? (Y/n) 是否安装单元测试
Setup e2e tests with Nightwatch(Y/n)? 是否安装 e2e 测试配置完成后,可以看到目录下多出了一个项目文件夹,然后 cd 进入这个文件夹
启动开发模式
npm run serve
项目打包
npm run build
代码检查
npm run lint
开发期间不要关闭命令窗口,如果关了,就重新 npm run serve
浏览器中打开 http://localhost:8080/
看到 vue 初始页面,则说明初始化创建成功了
node_modules // 第三方包存储目录
public // 静态资源,已被托管
src // 源代码
assets // 资源目录,存储静态资源,例如图片等
components // 存储其它组件的目录
App.vue // 根组件
main.js // 入口文件
.gitignore // git 忽略文件
babel.config.js // 不用关心
package.json // 包说明文件
README.md // 项目说明文件
package-lock.json // 包的版本锁定文件
找到 main.js 入口 > 加载 Vue > 加载 App 组件 > 创建 Vue 实例 > 将 App 组件替换到入口节点
组件 Component 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。
推荐把通用组件创建到 components 目录中
单文件组件只是承载组件的容器而已,既不是全局也不是局部,如果要使用这个单文件组件,必须注册
单文件组件模板结构如下:
<template>
<div>foo 组件</div>
</template>
<script>
export default {
data() {},
methods: {}
}
</script>
<style></style>
标签有 scoped 属性时,它的 CSS 只作用于当前组件中的元素在 main.js 文件中,可以在任何组件中使用
import Vue from 'vue'
import Com1 from './components/Com1.vue'
Vue.component('Com1', Com1)
// 接下来就可以在任何组件中使用 Com1 组件了
在某个组价中局部注册使用
<template>
<div>
<!-- 使用 Com2 组件 -->
<Com2></Com2>
</div>
</template>
<script>
import Com2 from './components/Com2'
export default {
components: {
Com2
}
}
</script>
初始化阶段
beforeCreate
表示组件创建前的准备工作,为事件的发布订阅和生命周期的开始做初始化
这个钩子函数中数据拿不到,真实DOM也拿不到,这个钩子在项目中我们没有什么实际用途
beforeCreate () { // 表示组件创建前的准备工作( 初始化事件和生命周期 )
console.log('beforeCreate')
console.log(this.msg) // undefind
console.log(document.querySelector('p')) // null 没有真实DOM
}
created
表示组件创建结束,这个钩子函数中数据拿到了,但是真实DOM没有拿到
这个钩子函数在项目一般数据请求,然后可以进行一次默认数据的修改
created () { // 组件创建结束
console.log('created')
console.log(this.msg) //(vue.js) 有数据
console.log(document.querySelector('p')) //null 没有真实DOM
axios({
url: './data.json'
}).then( res => {
this.msg = res
}).catch( error => {
throw error
})
}
beforeMounte
表示组件装载前的准备工作,判断 el 选项有没有, 判断 template 选项有没有, 如果没有那么需要手动装载,如果有那么通过 render 函数进行模板的渲染
这个钩子函数中数据拿到了,真实DOM没有拿到,这个钩子函数在项目中数据请求,它也可以进行一次数据修改
beforeMount () {
console.log('beforeMount')
console.log(this.msg) //(vue.js) 有数据
console.log(document.querySelector('p')) //null 没有真实DOM
// axios({
// url: './data.json'
// })
// .then( res => {
// this.msg = res
// })
// .catch( error => {
// throw error
// })
}
mounted
表示组件装载结束,就是我们可以在视图中看到了,这个钩子函数中数据拿到了,真实DOM也拿到了
这个钩子函数在项目 DOM 操作就可以进行了, 第三方库的实例化
mounted () {
console.log('mount')
console.log(this.msg) //(vue.js) 有数据
console.log(document.querySelector('p')) //有真实DOM
axios({
url: './data.json'
}).then( res => {
this.msg = res
}).catch( error => {
throw error
})
}
总结:由上对比,我们可以知道, 数据请求越提前越好一些, created 常用于数据的请求和数据的修改, 第三方库的实例化常在 mounted 中进行书写
运行中阶段
beforeUpdate
表示数据更新前的准备工作。这个钩子不主动执行,当数据修改了才会执行。这个钩子函数中数据拿到了,并且拿到的是修改后的数据,DOM也输出了
这个钩子函数更多的工作内容为:生成新的 VDOM , 然后通过 diff 算法进行两次 VDOM 对比
这个钩子在项目中因为他主要做的事情是内部进行的, 所以对我们而言没有太多的操作意义
updated
表示数据更新结束。通过 render 函数渲染真实 DOM 。这个钩子函数的执行也是当数据修改的时候才执行。这个钩子函数中数据拿到了, DOM也拿到了。
总结: 数据更新, 也要进行DOM操作那么, 我们使用update这个钩子
销毁阶段
beforeDestroy
destroyed
这两个钩子无差别,在项目中做善后工作。手动清除一些计时器和一些方法,还有第三方实例化出来的对象
Vue.component ('LifeCircle', {
template: '#life-circle',
methods: {
destroy () {
this.$destroy()
}
},
created () {
this.timer = setInterval( () => {
console.log('1')
},1000)
},
beforeDestroy () {
console.log('beforeDestory')
},
destroyed () {
console.log('destroyed')
clearInterval( this.timer )
// 如果是用$destroy这个方法来清除组件, 那么我们必须手动清除这个组件的外壳
document.querySelector('#app div').remove()
}
})
new Vue ({
el: '#app',
data: {
flag: true
}
})
组件就像零散的积木,我们需要把这些积木按照一定的规则拼装起来,而且要让它们互相之间能进行通讯,这样才能构成一个有机的完整系统。
在真实的应用中,组件最终会构成树形结构,就像人类社会中的家族树一样
在树形结构里面,组件之间有几种典型的关系:父子关系、兄弟关系、没有直接关系。
相应地,组件之间有以下几种典型的通讯方案:
直接的父子关系
父组件通过 this.$refs
访问子组件
子组件 this.$parent
访问其父组件
父组件 this.$children
访问其子组件
父组件通过 Props
给子组件下发数据
我们可以用 v-bind 来动态地将 prop 绑定到父组件的数据。每当父组件的数据变化时,该变化也会传导给子组件
// HelloWorld.vue
<div>
<child :myMessage="parentMsg"></child> // 父组件动态变量 parentMsg,通过 myMessage 传递给子组件
</div>
子组件通过prop属性接受,prop中的变量名不能在data中定义
<script>
import HelloWorld from './components/HelloWorld.vue'
export default {
name: 'app',
props: ['myMessage'] // 通过 props 接收到传递过来的数据,props 是一个数组对象
components: {
HelloWorld
}
}
</script>
Prop 是单向绑定的,当父组件的属性变化时,将传导给子组件,但是反过来不会,这是为了防止子组件无意间修改了父组件的状态。
每次父组件更新时,子组件的所有 prop 都会更新为最新值。这意味着你不应该在子组件内部改变 prop。如果你这么做了,Vue 会在控制台给出警告。
Prop 验证
我们可以为组件的 prop 指定验证规则。如果传入的数据不符合要求,Vue 会发出警告。这对于开发给他人使用的组件非常有用。要指定验证规则,需要用对象的形式来定义 prop。
export default {
props: {
propA: Number, // 基础类型检测 (`null` 指允许任何类型)
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 value > 10
}
}
}
}
type 可以是下面原生构造器:
子组件通过 事件
方式给父组件发送消息
父组件使用 prop 传递数据给子组件。但子组件怎么跟父组件通信呢?
需要在子组件中调用 $emit()
方法发布一个事件
methods: {
incrementCounter: function () {
this.counter += 1
this.$emit('increment') // 发布一个名字叫 increment 的事件
// this.$emit(事件名, '参数对象");
}
}
在父组件中提供一个子组件内部发布的事件处理函数
new Vue({
methods: {
incrementTotal: function () {
this.total += 1
}
}
});
在使用子组件的模板的标签上订阅子组件内部发布的事件
<div id="counter-event-example">
<!--
订阅子组件内部发布的 increment 事件
当子组件内部 $commit('increment') 发布的时候,就会调用到父组件中的 incrementTotal 方法
-->
<button-counter v-on:increment="incrementTotal"></button-counter>
</div>
没有直接关系
简单场景:借助于事件机制进行通讯
有时候,非父子关系的两个组件之间也需要通信。在简单的场景下,可以使用一个空的 Vue 实例作为事件总线:
var bus = new Vue();
// 触发组件 A 中的事件
bus.$emit('id-selected', 1);
// 在组件 B 创建的钩子中监听事件
bus.$on('id-selected', function (id) {
// ...
});
复杂场景:使用状态管理容器(例如 Vue 生态中的 Vuex、React 生态中的 Redux、Mobx 等)
在复杂的情况下,我们应该考虑使用专门的状态管理模式。
利用 sessionStorage 和 localStorage 进行通讯
Vue 不像 jQuery 内置了 ajax 请求函数,在 Vue 中没有提供这样的功能。所以当我们需要在 Vue 中和服务端进行通信的时候可选择的方式会更灵活一些。
注意:Vue 不提供的原因是为了让 Vue 本身更专注于视图部分,保持其渐进灵活的特性
axios 是一个基于 Promise 的第三方 HTTP 客户端请求库,可以用于浏览器或者 Node.js。 axios 本身和 Vue 没有一毛钱关系,只是简单纯粹的封装了 HTTP 请求功能。可以运行在任何支持 JavaScript 环境的平台。
axios 依赖原生的 ECMAScript 6 Promise 支持。如果浏览器不支持 ECMAScript 6 Promise,可以使用 es6-promise 进行兼容处理
npm install axios
const axios = require('axios');
// Make a request for a user with a given ID
axios.get('/user?ID=12345').then(function (response) {
// handle success
console.log(response);
}).catch(function (error) {
// handle error
console.log(error);
}).finally(function () {
// always executed
});
// Want to use async/await? Add the `async` keyword to your outer function/method.
async function getUser() {
try {
const response = await axios.get('/user?ID=12345');
console.log(response);
} catch (error) {
console.error(error);
}
}
axios.post('/user', {
firstName: 'Fred',
lastName: 'Flintstone'
}).then(function(response) {
console.log(response)
}).catch(function(error) {
console.log(error)
})
function getUserAccount() {
return axios.get('/user/12345')
}
function getUserPermissions() {
return axios.get('/user/12345/permissions')
}
axios.all([getUserAccount(), getUserPermissions()]).then(
axios.spread(function(acct, perms) {
// Both requests are now complete
})
)
axios(config),我们可以像使用 $.ajax() 一样来使用 axios
// Send a POST request
axios({
method: 'post',
url: '/user/12345',
data: {
firstName: 'Fred',
lastName: 'Flintstone'
}
})
// GET request for remote image
axios({
method: 'get',
url: 'http://bit.ly/2mTM3nY',
responseType: 'stream'
}).then(function(response) {
response.data.pipe(fs.createWriteStream('ada_lovelace.jpg'))
})
补充内容:以下补充参考《VueX(Vue状态管理模式)》
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,可以帮助我们管理共享状态,并以相应的规则保证状态以一种可预测的方式发生变化。
如果您不打算开发大型单页应用,使用 Vuex 可能是繁琐冗余的。确实是如此——如果您的应用够简单,您最好不要使用 Vuex。一个简单的 store 模式就足够您所需了。但是,如果您需要构建一个中大型单页应用,您很可能会考虑如何更好地在组件外部管理状态,Vuex 将会成为自然而然的选择。
在 VueX 对象中,其实不止有 state ,还有用来操作 state 中数据的方法集,以及当我们需要对 state 中的数据需要加工的方法集等等成员。
成员列表:
首先,Vue 组件如果调用某个 VueX 的方法过程中需要向后端请求时或者说出现异步操作时,需要 dispatch VueX 中 actions 的方法,以保证数据的同步。可以说,action 的存在就是为了让 mutations 中的方法能在异步操作中起作用。
如果没有异步操作,那么我们就可以直接在组件内提交状态中的M utations 中自己编写的方法来达成对 state 成员的操作。注意,1.3.3节中有提到,不建议在组件中直接对 state 中的成员进行操作,这是因为直接修改(例如:this.$store.state.name = ‘hello’)的话不能被 VueDevtools 所监控到。
最后被修改后的 state 成员会被渲染到组件的原位置当中去。
npm install vuex --save
在 src 文件目录下新建一个名为 store 的文件夹,为方便引入并在 store 文件夹里新建一个index.js(如果脚手架生成有 vuex 那直接跳到 3 步)
import Vue from 'vue';
import Vuex from 'vuex';
// 挂载Vuex
Vue.use(Vuex);
// 创建VueX对象
const store = new Vuex.Store({
state:{
name:'helloVueX' // 存放的键值对就是所要管理的状态
}
})
export default store;
将 store 挂载到当前项目的 Vue 实例(main.js),这样一来就可以在任何一个组件里面使用 this.$store 了
import Vue from 'vue';
import Vuex from 'vuex';
import store from './store'//引入store
new Vue({
// el: '#app',
store // store: store,将我们创建的Vuex实例挂载到这个 vue 实例中
render: h => h(App),
}).$mount('#app')
补充内容:Vue 的挂在方式一共,分别为
el;
$mount() 手动挂载到节点;
template 以组件形式注入;
render 以原生 js 的写法注入;
在组件中使用Vuex,例如在 App.vue 中,我们要将 state 中定义的 name 拿来在 h1 标签中显示
<template>
<div id='app'>
name:
<h1>{{ $store.state.name }}</h1>
</div>
</template>
或者要在组件方法中使用
methods:{
add(){
console.log(this.$store.state.name) // 注意,请不要在此处更改state中的状态的值
}
},
设置仓库要保存的值和操作那些值的方法
回到 store 文件的 index.js 里面,我们先声明一个 state 变量,并赋值一个空对象给它,里面随便定义两个初始属性值;然后再在实例化的 Vuex.Store 里面传入一个空对象,并把刚声明的变量 state 仍里面
import Vue from 'vue';
import Vuex from 'vuex';
//挂载Vuex
Vue.use(Vuex);
const state = { // 要设置的全局访问的 state 对象
showFooter: true,
changableNum: 0 // 要设置的初始属性值
};
const store = new Vuex.Store({
state
});
export default store;
mutations 是操作 state 数据的方法的集合,比如对该数据的修改、增加、删除等等
具体的用法就是给里面的方法传入参数 state 或额外的参数,然后利用 vue 的双向数据驱动进行值的改变,同样的定义好之后也把这个 mutations 扔进 Vuex.Store 里面
mutattions 也是一个对象,都有默认的形参([state] [,payload])
例如,我们编写一个方法,当被执行时,能把下例中的 name 值修改为 ‘jack’,我们只需要这样做
import Vue from 'vue'
import Vuex from 'vuex'
// 挂载Vuex
Vue.use(Vuex)
const store = new Vuex.store({
state:{
name:'helloVueX'
},
mutations:{
edit(state){ // es6 语法,等同 edit:funcion(){...}
state.name = 'jack'
}
}
})
export default store
而在组件中,我们需要这样去调用这个 mutation ——例如在 App.vue 的某个 method 中
this.$store.commit('edit')
在实际生产过程中,会遇到需要在提交某个 mutation 时需要携带一些参数给方法使用
// 单个值提交时
this.$store.commit('edit')
// 当需要多参提交时,推荐把他们放在一个对象中来提交
this.$store.commit('edit',{age: 15,sex: '男'})
接收挂载的参数
edit(state,payload){
state.name = 'jack'
console.log(payload) // 15 或 {age: 15,sex: '男'}
}
另一种提交方式
this.$store.commit({
type:'edit',
payload:{
age: 15,
sex: '男'
}
})
为了配合 Vue 的响应式数据,我们在 Mutations 的方法中,应当使用 Vue 提供的方法来进行操作。如果使用 delete 或者 xx.xx = xx 的形式去删或增,则 Vue 不能对数据进行实时响应。
Vue.set 为某个对象设置成员的值,若不存在则新增
例如对state对象中添加一个age成员
Vue.set(state, 'age', 15)
Vue.delete 删除成员
将刚刚添加的 age 成员删除
Vue.delete(state, 'age')
vuex 官方 API 提供了一个 getters,和 vue 计算属性 computed 一样,来实时监听 state 值的变化(最新状态),并把它也仍进 Vuex.Store 里面
getters 中的方法有两个默认参数
getters:{
nameInfo(state){
return "姓名:" + state.name // 当前 VueX 对象中的状态对象
},
fullInfo(state, getters){ // 参数 getters 用于将 getters 下的其他 getter ,这里也就是 nameInfo 拿来用
return getters.nameInfo + '年龄:' + state.age
}
}
组件中调用
this.$store.getters.fullInfo
由于直接在 mutation 方法中进行异步操作,将会引起数据失效。所以提供了 Actions 来专门进行异步操作,最终提交 mutation 方法
Actions 中的方法有两个默认参数,Action 提交的是 mutation,而不是直接变更状态
由于 setTimeout 是异步操作,所以需要使用 actions,请看如下示例
const store = new Vuex.Store({
state: {
name: ''
},
mutations: {
edit(state, payload){
state.name = 'jack'
console.log(payload) // 15 或 {age: 15,sex: '男'}
}
},
actions: {
aEdit(context, payload){
setTimeout(() => {
context.commit('edit', payload) // 通过 commit 提交 mutation 的方式来修改数据状态
}, 2000)
}
}
})
在组件中调用
this.$store.dispatch('aEdit', {age: 15})
当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。
models:{
a:{
state:{},
getters:{},
....
}
}
组件内调用模块a的状态
this.$store.state.a
而提交或者 dispatch 某个方法和以前一样,会自动执行所有模块内的对应 type 的方法
this.$store.dispatch('aEditKey')
模块中 mutations 和 getters 中的方法接受的第一个参数是自身局部模块内部的 state
models: {
a: {
state: {key: 5},
mutations: {
editKey (state) {
state.key = 9
}
}
}
}
getters 中方法的第三个参数是根节点状态
models: {
a: {
state: {key: 5},
getters: {
getKeyCount (state, getter, rootState) {
return rootState.key + state.key
}
}
}
}
actions 中方法获取局部模块状态是 context.state,根节点状态是 context.rootState
models: {
a: {
state: {key: 5},
actions: {
aEidtKey (context) {
if (context.state.key === context.rootState.key) {
context.commit('editKey')
}
}
}
}
}
补充内容:高级用法 《关于mapState,mapGetters,mapMutations,mapActions的学习笔记》
补充内容:Vue 建议我们 mutation 类型用大写常量表示
简单一句话:非异步操作修改状态使用 mutation,异步操作修改状态使用 action 。其次我们使用 Vuex 并不意味着你需要将所有的状态放入 Vuex。虽然将所有的状态放到 Vuex 会使状态变化更显式和易调试,但也会使代码变得冗长和不直观。如果有些状态严格属于单个组件,最好还是作为组件的局部状态。你应该根据你的应用开发需要进行权衡和确定。
补充内容:以下补充参考《从头开始学习vue-router》
这里的路由并不是指我们平时所说的硬件路由器,这里的路由就是 SPA(单页应用)的路径管理器。再通俗的说,vue-router 就是 WebApp 的链接路径管理系统。
vue-router 是 Vue.js 官方的路由插件,它和 vue.js 是深度集成的,适合用于构建单页面应用。vue 的单页面应用是基于路由和组件的,路由用于设定访问路径,并将路径和组件映射起来。传统的页面应用,是用一些超链接来实现页面切换和跳转的。在 vue-router 单页面应用中,则是路径之间的切换,也就是组件的切换。路由模块的本质 就是建立起 url 和页面之间的映射关系。
至于我们为啥不能用a标签,这是因为用 Vue 做的都是单页应用(当你的项目准备打包时,运行 npm run build 时,就会生成 dist 文件夹,这里面只有静态资源和一个 index.html 页面),所以你写的 标签是不起作用的,你必须使用 vue-router 来进行管理。
使用脚手架初始化的时候可以初始化安装 vue-router,如果没有就手动下载和使用
什么是单页应用
单页应用(英语:single-page application,缩写SPA):在传统的网页应用中,浏览器更多的是充当一个展示层,路由处理、服务调用、页面跳转流程都由服务端来处理。即 MVC 都放在服务器端,SPA 技术将逻辑从服务器转移到了客户端。这导致 Web 服务器发展为一个纯数据 API 或 Web 服务。这种架构的转变在一些圈子中被称为“瘦服务器架构”,以强调复杂性已从服务端转移到客户端,并认为这最终降低了系统的整体复杂性。
传统的网站有以下特点
重服务端,由于MVC 都存在于服务器上,因此这类应用在开发资源和开发的重心都偏向后端,往往是后端工程师来主导整个项目开发;
页面频繁刷新,由于浏览器端只是一个展现层,当页面功能有所变化的时,页面就刷新,这会导致资源的浪费,用户需要花费额外的时间等待页面刷新,用户体验不佳。
而单页面应用,只有一张 Web 页面的应用,是一种从Web服务器加载的富客户端,单页面跳转仅刷新局部资源 ,公共资源(js、css等)仅需加载一次。
单页应用的优缺点
优点
缺点
vue-router 实现原理
vue-router 在实现单页面前端路由时,提供了两种方式:Hash 模式和 History 模式;根据 mode 参数来决定采用哪一种方式。
Hash模式(默认)
使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。 hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 hash 出现在 URL 中,但不会被包含在 http 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用“后退”按钮,就可以回到上一个位置;所以说 Hash 模式通过锚点值的改变,根据不同的值,渲染指定 DOM 位置的不同数据。hash 模式的原理是 onhashchange 事件(监测 hash 值变化),可以在 window 对象上监听这个事件。
History模式
由于 hash 模式会在 url 中自带#,如果不想要很丑的 hash,我们可以用路由的 history 模式,只需要在配置路由规则时,加入"mode: ‘history’",这种模式充分利用了 html5 history interface 中新增的 pushState() 和 replaceState() 方法。这两个方法应用于浏览器记录栈,在当前已有的 back、forward、go 基础之上,它们提供了对历史记录修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。
// main.js文件中
const router = new VueRouter({
mode: 'history',
routes: [...]
})
npm install vue-router
新建 router 文件夹,然后建立一个 index.js 文件,写入路由的代码
import Vue from 'vue' //引入Vue
import Router from 'vue-router' //引入vue-router
import Hello from '@/components/HelloWorld' //引入组件目录下的 HelloWorld.vue 组件
import Demo from '@/components/Demo' //引入组件目录下的 Demo.vue 组件
Vue.use(Router) // Vue 全局使用 Router
export default new Router({
// 配置路由,这里是个数组
// 每一个链接都是一个对象
routes: [{
path: '/', //链接路径 当路径为 http://localhost:8080/#/ 显示此组件
name: 'Hello', //路由名称,
component: Hello //对应要显示的组件
}, { //每一个链接都是一个对象
path: '/demo', //链接路径,当路径为 http://localhost:8080/#/demo 显示此组件
name: 'Demo', //路由名称,
component: Demo //对应要显示的组件
}]
})
在 main.js 全局注册 router
import Vue from 'vue'
import App from './App.vue'
import router from "./router/index.js" // 导入路由
Vue.config.productionTip = false
new Vue({
// el: '#app',
router, // 使用路由
}).$mount('#app')
在 App.js 中要使用 router-view
组件
<template>
<div id="app">
<router-view></router-view> // 路由匹配到的组件将渲染在这里
</div>
</template>
声明式导航
<template>
<div id="app">
<p>
<router-link to="home">Home</router-link> // 字符串的形式
//下面几种为动态绑定
<router-link :to="index">Home</router-link>
<script>
export default {
data () {
return {
index: '/'
}
}
}
</script>
// 这个路径就是路由中配置的路径
<router-link :to="{ path: '/home' }">Home</router-link>
// 在路由的配置的时候,添加一个name属性
<router-link :to="{ name: 'User'}">User</router-link>
// 直接路由带查询参数 query,地址栏变成 /apple?color=red
<router-link :to="{path: 'apple', query: {color: 'red' }}">to apple</router-link>
</p>
<router-view></router-view> // 路由匹配到的组件将渲染在这里
</div>
</template>
内容补充:《vue2.0中router-link详解》
在vue2.0中,原来的 v-link 指令已经被 组件替代了
组件支持用户在具有路由功能的应用中点击导航。默认渲染为带有正确连接的 标签
组件的属性有:
to(必选参数):类型string/location。表示目标路由的链接,该值可以是一个字符串,也可以是动态绑定的描述目标位置的对象
tag:类型: string 默认值: ‘a’。如果想要 渲染成某种标签,例如。 于是我们使用 tag
active-class:类型: string 默认值: “router-link-active”。设置链接激活时使用的 CSS 类名
exact-active-class:类型: string 默认值: “router-link-exact-active”。配置当链接被精确匹配的时候应该激活的 class
exact:类型: boolean 默认值: false。“是否激活” 默认类名的依据是 inclusive match (全包含匹配)
event:类型: string | Array 默认值: ‘click’。声明可以用来触发导航的事件。可以是一个字符串
replace:类型: boolean 默认值: false。设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),于是导航后不会留下 history 记录
append:类型: boolean 默认值: false。设置 append 属性后,则在当前 (相对) 路径前添加基路径
编程式导航
除了使用 创建 a 标签来定义导航链接,我们还可以借助 router 的实例方法,通过编写代码来实现前进和后退 this.router.go(1):功能跟我们浏览器上的后退和前进按钮一样,这在业务逻辑中经常用到。比如条件不满足时,我们需要后退。
app.vue 文件里加入一个按钮,按钮并绑定一个 goback() 方法
<button @click="goback">后退</button>
<script>
export default {
name: 'app',
methods: {
goback () {
this.$router.go(-1);
}
}
}
</script>
编程式导航都作用就是跳转,比如我们判断用户名和密码正确时,需要跳转到用户中心页面或者首页,都用到这个编程的方法 this.$router.push
来操作路由。
export default {
name: 'app',
methods:{
goHome(){
this.$router.push('/'); // 字符串
this.$router.push({name: 'applename', query: {color: 'red' }}); // 命名路由带查询参数 query,地址栏变成/apple?color=red
}
}
}
补充内容:动态路由的获取参数方法
在组件中:{{KaTeX parse error: Expected 'EOF', got '}' at position 19: …te.params.color}̲} 在js里:this.route.params.color
export default new Router({
routes: [{
path: '/',
name: 'Hello',
component: Hello
}, {
// 对某个路由使用子路由,那么需得在 Hi1 组件内使用 否则切换
// 了子路由也无法显示出来
path: '/h1',
name: 'Hi1',
component: Hi1, // 当访问 /h1 的时候显示Hi1这个组件
children: [{
path: '/',
component: Hi1
}, {
path: 'h2',
component: Hi2
}]
}]
})
重定向也是通过 routes 配置来完成,下面例子是从 /a 重定向到 /b
const router = new VueRouter({
routes: [{
path: '/a',
redirect: '/b'
}]
})
重定向的目标也可以是一个命名的路由
const router = new VueRouter({
routes: [{
path: '/a',
redirect: { name: 'foo' }
}]
})
补充内容:《路由守卫》
路由跳转前做一些验证,比如登录验证,是网站中的普遍需求。对此,vue-route 提供的 beforeRouteUpdate 可以方便地实现导航守卫(navigation-guards)。