VUE学习总结

由于简书没有目录的功能,为了更加清晰的学习,我制作了一个目录地址如下:
学习Vue目录 链接地址: https://www.jianshu.com/p/2c9071c0436d
如果想快速、准确、查看、学习内容,那么你就点击上面的链接,查看到整体目录,然后点击查看自己想看的内容。
如果你不想单篇的了解,此篇文章汇总了所有章节的内容

第1章 课程介绍

1-1课程简介

学习流程

知识点

学习前提:
有一些css、js、es6、webpack、npm等基础知识

学习收获

第2章 Vue 起步

2-1 学习方法

多看一下官网api Vue官网
务必把官网的小的demo自己敲一遍加深一下对语法糖的理解

2-2 hello world

那么初步认识一下vue,那么从一个简单的hello world开始吧

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello world</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">{{content}}</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            content: 'hello world'
        }
    });

    setTimeout(function () {
        app.$data.content = 'bye world'
    }, 2000)
</script>
</body>
</html>

el:’#root’ vue实例化的数据只针对 id为root内使用
{{content}} : 获取 vue里面data 里面的数据值

2-3 开发TodoList(v-model、v-for、v-on)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello world</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <div>
        <input type="text" v-model="inputValue">
        <button v-on:click="handleAdd">提交</button>
    </div>
    <ul>
        <li v-for="(item,index) in list" v-on:click="handleRemove(index)">{{item}}</li>
    </ul>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            inputValue: '',
            list: []
        },
        methods: {
            handleAdd() {
                this.list.push(this.inputValue);
                this.inputValue = '';
            },
            handleRemove(index) {
                this.list.splice(index, 1);
            }
        }
    });
</script>
</body>
</html>

v-on:click=”handleClick” 绑定点击事件
v-model=”inputValue” 数据双向绑定
v-for=”(item,index) in list” 数据循环

2-4 MVVM模式

MVP (Model View Presenter)
Model:接口请求操作
View:页面展示
P:处理数据和页面(大量的dom操作)

MVP

MVVM

MVVM

而Vue则采用的是mvvm这种模式,它在乎的是数据的变化驱动Ui的变化,不用用户管dom的操作,vue则扮演的是VM的操作,我们的重心是放在了M层也就是数据这一层

2-5 前端组件化

页面有好多部分组成,把页面切成一部分一部分,而拆分出来的部分,就是一个组件

2-6 使用组件改造TodoList

1.全局组件的声明和使用

//Vue创建全局组件的方法
Vue.component('TodoItem', {
    props: ['content'],
    template: '<li>{{content}}</li>'
});
<todo-item v-bind:content="item" v-for="item in list"></todo-item>

ps:
数据的传递通过v-bind: 来定义传递的属性,后面跟上要传递的值
通过“props”来接受属性,再通过插值表达式来展示{{content}}

2.Vue局部组件的创建和使用

 //Vue局部组件的创建和使用
var TodoItem = {
        props: ['content'],
        template: '<li>{{content}}</li>'
};
var app = new Vue({
        el: '#root',
        components: {
            TodoItem: TodoItem
        }
})
ps:
定义一个变量,值为对象,把属性值和模板作为属性的键值
在Vue实例化中通过components这个参数来调用这个定义好的变量(局部组件)
2-7 简单的组件间传值

父组件向子组件传值:
子组件通过定义一个属性:v-bind:content=”item”,将item值传给子组件的content
子组件通过props:[‘content’] 来接受父组件传过来的值,再通过插值表达式来展示{{content}}
子组件向父组件传值:
子组件通过定义一个方法或者一个事件handleItemClick,在方法或者事件中,通过this.$emit(”delete”,this.index)方法给给父组件发送一个信号,
父组件监听这个信号:@delete=”handleDeleleItem”
下面代码演示:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Hello world</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <div>
        <input type="text" v-model="inputValue">
        <button v-on:click="handleAdd">提交</button>
    </div>
    <ul>
        <!--<li v-for="(item,index) in list" v-on:click="handleRemove(index)">{{item}}</li>-->
        <todo-item
                v-bind:content="item"
                v-bind:index="index"
                v-for="(item,index) in list"
                @delete="handleItemDelete"
        >
        </todo-item>
    </ul>
</div>
<script>
    //Vue创建全局组件的方法
    // Vue.component('TodoItem', {
    //     props: ['content'],
    //     template: '<li>{{content}}</li>'
    // });

    //Vue局部组件的创建和使用
    var TodoItem = {
        props: ['content', 'index'],
        template: '<li @click="handleItemClick">{{content}}</li>',
        methods: {
            handleItemClick() {
                this.$emit('delete', this.index);
            }
        }

    };

    var app = new Vue({
        el: '#root',
        components: {
            TodoItem: TodoItem
        },
        data: {
            inputValue: '',
            list: []
        },
        methods: {
            handleAdd() {
                this.list.push(this.inputValue);
                this.inputValue = '';
            },
            // handleRemove(index) {
            //     this.list.splice(index, 1);
            // },
            handleItemDelete(index) {
                console.log(index);
                this.list.splice(index, 1)
            }
        }
    });
</script>
</body>
</html>
2-8 Vue的一些指令简写方法

v-on:click 等价于 @click //this.emit(‘delete’) 接受也是 @delete
v-bind:content 等价于 :content

第3章 Vue 基础精讲

3-1 Vue实例

vue实例是根实例,组件也是vue实例,所以说页面是由很多vue实例组成的
.data(): 以开头的指的是vue实例的属性或方法 vm.destroy():用于销毁vue实例,但是之前的数据和方法并没有被销毁

var app = new Vue({
  el:'#root',
  data:{
    message:'hello world'
  },
  methods: {
    handleClick(){},
  },
  watch:{

  },
  computed:{

  }
})
3-2 Vue实例生命周期

//生命周期函数就是vue实例在某个时间点自动执行的函数
var app = new Vue({
        el:'#root',
        data:{
           inputValue:'' 
        },
        beforeCreate: function () {
            console.log('beforeCreate');
        },
        created: function () {
            console.log('created');
        },
        beforeMount: function () {
            console.log(this.$el);
            console.log('beforeMount');
        },
        mounted: function () {
            console.log(this.$el);
            console.log('mounted');
        },
        beforeDestroy: function () {
            //app.$destroy()
            console.log('beforeDestroy');
        },
        destroyed: function () {
            console.log('destroyed');
        },
        beforeUpdate: function () {
            //app.$data.inputValue = 1
            console.log('beforeUpdate')
        },
        updated: function () {
            console.log('updated')
        }
})
3-3 Vue的模版语法

插值表达式{{}} : 用{{输入的值}}
v-指令 写的是js表达式
v-text 就是innertext 其实就是 {{}}
v-html 就是innerhtml
v-指令 后面除了可以写js名称还可以加字符串,插值表达式也可以写字符串

var app = new Vue({
      el: '#root',
      data: {
          name: 'hello',
          bigName: '<h1>hello</h1>'
      }
})

{{name + ' world'}}
<div v-text="name + ' world' "></div>
<div v-html="name + ' world' "></div>

ps:v-html 会对bigName进行转义,字体变成h1字体大小,而不会出现标签
3-4 计算属性、方法与侦听器

计算属性
computed属性,因为他是属性,所以在用插值表达式取值的时候不用加括号
computed:内置变量缓存的功能,当data里面age变量更改时,如果不是计算属性内边的变量更改,那么他就不会渲染computed内部的变量

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    {{fullName}}
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            firstName: 'sunny',
            lastName: 'fan',
            age: 28
        },
        //计算属性:内置缓存(firstName、lastName)
        computed: {
            fullName: function () {
                console.log('计算了一次');
                return this.firstName + " " + this.lastName
            }
        }
    })
</script>
</body>
</html>

methods方法
因为是方法,所以插值表达式要用括号取值,
他不具有缓存变量的功能

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    {{fullName()}}
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            firstName: 'sunny',
            lastName: 'fan'
        },
        methods: {
            fullName: function () {
                console.log("计算了一次")
                return this.firstName + " " + this.lastName
            }
        }
    })
</script>
</body>
</html>

侦听器

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>侦听器</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    {{fullName}} {{age}}
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            firstName: 'sunny',
            lastName: 'fan',
            fullName: 'sunny fan',
            age: 28
        },
        watch: {
            firstName: function () {
                console.log('计算了一次');
                this.fullName = this.firstName + " " + this.lastName
            },
            lastName: function () {
                console.log('计算了一次');
                this.fullName = this.firstName + " " + this.lastName
            }
        }
    })
</script>
</body>
</html>

总结:我们可以通过methods、computed、watch来实现fullName显示的问题
computed和watch都具备缓存的功能
但是从代码量的编写程度来看,computed属性会更加方便和便捷一些。

3-5 计算属性的getter和setter

