Vue學習總結(jié)

由于簡書沒有目錄的功能摄职,為了更加清晰的學習,我制作了一個目錄地址如下:
學習Vue目錄 鏈接地址: http://www.reibang.com/p/2c9071c0436d
如果想快速拗引、準確窃判、查看、學習內(nèi)容,那么你就點擊上面的鏈接设塔,查看到整體目錄,然后點擊查看自己想看的內(nèi)容远舅。
如果你不想單篇的了解闰蛔,此篇文章匯總了所有章節(jié)的內(nèi)容

第1章 課程介紹

1-1課程簡介
學習流程
知識點

學習前提:
有一些css、js图柏、es6序六、webpack、npm等基礎知識


學習收獲

第2章 Vue 起步

2-1 學習方法

多看一下官網(wǎng)api Vue官網(wǎng)
務必把官網(wǎng)的小的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實例化的數(shù)據(jù)只針對 id為root內(nèi)使用
{{content}} : 獲取 vue里面data 里面的數(shù)據(jù)值

2-3 開發(fā)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" 數(shù)據(jù)雙向綁定
v-for="(item,index) in list" 數(shù)據(jù)循環(huán)

2-4 MVVM模式

MVP (Model View Presenter)
Model:接口請求操作
View:頁面展示
P:處理數(shù)據(jù)和頁面(大量的dom操作)


MVP

MVVM

MVVM

而Vue則采用的是mvvm這種模式随抠,它在乎的是數(shù)據(jù)的變化驅(qū)動Ui的變化,不用用戶管dom的操作繁涂,vue則扮演的是VM的操作拱她,我們的重心是放在了M層也就是數(shù)據(jù)這一層

2-5 前端組件化

頁面有好多部分組成,把頁面切成一部分一部分扔罪,而拆分出來的部分秉沼,就是一個組件

2-6 使用組件改造TodoList

1.全局組件的聲明和使用

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

ps:
數(shù)據(jù)的傳遞通過v-bind: 來定義傳遞的屬性,后面跟上要傳遞的值
通過“props”來接受屬性步势,再通過插值表達式來展示{{content}}

2.Vue局部組件的創(chuàng)建和使用

 //Vue局部組件的創(chuàng)建和使用
var TodoItem = {
        props: ['content'],
        template: '<li>{{content}}</li>'
};
var app = new Vue({
        el: '#root',
        components: {
            TodoItem: TodoItem
        }
})
ps:
定義一個變量氧猬,值為對象,把屬性值和模板作為屬性的鍵值
在Vue實例化中通過components這個參數(shù)來調(diào)用這個定義好的變量(局部組件)
2-7 簡單的組件間傳值

父組件向子組件傳值:
子組件通過定義一個屬性:v-bind:content="item",將item值傳給子組件的content
子組件通過props:['content'] 來接受父組件傳過來的值坏瘩,再通過插值表達式來展示{{content}}
子組件向父組件傳值:
子組件通過定義一個方法或者一個事件handleItemClick盅抚,在方法或者事件中,通過this.$emit(''delete",this.index)方法給給父組件發(fā)送一個信號倔矾,
父組件監(jiān)聽這個信號:@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創(chuàng)建全局組件的方法
    // Vue.component('TodoItem', {
    //     props: ['content'],
    //     template: '<li>{{content}}</li>'
    // });

    //Vue局部組件的創(chuàng)建和使用
    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實例哪自,但是之前的數(shù)據(jù)和方法并沒有被銷毀

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

  },
  computed:{

  }
})
3-2 Vue實例生命周期
//生命周期函數(shù)就是vue實例在某個時間點自動執(zhí)行的函數(shù)
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進行轉(zhuǎn)義,字體變成h1字體大小胧华,而不會出現(xiàn)標簽
3-4 計算屬性矩动、方法與偵聽器

計算屬性
computed屬性悲没,因為他是屬性甜橱,所以在用插值表達式取值的時候不用加括號
computed:內(nèi)置變量緩存的功能荧琼,當data里面age變量更改時偏化,如果不是計算屬性內(nèi)邊的變量更改苟翻,那么他就不會渲染computed內(nèi)部的變量

