从新对博客内容进行了修正,为了不本身的文字引来没必要要的歧义,因此删本身关于技术方面的,计划这一分类更多的采用引用为主。javascript
本文转自简书《教你如何学好vue《文档》》,但对内容有所删减(Vue 历史、做者以及案例)以及补充,若想了解还请移步至原文。css
ECMAScript 6.0(如下简称 ES6 )是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了也叫 ECMAScript 2015 。它的目标,是使得 JavaScript 语言能够用来编写复杂的大型应用程序,成为企业级开发语言html
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
声明一个只读的常量。一旦声明,常量的值就不能改变。vue
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
不存在变量提高html5
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}`;
属性的简洁表示法java
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 容许使用 =>
(箭头)定义函数。node
var f = v => v; // 等同于 var f = function (v) { return v; };
若是箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,而且使用return语句返回。webpack
var sum = (num1, num2) => { return num1 + num2; }
箭头函数有几个使用注意点:ios
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/vue@2.6.10/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/vue@2.6.10/dist/vue.js"></script> <script> new Vue({ el: '#app', // 实例选项 data: { message: 'Hello Vue.js!' // 声明式渲染 } }) </script>
实例选项 - el
实例选项 - 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>
在父组件引用的子组件中写入想要显示的内容,<p style="color:red">我是父组件插槽内容</p> 为组件插槽显示的内容
<template> <div class="slotOne1"> <slot></slot> </div> </template>
在子组件中写入slot,slot所在的位置承接的就是父组件中 <p style="color:red">我是父组件插槽内容</p>
具名插槽
<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>
<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) { // ... });
利用 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的学习笔记》
由于只有 mutation 才能改变 state, 若是须要须要执行异步操做修改 state 须要以下操做
补充内容: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 参数来决定采用哪种方式。
使用 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 指令已经被 <router-link> 组件替代了
<router-link> 组件支持用户在具备路由功能的应用中点击导航。默认渲染为带有正确链接的 标签
<router-link> 组件的属性有:
to(必选参数):类型string/location。表示目标路由的连接,该值能够是一个字符串,也能够是动态绑定的描述目标位置的对象
tag:类型: string 默认值: 'a'。若是想要 <router-link> 渲染成某种标签,例如 <li>。 因而咱们使用 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<string> 默认值: '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 } } }
补充内容:动态路由的获取参数方法
在组件中:{{$route.params.color}}
在js里:this.$route.params.color
export default new Router({ routes: [{ path: '/', name: 'Hello', component: Hello }, { // 对某个路由使用子路由,那么需得在 Hi1 组件内使用 <router-view></router-view> 不然切换 // 了子路由也没法显示出来 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)。