computed属性当中有两个方法,分别是:get 和 set

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>getter和setter</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<body>
<div id="root">
    {{fullName}} {{age}}
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            firstName: 'sunny',
            lastName: 'fan',
            age: 28
        },
        computed: {
            fullName: {
                get: function () {
                    return this.firstName + " " + this.lastName
                },
                set: function (value) {
                    console.log(value);
                    var arr = value.split(" ");
                    this.firstName = arr[0];
                    this.lastName = arr[1];
                }
            }
        }
    })
</script>
</body>
</body>
</html>
3-6 Vue中的样式绑定

class的对象绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>class的对象绑定</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <style>
        .activated {
            color: red;
        }
    </style>
</head>
<body>
<body>
<div id="root">
    <div @click="handleChangeColor" :class="{activated:isActivated}">
        hello world
    </div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            isActivated: false
        },
        methods: {
            handleChangeColor: function () {
                this.isActivated = !this.isActivated
            }
        }
    })
</script>
</body>
</body>
</html>

class的数组绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>class的数组绑定</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
    <style>
        .activated-one {
            font-size: 20px;
        }

        .activated {
            color: red;
        }
    </style>
</head>
<body>
<div id="root">
    <div @click="handleChangeColor" :class="[activated,activatedOne]">hello world</div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            activated: '',
            activatedOne: 'activated-one'
        },
        methods: {
            handleChangeColor: function () {
                this.activated = this.activated === 'activated' ? "" : "activated"
            }
        }
    })
</script>
</body>
</html>

style对象绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>style对象绑定</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <div @click="handleChangeColor" :style="styleObj">hello world</div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            styleObj: {
                color: 'black'
            }
        },
        methods: {
            handleChangeColor: function () {
                this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black'
            }
        }
    })
</script>
</body>
</html>

style的数组绑定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>style数组绑定</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <div @click="handleChangeColor" :style="[styleObj,{fontSize:'30px'},styleOne]">hello world</div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            styleObj: {
                color: 'black'
            },
            styleOne: {
                fontWeight: 'bold'
            }
        },
        methods: {
            handleChangeColor: function () {
                this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black'
            }
        }
    })
</script>
</body>
</html>
3-7 条件渲染

v-if 、v-else-if、v-else

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>V-if</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <h5>实例一:v-if</h5>
    <template v-if="isShow">
        hello world
    </template>
    <button @click="handleChange">{{toggleText}}</button>
    <hr>
    <h5>实例二:v-else</h5>
    <div v-if="isShowTwo">
        要是我显示
    </div>
    <div v-else>
        要么你显示
    </div>
    <button @click="handleChangeRole">切换显示</button>
    <hr>
    <h5>实例三:v-else-if</h5>
    <div v-if="status==='A'">
    A
    </div>
    <div v-else-if="status==='B'">
        B
    </div>
    <div v-else-if="status==='C'">
        C
    </div>
    <div v-else>
        其他
    </div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            isShow: false,
            toggleText: '显示',
            isShowTwo: true,
            status: 'A'
        },
        methods: {
            handleChange: function () {
                this.isShow = !this.isShow;
                this.toggleText = this.toggleText === '显示' ? '隐藏' : '显示'
            },
            handleChangeRole: function () {
                this.isShowTwo = !this.isShowTwo;
            }
        }
    })
</script>
</body>
</html>

key 管理可复用的元素
当切换两个input输入框的时候,为了不让input框的输入内容被占用,所以我们通过设置input的key值来解决这个问题

<template v-if="loginType === 'username'">
  <label>Username</label>
  <input placeholder="Enter your username" key="userName-input">
</template>
<template v-else>
  <label>Email</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

demo例子:https://codepen.io/sunnyfan/pen/JQjRry

v-show
v-show很相似,只要设置值为true则显示

v-if和v-show的区别

  • v-show不能和v-else 和 v-else-if结合使用
  • v-show 不管是ture还是fasle div元素都会渲染出来(false style的display:none),如果如果有频繁的切换,我们会首选v-show,减少对dom的频繁操作
3-8 Vue列表渲染

v-for
<li v-for=”(item,index) in list” :key=”index”>{{index}}–{{item}}</li>
<li v-for=”(item,key,index) of userInfo” :key=”index”>{{key}}-{{item}}-{{index}}</li>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>列表渲染</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="root">
    <ul>
        <li v-for="(item,index) in list" :key="index">{{index}}--{{item}}</li>
    </ul>
    <ul>
        <li v-for="(item,key,index) of userInfo" :key="index">{{key}}-{{item}}-{{index}}-</li>
    </ul>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {
            list: [
                'hello',
                'world'
            ],
            userInfo: {
                name: 'sunny',
                age: 29
            }
        }
    })
</script>
</body>
</html>

template可以当一个空标签做为for循环渲染,而这个template不会渲染到dom里面

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

为了防止子组件循环渲染出现dom结构不对的情况,我们一般会通过is来给子组件命名

//html
 <table>
    <tbody>
      <tr is="row" v-for="item in list" :title="item"></tr> //这个地方调用is属性
   </tbody>
</table>

//js
 Vue.component('row', {
        props: ['title'],
        template: '<tr><td>{{title}}</td></tr>'
});
var app = new Vue({
        el: '#root',
        data: {
            list: [
                'hello',
                'world'
            ]
        }
    })
更改数组值方法有哪些?

1.变异方法
push()、 pop()、 shift()、unshift()、splice()、sort()、reverse()
2.替换数组
当也可以创建一个新的数组,在通过
filter()、concat()、slice()
更改原始数组的值,再把更改后的数组替换旧的数组
3.set或者$set方法

Vue.set(app.userInfo,'age',22)
//或者
vm.$set(app.userInfo,'age',22)

4.Object.assign()或者_.extend()

vm.userInfo = Object.assign({},vm.userInfo,{
  sex:'男',
  email:'fx35792@163.com'
})

ps:不可以通过数组下标去更改数组的值

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // 不是响应性的
vm.items.length = 2 // 不是响应性的

3-9 Vue事件处理

监听事件、方法处理、内联处理器中的方法

1.监听事件

通过v-on指令监听DOM事件,并在触发时运行一些 JavaScript 代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>监听事件</title>
    <script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
    <div id="root">
        <button v-on:click="counter+=1">add 1</button>
        <p>The button above has been clicked {{counter}} times.</p>
    </div>
    <script type="text/javascript">
    var app = new Vue({
        el: '#root',
        data: {
            counter: 0
        }
    })
    </script>
</body>
</html>
2.事件处理方法

但是在开发的过程中,有时候直接把 JavaScript 代码写在 v-on 指令中是不可行的。因此 v-on 还可以接收一个需要调用的方法名称

<div id="root">
    <button v-on:click="greet">greet</button>
</div>
<script>
    const app = new Vue({
        el: '#root',
        data: {
            name: 'sunny'
        },
        methods: {
            greet: function (event) {
                console.log(`hello ${this.name}`)
                if (event) {
                    console.log(event.target.tagName)
                }

            }
        }
    })
</script>
3.内联处理器中的方法

除了直接绑定到一个方法,也可以在内联 JavaScript 语句中调用方法:

<button v-on:click="say('hi')">say hi</button>
<button v-on:click="say('hello')">say hello</button>

const app = new Vue({
        el: '#root',
        data: {
            name: 'sunny'
        },
        methods: {
            say: function (val) {
                console.log(`${this.name} ${val}`)
            }
        }
    })

方法总通过传递参数$event,可以原始的DOM

<button v-on:click="warning('please input number',$event)">warning</button>
const app = new Vue({
        el: '#root',
        data: {},
        methods: {
            warning: function (val, event) {
                if (event) event.preventDefault();
                console.log(val)
            }
        }
    })
4.事件修饰符

我们在开发个过程中经常调用一些
event.preventDefault() //阻止事件的默认行为
event.stopPropagation() //阻止冒泡的行为

而vue为了更好的让我们关注业务逻辑代码的编写,它封装了很多v-on修饰符来帮助我们完成上面那些工作。

  • stop //event.stopPropagation()
  • prevent //event.preventDefault()
  • capture
  • self
  • once
  • passive

第4章 深入理解Vue组件

4-1 使用组件细节点

1.is的使用
当我们写循环组件的时候,经常给
table中的tr
select中的option
ul中的li或者ol中的li
等等定义组件的时候,我们经常用is来定义组件的名称,为了让浏览器成功的渲染正确的dom结构

<div id="root">
    <table>
        <tbody>
        <tr is="row"></tr>
        <tr is="row"></tr>
        <tr is="row"></tr>
        </tbody>
    </table>
    <select name="" id="">
        <option is="selectOption"></option>
        <option is="selectOption"></option>
        <option is="selectOption"></option>
    </select>
    <ul>
        <li is="ulLi"></li>
        <li is="ulLi"></li>
        <li is="ulLi"></li>
    </ul>
</div>
<script>
    Vue.component('row', {
        template: '<tr><td>this is a row</td></tr>'
    });
    Vue.component('selectOption',{
        template: '<option>this is option</option>'
    });
    Vue.component('ulLi',{
        template: '<li>this is li</li>'
    });
    var app = new Vue({
        el: '#root',
        data: {},
    })
</script>

