1. Vue概述
1.1 Vue介绍
Vue 是一套用于构建用户界面的渐进式框架。
1.2 Vue核心思想
- 双向数据绑定
Vue
双向数据绑定利用了Object
对象的set()
和get()
方法,原理如下:
1.3 Vue与React对比
1.3.1 不同点
一、Vue
- 简单的语法和项目构建。
二、React
- 适用于大型项目以及更好的可测试性。
- 更大的生态圈带来的更多的支持工具。
1.3.2 相同点
- 虚拟
DOM
- 轻量级
- 响应式
- 服务端渲染
- 易于集成路由工具、打包工具和状态管理工具
- 优秀的支持和社区
2. Vue基础语法
2.1 Vue环境搭建
2.1.1 环境构建方式
- 官方拷贝
-
npm
安装 -
vue-cli
工具构建
2.1.1 vue-cli工具构建SPA应用
npm i -g vue-cli
-
vue init webpack-simple demo
初始化一个简单的webpack
项目 -
vue init webpack demo
初始化一个完整的webpack
项目
2.2 Vue基础语法
- 模板语法
-
Mustache
语法:{{msg}}
-
Html
赋值:v-html = ""
- 绑定属性:
v-bind:id = ""
- 使用表达式:
{{ok ? 'YES' : 'NO'}}
- 文本赋值:
v-text = ""
- 指令:
v-if = ""
- 过滤器:
{{message | capitalize}}
和v-bind:id = "rawId | formtaId"
注释:vue
组件中的data
推荐使用方法的方式data(){return {}}
返回数据,这样不同组件实例之间就不会共用数据。
-
Class
和Style
绑定
- 对象语法
- 数组语法
data: {
activeClass: 'active',
errorClass: 'text-danger'
}
- style绑定
- 条件渲染
v-if
v-else
v-else-if
v-show
v-cloak
- 事件处理器
-
v-on:click="greet"
、 @click="greet"
-
v-on:click.stop
、v-on:click.stop.prevent
、v-on:click.self
、v-on:click.once
v-on:keyup.enter/tab/delete/esc/space/up/down/left/right
-
Vue
组件
- 全局组件和局部组件
单页面应用一般使用的都是局部组件。
- 父子组件通讯-数据传递
//父组件
{{`parent: ${num}`}}
//子组件
{{num}}
-
Slot
使用slot
可以减少父组件props
传值的数量以及子组件$emit
触发父组件方法的数量。
加入购物车成功
继续购物
查看购物车
2.3 路由 vue-router
2.3.1路由基础介绍
- 路由
根据不同的url
地址展示不同的内容或页面。
- 后端路由
服务器根据url
地址返回不同页面。
- 前端路由
不同路由对应不同内容或页面的任务交给前端来做。
- 前端路由使用场景
单页面应用。
- 前端路由的优缺点
优点:路由跳转用户体验好。
缺点:不利于SEO
;浏览器前进后退重新发送请求,没有利用缓存;无法记住之前页面的滚动位置。
-
vue-router
介绍
vue-router官网
(1) 跳转组件
注释: router-link
就是一个封装好的a
标签,可以添加样式。
(2) js
跳转
this.$router.push({path: ''})
(3) 展示组件
注释: vue-router
是对historyAPI
的封装。
2.3.2. 动态路由匹配
- 动态路由介绍
模式
匹配路径
$route.params
/user/:username
/user/even
{username: 'even'}
/user/:username/post/:post_id
/user/even/post/123
{username: 'even', post_id: 123}
- 动态路由的使用
//路由入口文件: scr/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import GoodList from '@/views/GoodList'
Vue.use(Router)
export default new Router({
mode: 'history', //默认值为hash
routes: [
{
path: '/goods/:goodsId/user/:userName',
name: 'GoodList',
component: GoodList
}
]
})
//src/views/GoodsList.vue文件
这是商品列表页面
{{$route.params.goodsId}}
{{$route.params.userName}}
//src/views/Image.vue文件
图片子路由
//src/views/Title.vue文件
标题子路由
- 在
children
处注册路由时使用相对于父路由的路径即可,在router-link
的 to
属性跳转地址要使用完整路径。
- 从子路由的用法可知,路由的本质并不是整个页面的显示/隐藏切换,而是
页面某个区域
的显示隐藏切换。
2.3.4. 编程式路由
- 编程式路由介绍
使用js
实现页面的跳转。
$router.push("name")
$router.push({path: "name"})
$router.push({path: "name?a=123})
$router.push({path: "name", query: {a: 123}})
-
$router.go(1/-1)
注意: 对比区分query
参数的传递与动态路由params
参数的传递。 query传递的是?a=1;b=2
字段,通过$route.query.key
的方式取值。动态参数传递的是/a/b
字段,通过$route.params.key
的方式取值。
总结:①$route.query.key
获取的是当前url
中query
中的字段值,$route.params.key
获取的是当前url
中params
中的字段值。②使用 router-link
组件跳转和js
跳转都可以传递params
和query
。
- 编程式路由的使用
//路由入口文件: scr/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import GoodList from '@/views/GoodList'
import Cart from '@/views/Cart'
Vue.use(Router)
export default new Router({
mode: 'history', //默认值为hash
routes: [
{
path: '/goods',
name: 'GoodList',
component: GoodList
},
{
path: '/cart',
name: 'cart',
component: Cart
}
]
})
//src/views/GoodsList.vue文件
这是商品列表页面
//src/views/Cart.vue文件
这是购物车页面
{{$route.query.a}}
2.3.5. 命名路由和命名视图
1.命名路由和命名视图介绍
给路由定义不同的名字,根据名字进行匹配。
给不同的router-view
定义名字,通过名字进行对应组件的渲染。
- 命名路由的使用
//路由入口文件: scr/router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import GoodList from '@/views/GoodList'
import Cart from '@/views/Cart'
Vue.use(Router)
export default new Router({
mode: 'history', //默认值为hash
routes: [
{
path: '/goods',
name: 'GoodList',
component: GoodList
},
{
path: '/cart/:cartId',
name: 'cart',
component: Cart
}
]
})
//src/views/GoodsList.vue文件
这是商品列表页面
跳转到购物车页面
//src/views/Cart.vue文件
这是购物车页面
{{$route.params.cartId}}
{{$route.query.a}}
- 命名视图
Vue Router文档-命名视图
2.3.6 HTML5 History 模式
-
vue-router
默认hash
模式 —— 使用URL
的 hash
来模拟一个完整的 URL
,于是当 URL
改变时,页面不会重新加载。
如果不想要很丑的 hash
,我们可以用路由的 history
模式,这种模式充分利用 history.pushState API
来完成 URL
跳转而无须重新加载页面。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
- 当你使用
history
模式时,URL
就像正常的 url
,例如 http://yoursite.com/user/id
,也好看!
不过这种模式要玩好,还需要后台配置支持。因为我们的应用是个单页客户端应用,如果后台没有正确的配置,当用户在浏览器直接访问 http://oursite.com/user/id
就会返回 404
,这就不好看了。
所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL
匹配不到任何静态资源,则应该返回同一个index.html
页面,这个页面就是你 app
依赖的页面。
注释:vue-router
处理前端路由跳转,后端路由处理ajax/fetch
请求以及用户访问url
页面。当用户直接访问某一个路由页面url
(http://oursite.com/user/id
)时,该请求会由服务器端进行处理。
注意:把服务器的url
解析重定向到index.html
的首页里面即可。如果mode
取默认值hash
,通过# + 路径地址
才能访问到,后台是不识别锚点,不会出现404
的状态。
- 后端配置例子
(1)原生 Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80
http.createServer((req, res) => {
fs.readFile('index.htm', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(content)
})
}).listen(httpPort, () => {
console.log('Server listening on: http://localhost:%s', httpPort)
})
(2) 基于 Node.js
的 Express
对于 Node.js/Express,请考虑使用 connect-history-api-fallback 中间件。
- 给个警告,因为这么做以后,你的服务器就不再返回
404
错误页面,因为对于所有路径都会返回index.html
文件。为了避免这种情况,你应该在 Vue
应用里面覆盖所有的路由情况,然后在给出一个 404
页面。
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})
或者,如果你使用 Node.js
服务器,你可以用服务端路由匹配到来的 URL
,并在没有匹配到路由的时候返回 404
,以实现回退。更多详情请查阅 Vue 服务端渲染文档。
2.4 请求数据
2.4.1 vue-resource
-
vue-resource
的请求API
是按照REST
风格设计的,它提供了7种请求API
:
get(url, [options])
head(url, [options])
delete(url, [options])
jsonp(url, [options])
post(url, [body], [options])
put(url, [body], [options])
patch(url, [body], [options])
- 发送请求时的
options
选项对象包含以下属性
参数
类型
描述
url
string
请求的URL
method
string
请求的HTTP
方法,例如:'GET
', 'POST
'或其他HTTP
方法
body
Object
, FormData string
request body
params
Object
请求的URL
参数对象
headers
Object
request header
timeout
number
单位为毫秒的请求超时时间 (0 表示无超时时间)
before
function(request)
请求发送前的处理函数,类似于jQuery
的beforeSend
函数
progress
function(event)
ProgressEvent
回调处理函数
credientials
boolean
表示跨域请求时是否需要使用凭证
emulateHTTP
boolean
发送PUT
, PATCH
, DELETE
请求时以HTTP POST
的方式发送,并设置请求头的X-HTTP-Method-Override
emulateJSON
boolean
将request body
以application/x-www-form-urlencoded content type
发送
- 全局拦截器
interceptors
Vue.http.interceptors.push((request, next) => {
// ...
// 请求发送前的处理逻辑
// ...
next((response) => {
// ...
// 请求发送后的处理逻辑
// ...
// 根据请求的状态,response参数会返回给successCallback或errorCallback
return response
})
})
-
vue-resource
使用示例
vue-resource
注释: ①引入 vue-resource
之后可以通过this.$http
的方式使用。
2.4.2 Axios
- Axios简介
Axios
是一个基于 promise
的 HTTP
库,可以用在浏览器和 node.js
中。
- 请求方法介绍
axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
-
axios.patch(url[, data[, config]])
注意: ·Axios· 请求方法中没有jsonp
请求。
- 执行
get
请求
// 为给定 ID 的 user 创建请求
axios.get('/user?ID=12345')
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
// 可选地,上面的请求可以这样做
axios.get('/user', {
params: {
ID: 12345
}
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
- 执行
POST
请求
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) {
// 两个请求现在都执行完成
}));
-
Axios
使用示例
axios
注释:①axios
的参数传递方式与vue-resource
基本相同。② 注意区分get
请求与post
请求的参数传递方式。
2.5 Vuex基本用法
2.5.1 Vuex介绍
-
Vuex
是一个专门为Vue.js
应用程序开发的状态管理模式。
- 当我们构建一个中大型的单页应用程序时,
Vuex
可以更好的帮助我们在组件外部统一管理状态。
2.5.2 核心概念
State
Getters
Mutations
Actions
Modules
-
State
State
唯一数据源,单一状态树。
// 创建一个 Counter 组件
const Counter = {
template: `{{ count }}`,
computed: {
count () {
return store.state.count
}
}
}
-
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)
}
}
})
-
Mutations
更改Vuex
的store
中状态的唯一方法是提交mutation
。
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
触发mutation handler
:
store.commit('increment')
-
Actions
Action
提交的是mutation
,而不是直接改变状态。
Action
可以包含任意异步操作。
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++
}
},
actions: {
increment (context) {
context.commit('increment')
}
}
})
注释:mutations
中的方法必须是同步的,actions
中的方法可以有异步操作。
-
Modules
面对复杂的应用程序,当管理的状态比较多时,我们需要将Vuex
的store
对象分割成模块(modules
)。
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 的状态
-
概念关系图
2.5.3 项目结构
├── index.html
├── main.js
├── api
│ └── ... # 抽取出API请求
├── components
│ ├── App.vue
│ └── ...
└── store
├── index.js # 我们组装模块并导出 store 的地方
├── actions.js # 根级别的 action
├── mutations.js # 根级别的 mutation
└── modules
├── cart.js # 购物车模块
└── products.js # 产品模块
2.5.4 代码示例
Vuex
{{msg}}
3. Vue生态圈
3.1 模拟mock数据
在vue
开发过程中,有时需要使用本地json
模拟后台接口数据,测试前端页面展示情况。对于使用vue-cli
工具构建的项目,封装了express
框架,我们可以通过拦截请求的方式使用mock
数据。
- 创建
mock
数据json
文件
- 在
webpack.dev.conf.js
文件中拦截请求
//imoocmall/build/webpack.dev.conf.js文件
var goodsData = require('../mock/goods.json')
devServer: {
before (app) {
app.get('/goods', function (req, res) {
res.json(goodsData);
})
},
//...
}
//..
注释: 这里的app.get('/goods', (req, res) => {})
就是express
框架定义后端路由接口的写法。
- 使用mock数据
axios.get('/goods',)
.then(res => {
//...
})
3.2 图片懒加载
使用vue-lazyload插件可以实现图片懒加载。
- 安装
npm i vue-lazyload -d
- 引入
src/main.js
import VueLazyload from 'vue-lazyload'
Vue.use(VueLazyload, {
error: 'dist/error.png',
loading: 'dist/loading.gif',
})
- 使用
3.3 滚动加载
使用vue-infinite-scroll插件可以实现滚动加载。
- 安装
npm install vue-infinite-scroll --save
- 引入
src/main.js
import infiniteScroll from 'vue-infinite-scroll'
Vue.use(infiniteScroll)
- 使用
var count = 0;
new Vue({
el: '#app',
data: {
data: [],
busy: false
},
methods: {
loadMore: function() {
this.busy = true;
setTimeout(() => {
for (var i = 0, j = 10; i < j; i++) {
this.data.push({ name: count++ });
}
this.busy = false;
}, 1000);
}
}
});
注释:infinite-scroll-disabled
表示滚动加载是否禁用。
3.4 请求代理
- 开发过程中,前端服务与后端接口一般存在着跨域问题。
vue-cli
提供了proxyTable
代理功能解决跨域问题。
注释:① 开发环境前端服务端口8080
(/config/index.js中的port: 8080
)与后端服务端口(/server/bin/www中的var port = normalizePort(process.env.PORT || '3000');
)不同,存在跨域,所有需要使用请求代理。② 一般仅在开发环境中配置。
注意:①跨域问题只是web
前端浏览器的行为,在web
前端请求不符合同源策略接口数据时出现。②后端node
连接mongodb
数据库即使协议域名端口不同(不符合同源策略),也不存在跨域问题。
- 修改
/config/index.js
文件中的dev.proxyTable
配置
proxyTable: {
'/goods': {
target: 'http://localhost:3000'
}
}
}
此时,当我们请求 http://localhost:8888/goods
的时候,就等于请求了http://localhost:3000/goods
。
注意: '/goods'
规则不仅能够匹配'/goods'
规则的路径,还能够匹配'/goods/a'
、'/goods/a/b'
等规则的路径。
proxyTable: {
'/api': {
target: 'http://localhost:3000' ,
pathRewrite: {
'^/api': ''
}
}
}
}
此时,当我们请求 http://localhost:8888/api
的时候,就等于请求了http://localhost:3000
。
参考资料
- webpack+vue-cil 中proxyTable配置接口地址代理