VueJS组件
MintUI 组件
导入
导入所有MintUI组件:
1
2
3
4
5
6// 导入所有的 MIntUI 组件
import MintUI from "mint-ui"; //把所有的组件都导入进来
// 这里 可以省略 node_modules 这一层目录
import "mint-ui/lib/style.css";
// 将 MintUI 安装到 Vue 中
Vue.use(MintUI); // 把所有的组件,注册为全局的组件按需导入
1
2
3
4
5// 按需导入 Mint-UI组件
import { Button } from "mint-ui";
// 使用 Vue.component 注册 按钮组件
Vue.component(Button.name, Button);
// console.log(Button.name)使用的例子:
1
<mt-button type="primary" size="large">primary</mt-button>
MUI 代码片段
注意: MUI 不同于 Mint-UI,MUI只是开发出来的一套好用的代码片段,里面提供了配套的样式、配套的HTML代码段,类似于 Bootstrap;
而 Mint-UI,是真正的组件库,是使用 Vue 技术封装出来的 成套的组件,可以无缝的和 VUE项目进行集成开发;
从体验上来说, Mint-UI体验更好,因为这是别人帮我们开发好的现成的Vue组件; MUI和Bootstrap类似;
理论上,任何项目都可以使用 MUI 或 Bootstrap,但是,MInt-UI只适用于Vue项目;
注意: MUI 并不能使用 npm 去下载,需要自己手动从 github 上,下载现成的包,自己解压出来,然后手动拷贝到项目中使用;
使用
导入 MUI 的样式表:
1
import "./lib/mui/css/mui.min.css";
在
webpack.config.js
中添加新的loader规则:1
{ test: /\.(png|jpg|gif|ttf)$/, use: 'url-loader' }
根据官方提供的文档和example,尝试使用相关的组件
1
2<button type="button" class="mui-btn ">默认</button>
<div class="mui-btn mui-btn-primary mui-btn-outlined">蓝色</div>
布局示例
头部的固定导航栏使用
Mint-UI
的Header
组件;底部的页签使用
mui
的tabbar
;购物车的图标,使用
icons-extra
中的mui-icon-extra mui-icon-extra-cart
,同时,应该把其依赖的字体图标文件mui-icons-extra.ttf
,复制到fonts
目录下!将底部的页签,改造成
router-link
来实现单页面的切换;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58<template>
<div class="app-container">
<!-- 顶部 Header 区域 -->
<mt-header fixed title="黑马程序员·Vue项目"></mt-header>
<!-- 中间的 路由 router-view 区域 -->
<transition>
<router-view></router-view>
</transition>
<!-- 底部 Tabbar 区域 -->
<nav class="mui-bar mui-bar-tab">
<router-link class="mui-tab-item" to="/home">
<span class="mui-icon mui-icon-home"></span>
<span class="mui-tab-label">首页</span>
</router-link>
<router-link class="mui-tab-item" to="/member">
<span class="mui-icon mui-icon-contact"></span>
<span class="mui-tab-label">会员</span>
</router-link>
<router-link class="mui-tab-item" to="/shopcar">
<span class="mui-icon mui-icon-extra mui-icon-extra-cart">
<span class="mui-badge">0</span>
</span>
<span class="mui-tab-label">购物车</span>
</router-link>
<router-link class="mui-tab-item" to="/search">
<span class="mui-icon mui-icon-search"></span>
<span class="mui-tab-label">搜索</span>
</router-link>
</nav>
</div>
</template>
<script></script>
<style lang="scss" scoped>
.app-container {
padding-top: 40px;
overflow-x: hidden;
}
.v-enter {
opacity: 0;
transform: translateX(100%);
}
.v-leave-to {
opacity: 0;
transform: translateX(-100%);
position: absolute;
}
.v-enter-active,
.v-leave-active {
transition: all 0.5s ease;
}
</style>
高亮路由
全局设置样式如下:
1
2
3.router-link-active {
color: #007aff ;
}在
new VueRouter
的时候,通过linkActiveClass
来指定高亮的类:1
2
3
4
5
6
7
8
9
10
11
12// 创建路由对象
var router = new VueRouter({
routes: [
{ path: "/", redirect: "/home" },
{ path: "/home", component: Home },
{ path: "/member", component: Member },
{ path: "/shopcar", component: Shopcar },
{ path: "/search", component: Search },
],
// 覆盖默认的路由高亮的类,默认的类叫做 router-link-active
linkActiveClass: "mui-active",
});
兼容问题
- App.vue 中的
router-link
身上的类名mui-tab-item
存在兼容性问题,导致tab栏失效,可以把mui-tab-item
改名为mui-tab-item1
,并复制相关的类样式,来解决这个问题;
1 | .mui-bar-tab .mui-tab-item1 .mui-active { |
文件读取
封装一个方法,调用者提供要读取文件的路径,编写方法能读取文件,并把内容返回
1
2
3
4
5
6
7
8
9
10
11
12const fs = require("fs");
const path = require("path");
// 这是普通读取文件的方式
fs.readFile(
path.join(__dirname, "./files/1.txt"),
"utf-8",
(err, dataStr) => {
if (err) return console.log(err.message);
console.log(dataStr);
},
);封装成方法,添加一个回调callback ,规定 callback 中有两个参数,第一个参数,是失败的结果;第二个参数是成功的结果;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// 我们可以
function getFileByPath(fpath, callback) {
fs.readFile(fpath, 'utf-8', (err, dataStr) => {
// 失败了,则第一个位置放 Error对象,第二个位置防止一个 undefined
if (err) return callback(err)
// 成功返回的结果,应该位于 callback 参数的第二个位置,
//第一个位置 由于没有出错,所以,放一个 null;
callback(null, dataStr)
})
}
// 调用
var callback = (
function (data) {
console.log(data + '娃哈哈,成功了!!!')
}, function (err) {
console.log('失败的结果,我们使用失败的回调处理了一下:' + err.message)
})
var result = getFileByPath(path.join(__dirname, './1.txt'),callback)使用Promise改写
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function getFileByPath(fpath) {
return new Promise(function (resolve, reject) {
fs.readFile(fpath, "utf-8", (err, dataStr) => {
if (err) return reject(err);
resolve(dataStr);
});
});
}
getFileByPath("./files/2.txt").then(
function (data) {
console.log(data + "-------");
},
function (err) {
console.log(err.message);
},
);
eslint
- 用来做项目编码规范检查的工具
- 基本原理: 定义了很多规则, 检查项目的代码一旦发现违背了某个规则就输出相应的提示信息
- 有相应的配置, 可定制检查
Mixin
- Mixin是一种更好的复用代码的模式.我们知道 java , Object C 中的 interface , implements, extends 等关键字的意义,就 是为了让代码可以复用、继承.但是这几种方法, 都理解起来很不直观, 给人一种拐弯抹角的感觉. 特别是像我这样很不习惯 “设计 模式”的人。
- 在js, ruby等动态语言中, 我们如果要复用代码的话,直接使用 mixin 就好了.
- Mixin 的 概念Mixin 实际上是利用语言的特性(关键字),以更加简洁易懂的方式,实现了 “设计模式”中的 “组合模式”。 可以定义一个公共的类,这个类就叫做”mixin”.然后让其他的类,通过“include” 这样的语言特性,来包含mixin, 直接具备了 mixin 所具备的各种方法。
示例
建立一个Mixin文件,可以在 src/mixin 目录下创建, 例如: 文件: src/mixin/common_hi.js :
1
2
3
4
5
6
7export default {
methods: {
hi: function (name) {
return "你好, " + name;
},
},
};使用
Mixin使用起来很简单,在对应的 js文件, 或者 vue文件的
<script>
代码中引用即可.例如,新建一个vue文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<template>
<div>
{{ hi("from view") }}
</div>
</template>
<script>
import CommonHi from "@/mixins/common_hi.js";
export default {
mixins: [CommonHi],
mounted() {
alert(this.hi("from script code"));
},
};
</script>注意:
- 使用的时候,
mixins: [CommonHi]
这里的是中括号,表示是数组. - 在js代码中调用的话, 需要带有this关键字,例如:
this.hi()
- 使用的时候,
配置vue页面的路由如下:
1
2
3
4
5
6
7
8
9
10import SayHiFromMixin from "@/components/SayHiFromMixin";
export default new Router({
routes: [
{
path: "/say_hi_from_mixin",
name: "SayHiFromMixin",
component: SayHiFromMixin,
},
],
});查看运行效果
http://localhost:8080/#/say_hi_from_mixin
vuex
概念
vuex 是 Vue 配套的 公共数据管理工具,它可以把一些共享的数据,保存到 vuex 中,方便 整个程序中的任何组件直接获取或修改我们的公共数据
状态自管理应用
state: 驱动应用的数据源
view: 以声明方式将state映射到视图
actions: 响应在view上的用户输入导致的状态变化(包含n个更新状态的方法)
多组件共享状态的问题
- 多个视图依赖于同一状态
- 来自不同视图的行为需要变更同一状态
- 以前的解决办法
- 将数据以及操作数据的行为都定义在父组件
- 将数据以及操作数据的行为传递给需要的各个子组件(有可能需要多级传递)
- vuex就是用来解决多组件共享状态问题
vuex的核心概念
state
vuex管理的状态对象
它应该是唯一的
const state = {
xxx: initValue
}
mutations
包含多个直接更新state的方法(回调函数)的对象
谁来触发: action中的commit('mutation名称')
只能包含同步的代码, 不能写异步代码
const mutations = {
yyy (state, data) {
// 更新state的某个属性
}
}
actions
包含多个事件回调函数的对象
通过执行: commit()来触发mutation的调用, 间接更新state
谁来触发: 组件中: $store.dispatch('action名称') // 'zzz'
可以包含异步代码(定时器, ajax)
const actions = {
zzz ({commit, state}, data1) {
commit('yyy', data2)
}
}
getters
包含多个计算属性(get)的对象
谁来读取: 组件中: $store.getters.xxx
const getters = {
mmm (state) {
return ...
}
}
modules
包含多个module
一个module是一个store的配置对象
与一个组件(包含有共享数据)对应
向外暴露store对象
export default new Vuex.Store({
state,
mutations,
actions,
getters
})
组件中:
1
2
3
4
5
6
7import {mapGetters, mapActions} from 'vuex'
export default {
computed: mapGetters(['mmm'])
methods: mapActions(['zzz'])
}
{{mmm}} @click="zzz(data)"
映射store
import store from './store'
new Vue({
store
})
store对象
1.所有用vuex管理的组件中都多了一个属性$store, 它就是一个store对象
2.属性:
state: 注册的state对象
getters: 注册的getters对象
3.方法:
dispatch(actionName, data): 分发action
配置vuex
运行
cnpm i vuex -S
main.js导入包
import Vuex from 'vuex'
注册vuex到vue中
Vue.use(Vuex)
new Vuex.Store()
实例,得到一个数据仓储对象1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30var store = new Vuex.Store({
state: {
// 大家可以把 state 想象成 组件中的 data ,专门用来存储数据的
// 如果在 组件中,想要访问,store 中的数据,只能通过 this.$store.state. 来访问
count: 0,
},
mutations: {
// 注意: 如果要操作 store 中的 state 值,只能通过调用 mutations 提供的方法,才能操作对应的数据
// 不推荐直接操作 state 中的数据,因为万一导致了数据的紊乱,不能快速定位到错误的原因,因为,每个组件都可能有操作数据的方法;
increment(state) {
state.count++;
},
// 注意: 如果组件想要调用 mutations 中的方法,只能使用 this.$store.commit('方法名')
// 这种 调用 mutations 方法的格式,和 this.$emit('父组件中方法名')
subtract(state, obj) {
// 注意: mutations 的 函数参数列表中,最多支持两个参数,
// 其中,参数1: 是 state 状态; 参数2: 通过 commit 提交过来的参数;
console.log(obj);
state.count -= obj.c + obj.d;
},
},
getters: {
// 注意:这里的 getters, 只负责对外提供数据,不负责修改数据,如果想要修改 state 中的数据,请去找 mutations
optCount: function (state) {
return "当前最新的count值是:" + state.count;
},
// 经过咱们回顾对比,发现 getters 中的方法, 和组件中的过滤器比较类似,因为 过滤器和 getters 都没有修改原数据, 都是把原数据做了一层包装,提供给了 调用者;
// 其次, getters 也和 computed 比较像, 只要 state 中的数据发生变化了,那么,如果 getters 正好也引用了这个数据,那么 就会立即触发 getters 的重新求值;
},
});使用示例
- vuex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105var store = new Vuex.Store({
state: {
// this.$store.state.***
car: car, // 将 购物车中的商品的数据,用一个数组存储起来,在 car 数组中,存储一些商品的对象, 咱们可以暂时将这个商品对象,设计成这个样子
// { id:商品的id, count: 要购买的数量, price: 商品的单价,selected: false }
},
mutations: {
// this.$store.commit('方法的名称', '按需传递唯一的参数')
addToCar(state, goodsinfo) {
// 点击加入购物车,把商品信息,保存到 store 中的 car 上
// 分析:
// 1. 如果购物车中,之前就已经有这个对应的商品了,那么,只需要更新数量
// 2. 如果没有,则直接把 商品数据,push 到 car 中即可
// 假设 在购物车中,没有找到对应的商品
var flag = false;
state.car.some((item) => {
if (item.id == goodsinfo.id) {
item.count += parseInt(goodsinfo.count);
flag = true;
return true;
}
});
// 如果最终,循环完毕,得到的 flag 还是 false,则把商品数据直接 push 到 购物车中
if (!flag) {
state.car.push(goodsinfo);
}
// 当 更新 car 之后,把 car 数组,存储到 本地的 localStorage 中
localStorage.setItem("car", JSON.stringify(state.car));
},
updateGoodsInfo(state, goodsinfo) {
// 修改购物车中商品的数量值
// 分析:
state.car.some((item) => {
if (item.id == goodsinfo.id) {
item.count = parseInt(goodsinfo.count);
return true;
}
});
// 当修改完商品的数量,把最新的购物车数据,保存到 本地存储中
localStorage.setItem("car", JSON.stringify(state.car));
},
removeFormCar(state, id) {
// 根据Id,从store 中的购物车中删除对应的那条商品数据
state.car.some((item, i) => {
if (item.id == id) {
state.car.splice(i, 1);
return true;
}
});
// 将删除完毕后的,最新的购物车数据,同步到 本地存储中
localStorage.setItem("car", JSON.stringify(state.car));
},
updateGoodsSelected(state, info) {
state.car.some((item) => {
if (item.id == info.id) {
item.selected = info.selected;
}
});
// 把最新的 所有购物车商品的状态保存到 store 中去
localStorage.setItem("car", JSON.stringify(state.car));
},
},
getters: {
// this.$store.getters.***
// 相当于 计算属性,也相当于 filters
getAllCount(state) {
var c = 0;
state.car.forEach((item) => {
c += item.count;
});
return c;
},
getGoodsCount(state) {
var o = {};
state.car.forEach((item) => {
o[item.id] = item.count;
});
return o;
},
getGoodsSelected(state) {
var o = {};
state.car.forEach((item) => {
o[item.id] = item.selected;
});
return o;
},
getGoodsCountAndAmount(state) {
var o = {
count: 0, // 勾选的数量
amount: 0, // 勾选的总价
};
state.car.forEach((item) => {
if (item.selected) {
o.count += item.count;
o.amount += item.price * item.count;
}
});
return o;
},
},
});使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29methods: {
getGoodsList() {
// 1. 获取到 store 中所有的商品的Id,然后拼接出一个 用逗号分隔的 字符串
var idArr = [];
this.$store.state.car.forEach(item => idArr.push(item.id));
// 如果 购物车中没有商品,则直接返回,不需要请求数据接口,否则会报错
if (idArr.length <= 0) {
return;
}
// 获取购物车商品列表
this.$http
.get("api/goods/getshopcarlist/" + idArr.join(","))
.then(result => {
if (result.body.status === 0) {
this.goodslist = result.body.message;
}
});
},
remove(id, index) {
// 点击删除,把商品从 store 中根据 传递的 Id 删除,同时,把 当前组件中的 goodslist 中,对应要删除的那个商品,使用 index 来删除
this.goodslist.splice(index, 1);
this.$store.commit("removeFormCar", id);
},
selectedChanged(id, val) {
// 每当点击开关,把最新的 快关状态,同步到 store 中
// console.log(id + " --- " + val);
this.$store.commit("updateGoodsSelected", { id, selected: val });
}
}
总结:
state中的数据,不能直接修改,如果想要修改,必须通过 mutations
如果组件想要直接 从 state 上获取数据: 需要
this.$store.state.属性名
如果 组件,想要修改数据,必须使用 mutations 提供的方法,需要通过
this.$store.commit('方法的名称', 唯一的一个参数)
如果 store 中 state 上的数据, 在对外提供的时候,需要做一层包装,那么 ,推荐使用 getters, 如果需要使用 getters ,则用
this.$store.getters.
vuex 模块化
由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。
- 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const 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 的状态
模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const moduleA = {
state: () => ({
count: 0,
}),
mutations: {
increment(state) {
// 这里的 `state` 对象是模块的局部状态
state.count++;
},
},
getters: {
doubleCount(state) {
return state.count * 2;
},
},
};同样,对于模块内部的 action,局部状态通过
context.state
暴露出来,根节点状态则为context.rootState
:1
2
3
4
5
6
7
8
9
10const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit("increment");
}
},
},
};对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
1
2
3
4
5
6
7
8const moduleA = {
// ...
getters: {
sumWithRootCount(state, getters, rootState) {
return state.count + rootState.count;
},
},
};
#命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加
namespaced: true
的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40const store = new Vuex.Store({
modules: {
account: {
namespaced: true,
// 模块内容(module assets)
state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响
getters: {
isAdmin () { ... } // -> getters['account/isAdmin']
},
actions: {
login () { ... } // -> dispatch('account/login')
},
mutations: {
login () { ... } // -> commit('account/login')
},
// 嵌套模块
modules: {
// 继承父模块的命名空间
myPage: {
state: () => ({ ... }),
getters: {
profile () { ... } // -> getters['account/profile']
}
},
// 进一步嵌套命名空间
posts: {
namespaced: true,
state: () => ({ ... }),
getters: {
popular () { ... } // -> getters['account/posts/popular']
}
}
}
}
}
})启用了命名空间的 getter 和 action 会收到局部化的
getter
,dispatch
和commit
。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改namespaced
属性后不需要修改模块内的代码。
#在带命名空间的模块内访问全局内容(Global Assets)
如果你希望使用全局 state 和 getter,rootState
和 rootGetters
会作为第三和第四参数传入 getter,也会通过 context
对象的属性传入 action。
若需要在全局命名空间内分发 action 或提交 mutation,将 { root: true }
作为第三参数传给 dispatch
或 commit
即可。
1 | modules: { |
#在带命名空间的模块注册全局 action
若需要在带命名空间的模块注册全局 action,你可添加 root: true
,并将这个 action 的定义放在函数 handler
中。例如:
1 | { |
#带命名空间的绑定函数
当使用 mapState
, mapGetters
, mapActions
和 mapMutations
这些函数来绑定带命名空间的模块时,写起来可能比较繁琐:
1 | computed: { |
对于这种情况,你可以将模块的空间名称字符串作为第一个参数传递给上述函数,这样所有绑定都会自动将该模块作为上下文。于是上面的例子可以简化为:
1 | computed: { |
而且,你可以通过使用 createNamespacedHelpers
创建基于某个命名空间辅助函数。它返回一个对象,对象里有新的绑定在给定命名空间值上的组件绑定辅助函数:
1 | import { createNamespacedHelpers } from "vuex"; |
#给插件开发者的注意事项
如果你开发的插件(Plugin)提供了模块并允许用户将其添加到 Vuex store,可能需要考虑模块的空间名称问题。对于这种情况,你可以通过插件的参数对象来允许用户指定空间名称:
1 | // 通过插件的参数对象得到空间名称 |
#模块动态注册
在 store 创建之后,你可以使用 store.registerModule
方法注册模块:
1 | import Vuex from "vuex"; |
之后就可以通过 store.state.myModule
和 store.state.nested.myModule
访问模块的状态。
模块动态注册功能使得其他 Vue 插件可以通过在 store 中附加新模块的方式来使用 Vuex 管理状态。例如,vuex-router-sync
(opens new window)插件就是通过动态注册模块将 vue-router 和 vuex 结合在一起,实现应用的路由状态管理。
你也可以使用 store.unregisterModule(moduleName)
来动态卸载模块。注意,你不能使用此方法卸载静态模块(即创建 store 时声明的模块)。
注意,你可以通过 store.hasModule(moduleName)
方法检查该模块是否已经被注册到 store。
#保留 state
在注册一个新 module 时,你很有可能想保留过去的 state,例如从一个服务端渲染的应用保留 state。你可以通过 preserveState
选项将其归档:store.registerModule('a', module, { preserveState: true })
。
当你设置 preserveState: true
时,该模块会被注册,action、mutation 和 getter 会被添加到 store 中,但是 state 不会。这里假设 store 的 state 已经包含了这个 module 的 state 并且你不希望将其覆写。
#模块重用
有时我们可能需要创建一个模块的多个实例,例如:
- 创建多个 store,他们公用同一个模块 (例如当
runInNewContext
选项是false
或'once'
时,为了在服务端渲染中避免有状态的单例 (opens new window)) - 在一个 store 中多次注册同一个模块
如果我们使用一个纯对象来声明模块的状态,那么这个状态对象会通过引用被共享,导致状态对象被修改时 store 或模块间数据互相污染的问题。
实际上这和 Vue 组件内的 data
是同样的问题。因此解决办法也是相同的——使用一个函数来声明模块状态(仅 2.3.0+ 支持):
1 | const MyReusableModule = { |
vue-router
- vue用来实现SPA的插件
使用vue-router
创建路由器: router/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14new VueRouter({
routes: [
{
// 一般路由
path: "/about",
component: about,
},
{
// 自动跳转路由
path: "/",
redirect: "/about",
},
],
});注册路由器: main.js
1
2
3
4import router from "./router";
new Vue({
router,
});使用路由组件标签:
1
2<router-link to="/xxx">Go to XXX</router-link>
<router-view></router-view>
编写路由的3步
定义路由组件
映射路由
编写路由2个标签
嵌套路由
1
2
3
4
5
6
7
8
9
10children: [
{
path: "/home/news",
component: news,
},
{
path: "message",
component: message,
},
];向路由组件传递数据
1
2params: <router-link to="/home/news/abc/123">
props: <router-view msg='abc'>缓存路由组件
1
2
3<keep-alive>
<router-view></router-view>
</keep-alive>路由的编程式导航
1
2
3this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
this.$router.back(): 请求(返回)上一个记录路由
ajax
- 相关库:
- vue-resource: vue插件, 多用于vue1.x,官方文档
- axios: 第三方库, 多用于vue2.x
vue-resource使用
1 | // 引入模块 |
axios使用
1 | // 引入模块 |
设置 vue开发服务器的代理
正常来说, javascript在浏览器中是无法发送跨域请求的,所以我们需要在vuejs的"开发服务器"上做 个转发配置.
修改:
config/index.js
文件,增加下列内容:1
2
3
4
5
6
7
8
9
10
11
12module.exports = {
dev: {
proxyTable: {
'/api': { // 1. 对于所有以 "/api" 开头的url 做处理.
target: 'http://test.com', // 3. 转发到 siwei.me 上.
changeOrigin: true,
pathRewrite: {
'^/api': '' // 2. 把url中的 "/api" 去掉.
}
}
},
}上面的代码做了三件事:
- 对于所有以 “/api” 开头的url 做处理.
- 把url中的 “/api” 去掉.
- 把新的url 请求打到 siwei.me 上.
例如:
注意:以上的代理服务器内容,只能在"开发模式"下才能使用.在生产模式下,只能靠服务器的nginx的 特性来解决js跨域问题.
重启服务器,可以看到我们的转发设置已经生效:
1
2
3
4
5
6$ npm run dev
...
[HPM] Proxy created: /api -> http://siwei.me
[HPM] Proxy rewrite rule created: "^/api" ~> ""
> Starting dev server...
...
打包部署
直接使用下面命令,就可以把vue项目打包:
s 1
$ npm run build
将打包的dist目录上传到服务器
1
2
3
4# 把本地的 /dist 目录,上传到了远程的 /opt/app目录上
scp -P 6666 -r dist root@192.168.10.10:/opt/app
# 上传的文件夹重命名成: vue_demo
mv /opt/app/dist /opt/app/vue_demo配置nginx, 使域名:
vue_demo.test.com
指向该位置:把下面代码,加入到nginx的配置文件中(
/etc/nginx/nginx.conf
)1
2
3
4
5
6
7server {
listen 80;
server_name vue_demo.test.com;
client_max_body_size 500m;
charset utf-8;
root /opt/app/vue_demo;
}启动nginx
1
2
3# nginx -t
# nginx -s stop
# nginxnginx跑起来之后, 我们就要配置域名. 否则无法访问.
- 新增加二级域名: vue_demo.test.com,以dnspod为例, 需要设置这个二级域名的: A记录. IP地址(部署nginx的机器):
- 配制完成后,回到命令行, 输入
ping vue_demo.test.com
命令,确认后返回的ip正确表示配制完成,可以访问。
解决域名问题与跨域问题
在部署之后, 会发现Vuejs会遇到js 的经典问题: 远程服务器地址不对,或者跨域问题.
前提:我们的真正后台接口是: http://siwei.me/interface/blogs/all 如下:
域名404 问题
这个问题看起来如下:
这个问题是由于源代码中,访问
/interface/blogs/all
这个接口引起的:1
this.$http.get('/api/interface/blogs/all')...
在我们开发的时候, vuejs 会通过
$npm run dev
命令, 跑起一个 “开发服务器”, 这个server中有一个代理, 可以把所有的 以 ‘/api’ 开头的请求,例如:1
2
3localhost: 8080 / api / interface / blogs / all;
// 转发到:
siwei.me / interface / blogs / all;“开发服务器”的配置如下:
1
2
3
4
5
6
7
8
9proxyTable: {
'/api': {
target: 'http://siwei.me',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},所以, 在开发环境下,一切正常.但是在生产环境中, 发起请求的时候, 就不存在代理服务器,不存在dev server了,所以会出错.
解决方式有两种,一是使用代理,如何解决接口路径请求404的问题,二是直接写请求路径,需要解决cookie丢失的问题
跨域问题
这个问题,是js的经典问题.如果
vue_demo.siwei.me
直接访问siwei.me
域名下的资源,会报错. 因为他们是两个不同的域名.代码形如:
1
this.$http.get('http://siwei.me/api/interface/blogs/all')...
我们就会发现:
1
2
3XMLHttpRequest cannot load http://siwei.me/api/interface/blogs/all.
No 'Access-Control-Allow-Origin' header is present on the requested resource.
Origin 'http://vue_demo.siwei.me' is therefore not allowed access.完整过程如下:
解决域名和跨域问题
在代码端, 处理方式不变, 访问
/api
+ 原接口url:1
this.$http.get('/api/interface/blogs/all')...
在开发的时候, 继续保持vuejs 的代理存在. 配置代码如下:
1
2
3
4
5
6
7
8
9proxyTable: {
'/api': {
target: 'http://siwei.me',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
},在nginx的配置文件中,加入代理:(详细说明见代码中的注释)就可以了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17server {
listen 80;
server_name vue_demo.siwei.me;
client_max_body_size 500m;
charset utf-8;
root /opt/app/vue_demo;
# 第一步,把所有的 mysite.com/api/interface 转换成: mysite.com/interface
location /api {
rewrite ^(.*)\/api(.*)$ $1$2;
}
# 第二步, 把所有的 mysite.com/interface 的请求,转发到 siwei.me/interface
location /interface {
proxy_pass http://siwei.me;
}
}也就是说,上面的配置,把
http://vue_demo.siwei.me/api/interface/blogs/all
在服务器端做了个变换,相当于访问了:http://siwei.me/interface/blogs/all
,重启nginx ,就会发现生效了.如下所示:
原理理解
准备
1.[].slice.call(lis): 将伪数组转换为真数组
2.node.nodeType: 得到节点类型
3.Object.defineProperty(obj, propertyName, {}): 给对象添加/修改属性(指定描述符)
configurable: true/false 是否可以重新define
enumerable: true/false 是否可以枚举(for..in / keys())
value: 指定初始值
writable: true/false value是否可以修改存取(访问)描述符
get: 函数, 用来得到当前属性值
set: 函数, 用来监视当前属性值的变化
4.Object.keys(obj): 得到对象自身可枚举的属性名的数组
5.DocumentFragment: 文档碎片(高效批量更新多个节点)
6.obj.hasOwnProperty(prop): 判断prop是否是obj自身的属性
数据代理(MVVM.js)
1.通过一个对象代理对另一个对象中属性的操作(读/写)
2.通过vm对象来代理data对象中所有属性的操作
3.好处: 更方便的操作data中的数据
4.基本实现流程
1). 通过Object.defineProperty()给vm添加与data对象的属性对应的属性描述符
2). 所有添加的属性都包含getter/setter
3). 在getter/setter内部去操作data中对应的属性数据
模板解析(compile.js)
1 | 1.模板解析的关键对象: compile对象 |
数据劫持–>数据绑定
1 | 1.数据绑定(model==>View): |