2.在子组件定义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: function () {//返回的是一个函数
            return {
                content: 'this is row'
            }
        },
        template: '<tr><td>{{content}}</td></tr>'
    });
    var app = new Vue({
        el: '#root',
        data: {}
    })
</script>

3.有时候我们在开发过程中,因为一些业务的需求,少不了对dom的操作,那么我们就可以借助ref来实现

//实例一
<div id="root">
    <div ref="hello" @click="handleClick">hello world</div>
</div>
<script>
    var app = new Vue({
        el: '#root',
        data: {},
        methods: {
            handleClick: function () {
                console.log(this.$refs.hello.innerHTML);//通过refs属性 获取当前节点的文本
            }
        }
    });
</script>


//案例二 counter求和
<div id="root">
    <counter ref="one" @change="handleChange"></counter>
    <counter ref="two" @change="handleChange"></counter>
    <div>{{total}}</div>
</div>
<script>
    Vue.component('counter', {
        data: function () {
            return {
                number: 0
            }
        },
        template: '<div @click="handleClick">{{number}}</div>',
        methods: {
            handleClick: function () {
                this.number++;
                this.$emit('change');//触发一个监听器
            }
        }
    });
    var app = new Vue({
        el: '#root',
        data: {
            total: 0
        },
        methods: {
            handleChange: function () {
                this.total = this.$refs.one.number + this.$refs.two.number //通过refs 来回去组件的值
            }
        }
    });
</script>
4-2父子组件之间的数据传递

父组件向子组件传值:是通过属性的方式
子组件向父组件传值:可以通过$emit来触发一个事件

vue数据传递遵循的是单向数据流,
所以在下面的案例中我们并没有对content数据直接进行数据的累加,而是把content数据赋值给了number,对number进行数据的累加操作。

<div id="root">
    <counter :content="1" @inc="handleInc"></counter><!--父组件通过属性向子组件传值-->
    <counter :content="3" @inc="handleInc"></counter>
    <div>{{total}}</div>
</div>
<script>
    Vue.component('counter', {
        props: ['content'],
        data: function () {
            return {
                number: this.content //遵循单向数据流
            }
        },
        template: '<div @click="handleClick">{{number}}</div>',
        methods: {
            handleClick: function () {
                this.number += 2;
                //子组件通过方法向父组件传值
                this.$emit('inc', 2);
            }
        }
    });
    var app = new Vue({
        el: '#root',
        data: {
            total: 4
        },
        methods: {
            handleInc: function (step) {
                this.total += step
            }
        }
    })
</script>
4-3组件参数校验和非props特性

1.组件的的参数校验

<div id="root">
    <child content="hello"></child>
</div>
<script>
    Vue.component('child', {
        props: {
            content: {
                type: String,
                required: true,
                default: 'default Value',
                validator: function (value) {
                    return (value.length > 5)
                }
            }
        },
        template: '<div>{{content}}</div>'
    });
    var app = new Vue({
        el: '#root',
    })
</script>

2.props特性和非props特性的对比
props特性:
父组件传递属性,子组件要接受该属性
props属性不会显示在dom的标签之中
非props特性:
父组件传递属性,子组件没有去接受,而是直接调用
props属性会显示在dom的标签之中

4-4给组件绑定原生事件

通过.native属性来绑定原生事件

<div id="root">
    <child @click.native="handleClick"></child>
</div>
<script>
    Vue.component('child', {
        template: '<div>child</div>'
    })
    var app = new Vue({
        el: '#root',
        methods: {
            handleClick: function () {
                console.log('click');
            }
        }
    })
</script>
4-5 非父子组件间的传值

非父子组件间的传值

1.通过vuex
2.通过发布订阅模式(Bus/总线/发布订阅模式/观察者模式/)

<div id="root">
    <child content="sunny"></child>
    <child content="fan"></child>
</div>
<script>
    Vue.prototype.bus = new Vue();//定义bus
    Vue.component('child', {
        data: function () {
            return {
                newContent: this.content //保证单向数据流
            }
        },
        props: {
            content: String
        },
        template: '<div @click="handleClick">{{newContent}}</div>',
        methods: {
            handleClick: function () {
                this.bus.$emit('change', this.newContent); //在bus上发布一个事件,并且传值
            }
        },
        mounted: function () {//通过这个钩子,来监听change的变化,通过回调拿到相对应的的值
            var that = this;
            this.bus.$on('change', function (msg) {
                console.log(msg)
                that.newContent = msg//this 指向发生变更,所以上面要从新获取一下this的指向
            })
        }
    });
    var app = new Vue({
        el: '#root'
    })

4-6 在vue中使用插槽

插槽只能有一个
而剧名插槽可以有多个

  <div id="root">
    <body-content>
      <p slot="header">this is header</p>
      <p slot="footer">this is footer</p>
    </body-content>
  </div>
  <script>
    Vue.component('body-content',{
      template:
      `<div>
        <slot name="header">default header</slot> //设置默认值
        <p>this is content</p>
        <slot name="footer"></slot>
      </div>`
    })
    var app = new Vue({
      el:'#root'
    })
  </script>
4-7作用域插槽

父组件调用子组件的时候,给子组件传了一个插槽,这个插槽是一个作用域的插槽,这个插槽必须是一个<template slot-scope="props">{{props.item}}</template>
那什么时候使用作用插槽呢?
1.当子组件做循环
2.或者当子组件的dom结构由外部传递进来,或者有外部决定的时候

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

4-8 动态组件和v-once 指令

<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="hanleBtnClick">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 app = new Vue({
      el: '#root',
      data: {
        type: 'child-one'
      },
      methods: {
        hanleBtnClick: function () {
          this.type = this.type === 'child-one' ? 'child-two' : 'child-one'
        }
      }
    })
  </script>

第5章 表单

5-1双向数据绑定 v-model

<div id="root">
        <p>
            <label for="">请输入姓名</label>
            <input type="text" v-model="name" placeholder="请输入名字">
        </p>
        <p>
            你的名字是:{{name}}
        </p>
    </div>
    <script>
        var app = new Vue({
            el: '#root',
            data() {
                return {
                    name: ''
                }
            },
        })
    </script>
5-2复选框(checkbox)相关的操作

1)、单个复选框的取反操作
2)、多个复选框的数组操作

<div id="root">
    <p>单个复选框:</p>
    <p>
      <input type="checkbox" id="checkbox" v-model="checked">
      <label for="checkbox">{{checked}}</label>
    </p>
    <p>多个复选框:</p>
    <p>
      <input type="checkbox" id="chinese" value="chinese" v-model="checkedNames">
      <label for="chinese">chinese</label>
      <input type="checkbox" id="Math" value="Math" v-model="checkedNames">
      <label for="Math">Math</label>
      <input type="checkbox" id="English" value="English" v-model="checkedNames">
      <label for="English">English</label>
    </p>
    <p>选择的值为:{{checkedNames}}</p>
  </div>
  <script>
    var app = new Vue({
      el: '#root',
      data: {
        checked: false,
        checkedNames: []
      }
    })
  </script>

效果

5-3单选框(radio)相关的操作

<div id="root">
    <p>单个复选框:</p>
    <p>
      <input type="radio" id="man" value="man" v-model="picked">
      <label for="man">man</label>
      <input type="radio" id="female" value="female" v-model="picked">
      <label for="female">female</label>
    </p>
    <p>
      选中的值:{{picked}}
    </p>
  </div>
  <script>
    var app = new Vue({
      el: '#root',
      data: {
        picked: 'man',
      }
    })
  </script>

效果

5-4选择框(select)相关的操作

<div id="root">
    <p>选择框:</p>
    <select name="age" id="age" v-model="ages">
      <option value="0-12">儿童</option>
      <option value="12-18">少年</option>
      <option value="18-30">青年</option>
      <option value="30-40">中年</option>
      <option value="40-50">壮年</option>
      <option value="50-">老年</option>
    </select>
    <p>你先则的值是:{{ages}}</p>
  </div>
  <script>
    var app = new Vue({
      el: '#root',
      data: {
        ages: '0-12',
      }
    })
  </script>

效果

5-5 表单中一些修饰符的操作(.lazy、.number、.trim)

<div id="root">
    <p>.lazy(input事件同步输入看的值,通过lazy转为change事件中同步):</p>
    <input type="text" v-model.lazy="text">
    <p>你输入的文本内容是:{{text}}</p>
    <p>.number(输入文本内容为数字):</p>
    <input type="number" v-model.number="number">
    <p>输入的数字是:{{number}}</p>
    <p>.trim(去除输入框两端的空格):</p>
    <input type="text" v-model.trim="trimText">
    <p>显示输入的内容:{{trimText}}</p>
  </div>
  <script>
    var app = new Vue({
      el: '#root',
      data: {
        text: '',
        number: '',
        trimText: ''
      }
    })
  </script>

效果

第6章 动画

6-1 Vue中的css动画原理

我们给transition name属性定义的是fade 所以是下面名称:
fade-enter fade-enter-to fade-enter-active
fade-leave fade-leave-to fade-leave-active
如果我们没有给transition定义name属性,用默认的那么就是:
v-enter v-enter-to v-enter-active
v-leave v-leave-to v-leave-active

