Vue学习笔记(二)

博主 1214 2020-08-15

之前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>

打印顺序
image.png

.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里面
image.png


一个绑定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">

image.png
所有的键盘上的键都对应一个键盘码,例如keyup.113 代表按下F2
但是这样的代码不太好阅读,可以自定义全局按键修饰符
image.png
这样可以使用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实例的生命周期

image.png

<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>

image.png

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 被删除),在过渡/动画完成之后移除。
image.png

自定义动画实现标题的淡入淡出

<!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的属性
image.png

this.$router的属性
image.png

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 属性后不需要修改模块内的代码。