Vue.js 入门
MVVM (Model-View-ViewModel) 思想
为什么要使用 MVVM
MVVM 模式和 MVC 模式一样,主要目的是分离视图(View)和模型(Model),有几大好处:
- 低耦合:视图(View)可以独立于 Model 变化和修改,一个 ViewModel 可以绑定到不同的 View 上,当 View 变化的时候 Model 可以不变,当 Model 变化的时候 View 也可以不变;
- 可复用:你可以把一些视图逻辑放在一个 ViewModel 里面,让很多 View 重用这段视图逻辑;
- 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于页面设计;
- 可测试:界面素来是比较难于测试的,而现在测试可以针对 ViewModel 来写。
Vue 实例
数据的基本引用
<div id="app">
<p>
{{message}}
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "123",
}
});
</script>
v-bind
v-bind 用于绑定数据和元素属性 缩写为
:
<div id="app">
{{student}}
<span v-bind:title="message">
鼠标悬停
</span>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "hello world",
student: "张三",
}
});
</script>
v-on
v-on 调用函数 缩写为
@
<div id="app">
<button v-on:click="sayHi()">click me</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "啦啦啦",
},
methods: {
sayHi: function () {
alert(this.message);
}
}
});
</script>
v-model
v-model 双向数据绑定(表单数据监听)
<div id="app">
输入的文本:<input type="text" v-model="message"> {{message}}
性别:
<input type="radio" name="sex" value="man" v-model="sexChecked"> man
<input type="radio" name="sex" value="woman" v-model="sexChecked"> woman
<p>选中了谁:{{sexChecked}}</p>
<select v-model="letter">
<option value="" disabled>--请选择--</option>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
</select>
<p> {{letter}} </p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: "#app",
data: {
message: "123",
sexChecked: 'man',
letter: 'B',
}
});
</script>
Vue 的生命周期