进场动画原理

刚开始存在fade-enter和fade-enter-active
紧接第二帧的时候,fade-enter消失、fade-enter-to 出现
到最后的时候fade-enter-to消失、fade-enter-active消失

离开动画原理

刚开始存在fade-leave和fade-leave-active
紧接第二帧的时候,fade-leave消失、fade-leave-to 出现
到最后的时候fade-leave-to消失、fade-leave-active消失

//css动画效果(css过度动画效果)
<style>
    .fade-enter {
      opacity: 0;
    }
    .fade-enter-active {
      transition: opacity 1s;
    }

    .fade-leave-to {
      opacity: 0;
    }
    .fade-leave-active{
      transition: opacity 1s;
    }
  </style>

<div id="root">
    <transition name="fade">
      <div v-if="show">hello world</div>
    </transition>

    <button @click="handleClick">toggle</button>
  </div>
  <script>
    var app = new Vue({
      el: '#root',
      data: {
        show: true
      },
      methods: {
        handleClick: function () {
          this.show = !this.show;
        }
      }
    })
  </script>

ps:
显示操作
刚开始fade-enter opacity为0 第二帧fade-enter消失 opacity变为1 这个过程一直在fade-enter-active 监听1秒时间
隐藏操作
刚开始fade-leave opacity 默认是1 第二帧 fade-leave消失 fade-leave-to出现 opacity 变为0 这个过程一直在fade-leave-active监听1秒时间后消失

第7章 路由

7-1.什么是路由

路由:就是我们通过不同的 URL 访问不同的内容。

7-2.Vue 路由的安装

npm install vue-router
7-3.Vue 路由的简单案例

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <script src="https://cdn.jsdelivr.net/npm/vue"></script>
  <script src="https://cdn.bootcss.com/vue-router/2.8.1/vue-router.min.js"></script>
  <title>路由</title>
  <style>
    ._active {
      color: red
    }

    ._activeTwo {
      color: pink
    }
  </style>
</head>

<body>
  <div id="root">
    <h1>hello 路由!</h1>

    <!-- 使用 router-link 组件来导航. -->
    <!-- 通过传入 `to` 属性指定链接. -->
    <!-- <router-link> 默认会被渲染成一个 `<a>` 标签 -->
    <p>
      <router-link to="/header">go header</router-link>
      <router-link to="/footer">go footer</router-link>
    </p>

    <!-- <router-link> replace 和 append的应用 标签 -->
    <p>
      <router-link :to="{path:'/header'}" replace>go header(replace)</router-link>
      <router-link :to="{path:'footer'}" append>go footer(append)</router-link>
    </p>

    <!-- <router-link> tag 渲染 -->
    <p>
      <router-link :to="{path:'/header'}" tag="li">go header(tag)</router-link>
    </p>

    <!-- <router-link> exact-active-class 和 active-class 渲染 -->
    <p>
      <router-link :to="{path:'/header'}" exact-active-class="_active">go header(exact-active-class)</router-link>
      <router-link :to="{path:'/footer'}" active-class="_activeTwo">go header(active-class)</router-link>
    </p>

    <!-- <router-link> event渲染 -->
    <p>
      <router-link :to="{path:'/header'}" @click.native="mouseover">go header(event)</router-link>
    </p>

    <!-- 路由出口 -->
    <!-- 路由匹配到的组件将渲染在这里 -->
    <router-view></router-view>
  </div>
  <script>
    //0. 如果使用模块化机制编程,导入Vue和VueRouter,要调用Vue.use(VueRouter)

    //1. 定义路由组件
    //可以从其他文件 import 进来
    const Footer = { template: '<div>footer</div>' };
    const Header = { template: '<div>Header</div>' };

    //2. 定义路由
    //每个路由应该映射一个组件。其中”compoment“ 可以是:
    //通过Vue.extend() 创建的组件构造器
    //或者,只是一个组件对象
    const routes = [
      { path: '/header', component: Header },
      { path: '/footer', component: Footer }
    ]

    //3. 创建router实例,然后传 `routes` 配置
    const router = new VueRouter({
      routes
    })
    var app = new Vue({
      el: '#root',
      router,
      methods: {
        mouseover: function () {
          console.log(1111);
        }
      },
    })
  </script>
</body>

</html>

效果

7-4.router-link的相关配置

1)、to 表示路由链接
当被点击后,内部会立即把to的值传到router.push,所以这个值可以是一个字符串或者是描述目标位置的对象

<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染结果 -->
<a href="home">Home</a>

<!-- 使用 v-bind 的 JS 表达式 -->
<router-link v-bind:to="'home'">Home</router-link>

<!-- 不写 v-bind 也可以,就像绑定别的属性一样 -->
<router-link :to="'home'">Home</router-link>

<!-- 同上 -->
<router-link :to="{ path: 'home' }">Home</router-link>

<!-- 命名的路由 -->
<router-link :to="{ name: 'user', params: { userId: 123 }}">User</router-link>

<!-- 带查询参数,下面的结果为 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

2)、replace
设置 replace 属性的话,当点击时,会调用 router.replace() 而不是 router.push(),导航后不会留下 history 记录。

<router-link :to="{ path: '/home'}" replace></router-link>

3)、tag
有时候想要 <router-link> 渲染成某种标签,例如 <li>。 于是我们使用 tag prop 类指定何种标签,同样它还是会监听点击,触发导航。

<router-link to=”/foo” tag=”li”>foo</router-link>

<li>foo</li>

4)、active-class
设置 链接激活时使用的 CSS 类名。可以通过以下代码来替代。

<style>
   ._active{
      background-color : red;
   }
</style>
<p>
   <router-link v-bind:to = "{ path: '/route1'}" active-class = "_active">Router Link 1</router-link>
   <router-link v-bind:to = "{ path: '/route2'}" tag = "span">Router Link 2</router-link>
</p>

注意这里 class 使用 active_class=”_active”。

5)、exact-active-class
配置当链接被精确匹配的时候应该激活的 class。可以通过以下代码来替代。

<p>
   <router-link v-bind:to = "{ path: '/route1'}" exact-active-class = "_active">Router Link 1</router-link>
   <router-link v-bind:to = "{ path: '/route2'}" tag = "span">Router Link 2</router-link>
</p>

6)、event
声明可以用来触发导航的事件。可以是一个字符串或是一个包含字符串的数组。

<router-link v-bind:to = "{ path: '/route1'}" event = "mouseover">Router Link 1</router-link>

以上代码设置了 event 为 mouseover ,及在鼠标移动到 Router Link 1 上时导航的 HTML 内容会发生改变。
7)、exact-active-class 和 active-class 的区别
exact-active-class:路由严格匹配
active-class:路由模糊匹配
如果你访问的是:
/article或者/article/1

<router-link to="/article" active-class="router-active"></router-link>

都会被渲染

<a href="#/article" class="router-active" rel="nofollow"></a>

<router-link to="/article" exact-active-class="router-active"></router-link>

只有访问/article/1
才会被渲染

<a href="#/article" class="router-active" rel="nofollow"></a>

如果是/article,class不会被渲染出来

<a href="#/article" rel="nofollow"></a>

第8章 Vue项目预热

8-1.NodeJS安装

//验证node和npm 是否安装 以及安装的版本
node -v
npm -v

根据自己电脑是什么系统去安装:NodeJS安装

8-2.vue脚手架安装

//电脑全局安装
npm install --global vue-lci

//a. 实例出一个项目
vue init webpack vue-travel //vue-travel 名称自己定义

//b. 如果你本地有一个git项目了,你想把这个vue脚手架放入到这个项目中
vue init webpack git-project //git-project  本地git项目名称
8-3.运行脚手架项目

cd vue-travel  //or  cd git-project 
npm run dev //or   npm start

浏览器访问localhost:8080即可

8-4.如何更改端口号

项目config文件件,我们打开其目录下的index.js,就是端口号的最终设置的地方:

dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8081, //在这个地方进行端口号的更改
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    .....
}
8-5.如何通过ip来访问我们的网站呢

http://localhost:8080
http://127.0.0.1:8080
http://自己电脑ip:8080 //如果手机和电脑在同一个网段,手机可以联调项目,查看手机效果

第一种方法修改:package.json文件
在dev 命令里面添加 --host 0.0.0.0

"scripts": {
    "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "lint": "eslint --ext .js,.vue src",
    "build": "node build/build.js"
  },

第二种方法修改:config/index.js文件

dev: {
    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},

    // Various Dev Server settings
    host: '0.0.0.0', // 修改这个地方 将localhost 改为 0.0.0.0
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    ......
}

重启 npm run dev 或者npm start

区别:
修改package.json 之后的结果(和之前没有什么区别):
Your application is running here: http://localhost:8080
修改config/index.js文件 之后的运行结果:
Your application is running here: http://0.0.0.0:8080

所以我推荐 方法一(纯粹个人意见)

8-6.Vue 项目初始化的准备工作