<!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
        },
        //計算屬性:內(nèi)置緩存(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>

總結(jié):我們可以通過methods瞻凤、computed玫坛、watch來實現(xiàn)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的數(shù)組綁定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>class的數(shù)組綁定</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的數(shù)組綁定

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>style數(shù)組綁定</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框的輸入內(nèi)容被占用壁拉,所以我們通過設置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的區(qū)別

  • v-show不能和v-else 和 v-else-if結(jié)合使用
  • 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循環(huán)渲染驻啤,而這個template不會渲染到dom里面

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

為了防止子組件循環(huán)渲染出現(xiàn)dom結(jié)構(gòu)不對的情況先煎,我們一般會通過is來給子組件命名

//html
 <table>
    <tbody>
      <tr is="row" v-for="item in list" :title="item"></tr> //這個地方調(diào)用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'
            ]
        }
    })
更改數(shù)組值方法有哪些良风?

1.變異方法
push()歪脏、 pop()、 shift()、unshift()、splice()、sort()、reverse()
2.替換數(shù)組
當也可以創(chuàng)建一個新的數(shù)組乎折,在通過
filter()惕虑、concat()琳猫、slice()
更改原始數(shù)組的值紊遵,再把更改后的數(shù)組替換舊的數(shù)組
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:不可以通過數(shù)組下標去更改數(shù)組的值

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

3-9 Vue事件處理

監(jiān)聽事件学搜、方法處理、內(nèi)聯(lián)處理器中的方法

1.監(jiān)聽事件

通過v-on指令監(jiān)聽DOM事件,并在觸發(fā)時運行一些 JavaScript 代碼

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>監(jiān)聽事件</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.事件處理方法

但是在開發(fā)的過程中,有時候直接把 JavaScript 代碼寫在 v-on 指令中是不可行的闽坡。因此 v-on 還可以接收一個需要調(diào)用的方法名稱

<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.內(nèi)聯(lián)處理器中的方法

除了直接綁定到一個方法,也可以在內(nèi)聯(lián) JavaScript 語句中調(diào)用方法:

<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}`)
            }
        }
    })

方法總通過傳遞參數(shù)$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.事件修飾符

我們在開發(fā)個過程中經(jīng)常調(diào)用一些
event.preventDefault() //阻止事件的默認行為
event.stopPropagation() //阻止冒泡的行為

而vue為了更好的讓我們關注業(yè)務邏輯代碼的編寫幔亥,它封裝了很多v-on修飾符來幫助我們完成上面那些工作。

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

第4章 深入理解Vue組件

4-1 使用組件細節(jié)點

1.is的使用
當我們寫循環(huán)組件的時候瞒窒,經(jīng)常給
table中的tr
select中的option
ul中的li或者ol中的li
等等定義組件的時候,我們經(jīng)常用is來定義組件的名稱,為了讓瀏覽器成功的渲染正確的dom結(jié)構(gòu)

<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的時候采记,必須是一個函數(shù)奸远,而不能是一個對象薛窥,返回一個對象是為了每個子組件都能擁有一個獨立的數(shù)據(jù)存儲妥凳。這樣子組件之間的數(shù)據(jù)不會互相影響
而在根組件中,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 () {//返回的是一個函數(shù)
            return {
                content: 'this is row'
            }
        },
        template: '<tr><td>{{content}}</td></tr>'
    });
    var app = new Vue({
        el: '#root',
        data: {}
    })
</script>

3.有時候我們在開發(fā)過程中姥份,因為一些業(yè)務的需求埃难,少不了對dom的操作,那么我們就可以借助ref來實現(xiàn)

//實例一
<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屬性 獲取當前節(jié)點的文本
            }
        }
    });
</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');//觸發(fā)一個監(jiān)聽器
            }
        }
    });
    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父子組件之間的數(shù)據(jù)傳遞

父組件向子組件傳值:是通過屬性的方式
子組件向父組件傳值:可以通過$emit來觸發(fā)一個事件

vue數(shù)據(jù)傳遞遵循的是單向數(shù)據(jù)流惠昔,
所以在下面的案例中我們并沒有對content數(shù)據(jù)直接進行數(shù)據(jù)的累加来氧,而是把content數(shù)據(jù)賦值給了number,對number進行數(shù)據(jù)的累加操作瞄摊。

<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 //遵循單向數(shù)據(jù)流
            }
        },
        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組件參數(shù)校驗和非props特性

1.組件的的參數(shù)校驗

<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特性:
父組件傳遞屬性每聪,子組件沒有去接受狂丝,而是直接調(diào)用
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.通過發(fā)布訂閱模式(Bus/總線/發(fā)布訂閱模式/觀察者模式/)

<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 //保證單向數(shù)據(jù)流
            }
        },
        props: {
            content: String
        },
        template: '<div @click="handleClick">{{newContent}}</div>',
        methods: {
            handleClick: function () {
                this.bus.$emit('change', this.newContent); //在bus上發(fā)布一個事件,并且傳值
            }
        },
        mounted: function () {//通過這個鉤子,來監(jiān)聽change的變化跷叉,通過回調(diào)拿到相對應的的值
            var that = this;
            this.bus.$on('change', function (msg) {
                console.log(msg)
                that.newContent = msg//this 指向發(fā)生變更,所以上面要從新獲取一下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作用域插槽

父組件調(diào)用子組件的時候狮暑,給子組件傳了一個插槽缔逛,這個插槽是一個作用域的插槽敦冬,這個插槽必須是一個<template slot-scope="props">{{props.item}}</template>
那什么時候使用作用插槽呢唯沮?
1.當子組件做循環(huán)
2.或者當子組件的dom結(jié)構(gòu)由外部傳遞進來脖旱,或者有外部決定的時候

<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 動態(tài)組件和v-once 指令
<div id="root">
    <component :is="type"></component> <!--這就是動態(tài)組件-->
    <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雙向數(shù)據(jù)綁定 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)萌庆、多個復選框的數(shù)組操作

<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轉(zhuǎn)為change事件中同步):</p>
    <input type="text" v-model.lazy="text">
    <p>你輸入的文本內(nèi)容是:{{text}}</p>
    <p>.number(輸入文本內(nèi)容為數(shù)字):</p>
    <input type="number" v-model.number="number">
    <p>輸入的數(shù)字是:{{number}}</p>
    <p>.trim(去除輸入框兩端的空格):</p>
    <input type="text" v-model.trim="trimText">
    <p>顯示輸入的內(nèi)容:{{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 出現(xiàn)
到最后的時候fade-enter-to消失、fade-enter-active消失

離開動畫原理

剛開始存在fade-leave和fade-leave-active
緊接第二幀的時候鳍刷,fade-leave消失占遥、fade-leave-to 出現(xiàn)
到最后的時候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變?yōu)? 這個過程一直在fade-enter-active 監(jiān)聽1秒時間
隱藏操作
剛開始fade-leave opacity 默認是1 第二幀 fade-leave消失 fade-leave-to出現(xiàn) opacity 變?yōu)? 這個過程一直在fade-leave-active監(jiān)聽1秒時間后消失

第7章 路由

7-1.什么是路由

路由:就是我們通過不同的 URL 訪問不同的內(nèi)容倾剿。

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蚌成,要調(diào)用Vue.use(VueRouter)

    //1. 定義路由組件
    //可以從其他文件 import 進來
    const Footer = { template: '<div>footer</div>' };
    const Header = { template: '<div>Header</div>' };

    //2. 定義路由
    //每個路由應該映射一個組件。其中”compoment“ 可以是:
    //通過Vue.extend() 創(chuàng)建的組件構(gòu)造器
    //或者凛捏,只是一個組件對象
    const routes = [
      { path: '/header', component: Header },
      { path: '/footer', component: Footer }
    ]

    //3. 創(chuàng)建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 表示路由鏈接
當被點擊后坯癣,內(nèi)部會立即把to的值傳到router.push,所以這個值可以是一個字符串或者是描述目標位置的對象

<!-- 字符串 -->
<router-link to="home">Home</router-link>
<!-- 渲染結(jié)果 -->
<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>

<!-- 帶查詢參數(shù),下面的結(jié)果為 /register?plan=private -->
<router-link :to="{ path: 'register', query: { plan: 'private' }}">Register</router-link>

2)示罗、replace
設置 replace 屬性的話惩猫,當點擊時,會調(diào)用 router.replace() 而不是 router.push()蚜点,導航后不會留下 history 記錄轧房。

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

3)、tag
有時候想要 <router-link> 渲染成某種標簽绍绘,例如 <li>奶镶。 于是我們使用 tag prop 類指定何種標簽,同樣它還是會監(jiān)聽點擊陪拘,觸發(fā)導航厂镇。

<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
聲明可以用來觸發(fā)導航的事件茵臭∫叻蹋可以是一個字符串或是一個包含字符串的數(shù)組。

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

以上代碼設置了 event 為 mouseover 旦委,及在鼠標移動到 Router Link 1 上時導航的 HTML 內(nèi)容會發(fā)生改變奇徒。
7)、exact-active-class 和 active-class 的區(qū)別
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

根據(jù)自己電腦是什么系統(tǒng)去安裝: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來訪問我們的網(wǎng)站呢
http://localhost:8080
http://127.0.0.1:8080
http://自己電腦ip:8080 //如果手機和電腦在同一個網(wǎng)段查辩,手機可以聯(lián)調(diào)項目,查看手機效果

第一種方法修改: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

區(qū)別:
修改package.json 之后的結(jié)果(和之前沒有什么區(qū)別):
Your application is running here: http://localhost:8080
修改config/index.js文件 之后的運行結(jié)果:
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文件下面
創(chuàng)建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
 * 因网持,邊框是由偽元素區(qū)域遮蓋在父級
 * 故长踊,子級若有交互功舀,需要對子級設置
 * 定位 及 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 語法:支持嵌套阱佛,變量引用帖汞,省去冒號和花括號等等優(yōu)點
<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是只對當前頁面生效,但是在開發(fā)過程中凑术,我們可能需要引入第三方的樣式翩蘸,有時候為了滿足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 項目如何給長目錄定義變量

在開發(fā)過程中我們經(jīng)常要引入外部文件鹿鳖,比如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'),//配置圖片變量路徑
    }
  },

//頁面調(diào)用
<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項目開發(fā)之首頁

首頁制作完的整體效果:


效果圖

如果你想看首頁整體開發(fā)效果绣版,直接運行master分支即可:
https://github.com/fx35792/vue-travel

如果想看每部分的開發(fā)效果:

9-1 Vue項目開發(fā)首頁之header

在開發(fā)這塊的時候胶台,主要是一個布局的設置,還有就是icon圖標杂抽,這個icon圖標用的是阿里iconfont:https://www.iconfont.cn/
1.創(chuàng)建一個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" >‘’

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

如果你是線上項目铸磅,為了保險起見,推薦使用方法一
但是如果你平時自己做項目練習杭朱,你使用方法二就行

9-2 Vue項目開發(fā)首頁之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.具體代碼調(diào)用

//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//是否循環(huán)自動播放
      }
    }
  },
  computed: {
    swiperShow: function () {
      return this.list.length//當有數(shù)據(jù)的時候弧械,再去渲染swiper八酒,不然顯示的第一條數(shù)據(jù)是最后一條
    }
  }
}
</script>
9-3 Vue項目開發(fā)首頁之”熱門推薦“和”周末去哪“

這一小節(jié)主要是UI上布局制作,以及data里面模擬一些數(shù)據(jù)刃唐,渲染到頁面上羞迷。具體細節(jié)看
https://github.com/fx35792/vue-travel/tree/index-recommond
看代碼基本上都可以看明白的界轩,在這咱們就不詳細的贅述了

9-4 Vue項目開發(fā)首頁之a(chǎn)jax數(shù)據(jù)請求

我們通過ajax來實現(xiàn)接口請求的數(shù)據(jù),但是呢衔瓮?在開發(fā)過程中耸棒,很多時候都是我們前端自己mock數(shù)據(jù),通過代理报辱,最后映射到頁面上數(shù)據(jù)的与殃,隨后等服務器接口開發(fā)好了,我們在把mock數(shù)據(jù)地址替換為服務器地址即可碍现。
1.那么如何設置代理呢幅疼?
其實在vue-cli的腳手架中,已經(jīng)辦咱們配置好了設置昼接,只需要你自己配置一下即可:

//在config文件下面的index.js文件中:
proxyTable: {
      '/api': {
        target:'http://localhost:8080',//因為數(shù)據(jù)在我項目本地爽篷,所以我配置的localhost,如果是服務器你配置后端伙伴發(fā)給你的服務器地址即可
        pathRewrite: {
          '^/api':'/static/mock'  //當你接口請求中,以`/api`開頭的時候慢睡,會幫我們代理到我們本地的/static/mock目錄下面數(shù)據(jù)文件
        }
      }
    },

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項目開發(fā)首頁之父子組件之間的傳值

在制作的首頁過程中泪喊,我們將首頁拆分成了五部分,分別是:
header髓涯、banner輪播袒啼、icon輪播、熱門推薦纬纪、周末去哪
那么蚓再,為了減少http接口請求,后端小伙伴會把五部門的內(nèi)容都放到一個接口中去包各,在咱們本地模擬數(shù)據(jù)中我是放到了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倉庫看源碼就很容易明白。
第一步:接口請求拿到數(shù)據(jù)(axios.get('/api/index.json').then(this.getHomeInfoSucc)//第一步)
第二步:在data中我們初始化這五部分數(shù)據(jù)(swiperList: [],.//第二步)
第三步:把接口拿到的數(shù)據(jù)依次放入到data初始化的值中( this.swiperList = data.swiperList//第三步)
第四步:在Home.vue父組件中定義一個屬性按声,來給Swiper.vue子組件傳值(:list="swiperList")
第五步:在Swiper.vue子組件中接受父組件傳來的值(props: {
list: Array //第五步
})
第六步:子組件渲染出來父組件傳遞過來的數(shù)據(jù)(<swiper-slide v-for="item of list" :key="item.id">)

第10章 Vue項目開發(fā)之城市

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部分制作

上面咱們已經(jīng)完成了頭部的制作膳犹,這一節(jié)咱們來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 到不同的分支族阅,就能看到代碼了篓跛,總結(jié)嘛,不能能把所有的項目中的代碼都展示出來坦刀,更多的是展示難點愧沟、思路、注意事項等等一些小細節(jié)地方鲤遥。
city-ajax部分和index-ajax 方式是一樣沐寺,在這里咱們就不再次贅述了

知識點1:BetterScroll 的使用,讓城市列表可以滾動起來

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

在這個使用better-scroll的時候我們需要注意三點

  • dom結(jié)構(gòu)(要符合這種結(jié)構(gòu))
<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中的調(diào)用和使用方法
//dom部分
<div class="list" ref="wrapper">
.....
</div>

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

知識點2兄弟組件數(shù)據(jù)傳遞
我們知道:
City.vue是父組件
components/List.vue是一個子組件
components/Alphabet.vue也是一個子組件

那么子組件(Alphabet.vue)如何和子組件(List.vue)進行通信呢?
現(xiàn)在有這樣的一個需求盖奈,就是當我們點擊右側(cè)的字母(代碼在Alphabet.vue中)混坞,列表(List.vue)能自動滾動相對應的列表字母模塊部分,那么這個過程就是一個子組件和子組件的通信(兄弟組件數(shù)據(jù)傳遞)

思路:
第一步:子組件(Alphabet.vue)點擊字母的時候钢坦,通過$emit發(fā)送一個'change'的方法究孕,并且把攜帶的點擊入?yún)鬟f給父組(City.vue)

//dom部分
<li
      class="item"
      v-for="item of letters"
      :key="item"
      @click="handleLetterClick" //觸發(fā)點擊事件
    >{{item}}</li>

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

第二步:父組件(City.vue)通過屬性來監(jiān)聽‘change’事件,同時創(chuàng)建一個新的方法爹凹,在此方法中來接受子組件傳遞過來的參數(shù)厨诸,隨后把入?yún)⒎湃氲絛ata初始化的letter中,再然后逛万,把letter獲得入?yún)⒁詫傩缘姆绞絺鬟f給city-list組件

//1)dom 來監(jiān)聽子組件發(fā)出來的change
<city-alphabet :cities="cities" @change="handleLetterClick"></city-alphabet>

//4)dom 父組件從子組件那拿來的數(shù)據(jù)(letter)傳遞給新的子組件
<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>

//2)初始化data中的letter值 用來存儲子組件出來的入?yún)?data () {
    return {
      letter: ''
    }
},

//3)js change 創(chuàng)建的方法 來接受子組件傳遞過來的值泳猬,并把它存儲到data里面
handleLetterClick (letter) {
      this.letter = letter
}

第三步:子組件(List.vue)通過屬性props來接受父組件傳過來的值

//js
props: {
    letter: String//接受父組件傳遞過來的值
},

//js 監(jiān)聽傳過來值的變化
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"http://這個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 完成一個手指滑動右側(cè)字母,左側(cè)區(qū)域跟著滾動
這部分咱們需要給右側(cè)的字母綁定上三個事件:

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

為了只讓在touchmove里面去觸發(fā)這些操作宇植,所以我們需要定義個開關(標示位),我們把這個標示位放在了data里面

touchStatus: false //設置為false

所以當我們開始滑動的時候我們把touchStatus設置為true

handleTouchStart () {
  this.touchStatus = true
}

當我們手指劃出觸發(fā)操作區(qū)域的時候,我們需要把標示為設置為false

handleTouchEnd () {
  this.touchStatus = false
}

所以只有當標示位為true的這種情況埋心,我們采取進滑動字母相對應的操作

handleTouchMove () {
  if (this.touchStatus) {
  //滑動過程中所對應的邏輯
  }
}

思路
在滑動這個過程中指郁,最終我們在這個頁面上下滑動的時候,我們需要知道你滑動的位置是第幾個字母
1拷呆、我們需要知道A字母距離頂部的距離
2闲坎、我們需要知道手指滑動到當前字母距離頂部的的距離
3、把上面兩個做一個差值茬斧,那么我們就可以得到當前位置距離A字母之間的高度
4腰懂、我們把得到這個差值高度除以每個字母的高度,那么我們就得到了是第幾個字母了
根據(jù)上面這個思路项秉,我們需要得到這個字母的數(shù)組:

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

通過計算屬性绣溜,我們就可以把dom上的數(shù)據(jù)獲取從父組件傳遞過來的cities改為letters

<li
  class="item"
  v-for="item of letters" //通過計算屬性來獲得字母值
  :key="item"
  :ref="item"
  @click="handleLetterClick"
  @touchstart="handleTouchStart"
  @touchmove="handleTouchMove"
  @touchend="handleTouchEnd"
>
{{item}}
</li>

根據(jù)上面的思路咱們開始來編寫相對應邏輯

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]) //在有效的索引里面去 查找是第幾個字母
     }
    }
}

其實寫到這塊我們的功能是完成了的,但是細想還有一些地方需要優(yōu)化?
初始化

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

優(yōu)化一:每次都去求獲取字母A距離頂部的距離娄蔼?

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

優(yōu)化二:滑動字母的時候怖喻,需要做一下事件節(jié)流(通過一個定時器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 實現(xiàn)一個城市搜索功能
需求
1.根據(jù)字母或者漢字可以進行檢索想要的內(nèi)容
2.當搜索框沒數(shù)據(jù)的時候底哗,不顯示搜索區(qū)域內(nèi)容
3.當搜索框有數(shù)據(jù)且數(shù)據(jù)不在搜索內(nèi)容時,顯示暫無搜索內(nèi)容
4.當搜索出來的內(nèi)容比較多的時候锚沸,搜索內(nèi)容可以進行滾動(better-scroll)

第一步:獲取從父組件傳遞過來的cities值

props: {
    cities: Object
},

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

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

第三步:watch方法監(jiān)聽keyword的更改哗蜈、其中這里面包含timer優(yōu)化前标、list數(shù)據(jù)獲取、檢索操作的邏輯

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)
  }
},

第四步:數(shù)據(jù)處理好了距潘,要鋪到Ui上面
為了可以滾動:一定要符合better-scroll的dom結(jié)構(gòu)候生;search-content樣式要脫離文檔流。
只有當有關鍵字才會顯示搜索內(nèi)容绽昼;
當關鍵字搜索沒有數(shù)據(jù)的時候唯鸭,顯示”沒有搜索到匹配內(nèi)容“

<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">沒有搜索到匹配內(nèi)容</li>
  </ul>
</div>

第五步:搜索數(shù)據(jù)有了,但是過多的時候也要可以滾動硅确,better-scroll

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

知識點5 vuex實現(xiàn)數(shù)據(jù)共享
如果學過react的同學肯定知道redux目溉,react是處理ui層的,那么數(shù)據(jù)層就是通過redux來完成菱农,方便我們不同頁面之間的傳值缭付,一直值的更改等等
同樣在vue中,vue也只負責ui部分循未,vuex則是用來處理數(shù)據(jù)層的

vuex的原理圖

1.安裝vuex

npm install vuex -S

2.使用和調(diào)用vuex
因為vuex是處理數(shù)據(jù)模塊的陷猫,所以我們在src目錄下創(chuàng)建一個store目錄,在store目錄下面創(chuàng)建一個
index.js

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

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

創(chuàng)建好之后的妖,我們在main.js文件中去調(diào)用這個文件

import store from './store'

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

3.應用
在咱們這個項目中绣檬,首頁右上角的城市名稱是通過后端返給我們,那么我們可以通過vuex來初始化一個城市嫂粟,也可以通過vuex來更改城市這個值娇未。
在store/index.js 其實我們已經(jīng)做了city的初始化的值:北京
那么在首頁和城市頁面我們?nèi)绾潍@取vuex當中這個值呢?

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

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

點擊熱門城市或者點擊城市搜索出來列表切換城市的顯示星虹,那么我們?nèi)ト绾胃膕tate這個值呢零抬?

//點擊熱門城市事件
@click="handleCityClick(item.name)"

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

上面我們已經(jīng)觸發(fā)了一個dispatch的方法,那么我們通過actions來接受這個方法
store/index.js

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

從上面可以看出在我們發(fā)送dispatch的時候平夜,并沒有觸發(fā)異步請求,或者批量的數(shù)據(jù)操作卸亮,所以上面操作忽妒,我們可以直接跳過actions這部分,不需要去觸發(fā)dispatch操作,而是直接調(diào)用commit對mutations的操作
所以上面的代碼就可以改為:

//點擊熱門城市事件
@click="handleCityClick(item.name)"

methods: {
  handleCityClick (city) {
      //要調(diào)用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
    }
  }
})

講到這里其實就實現(xiàn)了vuex的數(shù)據(jù)一個設置以及顯示的一些操作锰扶,但是我們更具當前的產(chǎn)品需求我們還是需要完善一下頁面跳轉(zhuǎn)献酗。
之前我們實現(xiàn)頁面跳轉(zhuǎn)是通過
1.router-link 的to屬性來實現(xiàn)
2.那么還有一種通過js 來實現(xiàn)頁面跳轉(zhuǎn)的$router.push
那么我們希望我們在選擇完城市后,能自動跳轉(zhuǎn)到首頁坷牛,那么

this.$router.push('/')

知識點6 vuex的高級使用以及l(fā)ocalStorage

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) {}
    }
  }
})

寫到這里我們發(fā)現(xiàn)犯祠,將來如果我們業(yè)務比較復雜的話,store/index.js會變的越來越龐大酌呆,那么這不是我們希望看到的衡载,所以我們要對store/index.js進行拆分。
那么如何進行拆分呢隙袁?
store/index.js 只是一個總文件痰娱,而這個總文件包含很多部分:state、actions菩收、mutations等等梨睁,
那么我們將可以將這些模塊拆分成為:state.js、actions.js坛梁、mutations.js
最后再把他們引入到store/index.js文件中
那么而姐,根據(jù)這個思路咱們接下來拆分一下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 就變?yōu)榱耍?/p>

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的高級應用以及針對項目當中的優(yōu)化

我們上面調(diào)用城市的時候是通過{{this.$store.state.city}}來實現(xiàn)的
如果這么寫的話,略顯頁面比較冗余划咐。那么有沒有其他方法會比較簡單一些呢?
vuex幫我們封裝了一些方法和aip有一個mapState的方法就可以幫我們實現(xiàn)钧萍,那么應該如何使用呢褐缠?

import { mapState } from 'vuex'

//第一種通過數(shù)組方法獲取
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的數(shù)據(jù)我們可以通過mapState方法风瘦,那么設置vuex數(shù)據(jù)呢队魏?
我們可以通過vuex給我們提供的mapMutations方法,那么如何實現(xiàn)呢?

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官網(wǎng)昧谊,我們發(fā)現(xiàn)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
      }
  }
})

那么頁面上應該如何使用或者調(diào)用呢?

import { mapGetters } from 'vuex'

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

//頁面上調(diào)用
{{this.doubleCity}}

那么我們此時會想呢诬,這有什么用處呢涌哲?因為mapState就可以實現(xiàn)的方法,我為什么還要使用mapGetters來實現(xiàn)呢尚镰?
其實呢阀圾,我們發(fā)現(xiàn)getters方法有點類似vue組件當中的computed方法,他可以把我們state值進行處理后返給我們一個新值狗唉,從來來避免一些數(shù)據(jù)的冗余初烘。
getter講完了,那么module我們在什么情況下去使用呢分俯?
因為我們在store/index.js中 只寫了city相關的(state寓调、actions、mutations)等等操作籽暇,當我們在實際開發(fā)的過程中澈段,我們肯定不單單只有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 的狀態(tài)
store.state.b // -> moduleB 的狀態(tài)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市充坑,隨后出現(xiàn)的幾起案子减江,更是在濱河造成了極大的恐慌,老刑警劉巖捻爷,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辈灼,死亡現(xiàn)場離奇詭異,居然都是意外死亡也榄,警方通過查閱死者的電腦和手機巡莹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人降宅,你說我怎么就攤上這事骂远。” “怎么了腰根?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵激才,是天一觀的道長。 經(jīng)常有香客問我唠雕,道長贸营,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任岩睁,我火速辦了婚禮钞脂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捕儒。我一直安慰自己冰啃,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布刘莹。 她就那樣靜靜地躺著阎毅,像睡著了一般。 火紅的嫁衣襯著肌膚如雪点弯。 梳的紋絲不亂的頭發(fā)上扇调,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音抢肛,去河邊找鬼狼钮。 笑死,一個胖子當著我的面吹牛捡絮,可吹牛的內(nèi)容都是我干的熬芜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼福稳,長吁一口氣:“原來是場噩夢啊……” “哼涎拉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起的圆,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鼓拧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后越妈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體毁枯,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年叮称,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡瓤檐,死狀恐怖赂韵,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挠蛉,我是刑警寧澤祭示,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站谴古,受9級特大地震影響质涛,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜掰担,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一汇陆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧带饱,春花似錦毡代、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至执庐,卻和暖如春酪耕,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背轨淌。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工迂烁, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人猿诸。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓婚被,卻偏偏與公主長得像,于是被迫代替她去往敵國和親梳虽。 傳聞我的和親對象是個殘疾皇子址芯,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 前言 您將在本文當中了解到,往網(wǎng)頁中添加數(shù)據(jù),從傳統(tǒng)的dom操作過渡到數(shù)據(jù)層操作,實現(xiàn)同一個目標,兩種不同的方式....
    itclanCoder閱讀 25,803評論 1 12
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容,還有我對于 Vue 1.0 印象不深的內(nèi)容窜觉。關于...
    云之外閱讀 5,050評論 0 29
  • 構(gòu)造器 每一個Vue.js的應用都是通過夠構(gòu)造函數(shù)Vue創(chuàng)建的一個Vue的根實例谷炸。例如: varvm =newVu...
    楓葉笛花閱讀 443評論 0 0
  • 構(gòu)造器 每一個Vue.js的應用都是通過夠構(gòu)造函數(shù)Vue創(chuàng)建的一個Vue的根實例。例如: 上述代碼中在實例化vue...
    61ae56c48fc6閱讀 5,444評論 2 4
  • 主要還是自己看的禀挫,所有內(nèi)容來自官方文檔旬陡。 介紹 Vue.js 是什么 Vue (讀音 /vju?/,類似于 vie...
    Leonzai閱讀 3,353評論 0 25