生命周期钩子 | 组件状态 | 最佳实践 |
---|---|---|
beforeCreate | 实例初始化之后,this 指向创建的实例,不能访问到 data、computed、watch、methods 上的方法和数据 | 常用于初始化非响应式变量 |
created | 实例创建完成,可访问 data、computed、watch、methods 上的方法和数据,未挂载到 DOM,不能访问到 $el 属性,$ref 属性内容为空数组 | 常用于简单的 ajax 请求,页面的初始化 |
beforeMount | 在挂载开始之前被调用,beforeMount 之前,会找到对应的 template,并编译成 render 函数 | – |
mounted | 实例挂载到 DOM 上,此时可以通过 DOM API 获取到 DOM 节点,$ref 属性可以访问 | 常用于获取 VNode 信息和操作,ajax 请求 |
beforeupdate | 响应式数据更新时调用,发生在虚拟 DOM 打补丁之前 | 适合在更新之前访问现有的 DOM,比如手动移除已添加的事件监听器 |
updated | 虚拟 DOM 重新渲染和打补丁之后调用,组件 DOM 已经更新,可执行依赖于 DOM 的操作 | 避免在这个钩子函数中操作数据,可能陷入死循环 |
beforeDestroy | 实例销毁之前调用。这一步,实例仍然完全可用,this 仍能获取到实例 | 常用于销毁定时器、解绑全局事件、销毁插件对象等操作 |
destroyed | 实例销毁后调用,调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁 | – |
注意:
- created 阶段的 ajax 请求与 mounted 请求的区别:前者页面视图未出现,如果请求信息过多,页面会长时间处于白屏状态
mounted
不会承诺所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染
参考文章:
axios 请求框架
npm install –save axios vue-axios
HTML 内使用
<div id="vue" v-clock>
{{info.name}}
<p>{{info.address.city}}</p>
<p>{{info.address.street}}</p>
</div>
// CDN 链接
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script>
var vm = new Vue({
el: "#vue",
data: {
info: {}
},
mounted() { // 钩子函数 链式编程
axios.get('./data.json').then(res => (this.info = res.data))
}
});
</script>
发送单个请求
// axios 发送单个请求
axios({
url: 'http://127.0.0.1:8080/home/multidata',
methods: 'get'
}).then(res => {
console.log(res);
})
axios({
url: 'http://127.0.0.1:8080/home/data',
methods: 'get',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res);
})
发送并发请求
// axios 发送并发请求
axios.all([
axios({
url: 'http://127.0.0.1:8080/home/multidata',
}),
axios({
url: 'http://127.0.0.1:8080/home/data',
methods: 'get',
params: {
type: 'pop',
page: 1
}
})
]).then(axios.spread ((res1, res2) => {
console.log(res1);
console.log(res2);
}));
axios全局配置
axios.defaults.baseURL = 'http://127.0.0.1:8080';
axios.defaults.timeout = 5000;
axios 实例
// 创建对应的 axios 的实例
const instance1 = axios.create({
baseURL = 'http://127.0.0.1:8080',
timeout = 5000
});
instance1({
url: '/home/multidata'
}).then(res => {
console.log(res);
})
instance1({
url: '/home/data',
params: {
type: 'pop',
page: 1
}
}).then(res => {
console.log(res);
})
axios拦截器(请求成功,请求失败,响应成功,响应失败)
export function request(config) {
// 1.创建axios的实例
const instance = axios.create({
baseURL: 'http://123.207.32.32:8000',
timeout: 5000
})
// 2.axios的拦截器
// 2.1请求拦截的作用
instance.interceptors.request.use(config => {
console.log(config);
// 1.比如config中的一些配置信息不符合服务器要求
// 2.比如每次发送网络请求时,都希望在页面中显示一个请求的图标
// 3.某些网络请求(比如登陆(token)), 需要携带一些特殊的信息
return config
}, err => {
// 断网的时候(发不出去)
console.log(err);
});
// 2.2响应拦截的作用
instance.interceptors.response.use(res => {
console.log(res);
return res.data;
}, err => {
console.log(err);
});
// 3.发送真正的网络请求
return instance(config);
}
Component 组件
<div id="app">
<liu v-for="item in items" v-bind:title="item">
</liu>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 定义一个Vue组件component
Vue.component("liu", {
props: ['title'],
template: '<li>{{title}}</li>',
})
var vm = new Vue({
el: "#app",
data: {
items: ["Java", "Linux", "前端"],
}
});
</script>
slot 插槽
<div id="app">
<!-- 自定义事件内容分发 -->
<todo>
<todo-title slot="todo-title" :title="title"></todo-title>
<todo-items slot="todo-items" v-for="(todoItem, index) in todoItems"
:item="todoItem" @remove="removeItem(index)"></todo-items>
</todo>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
// 定义一个Vue组件component
Vue.component("todo", {
template:
`<div>
<slot name="todo-title"></slot>
<ul>
<slot name="todo-items"></slot>
</ul>
</div>`
});
Vue.component("todo-title", {
props: ['title'],
template: '<div>{{title}}</div>'
});
Vue.component("todo-items", {
props: ['item'],
template: '<li>{{item}}<button @click="remove">delete</button></li>',
methods: {
remove: function () {
this.$emit('remove');
}
}
});
var vm = new Vue({
el: "#app",
data: {
title: "liubihao123",
todoItems: ["Java", "Linux", "前端"],
},
methods: {
removeItem: function (index) {
this.todoItems.splice(index, 1);
}
}
});
</script>
创建一个Vue项目并启动
Vue Cli 2 初始化项目:
> vue init webpack myvue
> cd myvue
> npm install
> npm audit fix
> npm run dev
Vue Cli 3 初始化项目:
> vue create myvue
> ...
vue-router
npm install vue-router –save-dev
官方文档:https://router.vuejs.org/zh/
Webpack
安装 Webpack
npm install webpack -g
npm install webpack-cli -g
测试安装成功
webpack -v
webpack-cli -v
webpack.base.conf.js