因为是手机端的vue项目,所以项目index.html 我们要设置一下
1.禁止缩放

<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">

2.reset.css

@charset "utf-8";html{background-color:#fff;color:#000;font-size:12px}
body,ul,ol,dl,dd,h1,h2,h3,h4,h5,h6,figure,form,fieldset,legend,input,textarea,button,p,blockquote,th,td,pre,xmp{margin:0;padding:0}
body,input,textarea,button,select,pre,xmp,tt,code,kbd,samp{line-height:1.5;font-family:tahoma,arial,"Hiragino Sans GB",simsun,sans-serif}
h1,h2,h3,h4,h5,h6,small,big,input,textarea,button,select{font-size:100%}
h1,h2,h3,h4,h5,h6{font-family:tahoma,arial,"Hiragino Sans GB","微软雅黑",simsun,sans-serif}
h1,h2,h3,h4,h5,h6,b,strong{font-weight:normal}
address,cite,dfn,em,i,optgroup,var{font-style:normal}
table{border-collapse:collapse;border-spacing:0;text-align:left}
caption,th{text-align:inherit}
ul,ol,menu{list-style:none}
fieldset,img{border:0}
img,object,input,textarea,button,select{vertical-align:middle}
article,aside,footer,header,section,nav,figure,figcaption,hgroup,details,menu{display:block}
audio,canvas,video{display:inline-block;*display:inline;*zoom:1}
blockquote:before,blockquote:after,q:before,q:after{content:"\0020"}
textarea{overflow:auto;resize:vertical}
input,textarea,button,select,a{outline:0 none;border: none;}
button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
mark{background-color:transparent}
a,ins,s,u,del{text-decoration:none}
sup,sub{vertical-align:baseline}
html {overflow-x: hidden;height: 100%;font-size: 50px;-webkit-tap-highlight-color: transparent;}
body {font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;color: #333;font-size: .28em;line-height: 1;-webkit-text-size-adjust: none;}
hr {height: .02rem;margin: .1rem 0;border: medium none;border-top: .02rem solid #cacaca;}
a {color: #25a4bb;text-decoration: none;}

在我们项目assets文件下面
创建styles文件
在styles文件下面添加reset.css
最后在项目的main.js引入reset.css

import './assets/styles/reset.css'

3.border.css
解决1px边框问题

@charset "utf-8";
.border,
.border-top,
.border-right,
.border-bottom,
.border-left,
.border-topbottom,
.border-rightleft,
.border-topleft,
.border-rightbottom,
.border-topright,
.border-bottomleft {
    position: relative;
}
.border::before,
.border-top::before,
.border-right::before,
.border-bottom::before,
.border-left::before,
.border-topbottom::before,
.border-topbottom::after,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::before,
.border-topleft::after,
.border-rightbottom::before,
.border-rightbottom::after,
.border-topright::before,
.border-topright::after,
.border-bottomleft::before,
.border-bottomleft::after {
    content: "\0020";
    overflow: hidden;
    position: absolute;
}
/* border
 * 因,边框是由伪元素区域遮盖在父级
 * 故,子级若有交互,需要对子级设置
 * 定位 及 z轴
 */
.border::before {
    box-sizing: border-box;
    top: 0;
    left: 0;
    height: 100%;
    width: 100%;
    border: 1px solid #eaeaea;
    transform-origin: 0 0;
}
.border-top::before,
.border-bottom::before,
.border-topbottom::before,
.border-topbottom::after,
.border-topleft::before,
.border-rightbottom::after,
.border-topright::before,
.border-bottomleft::before {
    left: 0;
    width: 100%;
    height: 1px;
}
.border-right::before,
.border-left::before,
.border-rightleft::before,
.border-rightleft::after,
.border-topleft::after,
.border-rightbottom::before,
.border-topright::after,
.border-bottomleft::after {
    top: 0;
    width: 1px;
    height: 100%;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
    border-top: 1px solid #eaeaea;
    transform-origin: 0 0;
}
.border-right::before,
.border-rightbottom::before,
.border-rightleft::before,
.border-topright::after {
    border-right: 1px solid #eaeaea;
    transform-origin: 100% 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::before {
    border-bottom: 1px solid #eaeaea;
    transform-origin: 0 100%;
}
.border-left::before,
.border-topleft::after,
.border-rightleft::after,
.border-bottomleft::after {
    border-left: 1px solid #eaeaea;
    transform-origin: 0 0;
}
.border-top::before,
.border-topbottom::before,
.border-topleft::before,
.border-topright::before {
    top: 0;
}
.border-right::before,
.border-rightleft::after,
.border-rightbottom::before,
.border-topright::after {
    right: 0;
}
.border-bottom::before,
.border-topbottom::after,
.border-rightbottom::after,
.border-bottomleft::after {
    bottom: 0;
}
.border-left::before,
.border-rightleft::before,
.border-topleft::after,
.border-bottomleft::before {
    left: 0;
}
@media (max--moz-device-pixel-ratio: 1.49), (-webkit-max-device-pixel-ratio: 1.49), (max-device-pixel-ratio: 1.49), (max-resolution: 143dpi), (max-resolution: 1.49dppx) {
    /* 默认值,无需重置 */
}
@media (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 2.49), (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 2.49), (min-device-pixel-ratio: 1.5) and (max-device-pixel-ratio: 2.49), (min-resolution: 144dpi) and (max-resolution: 239dpi), (min-resolution: 1.5dppx) and (max-resolution: 2.49dppx) {
    .border::before {
        width: 200%;
        height: 200%;
        transform: scale(.5);
    }
    .border-top::before,
    .border-bottom::before,
    .border-topbottom::before,
    .border-topbottom::after,
    .border-topleft::before,
    .border-rightbottom::after,
    .border-topright::before,
    .border-bottomleft::before {
        transform: scaleY(.5);
    }
    .border-right::before,
    .border-left::before,
    .border-rightleft::before,
    .border-rightleft::after,
    .border-topleft::after,
    .border-rightbottom::before,
    .border-topright::after,
    .border-bottomleft::after {
        transform: scaleX(.5);
    }
}
@media (min--moz-device-pixel-ratio: 2.5), (-webkit-min-device-pixel-ratio: 2.5), (min-device-pixel-ratio: 2.5), (min-resolution: 240dpi), (min-resolution: 2.5dppx) {
    .border::before {
        width: 300%;
        height: 300%;
        transform: scale(.33333);
    }
    .border-top::before,
    .border-bottom::before,
    .border-topbottom::before,
    .border-topbottom::after,
    .border-topleft::before,
    .border-rightbottom::after,
    .border-topright::before,
    .border-bottomleft::before {
        transform: scaleY(.33333);
    }
    .border-right::before,
    .border-left::before,
    .border-rightleft::before,
    .border-rightleft::after,
    .border-topleft::after,
    .border-rightbottom::before,
    .border-topright::after,
    .border-bottomleft::after {
        transform: scaleX(.33333);
    }
}

在main.js文件下面

import './assets/styles/border.css'

4.fastclick 解决300毫秒点击延迟问题
在main.js文件下面

import fastClick from 'fastclick'
fastClick.attach(document.body)

如果想预览项目整体的效果和配置项git地址:
https://github.com/fx35792/vue-travel
5.安装stylus、stylus-loader 第三方依赖

npm install stylus -S
npm install stylus-loader -S

那么在vue组件中如何使用呢?

//1.设置 lang 为 `stylus`
//2.如果只想样式对当前页面生效,而不污染到全局的其他的样式  可以加 scoped属性
//3.stylus 语法:支持嵌套,变量引用,省去冒号和花括号等等优点
<style lang="stylus" scoped>
@import '~styles/varibles.styl';
.header
  display flex
  background $bgColor
  color #fff
  height .88rem
  line-height .88rem
  .header-left
    float left
    width .64rem
    text-align center
    padding 0 .1rem
    .back-icon
      font-size .48rem
  .header-content
    flex 1
    margin-top .1rem
    height .68rem
    line-height .68rem
    background #ffffff
    border-radius .05rem
    color #e4e7ea
    .search-icon
      padding-left .2rem
  .header-right
    padding 0 .22rem
</style>

上面我们说道style里面的scope是只对当前页面生效,但是在开发过程中,我们可能需要引入第三方的样式,有时候为了满足Ui的样式变化我们需要style书写覆盖第三方依赖的样式,那么我们改如何操作呢?

<style lang="stylus" scoped>
/*这块的穿透写法 `>>>`  就可以覆盖swiper 当前活跃圆点的样式*/
.wrapper >>> .swiper-pagination-bullet-active {
  background: #fff !important;
}

.wrapper {
  overflow: hidden;
  width: 100%;
  height: 0;
  padding-bottom: 26.67%;
  background: #eee;

  .swiper-img {
    width: 100%;
  }
}
</style>
8-7.Vue 项目如何给长目录定义变量

在开发过程中我们经常要引入外部文件,比如styles文件、图片文件等等

../../../assets/styles/border.css

像上面的例子我们肯定遇到过,如果有一个变量styles能直接代码../../../styles那就好了。
那在vue-cli的脚手架中应该怎么去配置呢?
在build/webpack.base.conf.js中

resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'styles': resolve('src/assets/styles'), //这是我们添加的变量
    }
  },

配置好以后,我们需要重启一下项目 npm startornpm run dev
那么上面例子长长的引用我们就可以改写为

styles/border.css

ps:如果我们在vue组件中引用的话我们需要注意在styles之前加个波浪符~

@import '~styles/varibles.styl';
8-7.Vue 项目如何引入本地图片呢?

require

require('@/assets/images/1.jpg')

import

//此处的@ 指的是src目录
import bannerImg1 from '@/assets/images/1.jpg'

alias

//webpack.base.conf.js
resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'styles': resolve('src/assets/styles'),
      'images': resolve('src/assets/images'),//配置图片变量路径
    }
  },

//页面调用
<img src="~images/1.jpg" class="swiper-img" alt="">

<script>
import bannerImg1 from '@/assets/images/1.jpg'
export default {
  name: 'HomeSwiper',
  data () {
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true
      },
      swiperList: [
        {
          id: '0001',
          url: bannerImg1
        },
        {
          id: '0002',
          url: require('@/assets/images/2.jpg')
        },
        {
          id: '0003',
          url: require('../../../assets/images/3.jpg')
        }
      ]
    }
  }
}
</script>

