Vue.js学习总结

一、什么是Vue.js

1. vue是一种数据驱动的前端框架

this.msg="我爱你",通过改变数据,然后自动渲染到绑定的DOM节点上

2. jQuery就是一种结构驱动的前端框架

$(#app).text('你真好'),先获取结构,然后在修改数据来更新结构

二、搭建环境

首先保证你的电脑上有node和npm,版本越新越好

  1. npm install -g vue-cli
    全局安装脚手架工具,安装的时候可以指定版本
  2. vue init webpack myProject
    用webpack工具初始化vue项目,myProject是项目名称,可以自己命名
  3. cd myProject
    进入创建的vue项目目录
  4. npm install
    安装项目所需要的依赖,也就是安装一些必要的插件
  5. npm run dev
    开始运行项目

三、Vue核心知识点

1. Vue实例

1.1

vue实例被创建时,它会自动的将data中的属性,methods中的方法绑定到Vue实例对象上,也就是Vue实例代理了data对象上的所有属性
调用时: vm.msg = "hello world"

1.2

data中存在的属性才是响应式的,新添加的不算
比如在浏览器端添加: vm.b = 1,那么b的变化不会对页面进行渲染

1.3

Object.freeze(),会阻止修改现有的属性,响应系统无法再追踪变化
var data = {msg: 1} ; Object.freeze(data)

1.4

Vue实例还暴露了一些有用的实例属性和方法,有前缀,_,Vue实例无法代理这些属性,为了与用户自定义的属性区分开 `vm.el vm.$data vm.$props……

3. Vue模板语法

2.1 动态参数

<a v-bind:[attrName] = 'url'></a>
如果data中有一个属性attrName值为 href,那么这个绑定v-bind:href = 'url'
v-on:[eventName] = 'doSomething'></a>
同样,在data中有eventName的值为focus函数时,v-on:focus = 'doSomething'

注意:
2.1.1. 动态参数值是字符串类型,值为null,用于解除绑定,其他任何非字符串类型的值都会触发一个警告
2.1.2 <a v-bind:['foo' + bar] = 'value'></a>
无效,要使用没有空格和引号的表达式,或者用计算属性代替

2.2 修饰符

.prevent告诉v-on指令对于触发的事件调用event.preventDefault()
<form v-on:submit.prevent = 'onSubmit'></form>

3. 计算属性、方法和侦听器

3.1 计算属性

计算属性有缓存,依赖改变才会重新计算

<div id="app">    
       {{fullName}}
        {{age}}
 </div>
<script>
var app = new Vue({
            el: '#app',
            data: {
                firstName: 'Dell',
                lastName: 'Lee',
                age: 28
            },
            computed: {
                //这个计算属性依赖于this.firstName this.firstName这两个变量,只要他们不发生
               //改变,那么这个计算属性就不会进行重新计算
              //vm.age = 27,页面重新渲染,但计算属性不会重新计算
              //vm.firstName = 'Mike',会重新计算
               fullName () {
                  console.log('打印了一次')
                  return this.firstName + ' ' + this.firstName
              }
           }
</script>            

3.1.1 计算属性中的 getter和setter

      computed: {
                fullName: {
                    //自动执行,获取 fullName 的值
                    //并且 get 依赖的变量发生改变时,get就会重新进行计算
                    get () {
                        return this.firstName + " " + this.lastName 
                    },
                    //当重新设置fullName的值的时候,set函数就会执行 vm.fullName = 'Mike Wang'
                    //value就是 vm.fullName = 'Mike Wang'
                    //set函数里,重新设置 fullName 改变了firstName lastName,触发了get 进行计算
                    set (value) {
                        var arr = value.split(' ')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            }

3.2 方法

            methods: {
                //没有缓存,只要页面上有内容变动,就会执行
                fullName () {
                    console.log('打印了一次')
                    return this.firstName + ' ' + this.lastName 
                }
            },

3.3 侦听器

            watch: {
                //有缓存,只有监听的属性变量发生改变,才会执行函数内的内容
                firstName () {
                    console.log('打印了一次')
                    return this.fullName =  this.firstName + ' ' + this.lastName 
                },
                lastName () {
                    console.log('打印了一次')
                    return this.fullName =  this.firstName + ' ' + this.lastName
                }
            },

注意: 计算属性和侦听器

  • 计算属性的键名是计算出来的,所以键名是一个新的名称,在data和props中都是不存在的,计算属性一般可以监听多个值的变化
  • watch侦听器就是已有值发生变化的时候执行的操作,所以侦听器的键名是data或者props中已经存在的

4.class与style的绑定

4.1 class的绑定

<style>
        .divStyle{
            background-color: red;
            widows: 100px;
            height: 100px;
        }
        .borderStyle{
            border: 10px solid black;
        }
        .btnBackground{
            background: red;
        }
        .active{
            background-color: yellow;
            widows: 100px;
            height: 100px;
        }
        .error{
            border: 10px solid red;
        }
    </style>
<div id="app">
        绑定class对象语法:对象键是类名,值是布尔值 <br>
        意思就是isActive是true,那么就绑定类名为divStyle的这个类
        <div v-bind:class= '{divStyle:isActive,borderStyle:isBorder}'>1223454</div>
        <button v-bind:class = '{btnBackground:isBackground}' v-on:click='changeColor'>哈哈,快点我</button>
        绑定class数组语法:数组中的成员直接对应类名
        意思是绑定的类由 activeClass 这个变量来决定,若为空,就绑定空,若是active,就绑定类名为active的这个类
        <div v-bind:class='[activeClass,errorClass]'>hhhhhh</div>
    
    </div>
<script>
    
        var app = new Vue({
            el:'#app',
            data:{
                isActive: true,
                isBorder: true,
                isBackground: true,
                activeClass: 'active',
                errorClass: 'error'
            },
            methods:{
                changeColor(){
                    this.isBackground = !this.isBackground
                }
            }
        })
    </script>

4.2 style的绑定

    绑定内联样式:键代表的是style的属性(color,size属性等),值就是属性值啦
    切记: 在vue中,只要是大写字母,就会转变为  -小写
    fontSize ----> font-size
    <div id="app">
        <div v-bind:style="{'color':color,'fontSize':fontSize}">你丫真傻啊</div>
    </div>
    <script src="https://cdn.bootcss.com/vue/2.6.6/vue.min.js"></script>
    <script>
    
        var app = new Vue({
            el:'#app',
            data:{
              color:'red',
              fontSize: '20px'
            },
           
        })
    </script>

5. 条件渲染

5.1 v-if

v-if绑定的变量若为true,则该元素就会出现在DOM中,并且在页面中渲染出来;若为false,则将该元素从DOM中移除

5.1.1 v-if使用

<div id="app">
       <div v-if='show'>{{msg}}</div>
    </div>
   <script>
       var app = new Vue({
           el: '#app',
           data:{ 
              show: false,
              msg: 'hello world' 
           },
           methods: {

            }
        })
   </script>

5.1.2 v-if v-else-if v-else的使用

<div id="app">
       v-if v-else-if v-else一定要连在一起写,中间不能有其他标签分隔开
       <div v-if='this.show === "a"'>this is A</div>
       <div v-else-if='this.show === "b"'>this is B</div>
       <div v-else>this is others</div>
    </div>
   <script>
       var app = new Vue({
           el: '#app',
           data:{ 
              show: 'a',
            //   msg: 'hello world' 
           },
           methods: {

            }
        })
   </script>

5.1.3 key值的使用

Vue 会尽可能高效地渲染元素,通常会复用已有元素而不是从头开始渲染,所以如果是完全一样的文本框,vue会完全借用上一个文本框,如果不想复用,可用不同的key值区分。

注意:元素复用并不局限与文本框,其他元素都会复用的,这会极大的提高Vue的渲染效率

<div id="app">
        <div v-if='show'>
            用户名:<input type="text" key="userName">
        </div>
        <div v-else>
            lalala: <input type="text" key="userName"> //会复用
            密码名:<input type="text" key="passwordName"> //不会复用
        </div>

    </div>
   <script>
       var app = new Vue({
           el: '#app',
           data:{ 
            show: true
           },
        })
   </script>

5.2 v-show

v-show绑定的变量无论是true还是false,它只是控制这个DOM元素的display:'none'这个style属性而已,这个元素一直在DOM中

6.列表渲染

6.1数组渲染

这种是工作中常用的循环,要加上key值,可以提高vue的性能,理想的 key 值是每项都有的唯一 id。key不建议使用index

注意:

  • Vue有一组观察数组的变异方法,所以他们也会触发视图更新

方法如下:

  1. push()----在数组末尾加上一项 vm.list.push({id:'004',text:'啦啦啦'})
  2. pop()----将数组的最后一个元素移除
  3. shift()----删除数组的第一个元素
  4. unshift()----在数组的第一个元素位置添加一个元素
  5. splice()----可以添加或者删除数组中的一个或多个元素—返回删除的元素

三个参数:

  1. 表示开始操作的位置
  2. 要操作的长度,为0,就是不删除
  3. 为可选参数,可以把要添加的元素或者数组对象写在这里
    arr.splice(4,1,{ll:true})
  1. sort()----排序
  2. reverse()----数组反转
  • 当我们想要修改数组中的元素的时候,不能通过下标的方式进行改变,只能通过Vue提供的几个数组变异方法来实现。
    比如想要在数组添加一项内容:
    vm.list[4]={id: '005',text:'hello world'}其实list数组中已经添加上了这一项,只是无法在页面中渲染出来
    比如在数组第二项后面添加一项内容:
    vm.list.splice(1,0,{id:'001',text:'第三项'})

  • 当我们想要修改数组中的元素的时候,还可以通过改变数组的引用,就是对数组进行重新赋值

  • 当我们想要修改数组中的元素的时候,还可以通过set语法
    Vue.set(vm.list,1,{id:'007',text: '你真傻'})或者vm.$set(vm.list,1,{id:'007',text: '你真傻'}),中间数字为操作元素的index值

<div id="app">
        <div v-for='(item,index) of list' :key = 'item.id'>{{item.text}}----{{index}}</div>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                list: [{
                    id: '001',
                    text: 'xiaoliu'
                },{
                    id: '002',
                    text: 'xiaoxv'
                },{
                    id: '003',
                    text: 'xiaofang'
                }]
            }
        })
    </script>

如果我们想要通过一个list循环两项,比如div和span

两个循环完全独立,不符合预期
<div id="app">
        <div v-for='(item,index) of list'  :key='item.id'>{{item.text}}----{{index}}</div>
        <span v-for='(item,index) of list'  :key='item.id'>{{item.text}}----{{index}}</span>
    </div>
外面包一层div,效果可以,但我们并不想要包裹的div出现在DOM中
<div id="app">
        <div v-for='(item,index) of list' :key='item.id'>
            <div>{{item.text}}----{{index}}</div>
            <span>{{item.text}}----{{index}}</span>
        </div>
    </div>
满足预期,且在DOM中不会出现template标签
<div id="app">
        <template v-for='(item,index) of list' :key='item.id'>
            <div>{{item.text}}----{{index}}</div>
            <span>{{item.text}}----{{index}}</span>
        </template>
    </div>

6.2 对象渲染

参数顺序:拿到value key index 的写法   v---k---i  外开
<div id="app">
        <div v-for = '(item,key,index) of userInfo'>{{key}}: {{item}}--{{index}}</div>
    </div>

    <script>
        var vm = new Vue({
            el: '#app',
            data: {
                userInfo: {
                    name: 'Dell',
                    sex: 'man',
                    age: 18
                }
            }
        })
    </script>

注意:
对象里面要想添加某一项,用点语法是行不通的
比如:vm.userInfo.address = 'ZhengZhou',在页面中渲染不出来

  • 通过改变对象引用,就是为对象重新赋值的方式来改变对象内容
  • 通过set语法 Vue.set(vm.userInfo,'address', 'beijing')或者vm.$set(vm.userInfo,'adress','beijing')

7.组件使用中的一些细节

7.1 table中的问题

正常情况下如此
<div id="root">
        <table>
            <tbody>
                <tr><td>1</td></tr>
                <tr><td>2</td></tr>
                <tr><td>3</td></tr>
            </tbody>
        </table>
    </div>
每一行都引入一个组件,但是渲染的时候发现渲染出来的 row 并不在 tbody 标签里面
因为 tbody 标签里面只能写 tr,其他标签他识别不了
<div id="root">
        <table>
            <tbody>
                <row></row>
                <row></row>
                <row></row>
            </tbody>
        </table>
    </div>
    <script>

        Vue.component('row',{
            template: '<tr><td>this is a row</td></tr>'
        })

        new Vue({
            el: '#root',
        })
    </script>
使用is属性可以解决这个问题
<table>
            <tbody>
                <tr is="row"></tr>
                <tr is="row"></tr>
                <tr is="row"></tr>
            </tbody>
        </table>
比如:这几种情况最好也不要直接写组件,用is属性来实现
 <ul>
      <li is="row"></li>
 </ul>
  <ol>
      <li is="row"></li>
  </ol>
  <section>
      <option is="row"></option>
  </section>

7.2 组件中的data

组件都是可以复用的,所以组件中data也得是一个独立的对象,那么组件之间的data才不会相互干扰

<div id="root">
        <table>
            <tbody>
                <tr is="row"></tr>
                <tr is="row"></tr>
                <tr is="row"></tr>
            </tbody>
        </table>      
    </div>
    <script>
        Vue.component('row',{
            data () {
                return {
                    content: 'this is a row'
                }
            },
            template: '<tr><td>{{content}}</td></tr>'
        })
        new Vue({
            el: '#root',
            data: {
            }
        })
    </script>

7.3 ref的使用

ref在dom标签上使用,指向的是这个dom节点

<div id="root">
        <div ref="hello"
        @click = 'handleClick'
        >
        hello world
    </div>
    </div>
    <script>
        new Vue({
            el: '#root',
            data: {

            },
            methods: {
                handleClick () {
                    console.log(this.$refs.hello)//就是指向的上面的div节点
                    console.log(this.$refs.hello.innerHTML)//hello world
                }                
            }
        })
    </script>

ref在一个组件上使用,实际上是对这个组件的一个引用

<div id="root">
        <counter ref="one" @change = 'sumNumber'></counter>
        <counter ref="two" @change = 'sumNumber'></counter>
        <div>{{sum}}</div>
    </div>
    <script>
        Vue.component('counter',{
            template:  '<div @click="handleClick">{{number}}</div>',
            data () {
                return {
                    number: 0
                }
            },
            methods: {
                handleClick () {
                    this.number ++
                    this.$emit('change',this.number)
                }
            }
        })
        new Vue({
            el: '#root',
            data: {
                sum: 0
            },
            methods: {
                sumNumber (number) {
                    this.sum = this.$refs.one.number + this.$refs.two.number
                }
            }
        })
    </script>

8. 父子组件间数据传递

8.1 父组件向子组件传递数据

注意:

  1. 父组件向子组件传值时,传值参数前最好加上v-bind,以下面为例:
    count="1"就是传递的字符串1
    :count="1"就是数字1,
    不加:,那么传递参数就是参数后的那个值
    加:,那么传递参数就是引号里面的js表达式
  2. 子组件接收到父组件传过来的值,是不能修改的,不要在子组件中修改props中的值,vue中有单向数据流的概念,即父组件向子组件传值时,父组件中可以随意修改所要传递地数据,子组件不能反过来修改父组件传递来的数据

如果你在子组件中修改了props:

  1. 如果传过来的是基本类型,那么会有警告,但在页面中正常渲染
  2. 如果是引用类型(对象,数组),vue会检测不到变化,页面中也不会渲染

如何修改传递过来的值?

  • 把传递过来的值赋值给子组件data的一个属性里,就可以修改啦
<div id="root">
    <counter :count = "1"></counter>
    <counter :count = "2"></counter>
   </div>
   <script>
       var counter = { //局部组件,需要在父组件中注册
           props: ['count'],
           template: '<div @click = "handleClick">{{this.count}}</div>',
           methods: {
               handleClick () {
                this.count ++
               }
           }
       }
       var vm = new Vue({
           el: '#root',
           data: {

           },
           components: {
               counter
           }
       })
   </script>

//正确写法
var counter = { //局部组件,需要在父组件中注册
           props: ['count'],
           data () {
            return {
                number: this.count
            }
           },
           template: '<div @click = "handleClick">{{number}}</div>',
           methods: {
               handleClick () {
                this.number ++
               }
           }
       }
      

8.2 子组件向父组件传递数据

<div id="root">
    <counter :count = "1" @change = 'numberChange'></counter>
    <counter :count = "2" @change = 'numberChange'></counter>
    <div>{{total}}</div>
   </div>
   <script>
       var counter = { //局部组件,需要在父组件中注册
           props: ['count'],
           data () {
            return {
                number: this.count
            }
           },
           template: '<div @click = "handleClick">{{number}}</div>',
           methods: {
               handleClick () {
                this.number = this.number + 2
                this.$emit('change',2) // 2是步长
               }
           }
       }      
       var vm = new Vue({
           el: '#root',
           data: {
            total: 3
           },
           components: {
               counter, 
           },
           methods: {
            numberChange (step) {
                this.total += step
            }
           }
       })
   </script>

8.3 组件参数校验与非props特性

以下是props的参数校验的几种写法,以及参数意思

props特性和非props特性

  1. props特性就是父组件传递过去,子组件有接收,接收后这个特性就不会出现在DOM结构中
  2. 非props特性就是父组件传递过去,子组件没有接收,会在DOM结构中出现(了解)
<div id="root">
        <child content="hello world"></child>
   </div>
   <script>
       Vue.component('child',{
        //    props: ['content'],
            props: {
                // content: [ String, Number ]
                content: {
                    type: String,
                    required: true,//要求父组件必须向子组件传content,不传会有提示
                    default: 'default value',//如果不传内容,那么这个内容就默认显示
                    validator (value) {//校验器,value就是传过来的数据
                        return (value.length < 5)
                    }
                }
            },
           template: '<div>{{content}}</div>'
       })      
       var vm = new Vue({
           el: '#root',
           data: {
            
           },         
        })
   </script>

8.3 自定义事件和原生事件

  1. 自定义事件:子组件在父组件中使用时,直接绑定在子组件上的事件就是自定义事件,必须经过子组件的触发才能执行
  2. 原生事件:直接在子组件里的模板上绑定的事件,子组件引入后是可以直接使用的
  3. 怎么在父组件的子组件里直接绑定原生事件,不用子组件的再次触发呢?

直接在绑定的自定义事件后加上修饰符.native

 <div id="root">
        <child @click = 'handleClick'></child> //在这里绑定的是自定义事件,必须经过子组件的触发才能执行
    </div>
    <script>
        Vue.component('child', {
            template: '<div @click = "handleChildClick">click</div>',//这里绑定的是原生事件
            methods: {
                handleChildClick () {
                    alert('child click')
                    this.$emit('click')//触发自定义事件
                }
            }
        })

        var vm = new Vue({
            el: '#root',
            methods: {
                handleClick () {
                    alert('click')
                }
            }
        })
    </script>
<div id="root">
        <child @click.native = 'handleClick'></child> 
    </div>
    <script>
        Vue.component('child', {
            template: '<div @click = "handleChildClick">click</div>'//这里绑定的是原生事件
        })
        var vm = new Vue({
            el: '#root',
            methods: {
                handleClick () {
                    alert('click')
                }
            }
        })
    </script>

9. 非父子组件之间的传值

bus/总线/发布订阅模式/观察者模式
步骤:

  1. 建立总线 Vue.prototype.bus = new Vue()
  2. 在子组件中触发 this.bus.$emit('change',this.selfContent)
  3. 在子组件mounted生命周期钩子函数中监听 this.bus.$on('change',(value)=>{})value就是传过来的值,可以进行操作
<div id="root">
        <child content="Dell" ></child>
        <child content="Lee"></child>
    </div>
    <script>
        Vue.prototype.bus = new Vue()
        Vue.component('child',{
            props: {
                content: String
            },
            data () {
                return {
                    selfContent: this.content
                }
            },
            template: '<div @click = "handleClick">{{selfContent}}</div>',
            methods: {
                handleClick () {
                   this.bus.$emit('change',this.selfContent)
                }
            },
            mounted () {
                this.bus.$on('change',(value)=>{
                    // alert(this.content)
                    this.selfContent = value
                })
            }
        })
        var vm = new Vue({
            el: '#root'
        })
    </script>

10. 插槽

10.1 没有插槽前的传值方式

父组件向子组件传值,采用props传值,可以传少量的值,但如果数值较多的话,代码可读性会很低

<div id="root">
        <child content="<p>Dell</p>"></child>
    </div>
    <script>

        Vue.component('child',{
            props: ['content'],
            // template: "<div><p>hello</p>{{content}}</div>"//content无法转义
            template: `<div>
                            <p>hello</p>
                            <div v-html='this.content'></div> //可以转义,但是content外包了一个div
                        </div>`
        })

        var vm = new Vue({
            el: '#root'
        })
    </script>

插槽

 <div id="root">
        <child>
            <p>Dell</p>
        </child>
    </div>
    <script>

        Vue.component('child',{
            template: `<div>
                            <p>hello</p>
                            <slot>默认数据</slot>//父组件中间没有传值的时候默认显示
                        </div>`
        })
        var vm = new Vue({
            el: '#root'
        })
    </script>

具名插槽,可以在父组件中插入多个模块的内容

<div id="root">
        <child>
            <div class="header" slot="header">header</div>
            <div class="footer" slot="footer">footer</div>
        </child>
    </div>
    <script>

        Vue.component('child',{
            template: `<div>
                            <slot name="header"></slot>
                            <p>hello</p>
                            <slot name="footer"></slot>
                        </div>`
        })

        var vm = new Vue({
            el: '#root'
        })
    </script>

作用域插槽

  • 执行逻辑:
  1. 父组件调用子组件时,向子组件传递了一个插槽
  2. 子组件通过slot向父组件传递数据,比如: :item = item
  3. 插槽是作用域插槽,插槽必须写在template里面,同时声明从子组件接收的数据都放在props里面
  4. 在template里面写上模板的信息,以什么方式进行展示
  • 什么时候用作用域插槽?
    子组件做循环或者有一部分的DOM结构要由外部传过来的时候
<div id="root">
        <child></child>
    </div>
    <script>
        Vue.component('child',{
            data () {
                return {
                    list: [1,2,3,4]
                }
            },
            template: `<div>
                          <ul>
                            <li v-for='item of list'>{{item}}</li>
                          </ul>
                       </div>`
        })
        var vm = new Vue({
            el: '#root'
        })
    </script>

作用域插槽改写

<div id="root">
        <child>
            <template slot-scope='props'>
                <h1>{{props.item}}</h1>
            </template>
        </child>
    </div>
    <script>
        Vue.component('child',{
            data () {
                return {
                    list: [1,2,3,4]
                }
            },
            template: `<div>
                          <ul>
                            <slot v-for='item of list' :item = item></slot>
                          </ul>
                       </div>`
        })
        var vm = new Vue({
            el: '#root'
        })
    </script>

11. 动态组件和v-once指令

  1. 动态组件

动态组件中is属性根据绑定的组件名的不同会动态的切换组件

  1. v-once 使用

子组件中加上v-once,也就是第一次执行的时候就把子组件放入内存,下次直接复用即可,所以如果子组件内容不变的话,加上v-once会提高vue性能

<div id="root">
        <component :is = 'type'></component>
        <child-one v-if='type === "child-one"'></child-one>
        <child-two v-if='type === "child-two"'></child-two>
        <button @click='handleClick'>change</button>
    </div>
    <script>
        Vue.component('child-one',{
            template: '<div v-once>child-one</div>'
        })
        Vue.component('child-two',{
            template: '<div v-once>child-two</div>'
        })
        var vm = new Vue({
            el: '#root',
            data: {
                type: 'child-one'
            },
            methods: {
                handleClick () {
                    this.type = this.type === 'child-one'? 'child-two':'child-one'
                }
            }
        })
    </script>

12.Vue动画

12.1 css过渡动画

过渡动画原理:

  1. 在需要过渡的元素外面包裹上transition标签,那么vue执行代码的时候就会对被包裹的元素进行解析
  2. 以缓动出现为例:
  • 动画开始前,vue刚开始解析代码,就会给transition包裹的标签(div)加上v-enter , v-enter-active两个类(注意可以给transition加name,那么v就换成name名称)
  • 动画开始时,去掉fade-enter这个类,添加fade-enter-to这个类
  • 动画结束时,所有的类都去掉
  1. 我们可以根据这些类的添加和删除为这些类添加一些样式,来做出动画效果
缓慢出现类名图示.png
缓慢离开类名图示.png

代码如下:

<style>
        /*缓动出现动画 */
        .v-enter {
            opacity: 0; /*将初始状态的透明度设为0*/
        }
        .v-enter-active {
            transition: opacity 1s; /*transition检测到opacity的变化,1s内完成*/
        }
        /*缓慢消失动画 */
        .v-leave-to {
            opacity: 0;/*将最终状态变为0*/
        }
        .v-leave-active {
            transition: opacity 1s;
        }
    </style>
    <div id="root">
        <transition>
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                }
            }
        })
    </script>

12.2 在Vue中使用animate.css库

Vue中使用keyframes

v-enter-active, v-leave-active在动画整个过程都是存在的,所以可以在这里面写效果

vue提供的原生类名太长,想换类名怎么办?

可以在transition里面自己设置类名 比如:enter-active-class='enter' leave-active-class='leave',那么这两个类就可以这样简写啦

<style>
       @keyframes bounce-in {
           0% {
               transform: scale(0)
           }
           50% {
               transform: scale(1.5)
           }
           100% {
               transform: scale(1)
           }
       }
        .v-enter-active {
            transform-origin: left center;/*设置元素变形的原点,左边线的中点*/
            animation: bounce-in 1s;
        }
        .v-leave-active {
            transform-origin: left center;
            animation: bounce-in 1s reverse;
        }
    </style>
    <div id="root">
        <transition>
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                }
            }
        })
    </script>

使用animate.css库
如何使页面出现以及刷新的时候也出现动画呢?

在transition中加入属性appear,类appear-active-class='animated swing'

<link rel="stylesheet" href="../animate.css">
<div id="root">
        <transition 
        appear
        enter-active-class = 'animated swing' 
        leave-active-class='animated shake'
        appear-active-class= 'animated swing'
        >
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                }
            }
        })
    </script>

12.3 在vue中同时使用animate.css库和transition

注意: animate.css动画默认时间是1s,所以如果transition和animate时间不一致?
怎么办?

  1. type="transition"用来指定以谁的时间为准
  2. 自定义时间:duration="5000"
<style>
            .v-enter,.v-leave-to{
                opacity: 0;
            }
    
            .v-enter-active,
            .v-leave-active{
                transition: opacity 3s;
            }
    </style>
    <div id="root">
        <transition 
        //type="transition"
        //:duration="5000"  动画执行时间为5s
         :duration="{enter: 5000,leave: 10000}" //可以设置进出场动画的时间
        appear
        enter-active-class = 'animated swing v-enter-active' 
        leave-active-class='animated shake v-leave-active'
        appear-active-class='animated swing'
        >
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>

12.4 js 动画

js动画钩子:

  1. before-enter: 动画开始执行前就执行啦
  2. enter: 动画开始的时候执行
  3. after-enter: 动画结束时执行
    离开动画与进入动画一样before-leave,leave,after-leave
<div id="root">
        <transition
        name="fade"
        @before-enter="handleBeforeEnter"
        @enter = "handleEnter"
        @after-enter = "handleAfterEnter"
        >
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                },
                handleBeforeEnter (el) {//动画开始执行前调用
                    // alert('beforeenter')
                    el.style.color="red"//el就是指transition里面包裹的元素
                },
                handleEnter (el,done) {//动画开始执行时调用
                    // alert('enter')
                    setTimeout(() => {
                        el.style.color="green"
                    },2000)
                    setTimeout(()=>{
                        done()//只有调用done这个回调函数之后,才会执行after-enter这个钩子
                    },4000)
                },
                handleAfterEnter (el) {
                    el.style.color= "#000"
                }
            }
        })
    </script>

12.5 Velocity动画库的使用

 <div id="root">
        <transition
        name="fade"
        @before-enter="handleBeforeEnter"
        @enter = "handleEnter"
        @after-enter = "handleAfterEnter"
        >
            <div v-if='show'>hello world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                },
                handleBeforeEnter (el) {
                    el.style.opacity= 0
                },
                handleEnter (el,done) {
                    Velocity(el,{opacity: 1},{duration: 1000})
                    //在1s之内把el的opacity从0变到1
                },
                handleAfterEnter (el) {

                }
            }
        })
    </script>

12.6 Vue中多个元素或组件的过渡

  • 多个元素的过渡

注意:多个元素的过渡,每个元素要加一个key,如果不加vue中会进行dom复用,就没有动画效果啦

<style>
        .v-enter,.v-leave-to{
            opacity: 0
        }
        .v-enter-active,.v-leave-active {
            transition: opacity 1s
        }
    </style>
    <div id="root">
        <transition mode="out-in">
            <div v-if='show' key="hello">hello world</div>
            <div v-else key="bye">bye world</div>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                }
            }
        })
    </script>
  • 多个组件的过渡
<style>
        .v-enter,.v-leave-to{
            opacity: 0
        }
        .v-enter-active,.v-leave-active {
            transition: opacity 1s
        }
    </style>
    <div id="root">
        <transition mode="out-in">
            <child v-if='show'></child>
            <child-one v-else></child-one>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        Vue.component('child',{
            template: '<div>child</div>'
        })
        Vue.component('child-one',{
            template: '<div>child-one</div>'
        })
        var vm = new Vue({
            el: '#root',
            data: {
                show: true
            },
            methods: {
                handleButton() {
                    this.show = !this.show
                }
            }
        })
    </script>

多个组件可以转化为动态组件

<style>
        .v-enter,.v-leave-to{
            opacity: 0
        }
        .v-enter-active,.v-leave-active {
            transition: opacity 1s
        }
    </style>
    <div id="root">
        <transition mode="out-in">
            <component :is="type"></component>
        </transition>
        <button @click='handleButton'>切换</button>
    </div>
    <script>
        Vue.component('child',{
            template: '<div>child</div>'
        })
        Vue.component('child-one',{
            template: '<div>child-one</div>'
        })
        var vm = new Vue({
            el: '#root',
            data: {
                type: "child"
            },
            methods: {
                handleButton() {
                    this.type = this.type === "child"?"child-one":"child"
                }
            }
        })
    </script>

12.7 vue中的列表过渡

原理:

<transition-group>
          <div v-for="item of list" :key="item.id">{{item.title}}</div>
</transition-group>

把循环的每一项都变成了

<transition>
          <div>{{item.title}}</div>
</transition>

在进行渲染

<style>
            .v-enter,.v-leave-to {
                opacity: 0
            }
            .v-enter-active,.v-leave-active {
                transition: opacity 1s
            }
    </style>
    <div id="root">
        <transition-group>
            <div v-for="item of list" :key="item.id">{{item.title}}</div>
        </transition-group>
        <button @click="handleAddOne">addOne</button>
    </div>
    <script>
        var count = 0
        var vm = new Vue({
            el: '#root',
            data: {
                list: []
            },
            methods: {
                handleAddOne () {
                    this.list.push({
                        title: "xxx我又想你" + count + "次",
                        id: count++
                    })
                }
            }

        })
    </script>

12.8 vue动画封装

<div id="root">
        <fade :show="show">
            <div>hello world</div>
        </fade>

        <fade :show="show">
            <div>I love you</div>
        </fade>

        <button @click='handleButton'>切换</button>
    </div>
    <script>
        Vue.component('fade',{
            props: ['show'],
            template: 
                `<transition @before-enter="handleBeforeEnter"
                @enter="handleEnter"
                >
                    <slot v-if="show"></slot>
                </transition>
                `,
            methods: {
                handleBeforeEnter (el) {
                    el.style.color = "red"
                },
                handleEnter (el,done) {
                    setTimeout(()=>{
                        el.style.color = "green"
                        done()
                    },2000)
                }
            }
        })

13. Vue-router的学习

基本写法:vue本质上是一个单页面应用,页面间跳转就是组件之间的跳转

13.1 vue-router的使用步骤(vue-cli的情况下)

  1. 在src目录下建立router目录,目录下创建index.js
  2. 在index.js中引入import Vue from 'vue'; import Router from 'vue-router'
  3. 使用Router Vue.use(Router) 因为vue-router是一个插件,vue中使用插件就要Vue.use()
  4. 定义路由
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'

export const routes = [
    {
      path: '/',
      name: 'Home', 
      component: Home
    },
    {
      path: '/city',
      name: 'City', 
      component: City
    },
]
  1. 创建Router实例,对路由进行配置
const router = new Router({
    routes, //routes: routes
    mode: 'history', //默认是hash模式(路径中有#,seo不好),改成history,可以把路径中#去掉,不过这样写刷新页面后会出现404,还需在后端配置
    base: /base/, //会在routes设置的路径前面加上/base/(可以自定义),用于区分一
                 //些页面,注意/base/不是必须的,去掉它页面还会正常显示
    
})
import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import City from '@/pages/city/City'

Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home', 
      component: Home
    },
    {
      path: '/city',
      name: 'City', 
      component: City
    },
  ]
})

四、vue-router学习

Ⅰ. vue-router在项目中的使用

当项目比较大时,可以将route拆分成两个文件
routes.js

import Todo from '../views/todo/todo.vue'
import Login from '../views/login/login.vue'

export default [
  {
    path: '/',
    redirect: '/app'
  },
  {
    path: '/app',
    component: Todo
  },
  {
    path: '/login',
    component: Login
  }
]

router.js

import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
const router = new Router({ // 不推荐这样做,这样做传出去的就一个router,做服务端渲染的时候会内存溢出
  routes,
  // mode: 'history', // 可以去掉hash路由的#
  // base: '/base/', // 在routes配置的路前都加上/base/, 里面可以是任何内容,
  linkActiveClass: 'active-class', // 可以自定义router-link a 标签里面的类名
  linkExactActiveClass: 'exact-active-class' // 精确匹配路径
})
export default router

router.js另一种写法:(做服务端渲染的时候要使用这种方法,要不然会内存溢出)

import Vue from 'vue'
import Router from 'vue-router'
import routes from './routes'
Vue.use(Router)
export default () => { // 这样每次传出去的就是一个新router
  return new Router({
    routes: routes
  })
}
// 在入口文件中引入
import creatRouter from './config/router'
const router = creatRouter()

然后在项目入口文件中引入router.js,在实例中注册好,将router-view放到app.vue中就好啦

Ⅱ、router对象(new Router)中的一些配置项

1. mode: history

作用:将URL中难看的#去掉
分析:

  • 为什么vue-router跳转要有hash和history两种模式呢?
    vue是单页面应用,所以vue-router的核心就在于---改变路由的同时不会向后端发送请求
    hash模式:hash(#)是URL 的锚点,代表的是网页中的一个位置,单单改变#后的部分,浏览器只会滚动到相应位置,不会重新加载网页,也就是说 #是用来指导浏览器动作的,对服务器端完全无用,HTTP请求中也不会不包括#;同时每一次改变#后的部分,都会在浏览器的访问历史中增加一个记录,使用”后退”按钮,就可以回到上一个位置
    history模式:HTML5 History API提供了一种功能,能让开发人员在不刷新整个页面的情况下修改站点的URL,就是利用 history.pushState API 来完成 URL 跳转而无须重新加载页面
    注意:但当用户直接在用户栏输入地址并带有参数时:
    Hash模式:xxx.com/#/id=5 请求地址为 xxx.com,没有问题;
    History模式: xxx.com/id=5 请求地址为 xxx.com/id=5,如果后端没有对应的路由处理,就会返回404错误
    所以,我们使用history模式的时候,一定要在后台进行配置
    我们在开发环境时,使用devServer启动服务,所以可以在devServer中配置
historyApiFallback: {
    index: '/index.html'
  },

2. base: '/base/'

---配置base项,base项的值可以任意写
作用:在routes配置的路前都加上/base/, 里面可以是任何内容,可以用来标记一些特殊的页面。
注意:这个base配置的值在url中不是必须的,去掉url还是正常显示的

3. linkActiveClass 和 linkExactActiveClass

作用:可以改变router-link形成的 a 标签中的两个类名
最初类名:
<a data-v-06ebb29e="" href="/base/app" class="router-link-exact-active router-link-active">app</a>
配置自定义类名:

linkActiveClass: 'active-class', 
linkExactActiveClass: 'exact-active-class' // 精确匹配路径

自定义后的类名:
<a data-v-06ebb29e="" href="/base/app" class="exact-active-class active-class">app</a>
问题:这两个类有什么作用?他们两个有什么区别?
作用:用来给激活的链接加样式
区别:

  1. linkActiveClass: 全局配置 <router-link> 的默认“激活 class 类名”
  2. linkExactActiveClass: 全局配置 <router-link> 精确激活的默认的 class
    举例:页面上两个链接:login,login exact, login 路径是/login, login exact的路径是login/exact
    那么当我们激活 login exact 的时候,页面上的类显示:
    <a data-v-06ebb29e="" href="/base/login" class="active-class">login</a>
    <a data-v-06ebb29e="" href="/base/login/exact" class="exact-active-class active-class">login exact</a>

4. scrollBehavior的配置

作用:配置路由页面的滚动行为
说明:

  1. to from 都是路由对象,to将要跳转的路由,from就是当前还未跳转的路由
  2. savedPosition就是要跳转的页面以前滚动保留下的位置,如存在,跳转后到保留的位置,不存在,就到页面顶端呗
  3. 当然不同页面的位置可以有to from定制
scrollBehavior (to, from, savedPosition) {
    if (savedPosition) {
      return savedPosition 
    } else {
      return { x: 0, y: 0 }
    }
  }

5. parseQuery 和 stringifyQuery

作用:url中的经常会有查询参数(?后面的一串),我们需要把它们转化为json对象才能使用,其实vue会帮我们转化它们,不过如果我们有一些特殊的需求的话,可以用他们配置

parseQuery (query) {
    // 把查询参数(string)转化为json对象
  },
  stringifyQuery (obj) {
    // 把对象转化为字符串
  }

6. fallback: true

作用:当浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式。默认值为 true。

如果你不想退回 hash 模式,那写成 false ,改成false之后,那么vue单页应用变成多页应用,每次router-link跳转都会去后端拿数据,比较耗时
所以一般不要改成false

Ⅲ、routes(路由)配置的的要点

1. name的配置与作用

{
    path: '/app',
    component: Todo,
    name: 'app' // 与path无关,可以定义任何名字r
  }

作用:路由跳转router-link中可以使用
比如:下面它们是等效的

<router-link to="/app">app</router-link>
<router-link :to="{name: 'app'}">app</router-link>

2. meta的配置与作用

作用:保存一下当前路由的一些信息的,我们再写html页面时,head标签里面的meta里面保存的信息,我们称他们为页面的源信息,这些信息有利于我们处理seo,根据description里面的内容来排列他们的搜索结果,而在vue组件里面,我们没有办法在组件中写这些东西,所以可以在路由里面的meta里面写我们需要的东西

meta: {
      title: 'this is an app',
      description: 'asddffg'
}

3. children的配置和使用

作用:当前路由的子路由
注意:当前路由可能不止一个子路由,所以children是一个数组,在routes配置好之后,在当前路由需要的位置引入router-view即可

children: [
  {
    path: 'test',
    component: Login
  }
]

4. 路由参数的配置

作用:通过向像跳转的组件传参数,来改变组件的行为(类似于props)
分为id和query

// 在routes中配置参数
{
    path: '/app/:id', // 配置参数
    component: Todo,
    name: 'app', 
    meta: {
      title: 'this is an app',
      description: 'asddffg'
}

// 向要跳转的路由传参数(这个参数一般都是一个变量)
<router-link to="/app/123">app</router-link>

//接收到参数的路由怎么使用参数?
mounted () {
  console.log(this.$route) // this.$route包含着路由匹配成功后路由中的所有信息
  console.log(this.$route.params.id) // 拿到了id值
  console.log(this.$route.query) // 拿到了query值(query不用在routes设置,传参的时候有query, 就能拿到)
}

注意:在同一个路由下面this.$route在哪一个组件中都是一样的
this.$route对象:

Snipaste_2019-06-02_20-00-11.png

使用场景:当我们定义路径的时候,有一个列表请求(比如商品列表),我们可以通过传参的方式拿到单个商品的id,进而去请求这个商品的详细内容(类似于props,都是通过传来的数据,来改变当前组件的某些行为)

5. props的配置

注意:尽量使用props方法,少使用$route,使组件与路由解耦,提高组件的复用率

  1. props: true 布尔模式
    作用:可以把要传递给要跳转路由的参数转化成props传递
    好处:在组件中使用$route会使之与对应路由高度耦合,该组件只能在特定url(路由)下使用,组件单独使用时this.$route会不匹配,拿不到id的值。使用props: true则可以解决这个问题,通过props: ['id']可以接收到外部传来的id的值,这其实就是一种解耦。
    上面的代码就可以改成下面这样:
// 在routes中配置参数
{
    path: '/app/:id', // 配置参数
    props: true,
    component: Todo,
    name: 'app', 
    meta: {
      title: 'this is an app',
      description: 'asddffg'
}

// 向要跳转的路由传参数(这个参数一般都是一个变量)
<router-link to="/app/123">app</router-link>

//接收到参数的路由怎么使用参数?
props: ['id']
mounted () {
  console.log(this.id)
}
// 注意:如果是命名视图,props要包一层
components:{
  default: Todo,
  a: login
},
props: {
  default: true,
  a: true
}
  1. props 对象模式
    作用:如果 props 是一个对象,它会被按原样设置为组件属性。当 props 是静态的时候有用
props: {
  id: '456' // 不会变化
}
  1. props 函数模式
    props: (route) => ({ query: route.query.a, id: route.query.b })
    接收route参数,返回一个对象

Ⅳ、vue-router的一些高级用法

1. 命名视图

作用:在router-view上设置好名字,这样可以根据我们的需求在同一个路由下显示不同的组件
使用场景:可以同时展示多个视图,而不用嵌套。例如创建一个布局,有 sidebar(侧导航) 和 main (主内容) 两个视图,这个时候命名视图就派上用场了。你可以在界面中拥有多个单独命名的视图,而不是只有一个单独的出口。如果 router-view 没有设置名字,那么默认为 default
配置和使用:

// 在routes中配置
{
    path: '/login',
    components: {
      default: Login,
      a: Todo
    }
  }
// 在组件中使用
<transition name="fade">
  <router-view></router-view>
</transition>
<Footer></Footer>
<router-view name='a'></router-view>

2. vue-router之导航守卫

路由守卫
全局守卫
---对所有的路由有效果,只要路由跳转,就会触发

  1. 全局前置守卫
    作用:对一些页面进行校验,比如:验证一些页面是需要用户登录才可以显示的
router.beforeEach((to, from, next) => {
  console.log('before each invoked')
   if (to.fullPath === '/app') {
    next('/login')
  } else {
    next()
  }
})
  1. 全局解析守卫
router.beforeResolve((to, from, next) => {
  console.log('before resolved invoked')
  next()
})
  1. 全局后置钩子
router.afterEach((to, from) => {
  console.log('after each invoked')
})

路由内守卫
---只对该路由有效果

  1. 路由独享的守卫
// 在routes中配置
beforeEnter: (to, from, next) => {
      console.log('before enter route')
      next()
}

组件内守卫
---对本组件进入,离开,以及组件的复用有效果

  1. beforeRouteEnter
    无法拿到this,可以在next方法里面执行一个回调拿到当前组件
beforeRouteEnter (to, from, next) {
    console.log('befor route enter')
    next(vm => {
      console.log(vm.id)
    })
  }
  1. beforeRouteUpdate
    执行:同一个组件在不同的路由下面显示的时候。比如:/app/123和/app/456
    可以拿到 this
beforeRouteUpdate (to, from, next) {
    console.log('before route update')
    next()
  }

注意:当我们在两个相似路径(/app/123和/app/456)跳转时,第二次还会不会触发mounted钩子呢?
不会,当我们进入/app/123时,mounted钩子执行,这时跳到/app/456钩子,mounted钩子就不再执行。所以我们不要把根据id变化而变化的数据写在mounted里面,可以写在beforeRouteUpdate里面,也可以写在watch里面

  1. beforeRouteLeave
    使用场景:控制页面离开行为。比如你修改了一个表单,还没有保存,现在你要跳到其他页面,可以在这里给你设置一个提醒
beforeRouteLeave (to, from, next) {
    console.log('before route leave')
    if (global.confirm('are you sure?')) {
      next()
    }
  }

3. 异步组件实现按需加载

作用:每次加载页面,就要把所有的业务逻辑代码加载一下,若是项目比较大,势必会大大影响页面的初始加载速度,若是app.js小于1mb以下,就没有必要使用异步组件啦
异步组件不仅可以在路由中使用,在页面组件中中只要引入组件的地方都可以使用

// 在路由routes 引入
component: () => import('../views/todo/todo.vue')
component: () => import('../views/login/login.vue')
// 安装 `babel-plugin-syntax-dynamic-import` 插件
npm i babel-plugin-syntax-dynamic-import
// 在 `babelrc`中配置
"plugins": [
    "transform-vue-jsx", // 解析 jsx 语法
    "syntax-dynamic-import" // 异步加载组件
  ]

五、vuex的学习

Ⅰ、vuex在项目中的使用

// 安装
npm install vuex --save
// 在 src 目录下建立store目录,建立store.js 文件
// store.js 文件配置
import Vuex from 'vuex'
import Vue from 'vue'
Vue.use(Vuex)
const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    updateCount (state, num) {
      state.count = num
    }
  }
})
export default store
// 在入口文件中引入 store ,注册
// 因为vue是一个树形结构,只有把 store 注册到入口文件根节点上,下面那些子节点才都可以拿到 store对象

在组件中使用store
拿到state中的值

computed: {
    count () {
      return this.$store.state.count
    }
  }
// 通过mutations修改state中的值
mounted () {
    let i = 0
    console.log(this.$store)
    setInterval(() => {
      this.$store.commit('updateCount', i++)
    }, 1000)
  }

在大型项目或者可扩展项目中可采用一下目录结构,把每个模块都拆分出来形成单独的目录


image.png

Ⅱ、getters的使用

作用:getters的作用就是对state中的原始数据做一些处理和封装,可以让我们在组件中更好的使用,相当于组件中的computed
很多时候,后端提供我们的数据我们不能直接使用,当然我们可以拿到数据在组件中通过computed进行处理,但是很多页面都需要处理数据,每个页面都写computed就会造成代码冗余,维护也困难,getters可以帮我们解决这个问题

export default {
  fullName (state) {
    return `${state.firstName} ${state.lastName}`
  }
}

Ⅲ、map 语法在state和getters中的使用

作用:使组件中属性方法和store中模块的属性方法形成一个映射,可以直接把state和getters中的值拿过来
注意:mapState和mapGetters是访问,拿到数据,所以在组件的计算属性 computed 里面混入

// 首先安装一个转义器包,是Babel用来翻译最新的语法的
npm i babel-present-stage-1 -D
// 在 .babelrc 文件中配置
{
  "presets": [
    "env", // babel-present-env 主要对javascript最新的语法糖进行编译,并不负责转译javascript新增的api和全局对象
    "stage-1" // babel-preset-stage-1,转义器包,要配合env使用,包含一些插件,可以识别最新语法
  ],
  "plugins": [
    "transform-vue-jsx", // 解析 jsx 语法
    "syntax-dynamic-import" // 异步加载组件
  ]
}
// 现在就可以在组件中使用 对象展开运算符 啦
import { mapState, mapGetters } from 'vuex'
...mapState(['count']) // 映射 this.count 为 store.state.count
...maoState({
  counter: 'count' 
})
...mapState({
  counter: (state) => state.counte // 通过函数可以对拿到的数据做一些处理
})
...mapGetters(['fullName'])

Ⅳ、Vuex之mutations和actions

1. mutations

代码演示:

// mutations.js
export default { // 只有两个参数
  updateCount (state, num) {
    state.count = num
  },
  firstName (state, num) {
    state.firstName = num
  }
}
// 多个数值的话
export default {
  updateCount (state, {num, num2}) {
    console.log(num2)
    state.count = num
  },
  firstName (state, num) {
    state.firstName = num
  }
}
// 组件中
this.$store.commit('updateCount', { // payload,载荷
   num: i++,
   num2: 2
})

注意:state中的值只能通过mutations中修改,其实在组件中可以通过
this.$store.state.count = 2也是可以修改的,但是这样修改的话,多人协作的话就会很困难,数据不利于维护。
所以我们一般会限制组件中直接修改 state 的值,做法是:
在开发环境下,给 store 实例对象加上严格模式,这样的话,我们如果在组件中修改 state,会有警告

const isDev = process.env.NODE_ENV === 'development'
export default () => {
  return new Vuex.Store({
    strict: isDev, // 在开发环境的时候使用
    state: defaultState,
    mutations: mutations,
    getters
  })
}

2. actions

作用:处理异步操作,或者一个动作要多次修改mutations,往往也用actions封装一下

export default {
  updateCountAsync (ctx, data) { // context 上下文, store 实例具有相同方法和属性的 context 对象
    setTimeout(() => {
      ctx.commit('updateCount', {
        num: data.num
      })
    }, data.time)
  }
}
// 在组件中调用 actions,用 dispatch 方法
this.$store.dispatch('updateCountAsync', {
      num: 5,
      time: 2000
})

3. mapMutatios 和 mapActions 的用法

注意:这两个map是操作,所以要写在 methods 里面
代码如下:

methods: {
    ...mapActions(['updateCountAsync']), // 将 `this.updateCountAsync()` 映射为 `this.$store.dispatch('updateCountAsync')`
    ...mapMutations(['updateCount'])
  }
// 现在代码就可以改了
this.$store.dispatch('updateCountAsync', { // store 对象的方法
      num: 5,
      time: 2000
})
this.updateCountAsync({ // 组件自身的方法
      num: 5,
      time: 2000
    })

Ⅴ、Vuex之模块(module)

由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。
为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割

1. 模块中的 state

// 在 store.js 中的store对象中配置如下:
modules: {
      a: {
        state: {
          text: 'a'
        }
      },
      b: {
        state: {
          text: 'b'
        }
      }
    }
// 在组件中调用
computed: {
    textA () {
      return this.$store.state.a.text
    }
  }

2. 模块中的 mutations

注意: vuex默认的会把全部的mutation都放在全局的命名空间中,所以在不同的模块也可以直接调用。
如果我们想把各个模块的mutation就在自己的模块,因为太多的mutation都放在全局,容易引发命名上的冲突,怎么办?
我们可以给模块加一个命名空间 namespaced: true,

modules: {
      // namespaced: true,
      a: {
        state: {
          text: 'a'
        },
        mutations: {
          updateText (state, text) {
            state.text = text
          }
        }
      }
    }
// 组件中使用
methods: {
    ...mapMutations([ 'updateText'])
  }
mounted () {
    this.updateText('123')
  }

为模块加上命名空间,如何调用?

methods: {
    ...mapMutations([ 'a/updateText'])
  }
mounted () {
    this['a/updateText']('123')
  }

3. 模块中的 getters

modules: {
      a: {
        namespaced: true,
        state: {
          text: 'a'
        },
        mutations: {
          updateText (state, text) {
            state.text = text
          }
        },
        getters: {
          textPlus (state) {
            console.log(state.text)
            return state.text + 1
          }
        }
      }
    }
// 在项目中使用
computed: {
    // ...mapGetters(['a/textPlus']), // this['a/textPlus']调用
    ...mapGetters({
      fullName: 'fullName',
      textPlus: 'a/textPlus' // 可以直接在模板中使用 {{textPlus}}
    })
  }

默认情况下,模块内部的 action、mutation 和 getter 是注册在全局命名空间的,当我们加上命名空间后,变成局部的,此时我们如何拿到全局的 state 和 getter 呢?

getters: {
          textPlus (state, getters, rootState, rootGetters) {
          // state 当前模块的state对象
          // getters 当前模块的 getters 对象
          // rootState rootGetters 全局的,不仅可以取到根模块的值,其他模块也可以
          //  return state.text + rootState.count
            return state.text + rootState.b.count
          }
        }

4. 模块中的 actions

modules: {
      a: {
        namespaced: true,
        state: {
          text: 'a'
        },
        mutations: {
          updateText (state, text) {
            state.text = text
          }
        },
        getters: {
          textPlus (state, getters, rootState, rootGetters) {
            console.log(state.text)
            return state.text + rootState.b.text
          }
        },
        actions: {
          add (ctx) { // 上下文
            ctx.commit('updateText', 8)
          }
        }
      }
// 使用
methods: {
    ...mapActions(['updateCountAsync', 'a/add']),
    ...mapMutations(['updateCount', 'a/updateText'])
  }
mounted () {
  this['a/add']()
}

但是我们一般都用 ES6 的结构赋值简化

// 此时调用的 updateText 在 a 命名空间下,如果我们想要调取全局的mutation怎么办?
actions: {
  add ({ state, commit, rootState }) { 
    commit('updateText', rootState.count)
  }
}
// 首先调用的mutation全局里面存在,加上参数 {root:true}
actions: {
   add ({ state, commit, rootState }) {
     commit('updateCount', { num: '56789' }, { root: true })
   }
}

// 模块的actions调用其他模块的mutation
// b 模块没有加命名空间
b: {
        state: {
          text: 'b'
        },
        actions: {
          testAction ({ commit }) {
            commit('a/updateText', 'test action')
          }
        }
      }
methods: {
    ...mapActions(['updateCountAsync', 'a/add', 'testAction']),
    ...mapMutations(['updateCount', 'a/updateText'])
  }
mounted () {
  this.testAction()
}
// b 模块加上命名空间呢
b: { 
        namespaced: true,
        state: {
          text: 'b'
        },
        actions: {
          testAction ({ commit }) {
            commit('a/updateText', 'test action', { root: true })
          }
        }
      }
methods: {
    ...mapActions(['updateCountAsync', 'a/add', 'b/testAction']),
    ...mapMutations(['updateCount', 'a/updateText'])
  }
mounted () {
  this['b/testAction']()
}

5. 动态注册模块

// 在有store对象的位置,一般在入口文件里,注册
const store = createStore()
store.registerModule('c', {
  state: {
    text: 'ccc'
  }
})
store.unregisterModule('c') // 解绑一个model
// 使用方法与其他一样

Ⅵ、 热重载

当我们修改store里面的代码时,我们发现都是刷新整个页面进行更新的, 在我们做webapp的时候,状态经常是变化的,如果因为修改了一下 store 中的数据,刷新整个页面,使得之前的操作记录也会消失,浪费时间
热重载

export default () => {
  const store = new Vuex.Store({
    strict: isDev, // 在开发环境的时候使用
    state: defaultState,
    mutations: mutations,
    getters,
    actions
  })
  if (module.hot) {
    module.hot.accept([
      './state/state',
      './mutations/mutation',
      './getters/getter',
      './actions/actions'
    ], () => {
      const newState = require('./state/state').default
      const newMutations = require('./mutations/mutation').default
      const newGetters = require('./getters/getter').default
      const newActions = require('./actions/actions').default
      store.hotUpdate({
        state: newState,
        mutations: newMutations,
        getters: newGetters,
        actions: newActions
      })
    })
  }
  return store
}

Ⅶ、vuex之其他一些API和配置

1. store.watch

第一个函数的返回值发生变化时,第二个函数就会执行

store.watch((state) => state.count + 1, (newCount) => {
  console.log(`newCount: ${newCount}`)
})

2. store.subscribe(常用于插件)

监听mutations,调用的话,可以得到哪一个mutation变化,以及变化的数据

store.subscribe((mutation, state) => {
  console.log(mutation.type)
  console.log(mutation.payload)
})
image.png

3. store.subscribeAction(常用于插件)

store.subscribeAction((action, state) => {
  console.log(action.type)
  console.log(action.payload)
})

4. vuex插件的制作

plugins 在vuex初始化的时候就已经执行,所以可以在里面使用上面subscribe 和 subscribeAction 订阅一些内容,来进行一些操作

export default () => {
  const store = new Vuex.Store({
    strict: isDev, // 在开发环境的时候使用
    state: defaultState,
    mutations: mutations,
    getters,
    actions,
    plugins: [
      (store) => {
        console.log('my plugin invoked')
      }
    ]
})

5. vuex 和 vue-router 图解

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容