使用 Webpack
创建项目
创建一个名为 modules 的目录,用于放置 JS 模块等资源文件
在 modules 下创建模块文件,如
hello.js
,用于编写 JS 模块相关代码
exports.sayHi = function () {
document.write("<div>Hello Webpack</div>");
};
在 modules 下创建一个名为
main.js
的入口文件,用于打包时设置 entry 属性var hello = require("./hello"); hello.sayHi();
在项目目录下创建
webpack.config.js
配置文件,使用 webpack 命令打包modules.exports = { entry: "./modules/main.js", output: { filename: "./js/bundle.js" } };
```html
```shell # 参数 --watch 用于监听变化 webpack --watch
Tips
v-clock 解决闪烁问题
<!-- v-clock 解决闪烁问题-->
<style>
[v-clock] {
display: none;
}
</style>
HTML5 History 模式
const router = new VueRouter({
mode: 'history',
routes: [...]
})
建立公用方法
新建
./utils/common.js
// 全局变量 const globalObj = {}; // 定义公共变量 globalObj.name = 'LPxz'; // 定义公共方法 globalObj.toWebsite = function (url) { window.location.href = url; }; globalObj.reloadPage = function () { window.location.reload(); }; export default globalObj
在
main.js
中引用import commonMethods from './utils/common' Vue.prototype.$common = commonMethods;
Vue 文件调用
<template> <div id="lp_header"> <img src="static/images/logo_LPxz.png" class="logo" alt="" @click="reloadPage()"> <el-menu-item index="3" @click="toWebsite('https://github.com/LiuPiPiPi')">Github</el-menu-item> </div> </template> <script> export default { name: "lp_header", methods: { handleSelect(key, keyPath) { console.log(key, keyPath); }, reloadPage() { this.$common.reloadPage(); }, toWebsite(url) { this.$common.toWebsite(url); } }, } </script>
去除 router-link 自带的下划线
a {
text-decoration: none;
}
Vue 路由验证
推荐文章:https://juejin.im/post/6844903916870565901
el-input @change 无法触发的问题
推荐文章:https://www.jianshu.com/p/18fa34a3efcb
Vue 修改对象的属性值后页面不重新渲染
HTML页面如下:
<template v-for="item in tableData">
<div :class="{'redBorder':item.red}">
<div>{{ item.name}}</div>
<div>
<el-button size="mini" @click="clickBtn(item.id)" type="info">编辑</el-button>
<p class="el-icon-error" v-show="item.tip"></p>
</div>
</div>
</template>
js 部分如下:
<script>
export default {
data() {
return {
tableData:[{id:0,name:"lili",red:false,tip:false}]
}
},
methods: {
clickBtn(id){
this.tableData[id].red=true;
this.tableData[id].tip=true;
}
}
}
</script>
绑定的 class 是加一个红色的边框,如下:
.redBorder {
border:1px solid #f00;
}
在项目中点击 button 后不出现红色边框和提示错误框,打开 debugger 查看,发现运行到了这里却没有执行,tableData 中的值并没有改变,这个方法在以前使用时会起作用,可能是这次的项目比较复杂引起的,具体原因不明。
后通过查找资料修改为使用 $set 来设定修改值,JS 如下:
this.$set(this.tableData[id], "red", true);
但是依然没有起作用,打开 debugger 发现 tableData 的值修改成功,没有渲染到页面上,查找的资料也是比较凌乱,并不能解决问题,后请教大神,才知道是数据层次太多,没有触发 render 函数进行自动更新,需手动调用,调用方式如下:
this.$forceUpdate();
JS 完整代码如下:
<script>
export default {
data() {
return {
tableData:[{id:0,name:"lili",red:false,tip:false}]
}
},
methods: {
clickBtn(id){
this.$forceUpdate();
this.$set(this.tableData[id],"red",true);
this.$set(this.tableData[id],"tip",true);
}}}
</script>
element-ui 中拿到表格当前行的索引
scope.$index 可取到当前索引
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="small" v-if="scope.row.auth_info.status===110"
@click="disabledUser(scope.row.id,scope.$index)">
封禁</el-button>
<el-button size="small" type="danger" v-else
@click="disabledUser(scope.row.id,scope.$index)">
解禁</el-button>
</template>
</el-table-column>
v-for 渲染页面获取不到 DOM 元素解决方案
HTML 代码
<div id="app">
<li v-for="item in abc" :name="{{item.name}}">{{item.age}}</li>
</div>
JS 代码(错误代码)
// 错误代码示例一
var vm = new Vue({
el: '#app',
data: {
abc: new Object()
},
mounted: function () {
this.getList();
var li = document.querySelectorAll('li');
console.log(li.length); // 输出0
},
methods: {
getList: function () {
var data = [
{ name: '1', age: '21' },
{ name: '2', age: '22' },
{ name: '3', age: '23' },
{ name: '4', age: '24' },
{ name: '5', age: '25' }
];
this.$set(this, 'abc', data);
}
}
})
// 错误代码示例二
var vm;
window.onload = function(){
vm = { ... } // 实例化代码如上,去除 mounted 生命周期
vm.getList();
var li = document.querySelectorAll('li');
console.log(li.length); // 输出0
}
上述代码中的 li 即是通过 v-for 渲染生成,但是两种方式获取 li 输出的长度都为 0。
最终发现将数据初始化放到 beforeMount 里面即可在 mounted 里面正确输出获取到 li 元素的长度。
JS 代码(解决方案)
// 解决方案一(数据初始化放在挂载之前)
var vm = new Vue({
el: '#app',
data: {
abc: new Object()
},
beforeMount: function(){
this.getList();
},
mounted: function () {
var li = document.querySelectorAll('li');
console.log(li.length) // 输出5
},
methods: {
getList: function () {
var data = [
{ name: '1', age: '21' },
{ name: '2', age: '22' },
{ name: '3', age: '23' },
{ name: '4', age: '24' },
{ name: '5', age: '25' }
];
this.$set(this, 'abc', data);
}
}
})
// 解决方案二(mounted周期里面使用VUE自带钩子函数$nextTick做处理)
mounted: function () {
this.getList();
this.$nextTick(function () {
var li = document.querySelectorAll('li');
console.log(li.length) // 输出 5
})
}