第9章 Vue项目开发之首页

首页制作完的整体效果:

效果图

如果你想看首页整体开发效果,直接运行master分支即可:
https://github.com/fx35792/vue-travel

如果想看每部分的开发效果:

9-1 Vue项目开发首页之header

在开发这块的时候,主要是一个布局的设置,还有就是icon图标,这个icon图标用的是阿里iconfont:https://www.iconfont.cn/
1.创建一个iconfont账号
2.登录后–>图标管理–>我的图标–>新建项目
3.去查找UI所需要的图标icon,添加购物车–>添加项目–>选择自己新建的项目
4.图标全部查找完以后可以把这个项目下载到本地
第一种方法:如果是下载到本地,放入到项目中的操作

程序引入字体样式文件
在main.js引入

import 'styles/iconfont.css'

//页面上使用的话
<span class="iconfont search-icon">&#xe609;</span>

第二种方法:不下载本地,用阿里iconfont的cdn

程序引入样式cdn

在index.html中添加

<link rel="stylesheet" href="//at.alicdn.com/t/font_892705_hf5c19omqen.css">‘’

//页面上使用的话(和方法一是一样的)
<span class="iconfont search-icon">&#xe609;</span>

如果你是线上项目,为了保险起见,推荐使用方法一
但是如果你平时自己做项目练习,你使用方法二就行

9-2 Vue项目开发首页之Swiper轮播图

1.安装https://github.com/surmon-china/vue-awesome-swiper

npm install vue-awesome-swiper --save

2.如何使用呢?
因为项目中很多地方可能需要用到swiper,所以我们打算把他放到main.js中

import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'

Vue.use(VueAwesomeSwiper)

3.具体代码调用

//template
<swiper :options="swiperOption" v-if="swiperShow">
      <swiper-slide v-for="item of list" :key="item.id">
        <img :src="item.imgUrl" alt class="swiper-img">
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>
</swiper>

//js
<script>
export default {
  name: 'HomeIcons',
  data () {
    return {
      swiperOption: {
        pagination: 'swiper-pagination',//是否显示轮播图下面的小圆点
        autoPlay: false//是否循环自动播放
      }
    }
  },
  computed: {
    swiperShow: function () {
      return this.list.length//当有数据的时候,再去渲染swiper,不然显示的第一条数据是最后一条
    }
  }
}
</script>
9-3 Vue项目开发首页之”热门推荐“和”周末去哪“

这一小节主要是UI上布局制作,以及data里面模拟一些数据,渲染到页面上。具体细节看
https://github.com/fx35792/vue-travel/tree/index-recommond
看代码基本上都可以看明白的,在这咱们就不详细的赘述了

9-4 Vue项目开发首页之ajax数据请求

我们通过ajax来实现接口请求的数据,但是呢?在开发过程中,很多时候都是我们前端自己mock数据,通过代理,最后映射到页面上数据的,随后等服务器接口开发好了,我们在把mock数据地址替换为服务器地址即可。
1.那么如何设置代理呢?
其实在vue-cli的脚手架中,已经办咱们配置好了设置,只需要你自己配置一下即可:

//在config文件下面的index.js文件中:
proxyTable: {
      '/api': {
        target:'http://localhost:8080',//因为数据在我项目本地,所以我配置的localhost,如果是服务器你配置后端伙伴发给你的服务器地址即可
        pathRewrite: {
          '^/api':'/static/mock'  //当你接口请求中,以`/api`开头的时候,会帮我们代理到我们本地的/static/mock目录下面数据文件
        }
      }
    },

2.安装axios

npm install axios -S

3.在页面上使用

import axios from 'axios'

 mounted () {//一般的异步请求,我们都会放在mounted的生命周期中
    this.getHomeInfo()//这个我们定义了一个方法,而不是直接写,是为了重复使用这个方法
  },
methods: {
    getHomeInfo () {
     //通过axios请求接口
    //当我们遇到`/api`,代理直接会找到/static/mock/index.js文件
      axios.get('/api/index.json').then(this.getHomeInfoSucc)
    },
    getHomeInfoSucc (res) {
      const result = res.data
      if (result.ret && result.data) {
        const data = result.data
        console.log(data)
        this.city = data.city
        this.swiperList = data.swiperList
        this.iconList = data.iconList
        this.recommendList = data.recommendList
        this.weekendList = data.weekendList
      }
    }
  }
9-4 Vue项目开发首页之父子组件之间的传值

在制作的首页过程中,我们将首页拆分成了五部分,分别是:
header、banner轮播、icon轮播、热门推荐、周末去哪
那么,为了减少http接口请求,后端小伙伴会把五部门的内容都放到一个接口中去,在咱们本地模拟数据中我是放到了static/mock/index.json中的
所以在Home.vue中

//Home.vue
<template>
  <div>
    <home-header :city="city"></home-header>
    <home-swiper :list="swiperList"></home-swiper>    <!-- 第四步 -->
    <home-icons :list="iconList"></home-icons>
    <home-recommend :list="recommendList"></home-recommend>
    <home-weekend :list="weekendList"></home-weekend>
  </div>
</template>

<script>
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
export default {
  name: 'Home',
  components: {
    HomeHeader,
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  data () {
    return {
      city: '',
      swiperList: [],//第二步
      iconList: [],
      recommendList: [],
      weekendList: []
    }
  },
  methods: {
    getHomeInfo () {
      axios.get('/api/index.json').then(this.getHomeInfoSucc)//第一步
    },
    getHomeInfoSucc (res) {
      const result = res.data
      if (result.ret && result.data) {
        const data = result.data
        console.log(data)
        this.city = data.city
        this.swiperList = data.swiperList//第三步
        this.iconList = data.iconList
        this.recommendList = data.recommendList
        this.weekendList = data.weekendList
      }
    }
  },
  mounted () {
    this.getHomeInfo()
  }
}
</script>
<style>
</style>

//Swiper.vue
<template>
  <div class="wrapper">
    <swiper :options="swiperOption" v-if="swiperShow">
      <swiper-slide v-for="item of list" :key="item.id"><!--第六步-->
        <img :src="item.imgUrl" alt class="swiper-img">
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>
    </swiper>
  </div>
</template>
<script>
export default {
  name: 'HomeSwiper',
  props: {
    list: Array //第五步
  },
  data () {
    return {
      swiperOption: {
        pagination: '.swiper-pagination',
        loop: true
      }
    }
  },
  computed: {
    swiperShow: function () {
      return this.list.length
    }
  }
}
</script>
<style lang="stylus" scoped>
.wrapper >>> .swiper-pagination-bullet-active {
  background: #fff !important;
}

.wrapper {
  overflow: hidden;
  width: 100%;
  height: 0;
  padding-bottom: 31.25%;
  background: #eee;

  .swiper-img {
    width: 100%;
  }
}
</style>

在这里咱们主要讲讲,首页父子组件的传值,咱们拿一个banner轮播图例子来说,其他的四部分咱们就不在这里赘述了。你去github仓库看源码就很容易明白。
第一步:接口请求拿到数据(axios.get(‘/api/index.json’).then(this.getHomeInfoSucc)//第一步)
第二步:在data中我们初始化这五部分数据(swiperList: [],.//第二步)
第三步:把接口拿到的数据依次放入到data初始化的值中( this.swiperList = data.swiperList//第三步)
第四步:在Home.vue父组件中定义一个属性,来给Swiper.vue子组件传值(:list=”swiperList”)
第五步:在Swiper.vue子组件中接受父组件传来的值(props: {
list: Array //第五步
})
第六步:子组件渲染出来父组件传递过来的数据(<swiper-slide v-for=”item of list” :key=”item.id”>)

第10章 Vue项目开发之城市

10-1.city页面路由配置

1.添加路由配置

// router/index.js文件
import City from '@/pages/city/City'
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Home',
      component: Home
    },
    {
      path: '/city',
      name: 'City',
      component: City
    }
  ]
})

