之前Vue真的只是简单入门了,现在重新跟着另外的课程学习一下。
之前的文章 :Vue学习笔记
参考教程:学 Vue.js 看这个就够了
2020前端VUE框架最新最全实战课程
参考文档:
Vue官方文档:Vue官网
Vue Router文档: Vue-Router
Axios文档:Axios
Vuex文档:Vuex
一、Vue基本属性
1. v-cloak
解决数据渲染时闪烁问题
2. v-text
渲染文本
3. v-html
有时候需要往标签里面渲染一些html标签,用text就不行了,需要这个
4. v-bind
用来绑定元素attribute和model的数据,简写为':'
5. v-on
v-on 指令添加一个事件监听器,简写为@
6. 事件修饰符
.stop 阻止冒泡
<div id="app" v-cloak class="inner" @click="divClick">
<input type="button" value="戳我" @click.stop="btnClick"/>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
},
methods: {
divClick(){
console.log("div点击");
},
btnClick(){
console.log("btn点击");
}
}
});
</script>
本来先执行btnClick,在执行divClick
加上.stop后,单击后,冒泡事件被阻止在btnClick执行之后,也就是只输出btn点击
.prevent 阻止默认事件
<a href="http://www.baidu.com" @click.prevent="linkClick">点我去百度</a>
在单击a标签后,执行linkClick函数,但是不跳转去百度,阻止了默认事件
.capture 添加事件侦听器时使用事件捕获模式
<div id="app" v-cloak class="inner" @click.capture="divClick">
<div @click="div2Click">
<input type="button" value="戳我" @click="btnClick"/>
</div>
<!-- <a href="http://www.baidu.com" @click.prevent="linkClick">点我去百度</a>-->
</div>
打印顺序
.self 只当事件在该元素本身(比如不是子元素)触发时触发回调
就是说只在元素本身事件触发时才触发,不因为冒泡,捕获等方式触发
.once 事件只触发一次
.self和.stop的区别:.self只针对自己的回调,外层还有事件也会触发;但是.stop会阻止到该元素以后的所有冒泡行为
7. v-model
实现输入框和model中的数据的双向数据绑定
8. vue中绑定样式
可以使用三元表达式,flag是定义在data中的数据,为true或者false
<h2 :class="['red', 'thin','italic', flag?'active':'']">这是标题h2</h2>
把三元表达式改造成这种形式{'active':flag},提高代码可读性
<h3 :class="['red', 'thin','italic',{'active':flag}]">这是标题h3</h3><br/>
可以所有的class都传对象,此时的class可以不加引号
<h4 :class="{red:true, thin:true, italic:false, active:flag}">这是标题h4</h4><br/>
可以抽取出来放在vm实例的data里面,class绑定这个数据
data: {
flag: true,
classobj: {red:true, thin:true, italic:false, active:this.flag}
}
<h4 :class="classobj">这是标题h4</h4><br/>
绑定内联样式
<h1 :style="{color:'red', 'font-weight':200}">我是h1标题</h1>
同样可以抽离出来定义在data里面
一个绑定class的例子
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
* {
margin: 0;
padding: 0;
}
.page{
width: 100vw;
height: 100vh;
background-color: #efefef;
position: fixed;
left: 0;
top: 0;
}
.rMenu{
width: 50vw;
height: 100vh;
position: fixed;
background-color: skyblue;
left: 0;
top: 0;
transition: 5s;
transform: translateX(100vw);
}
.active{
transform: translateX(50vw);
}
</style>
<body>
<div id="app">
<div class="page">
首页
<button type="button" @click="toggle">切换侧边栏</button>
</div>
<div class="rMenu" :class="{active: isShow}">
侧边栏
</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
isShow: false,
style1: {color:'red', 'font-weight':200}
},
methods: {
toggle(){
this.isShow = ! this.isShow;
}
}
});
</script>
</body>
</html>
9. v-for
循环数组
<div v-for="(item, i) in list">索引{{i}} : {{item}}</div>
可以循环对象数组,不做演示
可以遍历对象
<p v-for="(key,value) in user">键:{{key}},值:{{value}}</p>
data: {
user: {
id: 1,
username: 'root',
password: '123'
}
}
可以迭代数字,循环打印出 1-10
<p v-for="count in 10">{{count}}</p>
v-for在组件中使用的时候,必须指定key,否则会出问题,且这个key只能是number或者string类型的值
没绑定key时,下面这个单选框勾选后,添加数据后,如果有勾选,勾选只记得自己的索引位置,不会记得自己勾选了哪一个item,添加数据后就会勾选错误
<div id="app" v-cloak>
<div>
<label>
Id:<input type="text" v-model="id">
</label>
<label>
Name:<input type="text" v-model="name">
</label>
<input type="button" value="添加" @click="add">
</div>
<div v-for="item in list" :key="item.id">
<input type="checkbox"/>
{{item.id}}:{{item.name}}
</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
list:[
{id: 1, name: '小红'},
{id: 2, name: '小黑'},
{id: 3, name: '小白'}
]
},
methods: {
add(){
this.list.unshift(({id: this.id, name: this.name}));
}
}
});
</script>
10. v-if 和 v-show
v-if的特点:每次都会重新删除和创建元素
v-show的特点:每次不会重新进行DOM的删除和创建,只是切换了元素的display:none样式
如果需要频繁创建删除,使用v-show;v-show的初始渲染消耗高,不频繁切换的情况使用v-if,如果元素永远不会显示在界面,推荐使用v-if
可以使用 v-else 指令来表示 v-if 的“else 块”
2.1.0 新增v-else-if,可以连续使用
11. 列表的增删
主要是列表的几个函数用法,forEach,some,filter,findIndex
some可以通过return true进行终止,forEach不可以终止
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 新 Bootstrap 核心 CSS 文件 -->
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
</head>
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app" v-cloak>
<div class="panel panel-primary">
<div class="panel-heading">
<h3 class="panel-title">添加品牌</h3>
</div>
<div class="panel-body form-inline">
<label>
id:
<input type="text" class="form-control" v-model="id">
</label>
<label>
name:
<input type="text" class="form-control" v-model="name">
</label>
<input type="butoon" value="添加" class="btn btn-primary" @click="add">
<label>
搜索名称关键字:
<input type="text" class="form-control" v-model="keywords">
</label>
</div>
</div>
<table class="table table-bordered table-hover table-striped">
<thead>
<tr>
<th>id</th>
<th>name</th>
<th>ctime</th>
<th>operation</th>
</tr>
</thead>
<tbody>
<tr v-for="item in search(keywords)" :key="item.id">
<td>{{item.id}}</td>
<td>{{item.name}}</td>
<td>{{item.ctime}}</td>
<td><a href="#" @click.prevent="del(item.id)">删除</a></td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
keywords: '',
list:[
{id: 1, name: '奔驰', ctime: new Date()},
{id: 2, name: '宝马', ctime: new Date()},
{id: 3, name: '劳斯莱斯', ctime: new Date()},
{id: 4, name: '宾利', ctime: new Date()}
]
},
methods: {
add(){
this.list.push({id: this.id, name: this.name, ctime: new Date()});
this.id = this.name = '';
},
del(id){
// this.list.some((item,i)=>{
// if (item.id === id){
// this.list.splice(i,1);
// return true;
// }
// })
let index = this.list.findIndex(item=>{
if (item.id === id){
return true;
}
});
this.list.splice(index, 1);
},
search(keywords){
//方式一 通过forEach
// const newList = [];
// this.list.forEach(item=>{
// if (item.name.indexOf(keywords) !== -1){
// newList.push(item);
// }
// });
// return newList;
//方式二 通过filter
return this.list.filter(item => {
//includes 方法 如果包含字符串,则返回true,不包含则返回false
if (item.name.includes(keywords)) {
return true;
}
});
}
}
});
</script>
</body>
</html>
12. 过滤器
Vue.js允许自定义过滤器,可被用作一些常见的文本格式化,过滤器可以用在两个地方:mustachc插值和v-bind表达式,过滤器应该添加在JS表达式的尾部,由“管道”符指示:
全局过滤器
在vm实例之前创建全局过滤器,将单纯替换为邪恶,使用正则
Vue.filter('msgFormat', function (msg) {
return msg.replace('/单纯/g', '邪恶');
});
定义时间格式化过滤器
Vue.filter('dateFormat', function (data) {
//根据给定的时间字符串,得到特定的时间
var dt = new Date(data);
var yy = dt.getFullYear();
var MM = dt.getMonth()+1;
var dd = dt.getDate();
var HH = dt.getHours();
var mm = dt.getMinutes();
// return yy+'-'+mm+'-'+dd;
return `${yy}-${MM}-${dd} ${HH}:${mm}`
});
调用过滤器
<td>{{item.ctime | dateFormat}}</td>
私有过滤器
写在vm实例中
//定义私有过滤器 过滤器有两个条件 过滤器名称和处理函数
filters: {
dateFormat: function (data) {
//根据给定的时间字符串,得到特定的时间
var dt = new Date(data);
var yy = dt.getFullYear();
var MM = (dt.getMonth()+1).toString().padStart(2, '0');
var dd = dt.getDate();
var HH = dt.getHours().toString().padStart(2, '0');
var mm = dt.getMinutes().toString().padStart(2, '0');
var ss = dt.getSeconds().toString().padStart(2, '0');
// return yy+'-'+mm+'-'+dd;
return `${yy}-${MM}-${dd} ${HH}:${mm}:${ss}`;
}
}
过滤器调用时,同名优先找私有过滤器,再找全局过滤器
13. 自定义按键修饰符
gei文本输入框绑定enter按键事件,按下后触发add函数
<input type="text" class="form-control" v-model="name" @keyup.enter="add">
所有的键盘上的键都对应一个键盘码,例如keyup.113 代表按下F2
但是这样的代码不太好阅读,可以自定义全局按键修饰符
这样可以使用keyup.f2即可
14. 自定义全局指令让文本框获取焦点
注意,Vue中的所有的指令,在调用的时候,都以v-开头
给搜索框添加v-focus指令
<input type="text" class="form-control" v-model="keywords" id="search" v-focus/>
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
在Vue.directive的参数中:
参数1: 指令的名称 注意,在定义的时候,指令的名称前面不需要加v-前缀
但是在调用的时候,必须在指令名称前加上v-前缀来进行调用
参数2:是一个对象,这个对象身上,有一些指令相关的函数,这些函数可以在特定的阶段,执行相关的操作
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
componentUpdated:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
unbind:只调用一次,指令与元素解绑时调用。
钩子函数的参数:
el、binding、vnode 和 oldVnode
注意在每个函数中,第一个参数永远是el,表示被绑定了指令的那个元素,这个el参数,是一个原生的js对象
自定义v-color执行让文本输入框输入的文字变为红色
Vue.directive('color',{
bind: function (el) {
el.style.color = 'red';
},
inserted: function (el) {// inserted 表示元素插入到DOM中的时候,会执行inserted函数
// el.focus();
},
update: function (el) {// 当VNOde更新的时候,会执行updated,可能会触发多次
}
});
binding:一个对象,包含以下 property:
name:指令名,不包括 v- 前缀。
value:指令的绑定值,例如:v-my-directive="1 + 1" 中,绑定值为 2。
oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 钩子中可用。无论值是否改变都可用。
expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1" 中,表达式为 "1 + 1"。
arg:传给指令的参数,可选。例如 v-my-directive:foo 中,参数为 "foo"。
modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }。
传递参数,设置颜色
<input type="text" class="form-control" v-model="keywords" id="search" v-focus v-color="'green'">
bind: function (el, binding) {
el.style.color = binding.value;
//console.log(binding.name);
//console.log(binding.value);
//console.log(binding.expression);
}
同样可以定义私有指令
directives: {
'fontweight': {
bind: function (el, binding) {
console.log(el.style.fontWeight);
el.style.fontWeight = binding.value;
console.log(el.style.fontWeight);
}
}
}
在很多时候,你可能想在 bind 和 update 时触发相同行为,而不关心其它的钩子。比如这样写:
directives: {
'fontsize': function (el, binding) {// 注意: 这个function等于把代码写到了bind和update里面
console.log(parseInt(binding.value));
el.style.fontSize = parseInt(binding.value) + 'px';
// el.style.fontSize = 200 + 'px';
console.log(el.style.fontSize);
}
}
15. Vue实例的生命周期
<div id="app" v-cloak>
<h3 id="h3">{{msg}}</h3>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
msg: '我是msg'
},
methods: {
show: function () {
console.log("执行show方法")
}
},
beforeCreate: function () { // 第一个生命下周起函数,表示实例完全被创建出来后,就执行的一个函数,在这里面data和methods都还没初始化
// console.log(this.msg); //undefined
// this.show(); //this.show is not a function
},
created: function () {// 第二个生命周期函数,在created中,data和methods已经被初始化好了
console.log(this.msg); // 输出 我是msg
this.show(); // 输出 执行show方法
},
beforeMount: function () {// 第三个生命周期函数,表示实例已经在内存中编辑完成了,但是尚未把模板渲染到页面中
let innerText = document.getElementById('h3').innerText;
console.log(innerText); // 打印出{{msg}} 此时页面中的数据还没有被真正替换,data中的数据没有挂载到页面
},
mounted: function () {// 第四个生命周期函数,表示内存中的模板已经挂载到了页面中,用户已经可以看到渲染好的页面了
let innerText = document.getElementById('h3').innerText;
console.log(innerText); // 打印 我是msg
},
beforeUpdate: function () {// 这时候表示 我们的界面还没有更新,但是数据更新了
console.log(document.getElementById('h3').innerText);// 在控制台输入vm.msg='我是修改后的msg',打印出 我是msg
},
updated: function () {
console.log(document.getElementById('h3').innerText);// 在控制台输入vm.msg='我是修改后的msg',打印出 我是修改后的msg
}
});
</script>
16. vue-resource发起get post jsonp请求
<div id="app" v-cloak>
<input type="button" value="get请求" @click="getInfo"/>
<input type="button" value="post请求" @click="postInfo"/>
<input type="button" value="jsonp请求" @click="jsonpInfo"/>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
},
methods: {
getInfo: function () {
//发起get请求后 通过.then传入回调函数
this.$http.get('https://cn.vuejs.org/').then(function (result) {
// console.log(result);
//通过.body获取请求的数据
console.log(result.body);
})
},
postInfo: function () {
//手动发起的post请求,默认没有表单格式,所以有的服务器处理不了
// 第二个参数是请求的数据封装的对象
// 通过post请求的第三个参数,{emulateJSON: true} 设置提交的内容类型为普通表单数据格式
this.$http.post('https://cn.vuejs.org/', //注意此地址无法发起post请求 我这里随便写的
{},{emulateJSON: true}).then(result =>{
console.log(result.body)
})
},
jsonpInfo: function () {
this.$http.jsonp('https://cn.vuejs.org/').then(result=>{
console.log(result.body);
})
}
}
});
</script>
17. Vue动画
Vue对进入/离开的过渡定义了6个class
在进入/离开的过渡中,会有 6 个 class 切换。
v-enter:定义进入过渡的开始状态。在元素被插入之前生效,在元素被插入之后的下一帧移除。
v-enter-active:定义进入过渡生效时的状态。在整个进入过渡的阶段中应用,在元素被插入之前生效,在过渡/动画完成之后移除。这个类可以被用来定义进入过渡的过程时间,延迟和曲线函数。
v-enter-to:2.1.8 版及以上定义进入过渡的结束状态。在元素被插入之后下一帧生效 (与此同时 v-enter 被移除),在过渡/动画完成之后移除。
v-leave:定义离开过渡的开始状态。在离开过渡被触发时立刻生效,下一帧被移除。
v-leave-active:定义离开过渡生效时的状态。在整个离开过渡的阶段中应用,在离开过渡被触发时立刻生效,在过渡/动画完成之后移除。这个类可以被用来定义离开过渡的过程时间,延迟和曲线函数。
v-leave-to:2.1.8 版及以上定义离开过渡的结束状态。在离开过渡被触发之后下一帧生效 (与此同时 v-leave 被删除),在过渡/动画完成之后移除。
自定义动画实现标题的淡入淡出
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 自定义两组样式,来控制transition-->
<style>
.v-enter, /* v-enter 是进入之前,元素的起始状态 此时还没有进入 */
.v-leave-to{ /* 是动画离开之后,元素的终止状态,此时动画已经结束*/
opacity: 0;
transform: translate(100px);
}
/*离场动画的时间段 */
.v-enter-active,
.v-leave-active{
transition: all 0.8s ease;
}
</style>
</head>
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app" v-cloak>
<input type="button" value="按钮" @click="flag = !flag">
<!-- 需求 点击按钮 h3显示,再点击 消失-->
<transition>
<h3 v-if="flag">这是一个标题</h3>
</transition>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
flag: true
},
methods: {
}
});
</script>
</body>
</html>
可以通过给transition的name属性设置值,来修改当前组对应的css
.v-enter, /* v-enter 是进入之前,元素的起始状态 此时还没有进入 */
.v-leave-to{ /* 是动画离开之后,元素的终止状态,此时动画已经结束*/
opacity: 0;
transform: translate(100px);
}
/*离场动画的时间段 */
.v-enter-active,
.v-leave-active{
transition: all 0.8s ease;
}
css样式加上my-前缀表示该组动画
.my-enter,
.my-leave-to{ /* 是动画离开之后,元素的终止状态,此时动画已经结束*/
opacity: 0;
transform: translateY(100px);
}
/*离场动画的时间段 */
.my-enter-active,
.my-leave-active{
transition: all 0.8s ease;
}
可以使用第三方css库,例如animate.css
<div id="app" v-cloak>
<input type="button" value="按钮" @click="flag = !flag">
<!-- 需求 点击按钮 h3显示,再点击 消失-->
<transition enter-active-class="animate__bounceIn" leave-active-class="animate__bounceOut">
<h3 v-if="flag">这是一个标题</h3>
</transition>
<!-- <h1 class="animate__animated animate__bounce">An animated element</h1>-->
</div>
使用:duration,统一设置入场和离场时长 单位是ms
<transition enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOut" :duration="1000">
<h3 v-if="flag">这是一个标题</h3>
</transition>
可以分开指定时长:
<transition enter-active-class="animate__bounceIn"
leave-active-class="animate__bounceOut" :duration="{enter: 200, leave:400}">
<h3 v-if="flag">这是一个标题</h3>
</transition>
使用钩子函数进行动画
可以在 attribute 中声明 JavaScript 钩子
<transition
v-on:before-enter="beforeEnter"
v-on:enter="enter"
v-on:after-enter="afterEnter"
v-on:enter-cancelled="enterCancelled"
v-on:before-leave="beforeLeave"
v-on:leave="leave"
v-on:after-leave="afterLeave"
v-on:leave-cancelled="leaveCancelled"
>
<!-- ... -->
</transition>
methods: {
// --------
// 进入中
// --------
beforeEnter: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
enter: function (el, done) {
// ...
done()
},
afterEnter: function (el) {
// ...
},
enterCancelled: function (el) {
// ...
},
// --------
// 离开时
// --------
beforeLeave: function (el) {
// ...
},
// 当与 CSS 结合使用时
// 回调函数 done 是可选的
leave: function (el, done) {
// ...
done()
},
afterLeave: function (el) {
// ...
},
// leaveCancelled 只用于 v-show 中
leaveCancelled: function (el) {
// ...
}
}
一个小球半场动画的例子
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<style>
.ball{
width: 15px;
height: 15px;
border-radius: 50%;
background-color: red;
}
</style>
</head>
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app" v-cloak>
<input type="button" value="加入购物车" @click="flag = !flag"/>
<transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
>
<div class="ball" v-show="flag"></div>
</transition>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
flag: false
},
methods: {
// 动画钩子函数的第一个参数: el,表示要执行动画的哪个DOM元素,是原生的JS DOM对象
beforeEnter(el){
//beforeEnter 表示动画入场之前,此时,动画尚未开始,可以在beforeEnter中,设置元素
el.style.transform = "translate(0, 0)";
},
enter(el, done){
// enter 表示动画开始之后的样式,这里,可以设置小球完成动画之后的,结束状态
el.offsetWidth; // 这句话 没有实际的作用, 但是如果不写 出不来动画效果 可以认为el.offsetWidth 会强制动画刷新
el.style.transform = "translate(150px, 450px)";
el.style.transition = "all 1s ease";
done(); // 当动画完成后立即消失 这个done实际上就是afterEnter函数,done是afterEnter函数的引用
},
afterEnter(){
// 动画完成之后调用
// console.log("ok");
this.flag = !this.flag;
}
}
});
</script>
</body>
</html>
实现列表过渡的时候,需要过渡的元素是通过v-for循环渲染出来的,不能使用transition包裹,需要使用transtion-group,需要为循环的元素绑定:key属性
下面为一个列表元素插入,删除的动画例子,实现了入场动画,删除动画,还有一些小的优化功能
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<style>
[v-cloak] {
display: none;
}
li{
border: 1px dashed #999999;
margin: 5px;
padding-left: 5px;
font-size: 20px;
width: 100%;
}
.v-enter,
.v-leave-to{
opacity: 0;
transform: translateY(80px);
}
.v-enter-active,
.v-leave-active{
transition: all 0.6s ease;
}
/*v-move 和 v-leave-active 配合使用 能够实现列表后续的元素,渐渐地飘上来的效果*/
.v-move{
/* 设置元素位移的效果*/
transition: all 0.6s ease;
}
.v-leave-active{
position: absolute;
}
</style>
<body>
<div id="app" v-cloak>
<label>
Id:
<input type="text" v-model="id">
</label>
<label>
Name:
<input type="text" v-model="name">
</label>
<input type="button" value="添加" @click="add">
<ul>
<transition-group>
<li v-for="(item,i) in list" :key="item.id" @click="del(i)">
{{item.id}} --- {{item.name}}
</li>
</transition-group>
</ul>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#app',
data: {
id: '',
name: '',
list:[
{id:1, name:'赵高'},
{id:2, name:'秦桧'},
{id:3, name:'严嵩'},
{id:4, name:'魏忠贤'}
]
},
methods: {
add(){
this.list.push({id: this.id, name:this.name});
this.id = this.name = '';
},
del(i){
this.list.splice(i,1);
}
}
});
</script>
</body>
</html>
给transtion-group添加appear属性,可以实现页面刚展现出来时的入场元素渐入的效果
<transition-group appear>
<li v-for="(item,i) in list" :key="item.id" @click="del(i)">
{{item.id}} --- {{item.name}}
</li>
</transition-group>
在上述渲染过程中, transition-group会把所有li项放在一个span里面,
<ul>
<span>
<li>...
</li>...
...
</span>
</ul>
这不符合规范,将此处ul标签删除,然后通过tag指定渲染li项的外部标签,如果不指定tag属性 默认渲染为span标签
<transition-group appear tag="ul">
<li v-for="(item,i) in list" :key="item.id" @click="del(i)">
{{item.id}} --- {{item.name}}
</li>
</transition-group>
18. 计算属性
参考上一次笔记,略
19. 侦听属性
使用watch监听msg的变化
let vm = new Vue({
el: '#app',
data: {
msg: "我是msg"
},
methods: {
},
watch: {
// val是传进来的改变的值
msg: function (val) {
console.log('监听事件:');
console.log(val);
}
}
});
20. 插槽
在组件里面使用slot充当占位符,在父组件使用时,向子组件传递模板,子组件将其渲染到slot声明处,slot可以由text,html甚至一个组件进行渲染
<template id="tmp1">
<div class="alert">
<h1>温馨提示</h1>
<div class="content">
<slot></slot>
</div>
</div>
</template>
<div id="app">
<alert-com>
<p>小心熊出没</p>
</alert-com>
</div>
二、组件
1. 创建组件
Vue.component('mycom', {
template: '<h3>这是使用组件的模板创建的</h3>'
});
注意template只能有一个根元素
因为组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项。
注意,一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝:
data: function () {
return {
count: 0
}
}
私有组件
var vm = new Vue({
el: '#app',
components: {
login: {
template: '<h1>这是私有的login组件</h1>'
}
},
});
2. 组件的data属性
Vue.component('mycom', {
template: '<h3>这是使用组件的模板创建的</h3>',
data: function () {
return {
}
}
});
组件的data和Vue实例的data不同,实例中的data可以为一个对象,但是组件中的data必须是一个方法;组件中的data除了必须为一个方法之外,这个方法内部必须返回一个对象才行
组件中的data可以在template中引用
3. component占位符
使用component占位符,:is属性可以用来指示要展示的组件的名称
<component :is="'counter'"></component>
可以使用这个特性完成组件的切换显示,将:is绑定到一个变量上,修改变量的值
4. 组件的动画
多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:
<transition name="component-fade" mode="out-in">
<component v-bind:is="view"></component>
</transition>
这里的mode属性是控制组件切换的方式,要消失的组件先执行出动画,进入的组件再进行进入动画
5. 父组件向子组件传值
父组件向子组件传值,通过绑定自定义属性名进行传值,子组件通过pros接收父组件传递的值
<div id="app">
<!-- 父组件,可以在引用子组件的时候,通过属性绑定(v-bind)的形式,
把需要传递给子组件的数据,以属性绑定的形式,传递到子组件内部,供子组件使用-->
<com1 :parentmsg="msg"></com1>
</div>
<script>
let vm = new Vue({
el: '#app',
data: {
msg: '123 父组件中的数据'
},
methods: {},
components: {
com1: {
//在子组件中无法直接访问到父组件data中的数据和methods中的方法
template: "<h1>这是com1子组件---{{parentmsg}}</h1>",
// 组件中所有props数据 都是通过父组件传递给子组件的
props:['parentmsg']//把父组件传递过来的parentmsg属性,先在props数组中,定义一下,这样才能使用这个数组
},
}
});
</script>
6. 父组件向子组件传递方法
父组件向子组件传递方法,使用v-on绑定方法
子组件获取父组件的方法,并且可以变向地向父组件传递参数,使用this.$emit()
<div id="app">
<!-- 父组件向子组件传递方法,使用的是事件绑定机制; v-on ,
当我们自定义一个事件属性之后,name子组件就能够通过某些方式,来调用传递进去的这个方法
-->
<com2 @func="show"></com2>
</div>
<template id="tmp1">
<div>
<h1>这是一个子组件</h1>
<input type="button" value="子组件按钮 点击后,触发父组件传递过来的方法"
@click="myclick">
</div>
</template>
<script type="text/javascript">
let com2 = {
template: '#tmp1',
data: function () {
return {
son: {age: 6, name: '小头儿子'}
}
},
methods: {
myclick() {
//当点击子组件的按钮的时候,如何拿到父组件传递过来的func方法,并调用这个方法?
// emit 英文意思是 触发,调用,发射的意思
// 可以进行传参,后面的123就是参数
this.$emit('func', '---' + this.son.age + '---' + this.son.name)
}
}
};
let vm = new Vue({
el: '#app',
data: {
msgFromSon: null
},
methods: {
show(data) {
console.log("调用了父组件的show方法" + data);
// this.msgFromSon = data 将子组件传递的数据保存到父组件的参数中
this.msgFromSon = data;
}
},
components: {
com2
}
});
</script>
实际上还可以把父组件的方法作为属性传递过去(使用v-bind),子组件使用prop接收
由于父组件的方法可以直接修改父元素的数据,所以传递以后,子组件通过this.func()调用也可以修改父元素的数据
在子组件中可以通过this.$parent.func()调用父元素的方法
在子组件元素的绑定中可以直接写 @click='$parent.func'绑定事件
实际上this.$parent是父元素的一个引用,还可以去到数据
还有$root,$children可供使用
一个评论列表的例子
<!DOCTYPE html>
<html lang="en" xmlns:v-bind="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css"
rel="stylesheet">
</head>
<style>
[v-cloak] {
display: none;
}
</style>
<body>
<div id="app">
<comment-box @loadcomments="loadComments"></comment-box>
<ul class="list-group">
<li class="list-group-item" v-for="item in list" :key="item.user">
<span class="badge">评论人:{{item.user}}</span>
{{item.content}}
</li>
</ul>
</div>
<template id="tmp1">
<div>
<div class="form-group">
<label>评论人</label>
<input type="text" class="form-control" v-model="user">
</div>
<div class="form-group">
<label>评论内容</label>
<textarea class="form-control" v-model="content"></textarea>
</div>
<div class="form-group">
<input type="button" value="发表评论" class="btn btn-primary" @click="postcomment"/>
</div>
</div>
</template>
<script type="text/javascript">
let commentBox = {
template: '#tmp1',
data: function () {
return {
user: '',
content: ''
}
},
methods: {
postcomment(){
// 构造一个评论组成的对象
let comment = {id: Date.now(), user: this.user, content: this.content};
// 从localStorage中获取所有的评论
let list = JSON.parse(localStorage.getItem('cmts') || '[]');
list.push(comment);
// 重新序列化
localStorage.setItem('cmts', JSON.stringify(list));
this.user = this.content = '';
this.$emit('loadcomments')
}
}
};
let vm = new Vue({
el: '#app',
data: {
list: [
{id: new Date(), user: '李白', content: '举杯邀明月'},
{id: new Date(), user: '江小白', content: '劝君更尽一杯酒'},
{id: new Date(), user: '小马', content: '我是小马'}
]
},
methods: {
loadComments(){
//从本地的localStorage中加载评论列表
let list = JSON.parse(localStorage.getItem('cmts') || '[]');
this.list = list;
}
},
components: {
'commentBox': commentBox
},
created: function () {
this.loadComments()
}
});
</script>
</body>
</html>
7. 使用ref获取DOM元素和组件引用
<div id="app">
<input type="button" value="获取元素" @click="getElement">
<h1 id="h1" ref="myh1">我是一个h1</h1>
<hr>
<login ref="login"></login>
</div>
let login = {
template: '<h1>登录组件</h1>',
data() {
return {
msg: '登录组件的msg'
}
},
methods: {
show(){
console.log('login的show方法')
}
}
};
let vm = new Vue({
el: '#app',
data: {},
methods: {
getElement() {
// console.log(document.getElementById('h1').innerText);
// 使用ref替代了dom元素的操纵
console.log(this.$refs.myh1.innerText);
console.log(this.$refs.login.msg);
this.$refs.login.show();
}
},
components: {
'login': login
}
});
8. vue-cli脚手架
Vue CLI 是一个基于 Vue.js 进行快速开发的完整系统,提供:
通过 @vue/cli 实现的交互式的项目脚手架。
通过 @vue/cli + @vue/cli-service-global 实现的零配置原型开发。
一个运行时依赖 (@vue/cli-service),该依赖:
可升级;
基于 webpack 构建,并带有合理的默认配置;
可以通过项目内的配置文件进行配置;
可以通过插件进行扩展。
一个丰富的官方插件集合,集成了前端生态中最好的工具。
一套完全图形化的创建和管理 Vue.js 项目的用户界面。
Vue CLI 致力于将 Vue 生态中的工具基础标准化。它确保了各种构建工具能够基于智能的默认配置即可平稳衔接,这样你可以专注在撰写应用上,而不必花好几天去纠结配置的问题。与此同时,它也为每个工具提供了调整配置的灵活性,无需 eject。
这里假设你已经知道如何使用node
运行
npm install -g @vue/cli
安装vue-cli
然后到一个适合的目录,使用vue create project-name 创建工程
可以使用vue ui 然后进入ui界面进行项目的打包,编译,部署,配置等等操作
三、路由
1. 使用router-link和router-view
<div id="app">
<h1>Hello App!</h1>
<p>
<!-- 使用 router-link 组件来导航. -->
<!-- 通过传入 `to` 属性指定链接. -->
<!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<!-- 路由出口 -->
<!-- 路由匹配到的组件将渲染在这里 -->
<router-view></router-view>
</div>
2. this.$router和this.$route
通过 this.$router 访问路由器,也可以通过 this.$route 访问当前路由
this.$route的属性
this.$router的属性
3. 动态路由
先配置路由的动态路径参数
{
path: '/news/:id',
component: newsCom
}
一个“路径参数”使用冒号 : 标记。当匹配到一个路由时,参数值会被设置到 this.$route.params,可以在每个组件内使用。于是,我们可以更新 newsCom的模板,输出当前用户的 ID
<template>
<div>
<h1>新闻内容页面:{{$route.params.id}}</h1>
</div>
</template>
4.监听路由参数的变化
使用watch监听$route 对象:
watch: {
$route(to, from) {
// 对路由变化作出响应...
}
}
或者使用 2.2 中引入的 beforeRouteUpdate 导航守卫:
beforeRouteUpdate (to, from, next) {
// react to route changes...
// don't forget to call next()
}
5.嵌套路由
定义路由,在children中定义子路由
{
path: '/bignews',
component: bigNews,
children: [
{
path: 'video',
component: VideoView
}, {
path: 'text',
component: textView
}
]
}
在bigNews组件的模板中添加routeview标签,就可以把子路由匹配到的组件显示到这里
<template>
<div>
<h1>bigNews页面</h1>
<router-view></router-view>
</div>
</template>
6. 编程式导航
除了route-link可以借助 router 的实例方法,通过编写代码来实现导航跳转
router.push(location, onComplete?, onAbort?)
通过路径跳转
goEvent(){
this.$router.push({
path: '/about'
})
}
通过路由的名字跳转
goEvent(){
this.$router.push({
name: 'About'
})
}
定义的路由
{
path: '/about',
name: 'About',
// route level code-splitting
// this generates a separate chunk (about.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
}
需要传递动态参数
goEvent(){
this.$router.push({
name: 'news',
params: {id: '123'}
})
}
注意path和params不能一起使用,params会被忽略
const userId = '123'
router.push({ name: 'user', params: { userId }}) // -> /user/123
router.push({ path: `/user/${userId}` }) // -> /user/123
// 这里的 params 不生效
router.push({ path: '/user', params: { userId }}) // -> /user
需要带查询参数
// 带查询参数,变成 /register?plan=private
router.push({ path: 'register', query: { plan: 'private' }})
router.replace(location, onComplete?, onAbort?)
跟 router.push 很像,唯一的不同就是,它不会向 history 添加新记录,而是跟它的方法名一样 —— 替换掉当前的 history 记录。
在 2.2.0+,replace和push都提供了回调函数,按需要使用
router.go(n)
n大于0就前进n步,n小于0就后退-n步。记录数量不够时默默失败
7. 命名路由
在创建路由时,给路由加一个属性name
要链接到一个命名路由,可以给 router-link 的 to 属性传一个对象:
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>
这跟代码调用 router.push() 是一回事:
8. 命名视图
当想要同时展示多个视图时,而不是嵌套展示,例如一个布局有导航,侧边栏,主要内容,这时候就需要命名视图
布局:
<template>
<div id="app">
<div id="nav">
<router-link to="/">Home</router-link> |
<router-link to="/about">About</router-link> |
<router-link to="/me">Me</router-link> |
<router-link to="/news">News</router-link>
</div>
<!-- 导航-->
<router-view name="nav"/>
<!-- 侧边栏-->
<router-view name="aside"/>
<!-- 主要内容-->
<router-view/>
</div>
</template>
定义路由,没有名字的router-view与default对应
{
path: '/',
name: 'Home',
// component: Home
components: {
nav: Nav,
aside: Aside,
default: Home
}
}
9.重定向
访问/a重定向到/about
{
path: '/a',
redirect: '/about'
}
也可以重定向到一个命名的路由
{ path: '/a', redirect: { name: 'foo' }}
还可以是一个函数
{ path: '/a', redirect: to => {
// 方法接收 目标路由 作为参数
// return 重定向的 字符串路径/路径对象
return {name:'about', params:{id: 123}}
}}
10. 别名
{ path: '/a', component: A, alias: '/b' }
/a 的别名是 /b,意味着,当用户访问 /b 时,URL 会保持为 /b,但是路由匹配则为 /a,就像用户访问 /a 一样。
11. 路由组件传参
当传递动态参数时,在组件中使用$route.params.xx获取参数,使得组件与对应路由高度耦合,可以使用props解耦
在组件使用props接收参数,在定义路由时指定props接收参数为true
const User = {
props: ['id'],
template: '<div>User {{ id }}</div>'
}
const router = new VueRouter({
routes: [
{ path: '/user/:id', component: User, props: true },
// 对于包含命名视图的路由,你必须分别为每个命名视图添加 `props` 选项:
{
path: '/user/:id',
components: { default: User, sidebar: Sidebar },
props: { default: true, sidebar: false }
}
]
})
如果想将传入的query参数也使用props接收,可以创建一个函数返回 props。这样你便可以将参数转换成另一种类型,将静态值与基于路由的值结合等等
{ path: '/search', component: SearchUser, props: (route) => ({ query: route.query.q }) }
URL /search?q=vue 会将 {query: 'vue'} 作为属性传递给 SearchUser 组件。
记得在组件中使用props接收query参数
12. history模式
Vue默认使用hash模式,使用 URL 的 hash 来模拟一个完整的 URL,于是当 URL 改变时,页面不会重新加载。
如果不想要很丑的 hash,我们可以用路由的 history 模式,这种模式充分利用 history.pushState API 来完成 URL 跳转而无须重新加载页面。
const router = new VueRouter({
mode: 'history',
routes: [...]
})
这需要后端服务器的配置来支持
参考 HTML5 History 模式
这样做以后,服务器就不再返回 404 错误页面,因为对于所有路径都会返回 index.html 文件。为了避免这种情况,你应该在 Vue 应用里面覆盖所有的路由情况,然后在给出一个 404 页面。
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
})
13. 导航守卫
“导航”表示路由正在发生改变。
vue-router 提供的导航守卫主要用来通过跳转或取消的方式守卫导航。
参数或查询的改变并不会触发进入/离开的导航守卫
注册全局前置守卫和全局后置钩子
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
//全局前置守卫
router.beforeEach(((to, from, next) => {
}))
//全局后置钩子
router.afterEach((to, from) => {
})
每个守卫方法接收三个参数:
to: Route: 即将要进入的目标 路由对象
from: Route: 当前导航正要离开的路由
next: Function: 一定要调用该方法来 resolve 这个钩子。执行效果依赖 next 方法的调用参数。
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。
next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。
后置钩子函数中没有next函数,后置钩子不会改变导航本身
需要定义路由独享的守卫时,直接在定义路由时定义守卫
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// ...
}
}
可以在定义组件时,直接定义以下路由导航守卫:
const Foo = {
template: `...`,
beforeRouteEnter (to, from, next) {
// 在渲染该组件的对应路由被 confirm 前调用
// 不!能!获取组件实例 `this`
// 因为当守卫执行前,组件实例还没被创建
},
beforeRouteUpdate (to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
// 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
// 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
// 可以访问组件实例 `this`
},
beforeRouteLeave (to, from, next) {
// 导航离开该组件的对应路由时调用
// 可以访问组件实例 `this`
}
}
14. 路由元信息
定义路由的时候可以配置meta字段
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
children: [
{
path: 'bar',
component: Bar,
// a meta field
meta: { requiresAuth: true }
}
]
}
]
})
一个路由匹配到的所有路由记录会暴露为 $route 对象 (还有在导航守卫中的路由对象) 的 $route.matched 数组。因此,我们需要遍历 $route.matched 来检查路由记录中的 meta 字段。
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!auth.loggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
})
} else {
next()
}
} else {
next() // 确保一定要调用 next()
}
})
15. 过渡动效
可以给router-view添加过渡动态,同样适用transition组件添加过渡动画
<transition>
<router-view></router-view>
</transition>
如果需要给每个路由单独设置过渡动效,则在各自的组件内适用transition包括组件
<template>
<transition>
<div class="hello">
<div>HelloWorld</div>
</div>
</transition>
</template>
16. 数据获取
在进入路由之后,如果需要从服务器获取数据,有两种方式来进行这个操作
导航完成之后获取:先完成导航,然后在接下来的组件生命周期钩子中获取数据。在数据获取期间显示“加载中”之类的指示。
导航完成之前获取:导航完成前,在路由进入的守卫中获取数据,在数据获取成功后执行导航
具体参考文档 https://router.vuejs.org/zh/guide/advanced/data-fetching.html
17. 滚动行为
在创建一个Router实例时,可以提供一个 scrollBehavior 方法:
const router = new VueRouter({
routes: [...],
scrollBehavior (to, from, savedPosition) {
// return 期望滚动到哪个的位置
if (savedPosition) {
return savedPosition
} else {
return { x: 0, y: 0 }
}
}
})
18. 路由懒加载
参考 https://router.vuejs.org/zh/guide/advanced/lazy-loading.html
四、axios
axios的基本使用略过
1. 拦截器
在请求或响应被 then 或 catch 处理前拦截它们。
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
return Promise.reject(error);
});
五、 Vuex
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
1. 使用Store管理状态
在store中提供一个state对象和一个mutations对象
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
// 状态
state: {
num: 0
},
// 状态处理
mutations: {
addNum(state){
state.num++;
}
},
actions: {
},
modules: {
}
})
组件被挂载了一个store属性,通过他进行数据获取,方法调用
<div class="home">
<h1>点击数量:{{count}}</h1>
<button @click="add">点我+1</button>
<hr>
<h1>使用vuex的点击数量:{{$store.state.num}}</h1>
<button @click="$store.commit('addNum')">点我+1</button>
</div>
2. vuex-state
在组件中频繁的书写$store.state获取参数不太好,可以使用计算属性
同时,如果input绑定的计算属性,可以使用set方法触发mutations中的方法来进行更改
<template>
<div>
<h3>名字:{{name}}</h3>
<h3>年龄:{{age}}</h3>
输入你的姓名:<input type="text" v-model="name">
</div>
</template>
<script>
export default {
name: "State",
computed: {
name:{
get() {
return this.$store.state.name;
},
set(val) {
this.$store.commit('setName', val)
}
},
age(){
return this.$store.state.age;
}
}
}
</script>
<style scoped>
</style>
mapState 辅助函数
可以简化一些计算属性的步骤
import {mapState} from 'vuex'
export default {
name: "State",
computed: mapState({
// 箭头函数可使代码更简练
name: state=>state.name,
// 传字符串参数 'count' 等同于 `state => state.count`
age: 'age',
// 为了能够使用 `this` 获取局部状态,必须使用常规函数
num (state) {
return state.num
}
})
}
如果state中的数据和组件中的数据名字相同,可以直接传入一个列表
computed: mapState(['name', 'age', 'num'])
使用对象展开运算符
computed: {
...mapState({
age: state=> state.age
}),
name:{
get() {
return this.$store.state.name;
},
set(val) {
this.$store.commit('setName', val)
}
}
}
3. Getter
Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。
在store中定义getters
getters:{
reversedMsg(state){
return state.msg.split('').reverse().join('');
}
},
在组件中通过store.getters.xxx访问这个属性
computed: {
reversedMsg(){
return this.$store.getters.reversedMsg
},
}
可以通过让 getter 返回一个函数,来实现给 getter 传参。在你对 store 里的数组进行查询时非常有用。
getters: {
// ...
getTodoById: (state) => (id) => {
return state.todos.find(todo => todo.id === id)
}
}
store.getters.getTodoById(2) // -> { id: 2, text: '...', done: false }
mapGetters 辅助函数
mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// 使用对象展开运算符将 getter 混入 computed 对象中
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
如果你想将一个 getter 属性另取一个名字,使用对象形式:
...mapGetters({
// 把 `this.doneCount` 映射为 `this.$store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
4. Mutation
更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数:
const store = new Vuex.Store({
state: {
count: 1
},
mutations: {
increment (state) {
// 变更状态
state.count++
}
}
})
通过commit进行触发,不能直接调用函数
store.commit('increment')
当需要向increment提供额外的参数时,可以通过以下方式进行
mutations: {
increment (state, n) {
state.count += n
}
}
store.commit('increment', 10)
额外的参数被称为一个载荷,大多数情况下,推荐载荷传递一个对象,这样可以包含多个字段并且记录的 mutation 会更易读:
mutations: {
increment (state, payload) {
state.count += payload.amount
}
}
store.commit('increment', {
amount: 10
})
5. Action
Action与Mutation类似,但是可以包含异步操作
写一个action的函数
actions: {
// 200个用户 http://jsonplaceholder.typicode.com/todos
getUserList(context){
Vue.axios.get('http://jsonplaceholder.typicode.com/todos?_limit=20').then(res =>{
// console.log(res.data);
context.commit('setUserList', res.data);
})
}
}
mutations:{
setUserList(state, list){
console.log('list:');
console.log(list)
state.userList = list;
}
}
通过store.dispatch触发
在组件中定义方法触发Action
methods: {
getUserList(){
this.$store.dispatch('getUserList')
}
}
通过计算属性获取state中的属性
computed: {
userList(){
return this.$store.state.userList;
}
}
循环获取列表信息
<ul>
<li v-for="item in userList">
用户id: {{item.id}},title:{{item.title}}
</li>
</ul>
同样的,在组件中频繁的使用store.dispatch分发action,可以用辅助函数mapActions代替
methods: {
...mapActions({
//将 this.getUser() 映射为this.$sotre.dispatch(''getUserList'')
getUser: 'getUserList'
})
}
或者
methods: {
//将 this.getUserList() 映射为this.$sotre.dispatch(''getUserList'')
...mapActions(['getUserList'])
}
6.Module
单一状态树如果管理的变量太多,会变得臃肿,用模块化解决这个问题
----index.js引入模块a----
export default new Vuex.Store({
state,
// 状态处理
mutations,
getters,
actions,
modules: {
a: shoppingCart
}
})
-----shoppingCart.js----
export default {
state: {
productNum:10,
},
getters: {
brief(state){
return state.productNum + '件衣服'
}
},
actions: {
changeProductNum(content){
setTimeout(()=>{
content.commit('addProductNum')
}, 1000)
}
},
mutations: {
addProductNum(state){
state.productNum++;
}
}
}
然后可以通过store.state.a访问到a的状态树
在组件中解构
<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: "buyCar",
methods:{
...mapActions(['changeProductNum']),
...mapMutations(['addProductNum'])
},
mounted() {
console.log(this)
},
computed: {
...mapState(['a']),
...mapGetters(['brief'])
}
}
</script>
模块的局部状态
对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。
同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState
对于模块内部的 getter,根节点状态会作为第三个参数暴露出来:
const moduleA = {
// ...
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
// ...
getters: {
sumWithRootCount (state, getters, rootState) {
return state.count + rootState.count
}
}
}
命名空间
默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的——这样使得多个模块能够对同一 mutation 或 action 作出响应。
如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如:
const 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等
<template>
<div>
<h1>buycar</h1>
<h2>数量:{{a.productNum}}</h2>
<h2>brief:{{brief}}</h2>
<button @click="addProductNum">点我+1件衣服</button><br>
<button @click="changeProductNum">点我隔1s也+1</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions, mapMutations} from 'vuex'
export default {
name: "buyCar",
methods:{
...mapActions('a', ['changeProductNum']),
...mapMutations('a', ['addProductNum'])
},
mounted() {
console.log(this)
},
computed: {
...mapState(['a']),
...mapGetters('a', ['brief'])
}
}
</script>
通过mapxx('模块名',['参数名'])获取对应的getter,action,mutation
启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。
Comments | 0 条评论