2.添加相对应的页面
在pages文件下面添加city文件夹和City.vue文件

效果图

3.初始化City.vue页面

<template>
  <div>
    city
  </div>
</template>

<script>
export default {
  name: 'City'
}
</script>
<style lang="stylus" scoped>

</style>
10-2.city-header部分制作

//city/City.vue
<template>
  <div>
    <city-header></city-header>
  </div>
</template>

<script>
import CityHeader from './components/Header'
export default {
  name: 'City',
  components: {
    CityHeader
  }
}
</script>
<style lang="stylus" scoped>

</style>


//city/components/header.vue
<template>
  <div class="header">
    城市选择
    <router-link to="/">
      <div class="iconfont back-city">&#xe696;</div>
    </router-link>
  </div>
</template>

<script>
export default {
  name: 'CityHeader'
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header
  position relative
  height $headHeight
  line-height $headHeight
  background $bgColor
  text-align center
  color #ffffff
  .back-city
    position absolute
    left 0
    top 0
    width .64rem
    text-align center
    padding 0 .1rem
    font-size .48rem
    color #fff
</style>

效果图

10-3.city-search部分制作

上面咱们已经完成了头部的制作,这一节咱们来city-search的ui部分制作,随后等咱们把city列表制作完成后,咱们再来制作city-search相关的逻辑部分,代码如下

//city/components/Search.vue
<template>
  <div class="search">
    <input class="search-input" type="text" placeholder="输入城市名称或者拼音" />
  </div>
</template>

<script>
export default {
  name: 'CitySearch'
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
  .search
    height .722rem
    padding 0 .1rem
    background $bgColor
    .search-input
      box-sizing border-box
      width 100%
      height .62rem
      padding 0 .1rem
      line-height .62rem
      border-radius .06rem
      color #666
      text-align center
</style>

city/City.vue,在city的主页面引入我们制作好的city-search模块

image.png

10-3 city-list、city-ajax 、city-vuex、city-search-logic 部分的制作

城市整体效果图

Ui上面的制作,直接从github下载下来,git checkout 到不同的分支,就能看到代码了,总结嘛,不能能把所有的项目中的代码都展示出来,更多的是展示难点、思路、注意事项等等一些小细节地方。
city-ajax部分和index-ajax 方式是一样,在这里咱们就不再次赘述了

知识点1:BetterScroll 的使用,让城市列表可以滚动起来

//安装better-scroll
npm install better-scroll -S

在这个使用better-scroll的时候我们需要注意三点

  • dom结构(要符合这种结构)

<div class="wrapper">
  <ul class="content">
    <li>...</li>
    <li>...</li>
    ...
  </ul>
  <!-- you can put some other DOMs here, it won't affect the scrolling
</div>
  • 样式(要滚动的list 要脱离文档流)

.list 
  overflow: hidden;
  position: absolute;
  top: 1.6rem;
  left: 0;
  right: 0;
  bottom: 0;
  • 在vue中的调用和使用方法

//dom部分
<div class="list" ref="wrapper">
.....
</div>

//js部分
import BScroll from 'better-scroll'
mounted () {
    this.scroll = new BScroll(this.$refs.wrapper)
}

知识点2兄弟组件数据传递
我们知道:
City.vue是父组件
components/List.vue是一个子组件
components/Alphabet.vue也是一个子组件

那么子组件(Alphabet.vue)如何和子组件(List.vue)进行通信呢?
现在有这样的一个需求,就是当我们点击右侧的字母(代码在Alphabet.vue中),列表(List.vue)能自动滚动相对应的列表字母模块部分,那么这个过程就是一个子组件和子组件的通信(兄弟组件数据传递)

思路:
第一步:子组件(Alphabet.vue)点击字母的时候,通过$emit发送一个’change’的方法,并且把携带的点击入参传递给父组(City.vue)

//dom部分
<li
      class="item"
      v-for="item of letters"
      :key="item"
      @click="handleLetterClick" //触发点击事件
    >{{item}}</li>

//js部分
methods: {
    handleLetterClick (e) {
      this.$emit('change', e.target.innerText)
    }
}

第二步:父组件(City.vue)通过属性来监听‘change’事件,同时创建一个新的方法,在此方法中来接受子组件传递过来的参数,随后把入参放入到data初始化的letter中,再然后,把letter获得入参以属性的方式传递给city-list组件

//1)dom 来监听子组件发出来的change
<city-alphabet :cities="cities" @change="handleLetterClick"></city-alphabet>

//4)dom 父组件从子组件那拿来的数据(letter)传递给新的子组件
<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>

//2)初始化data中的letter值 用来存储子组件出来的入参
data () {
    return {
      letter: ''
    }
},

//3)js change 创建的方法 来接受子组件传递过来的值,并把它存储到data里面
handleLetterClick (letter) {
      this.letter = letter
}

第三步:子组件(List.vue)通过属性props来接受父组件传过来的值

//js
props: {
    letter: String//接受父组件传递过来的值
},

//js 监听传过来值的变化
watch: {
    letter () {
      if (this.letter) {
        const element = this.$refs[this.letter][0]  //通过获取字母的值
        this.scroll.scrollToElement(element) //滚动到指定元素模块
      }
    }
  }

//dom 需要在字母模块添加ref属性
<div
  class="area"
  v-for="(item,key) of cities"
  :key="key" 
  :ref="key"//这个key值刚好和兄弟组件传过来的值相同
>
  <div class="title border-topbottom">{{key}}</div>
  <div class="item-list">
    <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{{innerItem.name}}</div>
  </div>
</div>

知识点3 完成一个手指滑动右侧字母,左侧区域跟着滚动
这部分咱们需要给右侧的字母绑定上三个事件:

@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"

为了只让在touchmove里面去触发这些操作,所以我们需要定义个开关(标示位),我们把这个标示位放在了data里面

touchStatus: false //设置为false

所以当我们开始滑动的时候我们把touchStatus设置为true

handleTouchStart () {
  this.touchStatus = true
}

当我们手指划出触发操作区域的时候,我们需要把标示为设置为false

handleTouchEnd () {
  this.touchStatus = false
}

所以只有当标示位为true的这种情况,我们采取进滑动字母相对应的操作

handleTouchMove () {
  if (this.touchStatus) {
  //滑动过程中所对应的逻辑
  }
}

思路
在滑动这个过程中,最终我们在这个页面上下滑动的时候,我们需要知道你滑动的位置是第几个字母
1、我们需要知道A字母距离顶部的距离
2、我们需要知道手指滑动到当前字母距离顶部的的距离
3、把上面两个做一个差值,那么我们就可以得到当前位置距离A字母之间的高度
4、我们把得到这个差值高度除以每个字母的高度,那么我们就得到了是第几个字母了
根据上面这个思路,我们需要得到这个字母的数组:

computed: {
    letters () {
       const letters = []
       for (let i in this.cities) {
          letters.push(i)
       }
       return letters
    }
}

通过计算属性,我们就可以把dom上的数据获取从父组件传递过来的cities改为letters

<li
  class="item"
  v-for="item of letters" //通过计算属性来获得字母值
  :key="item"
  :ref="item"
  @click="handleLetterClick"
  @touchstart="handleTouchStart"
  @touchmove="handleTouchMove"
  @touchend="handleTouchEnd"
>
{{item}}
</li>

根据上面的思路咱们开始来编写相对应逻辑

handleTouchMove (e) {
    //标示位开始
    if (this.touchStart) {
      const startY = this.$refs['A'].offsetTop //获取字母A距离顶部的距离
      const touchY = e.touches[0].clientY - 79 //获取手机滑动当前字母距离顶部距离(79是header和搜索框的高度)
      const index = Math.floor((touchY-startY) / 20) //获得是第几个字母

     if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index]) //在有效的索引里面去 查找是第几个字母
     }
    }
}

其实写到这块我们的功能是完成了的,但是细想还有一些地方需要优化?
初始化

data () {
  return {
    startY: 0,
    timer: null
  }
},

优化一:每次都去求获取字母A距离顶部的距离?

updated () {
    this.startY = this.$refs['A'][0].offsetTop
},

优化二:滑动字母的时候,需要做一下事件节流(通过一个定时器timer)

handleTouchMove (e) {
  if (this.touchStatus) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    this.timer = setTimeout(() => {
      const startY = this.startY
      const touchY = e.touches[0].clientY - 79
      const index = Math.floor((touchY - startY) / 20)
      if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index])
      }
    }, 16)
  }
},

知识点4 实现一个城市搜索功能
需求
1.根据字母或者汉字可以进行检索想要的内容
2.当搜索框没数据的时候,不显示搜索区域内容
3.当搜索框有数据且数据不在搜索内容时,显示暂无搜索内容
4.当搜索出来的内容比较多的时候,搜索内容可以进行滚动(better-scroll)

第一步:获取从父组件传递过来的cities值

props: {
    cities: Object
},

第二步:data里面初始化keyword、list、timer

data () {
  return {
    keyword: '',
    list: [],
    timer: null
  }
},

第三步:watch方法监听keyword的更改、其中这里面包含timer优化、list数据获取、检索操作的逻辑

watch: {
  keyword () {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    if (!this.keyword) {
      this.list = []
      return false
    }
    this.timer = setTimeout(() => {
      const result = []
      for (let i in this.cities) {
        this.cities[i].forEach(value => {
          if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
            result.push(value)
          }
        })
      }
      this.list = result
    }, 100)
  }
},

第四步:数据处理好了,要铺到Ui上面
为了可以滚动:一定要符合better-scroll的dom结构;search-content样式要脱离文档流。
只有当有关键字才会显示搜索内容;
当关键字搜索没有数据的时候,显示”没有搜索到匹配内容“

<div class="search-content" ref="search" v-show="keyword">
  <ul>
    <li class="search-item border-bottom" v-for="item of list" :key="item.id">{{item.name}}</li>
    <li class="search-item border-bottom" v-show="hasNoData">没有搜索到匹配内容</li>
  </ul>
</div>

第五步:搜索数据有了,但是过多的时候也要可以滚动,better-scroll

mounted () {
  this.scroll = new Bscroll(this.$refs.search)
}

知识点5 vuex实现数据共享
如果学过react的同学肯定知道redux,react是处理ui层的,那么数据层就是通过redux来完成,方便我们不同页面之间的传值,一直值的更改等等
同样在vue中,vue也只负责ui部分,vuex则是用来处理数据层的

vuex的原理图

1.安装vuex

npm install vuex -S

2.使用和调用vuex
因为vuex是处理数据模块的,所以我们在src目录下创建一个store目录,在store目录下面创建一个
index.js

import Vue from 'vue'
import Vuex from 'vuex'

export default new Vuex.Store({
  state: {
    city: '北京'
  }
})

创建好之后,我们在main.js文件中去调用这个文件

import store from './store'

new Vue({
  el: '#app',
  store,//根实例引入store
  router,
  components: { App },
  template: '<App/>'
})

3.应用
在咱们这个项目中,首页右上角的城市名称是通过后端返给我们,那么我们可以通过vuex来初始化一个城市,也可以通过vuex来更改城市这个值。
在store/index.js 其实我们已经做了city的初始化的值:北京
那么在首页和城市页面我们如何获取vuex当中这个值呢?

//pages/home/components/Header.vue
{{this.$store.state.city}}

//pages/city/components/List.vue  当前城市
{{this.$store.state.city}}

点击热门城市或者点击城市搜索出来列表切换城市的显示,那么我们去如何更改state这个值呢?

//点击热门城市事件
@click="handleCityClick(item.name)"

methods: {
  handleCityClick (city) {
      //要调用store里面的dispatch方法
      this.$store.dispatch('changeCity', city)
  }
}

上面我们已经触发了一个dispatch的方法,那么我们通过actions来接受这个方法
store/index.js

export default new Vuex.Store({
  state: {
    city: '上海'
  },
 actions: {
    changeCity(ctx, city) {
      //console.log(city)
      //那么action如何调用mutations呢?通过commit方法
      ctx.commit('changeCity',city)
    }
  },
  mutations: {
    changeCity (state, city) {
      state.city = city
    }
  }
})

从上面可以看出在我们发送dispatch的时候,并没有触发异步请求,或者批量的数据操作,所以上面操作,我们可以直接跳过actions这部分,不需要去触发dispatch操作,而是直接调用commit对mutations的操作
所以上面的代码就可以改为:

//点击热门城市事件
@click="handleCityClick(item.name)"

methods: {
  handleCityClick (city) {
      //要调用store里面的dispatch方法
      this.$store.commit('changeCity', city)  //将dispatch 改为commit
  }
}


//store/index.js
export default new Vuex.Store({
  state: {
    city: '上海'
  },
  //删除actions的相关操作
  mutations: {
    changeCity (state, city) {
      state.city = city
    }
  }
})

讲到这里其实就实现了vuex的数据一个设置以及显示的一些操作,但是我们更具当前的产品需求我们还是需要完善一下页面跳转。
之前我们实现页面跳转是通过
1.router-link 的to属性来实现
2.那么还有一种通过js 来实现页面跳转的$router.push
那么我们希望我们在选择完城市后,能自动跳转到首页,那么

this.$router.push('/')

知识点6 vuex的高级使用以及localStorage

store/index.js文件的拆分和localStorage的应用

在上面使用vuex中我们给city设置了一个初始值:’上海’,但是当我们切换完城市后,返回首页,如果我们刷新首页,那么我们选择的城市就又变回为了默认值:’上海’,那么针对这种情况,我们需要引入本地缓存localStorage,但是呢,有些浏览器会屏蔽localStorage的一些东西,为了程序的健壮性,减少没必要的浏览器异常,所以在对localStorage进行相关操作的时候,我们先进行一层try catch的操作

//store/index.js
let defaultCity = '上海'
try {
  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {}

export default new Vuex.Store({
  state: {
    city: defaultCity
  },
  mutations: {
    changeCity (state, city) {
      state.city = city
      try {
        localStorage.city = city
      } catch (e) {}
    }
  }
})

写到这里我们发现,将来如果我们业务比较复杂的话,store/index.js会变的越来越庞大,那么这不是我们希望看到的,所以我们要对store/index.js进行拆分。
那么如何进行拆分呢?
store/index.js 只是一个总文件,而这个总文件包含很多部分:state、actions、mutations等等,
那么我们将可以将这些模块拆分成为:state.js、actions.js、mutations.js
最后再把他们引入到store/index.js文件中
那么,根据这个思路咱们接下来拆分一下store/index.js

//store/state.js

let defaultCity = '北京'
try {
  if (localStorage.city) {
    defaultCity = localStorage.city
  }
} catch (e) {

}

export default {
  city: defaultCity
}


//store/mutions.js
export default{
  changeCity (state, city) {
    state.city = city
    try {
      localStorage.city = city
    } catch (e) {}
  }
}

那么store/index.js 就变为了:

import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations
})

vuex的高级应用以及针对项目当中的优化

我们上面调用城市的时候是通过{{this.$store.state.city}}来实现的
如果这么写的话,略显页面比较冗余。那么有没有其他方法会比较简单一些呢?
vuex帮我们封装了一些方法和aip有一个mapState的方法就可以帮我们实现,那么应该如何使用呢?

import { mapState } from 'vuex'

//第一种通过数组方法获取
computed: {
    ...mapState(['city']) //这样就把把store中的city值获取到
}
//第二种通过对象方法获取(起一个别名)
computed: {
    ...mapState({
      currentCity: 'city'
    }) //这样就把把store中的city值获取到
}

//如果是第一种方法获取的
将原来的 {{this.$store.state.city}} 改为 {{this.city}}

//如果是第二种方法获取的
将原来的 {{this.$store.state.city}} 改为 {{this.currentCity}}

获取vuex中store的数据我们可以通过mapState方法,那么设置vuex数据呢?
我们可以通过vuex给我们提供的mapMutations方法,那么如何实现呢?

import {mapMutations} from 'vuex'

methods: {
    handleCityClick (city) {
       //this.$store.commit('changeCity', city) 改为下面:
       this.changeCity(city)
       this.$router.push('/')
    },
    ...mapMutations(['changeCity'])
}

讲的这里我们使用了vuex给我们提供的state、actions、mutations,我们登录vue官网,我们发现vuex还给我们提供了两个一个是getter、另一个是module
那么我们来看一下getter的使用

//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
Vue.use(Vuex)

export default new Vuex.Store({
  state,
  mutations,
  getters: {
      doubleCity (state) {
          return state.city + ' ' + state.city
      }
  }
})

那么页面上应该如何使用或者调用呢?

import { mapGetters } from 'vuex'

computed: {
    ...mapGetters(['doubleCity'])
}

//页面上调用
{{this.doubleCity}}

那么我们此时会想,这有什么用处呢?因为mapState就可以实现的方法,我为什么还要使用mapGetters来实现呢?
其实呢,我们发现getters方法有点类似vue组件当中的computed方法,他可以把我们state值进行处理后返给我们一个新值,从来来避免一些数据的冗余。
getter讲完了,那么module我们在什么情况下去使用呢?
因为我们在store/index.js中 只写了city相关的(state、actions、mutations)等等操作,当我们在实际开发的过程中,我们肯定不单单只有city这一个模块的,如果有很多页面的功能模块的话,我们拆分的state.js、actions.js、mutations.js会变得很臃肿的,这不是我们期盼看到的。
所以我们通过module模块对我们的功能模块进行进一步的拆分,每个功能模块包含自己的(state、actions、mutations等等)。如下面例子:

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
}

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... }
}

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
})

store.state.a // -> moduleA 的状态
store.state.b // -> moduleB 的状态

发表评论

邮箱地址不会被公开。