vue

266 阅读33分钟

Vue

vue原理

 <div id="box">111</div>
    <script>
        let box = document.getElementById('box')
        var obj = {};
        Object.defineProperty(obj, 'name', {
            get() {

            },
            set(data) {  //当用obj.name = xxx的时候,就会拿到要修改的数据
                console.log('set调用', data)
                box.innerHTML = data;
            }
        })
        //用obj.name = '页面变了' ; 用这种方法修改数据,页面就会变更
        /*
            vue 数据驱动原理
            1、数据通过Object.defineProperty 进行get,set拦截
			2、通知watcher,(观察者模式,订阅发布模式),触发组件重新渲染,创建行的虚拟dom(js对象模拟dom对比旧的虚拟dom,找到不同的地方,以最小的代价更新节点)
        */
    </script>

hello word

<!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">
    <title>Document</title>
</head>

<body>
    <div id="app">{{content}}</div>  //{{}}模版语法
    <script src="../vue.js"></script>
    <script>
        let app = new Vue({   
            el: '#app',  //指定要渲染的元素
            data: {
                content: 'hello word'
            }
        })
        
    </script>
</body>

</html>

指令

v-for:循环

  <li v-for="item in list">{{item}}</li>

v-on: 事件绑定,冒号后面是跟要绑定的事件,例如:

点击事件: v-on:click

 <button v-on:click="handleBtnClick">提交</button>

v-on可以缩写

用@表示v-on

<button @click="handleBtnClick">提交</button>

v-on修饰符:

事件类:

  <button @click.stop="handlClick">阻止事件冒泡</button>
  <button @click.prevent="handlEventClick">阻止默认事件</button>

键盘按钮:

//键盘抬起事件
  <input @keyup="handlClick">两种都有</input>

v-model:双向数据绑定

v-model绑定的值如果发生了变化,那么data中对应的数据也会发生变化,数据一旦发生变化,视图也会跟着改变

<div id="app">
        <input type="text" v-model="inputValue" /> //绑定了inputValue
        <button v-on:click="handleBtnClick">提交</button>
        <ul>
            <li v-for="item in list">{{item}}</li>
        </ul>
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                inputValue: '',
                list: ['小红', '小燕']
            },
            methods: {
                handleBtnClick() {
                    this.list.push(this.inputValue)
                    this.inputValue = ''
                }
            }
        })

    </script>

v-model的原理:

  <div id="app" v-cloak>
        <input type="text" :value="msg" @input="handlChange"> {{msg}}
        //input事件可以监听到input框change的事件
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: "自己实现数据的双向绑定"

            },
            methods: {
                handlChange(e) {
                    console.log(e.target.value)
                    this.msg = e.target.value
                }
            }
        })
    </script>

v-model控制表单控件:

radio:

 <div id="app">
        <form>
            <label for="male">
            <input type="radio" id="male"  value="男" v-model="sex">
            男
        </label>
            <label for="famale">
            <input type="radio" id="famale"  value="女" v-model="sex">
            女
        </label>
        </form>
        <h2>{{"选择的性别是:"+sex}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                sex: '男'
            }

        })
    </script>

checkbox:

单选:

 <div id="app">
        <form>
            <label for="agree">
            <input type="checkbox" id="agree"  value="男" v-model="isAgree">
            同意协议
        </label>
            <button :disabled="!isAgree">下一步</button>
        </form>
        <h2>{{"选择的是:"+isAgree}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                isAgree: false
            }

        })
    </script>

多选:

 <div id="app">
        <input type="checkbox" v-model="hobbies" value="足球">足球
        <input type="checkbox" v-model="hobbies" value="蓝球">蓝球
        <input type="checkbox" v-model="hobbies" value="乒乓球">乒乓球
        <input type="checkbox" v-model="hobbies" value="羽毛球">羽毛球

        <h2>{{"选择的是:"+hobbies}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                hobbies: []
            }

        })
    </script>

select标签:

  <div id="app">
        <!-- 单选 -->
        <select name="shuiguo" v-model="fruit">
        <option value="榴莲">榴莲</option>
        <option value="香蕉">香蕉</option>
        <option value="苹果">苹果</option>
        <option value="葡萄">葡萄</option>
       </select>

        <h2>{{"选择的是:"+fruit}}</h2>

        <!-- 多选 -->
        <select name="shuiguo" v-model="fruits" multiple>
                <option value="榴莲">榴莲</option>
                <option value="香蕉">香蕉</option>
                <option value="苹果">苹果</option>
                <option value="葡萄">葡萄</option>
               </select>

        <h2>{{"选择的是:"+fruits}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                fruit: '榴莲',
                fruits: []
            }

        })
    </script>

v-model的值绑定:

  <div id="app">
        <label v-for="item in originHobbies" :for="item"> //根据数据动态渲染值
            <input type="checkbox" :id="item" :value="item" v-model="hobbies">
            {{item}}
           </label>
        <h2>{{"选择的是:"+hobbies}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                hobbies: [],
                originHobbies: ["足球", "蓝球", "乒乓球", "羽毛球", "高尔夫球"]
            }

        })
    </script>

v-model的修饰符

lazy修饰符

不会实时加载双向绑定的数据,只会在Input框失去焦点或者回车的时候才会更新

 <div id="app">
        <input type="text" v-model.lazy="msg">
        <h2>{{msg}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '失去焦点的时候才会更新'
            }

        })
    </script>

number修饰符

  <div id="app">
        <input type="number" v-model.number="msg">
        <h2>{{typeof msg}}</h2>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: 0
            }

        })
    </script>

trim修饰符

去除左右两边的空格

 <div id="app">
     
        <div>
            <input type="text" v-model.tirm="name">
            <h2>{{name}}</h2>
        </div>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
              
                name: ''
            }

        })
    </script>

v-bind:

父组件向子组件传递值

  <todo-item v-for="item in list" v-bind:content="item"></todo-item>  //子组件可以用props:['content]来接收这个值

完整用法:

<!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">
    <title>Document</title>
    <script src="../vue.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="inputValue" />
        <button v-on:click="handleBtnClick">提交</button>
        <ul>
            <todo-item v-for="item in list" v-bind:content="item"></todo-item>
        </ul>
    </div>
    <script>
        Vue.component("TodoItem", {  //用Vue.component创建一个全局的组件
            props: ['content'], //接收传递过来的值
            template: '<li>{{content}}</li>' //模版语法
        })

        let app = new Vue({
            el: '#app',
            data: {
                inputValue: '',
                list: ['小红', '小燕']
            },
            methods: {
                handleBtnClick() {
                    this.list.push(this.inputValue)
                    this.inputValue = ''
                }
            }
        })

    </script>
</body>

</html>

v-bind的简写

可以写成:

 <todo-item v-for="item in list" :content="item"></todo-item>

v-text:模版语法

和{{}}插值表达式结果是一样的

 <div id="app">
       <h1 v-text="message"></h1>
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data:{
                message: 'hello word'
            },
           
        })

    </script>

v-html

v-text是会将内容中的标签转义,v-html则不会转义,而是以标签的形式渲染到页面中

{{}}插值表达式和v-text是一模一样的

  <div id="app">
       <div v-text="message"></div>
       <div v-html='name'></div>
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data:{
                message: '<h1>我是text</h1>',
                name: '<h1>我是html</h1>',
            },
           
        })

    </script>

v-once

只会渲染一次,改变数据之后也不会渲染了

 <div id="app">
       <div v-once>{{message}}</div>
     
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data:{
                message: '我的法克'
            
            },
           
        })

    </script>

v-pre

转义{{}}插值表达式,让插值表达式失效,输出原本的内容

 <div id="app">
        <div v-pre>{{msg}}</div>  //页面会原封不动的显示{{msg}}
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '小红'
            },


        })
    </script>

v-cloak

当加载代码比较慢的时候,页面的插值表达式会被展示出来 ,用户将会看到:{{msg}},如果不希望用户看到这种样子,那么就需要给根组件v-cloak指令

用法:

 <div id="app" v-cloak>
        <div>{{msg}}</div>
    </div>
    <script>
        setTimeout(() => {
            let vm = new Vue({
                el: '#app',
                data: {
                    msg: '小红'
                },

            })
        }, 1000)
    </script>

v-for

遍历数组:

 <div id="app" v-cloak>
        <ul>
            <li v-for="(item,index) in arr">{{item}}</li> //item=>value index=>下标
        </ul>

    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                arr: ['小红', '小燕', '小蓝']
            }
        })
    </script>

遍历对象:

 <div id="app" v-cloak>
        <ul>
            <li v-for="(value,key) in info">{{value}} {{key}}</li> //第一个是value,第二个是key
        </ul>

    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                info: {
                    name: '小红',
                    age: 22

                }
            }
        })
    </script>

绑定方法

methods

事件绑定的函数或者其他的函数可以写在methods里面

 let app = new Vue({
            el: '#app',
            data: {
                inputValue: '',
                list: ['小红', '小燕']
            },
            methods: {  //事件写在里面
                handleBtnClick() {
                    alert('click')
                }
            }
        })

浏览器Event对象

<div id="app" v-cloak>
        <div>{{msg}}</div>
        <button @click="handlBtnClick('参数')">带参数</button> //调用的时候带了参数
        <button @click="handlEventClick">不带参数</button> //调用的时候不带参数

    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '小红'
            },
            methods: {
                handlBtnClick(name) {  //可以拿到传递的参数
                    console.log(name) //参数
                },
                handlEventClick(e) {//不带参数,默认得到的是浏览器的Event对象
                    console.log(e) //MouseEvent {}
                }
            }
        })
    </script>

即可传递自定义参数,又可以接收浏览器Event对象:

 <div id="app" v-cloak>
        <div>{{msg}}</div>
     
        <button @click="handl$EventClick('自己的参数',$event)">两种都有</button>
      //是用 $event关键字作为浏览器的Event对象实参,这样就可以接收到了

    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '小红'
            },
            methods: {
             
                handl$EventClick(txt, e) {//接收自定义的参数和event对象
                    console.log(txt, e)
                }

            }
        })
    </script>

局部组件注册

components

用法:

<!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">
    <title>Document</title>
    <script src="../vue.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="inputValue" />
        <button v-on:click="handleBtnClick">提交</button>
        <ul>
            <!-- <li v-for="item in list" >{{item}}</li> -->
            <todo-item v-for="item in list" v-bind:content="item"></todo-item>
        </ul>
    </div>
    <script>
        let TodoItem = {   //这是一个子组件
            props: ['content'],
            template: '<li>{{content}}</li>'
        }

        let app = new Vue({
            el: '#app',
            components: {   //使用componets将子组件注册进来,这样才能正常使用 
                TodoItem: TodoItem
            },
            data: {
                inputValue: '',
                list: ['小红', '小燕']
            },
            methods: {
                handleBtnClick() {
                    this.list.push(this.inputValue)
                    this.inputValue = ''
                }
            }
        })

    </script>
</body>

</html>

组件data必须是函数

1.组件对象也有一个data属性(也可以有methosd等属性)

因为每个组件都是自己有自己的数据,自己管理自己的数据

2.只是这个对象里的data属性必须是一个函数

每个组件data属性必须是一个函数,因为这是利用函数的闭包性质,保存这块堆内存

3.而且这个函数还要返回一个对象,对象内部保存着数据

这个函数要返回一个对象,是因为要开辟一块新的内存空间,这样才能保证每个组件的内存空间不同,这样就可以实现自己管理自己的数据

父子组件传值

父子组件之间的传值可以用v:bind和$emit共同完成

父组件:

 <todo-item 
                v-for="(item,index) in list"  
                v-bind:content="item"   //传值
                @delete="handleItemDelete" //监听子组件的事件,如果子组件触发了这个事件那么就会执行handleItemDelete这个方法
                v-bind:index="index">  //传值
            </todo-item>

子组件

 let TodoItem = {
            props: ['content', 'index'], //接收父组件传递过来的值
            template: "<li @click='handleItemClick'>{{content}}</li>", //绑定方法
            methods: {
                handleItemClick() {  
                    this.$emit('delete', this.index) //使用$emit触发delete方法,并带上参数,使用$emit触发的方法,使得父组件可以监听得到
                }
            }
        }

全部代码:

<!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">
    <title>Document</title>
    <script src="../vue.js"></script>
</head>

<body>
    <div id="app">
        <input type="text" v-model="inputValue" />
        <button v-on:click="handleBtnClick">提交</button>
        <ul>
            <todo-item 
                v-for="(item,index) in list" 
                v-bind:content="item" 
                @delete="handleItemDelete"
                v-bind:index="index">
            </todo-item>
        </ul>
    </div>
    <script>
        let TodoItem = {
            props: ['content', 'index'],
            template: "<li @click='handleItemClick'>{{content}}</li>",
            methods: {
                handleItemClick() {
                    this.$emit('delete', this.index)
                }
            }
        }

        let app = new Vue({
            el: '#app',
            components: {
                TodoItem: TodoItem
            },
            data: {
                inputValue: '',
                list: ['小红', '小燕']
            },
            methods: {
                handleBtnClick() {
                    this.list.push(this.inputValue)
                    this.inputValue = ''
                },
                handleItemDelete(index) {
                    this.list.splice(index, 1)
                }
            }
        })

    </script>
</body>

</html>

父组件通过标签的形式给子组件传值

<div id="app">
        <counter :count="3"></counter> //给子组件传了个3
        <counter :count="2"></counter> //给子组件传了个2
        
    </div>
    <script>
        Vue.component('counter', {
            props:['count'], //接收 
            template: '<div @click="handleChange">{{count}}</div>', //显示
            data() {
                return {
                  
                }
            },
            methods:{
                handleChange(){
                   
                }
            }
        })
        let vm = new Vue({
            el: '#app',
            data: {
                total: 0
            },
            methods:{
                handleTotal(){
                
                    this.total = this.$refs.one.number + this.$refs.two.number
                }
            }

        })

    </script>

父子组件的访问方式

父组件访问子组件:使用children 或者refs

<div id="app">
        <com></com>
        <button @click='handlClick'>按我</button>
    </div>
    <template id="com">
        <div>子组件</div>
    </template>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {


            },
            methods: {
                handlClick() {
                    console.log(this.$children)
                    this.$children[0].showMessage() //showMessage
                    console.log(this.$children[0].name) //我是子组件的name
                }
            },
            components: {
                com: {
                    template: "#com",
                    data() {
                        return {
                            name: '我是子组件的name'
                        }
                    },
                    methods: {
                        showMessage() {
                            console.log('showMessage')
                        }
                    },
                }
            }

        })
    </script>


子组件访问父组件:使用$parent

  <div id="app">
        <com></com>

    </div>
    <template id="com">
        <div>
            <div>子组件</div>
            <button @click='handlClick'>按我</button>
        </div>
    </template>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '我是父组件的信息'

            },
            methods: {

            },
            components: {
                com: {
                    template: "#com",
                    methods: {
                        handlClick() {
                            console.log(this.$parent)
                        }
                    },
                }
            }

        })
    </script>

访问根组件:$root

  handlClick() {
        console.log(this.$parent)
        console.log(this.$root.msg)
  }

生命周期函数

初始化阶段

beforeCreate: 初始化事件

created:初始化注入&校验

挂载阶段

beforeMount:DOM挂载之前会执行这个方法,就是说这时候DOM还没渲染在页面上

mounted:DOM挂载之后后执行这个方法 ,这时候DOM已经挂载在页面上了

更新阶段

beforeUpdate:数据更新之前会执行

updated:数据更新之后会执行

卸载阶段

beforeDestory:实例即将被销毁的时候执行

destroyed:实例被销毁之后会执行

总结:

 let app = new Vue({
            el: '#app',
            template:"<div>{{message}}</div>",
            data:{
                message: 'hello word'
            },
            beforeCreate() {
                //初始化事件
                console.log('beforeCreate')
            },
            created(){
                //初始化注入&校验
                console.log('created')
            },
            beforeMount(){
                //DOM挂载之前会执行这个方法,就是说这时候DOM还没渲染在页面上
                console.log(this.$el)
                console.log('beforeMount')
            },
            mounted(){
                //DOM挂载之后后执行这个方法 ,这时候DOM已经挂载在页面上了
                console.log(this.$el)
                console.log('mounted')
            },
            beforeUpdate(){
                //数据更新之前会执行
                console.log('beforeUpdate')
            },
            updated(){
                //数据更新之后会执行
                console.log('updated')
            },
            beforeDestory(){  
                //实例即将被销毁的时候执行
                console.log('beforeDestory')
            },
            destroyed(){
                //实例被销毁之后会执行
                console.log('destroyed')
            }

        })

计算属性

computed

computed是一个对象,里面放的是要用于计算的函数,并且每个函数都要有一个返回值

使用:

<div id="app">
        {{fullName}}  //返回的内容用插值表达式显示出来
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                firstName: 'Mike',
                lastName: 'jie'
            },
            computed: {  //计算属性
                fullName() {  //计算属性函数,要求有返回值
                    return this.firstName + ' ' + this.lastName
                }
            }
        })

    </script>

优点:计算属性有一个缓存的机制,就是要计算的属性改变了,它才会执行,如果页面重新渲染了,但是要计算的属性没有改变,那么它就不会执行,继续用上一次执行的结果渲染,只有要计算的属性改变了,才会重新执行。这样能提高性能

计算属性中的set和get

使用:

 <div id="app">
        {{fullName}}
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                firstName: 'Mike',
                lastName: 'jie'
            },
            computed: {
                fullName: {
                    get() {  //获取
                        return this.firstName + ' ' + this.lastName
                    },
                    set(value) {  //设置 
                        //可以拿到fullName设置的value
                        let arr = value.split(' ')  //以空格分割字符串,使其变为数组
                        this.firstName = arr[0]  //数组第一项赋值为firstName
                        this.lastName = arr[1]  //数组第二项赋值为lastName

                    }
                }
            }

        })
        vm.fullName = 'xiao hong'
    </script>

watch监听器

watch是一个对象,它可以监听data中的数据变化情况,如果数据变化了,可以执行相应的函数,做出相应的操作

watch也是有缓存机制的,和computed一样

使用:

<div id="app">
        {{fullName}}  
    </div>
    <script>
        let app = new Vue({
            el: '#app',
            data: {
                firstName: 'Mike',
                lastName: 'jie',
                fullName: 'Mike jie'
            },
            watch: {
                firstName() { //监听函数,函数名对应data中的数据名字,一旦数据变化,就会执行这个函数
                    this.fullName = this.firstName + ' ' + this.lastName
                },
                lastName() {
                    this.fullName = this.firstName + ' ' + this.lastName
                }
            }
        })

    </script>

动态样式

第一种:class的对象绑定

<div id="app">
       <div @click="handleDivClick" :class="{activated:isActivated}"> //动态绑定class,判断activated是否为true
           hello word
       </div>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                isActivated:false  //控制class中的activated是否显示
            },
            methods:{
                handleDivClick(){
                    this.isActivated = !this.isActivated  //切换取反
                }
            }

        })
       

第二种:class的数组绑定

<div id="app">
       <div @click="handleDivClick" :class="[activated,activatedOne]"> //绑定data中相对于的数据
           hello word
       </div>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                activated:'',
                activatedOne:'activated-one'
            },
            methods:{
                handleDivClick(){
                 this.activated = this.activated === ''? 'activated' : '' //切换
                }
            }

        })
       
    </script>

第三中:style内联样式

 <div id="app">
        <div @click="handleDivClick" :style="styleObj">  //双向绑定style,styleObj是一个对象
            hello word
        </div>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                styleObj: {  //这个对象里面有个color属性
                    color: 'black'
                }
            },
            methods: {
                handleDivClick() {
                    this.styleObj.color = this.styleObj.color === 'black' ? 'red' : 'black'   //切换styleObj的color属性
                }
            }

        })

    </script>

把:style变成一个数组也是可以的

 <div @click="handleDivClick" :style="[styleObj]"> 
            hello word
        </div>

条件渲染

v-if

  <div v-if="show"> 
            {{msg}}
        </div>

v-else

v-if和v-else一定要连在一起写

  <div v-if="show">
            {{msg}}
  </div>
  <div v-else>bye bye</div>

v-else-if

 <div v-if="show">
            {{msg}}
        </div>
        <div v-else-if="show === b">这是B</div>
        <div v-else>bye bye</div>

v-if,v-else-if,v-else,这三个指令是要连载一起写的,中间不能有任何标签分隔开来

v-show

  <div v-show="show">我系v-show</div> 

总结:

v-if和v-show都可以控制页面元素的出现与隐藏,但是它们有一个不同点,就是v-if隐藏的时候会移除DOM节点,就是把DOM下树,而v-show则是用display:none隐藏,它的DOM节点还是存在于DOM树上的,如果有一个组件频繁的显示与隐藏,那么使用v-show性能就可以提高很多

key

给标签加上key值,可以避免一些不必要的bug,vue的虚拟DOM中,如果一个标签没加上key值,那么在进行隐藏显示的时候,vue会尽量复用已存在的DOM,这样有时候会造成一种bug,例如复用input框的时候,会把input框中用户输入的内容也存留下来

所以,为了解决这个bug,可以使用key来标识这是一个唯一的input

<inpit key="username"/>

操作数组的变异方法

  1. pop(删掉数组中最后一项)
  2. push(往数组末尾增加一条)
  3. shift(删除数组第一项)
  4. unshift(往数组第一项增加一条)
  5. splice(数组截取)
  6. sort(数组排序)
  7. reveres(倒转数组)

改变数组的引用地址,也可以改变页面

<div id="app">
        <button @click="handleDivClick">按我</button>
        <div v-for="item in msg">
            {{item}}
        </div>

    </div>
    <script>
        [].reveres
        let vm = new Vue({
            el: '#app',
            data: {
                msg: ['小红','小燕','小蓝']
                
            },
            methods: {
                handleDivClick() {
                    this.msg = [...this.msg,'新来的'] //ES6语法,复制数组,然后加入新内容,同时改变引用地址,页面也会重新渲染
                }
            }

        })

    </script>

过滤器

<div id="app" v-cloak>
        {{prc | showPrice()}} // | 后面跟着的就是过滤器

    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                prc: 2800.89

            },
            filters: {  //过滤器要写在filters这个对象里面
                showPrice(prc) {
                    return '¥' + prc.toFixed(2)
                }
            }
        })
    </script>

模版占位符

<template>

如果想循环内容,但是又不想被div包裹,产生无用的标签,那么可以使用template包裹要循环的内容,这个template不会渲染成标签

  <template v-for="item in msg">
            <div>
                {{item}}
            </div>
            <div>顶你</div>
        </template>

循环一个对象

循环对象的第一个属性是 =>键值,第二个属性是 => 键名,第三个属性是下标

<div id="app">

        <template v-for="(item,key,index) of msg">
            <div>
                {{item}} ---- {{key}} --- {{index}}
                <!-- item =>键值 -->
                <!-- key =>键名 -->
                <!-- index =>下标 -->
            </div>

        </template>


    </div>
    <script>
       
        let vm = new Vue({
            el: '#app',
            data: {
                msg: {
                    name: '小红',
                    age: 28
                }
            }

        })

    </script>

为对象添加一个属性

和添加数组是一个道理,也是要改变引用地址,才会引起视图的更新

 <div id="app">

        <template v-for="(item,key,index) of msg">
            <div>
                {{item}} ---- {{key}} --- {{index}}
                <!-- item =>键值 -->
                <!-- key =>键名 -->
                <!-- index =>下标 -->
            </div>

        </template>
        <button @click='add'>按我</button>

    </div>
    <script>
       
        let vm = new Vue({
            el: '#app',
            data: {
                msg: {
                    name: '小红',
                    age: 28
                }
            },
            methods:{
                add(){
                    this.msg = {
                        ...this.msg,
                        word:'前端开发'
                    }
                }
            }

        })

    </script>

全局的set方法和实例的set方法

如果在外部改变data中的数据,可以使用全局的set方法或者使用实例的set方法

全局:

Vue.set(vm.msg,'word','web')

实例:

vm.$set(msg.msg,'word','php')

is

使用is属性解决模版标签出现的Bug问题

 <div id="app">
        <table>
            <tbody>   //tbody里面只能写tr标签,如果写其他标签会有Bug,为了解决这个问题用了is
                <tr is="row"></tr>  //is表示这个tr标签还是这个row组件
                <tr is="row"></tr>
                <tr is="row"></tr>
            </tbody>
        </table>

    </div>
    <script>
        Vue.component('row', {
            template: '<tr><td>this is row </td></tr>'
        })
        let vm = new Vue({
            el: '#app',


        })

    </script>

ref

需要操作DOM的时候,就要用到ref了

用法:

<div id="app">
       <div ref='hello' @click='getRef'>hello world</div>
			//关键代码
    </div>
    <script>
        Vue.component('row', {
            template: '<tr><td>this is row </td></tr>'
        })
        let vm = new Vue({
            el: '#app',
            methods:{
                getRef(){
                    console.log(this.$refs.hello.innerHTML)//关键代码
                    //用this.$refs.hello就可以获取这个DOM
                }
            }

        })

    </script>

例子:

 <div id="app">
        <counter ref='one' @add='handleTotal'></counter>
        <counter ref='two' @add='handleTotal'></counter>
        <div >{{total}}</div>
    </div>
    <script>
        Vue.component('counter', {
            template: '<div @click="handleChange">{{number}}</div>',
            data() {
                return {
                    number: 0
                }
            },
            methods:{
                handleChange(){
                   this.number++;
                   this.$emit('add') //监听事件
                }
            }
        })
        let vm = new Vue({
            el: '#app',
            data: {
                total: 0
            },
            methods:{
                handleTotal(){
                
                    this.total = this.$refs.one.number + this.$refs.two.number
                }
            }

        })

    </script>

组件参数校验

props:{}

<div id="app">
        <child content='hello world'></child> //父组件传值
    </div>
    <script>
        Vue.component('child', { 
            props: {   //接收。props是一个对象,他要求传递过啦的content是一个stirng类型,如果是其他类型的话就会报错
                content: String
            },
            template: '<div>{{content}}</div>',

        })
        let vm = new Vue({
            el: '#app'

        })

    </script>

有时候需要传递的数据要么是stirng,要么是number类型,那么就要这样写:

  props: {   //接收。props是一个对象,他要求传递过啦的content是一个stirng类型,如果是其他类型的话就会报错
                content: [String,Number]
            },

把数据变成必须要传递的数据,不传则会报错

 props: {
                content: {
                    type: String,  //类型
                    required: true //是否是必须要传递的,true是要传递,false是可传可不传
                }
            },

传递的数据可以有默认值

 props: {
                content: {
                    type: String,
                    required: true,
                    default:'默认值'  //如果没传递这个数据,那么就会使用这个默认值
                }
            },

使用校验器检查传递过来的内容:

 props: {
                content: {
                    type: String,
                    required: true,
                    default: '默认值',
                    validator: function (value) {  //校验器,value就是传递过来的值
                        //返回true则不会报错,
                        //返回false则会报错
                        //这里可以定义想校验的规则
                        return (value.length > 5)
                    }
                }
            },

如果默认值是对象(Object)或者是数组(Array)的话,那么default就必须是一个函数,里面要返回一个对象或者数组

 props: {
                content: {
                    type: Array,
                    required: true,
                    default(){   //默认值是数组,default一定要是函数,里面返回的是默认值
                    return []
					}
                }
            },

props特性和非props特性

props特性是指父组件传递了值,而子组件又在props接收了值,那么就可以直接使用传递过来的值,这叫props特性

非props特性是指父组件传递了值,但子组件并没有在props中接收,这样就使用不了这个值

props特性在DOM标签中不会显示出来

非props特性则会在DOM标签中显示出来

给组件绑定原生事件

给子组件绑定原生事件的时候,而不用通过子组件使用$emit触发

 <div id="app">
        <child @click.native="handleClick"></child>  //在@click后面加上.native就可以绑定原生事件,而不是自定义事件,如果不加native,那么就是自定义事件,需要子组件使用$emit触发
    </div>
    <script>
        Vue.component('child', {
            template: "<div>child</div>",
            
        })
        let vm = new Vue({
            el: '#app',
            methods: {
                handleClick() {
                    console.log('11')
                }
            }

        })

    </script>

总线机制(发布订阅模式、观察者模式、Bus)

<div id="app">
        <child content="小红"></child>
        <child content="小燕"></child>
    </div>
    <script>
        Vue.prototype.bus = new Vue() //把实例的地址挂载到Vue原型链的bus属性上
        Vue.component('child', {
            props: {
                content: String
            },
            data() {
                return {
                    selfContent: this.content
                }
            },
            template: "<div @click='handlClick'>{{selfContent}}</div>",
            methods:{
                handlClick(){
                    //实例的bus的地址指向实例,所以bus能使用$emit方法,用$emit触发一个change事件
                    //并且带上参数
                    this.bus.$emit('change',this.selfContent)
                }
            },
            mounted(){
                let self = this;
                //使用bus身上的$on方法监听change事件,一旦触发了change事件,就会执行这个方法
                //改变数据
                this.bus.$on('change',function(val){
                    self.selfContent = val
                })
            }

        })
        let vm = new Vue({
            el: '#app'


        })

    </script>

插槽

当子组件的内容有一部分是由父组件决定的,那么就可以是用插槽来解决这个问题

<slot>

使用:

<div id="app">
        <child>
            <h1>小红</h1>  //作为插槽的内容传递过去
        </child>
    
    </div>
    <script>
        Vue.component('child', {
          template:`
                <div>
                    <p>hello</p>
                    <slot></slot>  //slot接收显示内容
                </div>
          `

        })
        let vm = new Vue({
            el: '#app'


        })

    </script>

默认插槽内容:

 <div id="app">
        <child>
                //这里不传内容
        </child>
    
    </div>
    <script>
        Vue.component('child', {
          template:`
                <div>
                    <p>hello</p>   
                    <slot>默认内容</slot>  //如过不传递内容,将会使用默认的内容
                </div>
          `

        })
        let vm = new Vue({
            el: '#app'


        })

    </script>

具名插槽

一个组件中如果想使用两个插槽,那么就要使用具名插槽,如果不是用具名插槽,那么将会渲染两次

使用:

 <div id="app">
        <body-content>
            <div slot="header">header</div>  //具名插槽
            <div slot="footer">footer</div>
        </body-content>

    </div>
    <script>
        Vue.component('body-content', {
            template: `
                <div>
                    <slot name="header"></slot>  //具名插槽,name对应父组件的slot
                    <div>content</div>
                    <slot name="footer">默认值</slot> //具名插槽也会有默认值
                </div>
          `

        })
        let vm = new Vue({
            el: '#app'


        })

    </script>

Vue中的作用域插槽

父组件替换插槽的标签,但是内容由子组件来提供

使用:

<div id="app">
        <child>
            <template slot-scope="props">  //父组件通过template接收子组件传递过来的参数
                <h1>{{props.item}}</h1>  //给子组件传递插槽并显示子组件传递过来的参数
            </template>
        </child>

    </div>
    <script>
        Vue.component('child', {
            data() {
                return {
                    list: [1, 2, 3, 4]
                }
            },
            template: `
                <div>  //这里的意思就是子组件帮你遍历数据,然后返回每一个item数据,但是父组件要传递插槽
                    <slot v-for="item in list" :item="item"></slot>
                </div>
          `

        })
        let vm = new Vue({
            el: '#app'


        })

    </script>

动态组件

<component></component>

使用动态组件可以判断是否显示这个组件

使用:

<div id="app">
        <child>
            <component :is="type"></component>  //is绑定了data中的type ,type里面的数据一定要和子组件同名,这样才能成功显示
            <button @click='handleBtnClick'>按我切换</button>
        </child>

    </div>
    <script>
        Vue.component('child-one', {
            template: '<div>child-one</div>'

        })
        Vue.component('child-two', {
            template: '<div>child-two</div>'

        })
        let vm = new Vue({
            el: '#app',
            data: {
                type: 'child-one'   //type,要和子组件同名
            },
            methods: {
                handleBtnClick() {
                    this.type = this.type === 'child-one' ? 'child-two' : 'child-one'
                }
            },

        })

    </script>

这样就可以使用动态组件的功能实现两个组件之间的切换

CSS动画

<transition>做出来的动画,是通过vue识别元素的某一时刻自动帮我们加上类名完成的

使用transition包裹的元素,如果没有写name属性,那么将会使用v-开头的类名作为默认值

 <style>
        .v-enter {                 //动画开始第一帧
            opacity: 0;
        }

        .v-enter-active {  //监听opacity改变
            transition: opacity 2s;
        }

        .v-leave-to {     //动画移除第一帧
            opacity: 0;
        }

        .v-leave-active {
            transition: opacity 2s;
        }
    </style>
</head>

<body>
    <div id="app">
        <transition>
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick() {
                    this.show = !this.show
                }
            }

        })

    </script>

为了避免冲突,给transition加上name,相应的class类名也要加上一样的类名

<style>
        .fate-enter {
            opacity: 0;
        }

        .fate-enter-active {
            transition: opacity 2s;
        }

        .fate-leave-to {
            opacity: 0;
        }

        .fate-leave-active {
            transition: opacity 2s;
        }
    </style>
</head>

<body>
    <div id="app">
        <transition name="fate">
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick() {
                    this.show = !this.show
                }
            }

        })

    </script>

使用animation执行动画

<style>
        @keyframes bounce-in {
            0% {
                transform: scale(0);
            }

            50% {
                transform: scale(1.5);
            }

            100% {
                transform: scale(1);
            }
        }

        .fate-enter-active {
            transform-origin: left center;
            animation: bounce-in 2s;
        }

        .fate-leave-active {
            transform-origin: left center;
            animation: bounce-in 2s reverse;
        }
    </style>
</head>

<body>
    <div id="app">
        <transition name="fate">
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick() {
                    this.show = !this.show
                }
            }

        })

    </script>

使用Animate.css完成动画

先引入animate.css

使用:

<div id="app">
        <transition 
            name="fate" 
            enter-active-class='animated swing'   //进入的时候加类名
            leave-active-class='animated swing'    //淡出的时候加类名
        >
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick() {
                    this.show = !this.show
                }
            }

        })

    </script>

当页面刷新的时候,也能让动画执行一次

 <transition 
            name="fate" 
            enter-active-class='animated swing'   
            leave-active-class='animated shake'  
             appear     //第一步,加上appeart
             appear-active-calss='animated swing'  //第二步
        >
            <div v-if="show">hello world</div>
        </transition>

使用自己写的动画加上animate.css的动画并解决时长的冲突问题

<style>
         .fate-enter,.fate-leave-to {
            opacity: 0;
        }

        .fate-enter-active ,.fate-leave-active{
            transition: opacity 2s;
        }

    </style>
</head>

<body>
    <div id="app">
            <transition 
            type="transition"  //因为两个动画都有时长,两个时长都不一样,而animate.css是使用animation这个CSS3做的动画效果,自己写的是用transition的这个CSS3做的效果,两个时长可能不一致,为了动画的一致性,所以写了一个type,告诉vue这个时长是以transition为主
                        
            name="fate" 
            enter-active-class='animated swing fate-enter-active'   
            leave-active-class='animated shake fate-leave-active'  
             appear     
             appear-active-calss='animated swing' 
        >
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick() {
                    this.show = !this.show
                }
            }

        })

    </script>

自定义vue动画时长

 <transition 
            :duration="5000" //使用duration这个属性定义时长
            name="fate" 
            enter-active-class='animated swing fate-enter-active'   
            leave-active-class='animated shake fate-leave-active'  
             appear     
             appear-active-calss='animated swing' 
        >
            <div v-if="show">hello world</div>
        </transition>
================================================================================>
也可以传递一个对象,设置入场和出场时间
 <transition 
            :duration="{enter:5000,leave:6000}"  //这是一个对象,设置了入场5s,出场6s
            name="fate" 
            enter-active-class='animated swing fate-enter-active'   
            leave-active-class='animated shake fate-leave-active'  
             appear     
             appear-active-calss='animated swing' 
        >
            <div v-if="show">hello world</div>
        </transition>

JS动画

js入场动画:

<div id="app">
        <transition 
            name="fate" 
            @before-enter="handleBeforeEnter"
            @enter="handleEnter" 
            @after-enter="handleAfterEnter">
            <div v-if="show">hello world</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick(){
                    this.show = !this.show
                },
                handleBeforeEnter(el) {
                    //动画执行之前,先把颜色变为红色
                    el.style.color = 'red'
                },
                handleEnter(el,done) {
                    //动画执行的时候
                    setTimeout(()=>{
                        el.style.color = 'green'
                    },2000)
                    // done是一个回调函数,需要调用它才可以执行下面的动画
                    setTimeout(()=>{
                        done()
                    },4000)
                },
                handleAfterEnter(el) {
                    el.style.color = 'black'
                }
            }

        })

    </script>

js出场动画

<transition 
            name="fate" 
            @before-enter="handleBeforeEnter"
            @enter="handleEnter" 
            @after-enter="handleAfterEnter">
            <div v-if="show">hello world</div>
        </transition>

velocity.js

使用:

 <script>
        let vm = new Vue({
            el: '#app',
            data: {
                show: true
            },
            methods: {
                hanldClick(){
                    this.show = !this.show
                },
                handleBeforeEnter(el) {
                    //动画执行之前,先把颜色变为红色
                    el.style.color = 'red'
                },
                handleEnter(el,done) {
                   Velocity(el,{    //关键代码
                       opacity:1
                   },{
                       duration:1000,
                       complete:done
                   })
                },
                handleAfterEnter(el) {
                    el.style.color = 'black'
                }
            }

        })

    </script>

多个元素或者组件的过渡

多元素

 <style>
        .fate-enter,
        .fate-leave-to {
            opacity: 0;
        }

        .fate-enter-active,
        .fate-leave-active {
            transition: opacity 1s;
        }
    </style>
    
     <div id="app">
        <transition 
            name="fate" 
            mode="in-out"  //关键代码
        >
            <div v-if="show" key="hello">hello world</div> //关键在于key,不能重复
            <div v-else key="bye">溜了溜了</div>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>

多组件

使用component进行切换

<style>
        .fate-enter,
        .fate-leave-to {
            opacity: 0;
        }

        .fate-enter-active,
        .fate-leave-active {
            transition: opacity 1s;
        }
    </style>
</head>

<body>
    <div id="app">
        <transition 
            name="fate" 
            mode="in-out"
        >
          <component :is="type"></component>
        </transition>
        <button @click='hanldClick'>按我</button>
    </div>
    <script>
        Vue.component('child',{
            template:'<div>child</div>'
        })
        Vue.component('child-one',{
            template:'<div>child-one</div>'
        })


        let vm = new Vue({
            el: '#app',
            data: {
                type: 'child'
            },
            methods: {
                hanldClick(){
                    this.type = this.type === 'child'? 'child-one' : 'child'
                }
               
            }

        })

    </script>

Vue中的列表过渡

transition-group

注意点:transition-group里面的元素都是要带上key值的,如果不带上key值,是会报错的

 <style>
        .fate-enter,
        .fate-leave-to {
            opacity: 0;
        }

        .fate-enter-active,
        .fate-leave-active {
            transition: opacity 1s;
        }
    </style>
</head>

<body>
    <div id="app">
        <transition-group name="fate"> //关键代码
            <div v-for="item in list" :key="item.id">
                {{item.title}}
            </div>
           
        </transition-group>
        <button @click='addList'>按我</button>
    </div>
    <script>
        let count = 0;

        let vm = new Vue({
            el: '#app',
            data: {
                list: []
            },
            methods: {
                addList() {
                    this.list.push({
                        id: count++,
                        title: 'hello world'
                    })
                }

            }

        })

    </script>

动画的封装

使用的时候调用子组件,并传入要做动画的元素和show就可以了

 <style>
        .v-enter,
        .v-leave-to {
            opacity: 0;
        }

        .v-enter-active,
        .v-leave-active {
            transition: opacity 1s;
        }
    </style>
</head>

<body>
    <div id="app">
        <fade :show="show">
            <div>hello 小红</div>
        </fade>
        <button @click='handlClick'>按我</button>
    </div>
    <script>
        Vue.component('fade', {
            props: ['show'],
            template: `
            <transition>
                <slot v-if="show"></slot>
            </transition>`
        })

        let vm = new Vue({
            el: '#app',
            data: {
                show: false
            },
            methods: {
                handlClick() {
                    this.show = !this.show
                }

            }

        })

    </script>

把CSS动画和组件抽离

</head>

<body>
    <div id="app">
        <fade :show="show">
            <div>hello 小红</div>
        </fade>
        <button @click='handlClick'>按我</button>
    </div>
    <script>
        //所有的动画都封装在了子组件中,解耦
        Vue.component('fade', {
            props: ['show'],
            template: `
            <transition 
            @before-enter="handleBeforeEnter"
            @enter="handleEnter"
            >
                <slot v-if="show"></slot>
            </transition>`,
            methods:{
                handleBeforeEnter(el){
                    el.style.color = 'red'
                },
                handleEnter(el,done){
                    setTimeout(()=>{
                        el.style.color = 'green'
                        done()
                    },2000)
                }
            }
        })

        let vm = new Vue({
            el: '#app',
            data: {
                show: false
            },
            methods: {
                handlClick() {
                    this.show = !this.show
                }

            }

        })

    </script>

项目工程化

第一步:安装vue-cli脚手架

yarn global add @vue/cli

第二步:vue create创建项目

vue create hello-world

第三步:进入创建好的文件夹,运行命令

yarn serve

这样就运行起项目了

在项目中加入vue-Router

安装:

yarn add vue-router

配置:

在src的Appp.vue文件夹中使用<router-view />标签

<router-view />这个标签可以根据页面的地址返回相应的组件

App.vue:

<template>
  <div id="app">
    <router-view /> 
  </div>
</template>

<script>


export default {
  name: 'app',
  
}
</script>

<style>

</style>


在src目录下创建一个pages文件夹,里面存放的是页面的文件

然后分别在pages文件夹下创建两个文件夹,一个home,一个list

home文件夹里面存放Home.vue:

<template>
    <div>
        我是home
    </div>
</template>


<script>
export default {
    name:'Home'  //创建完记得导出组件
}
</script>
<style lang="">
    
</style>

list文件夹也一样存放一个List.vue

<template>
    <div>
        List页面
    </div>
</template>
<script>
export default {
    name:'List'
}
</script>
<style lang="">
    
</style>

然后在src下创建一个router文件夹,在文件夹中创建一个Index.js文件

router->index.js:

import Vue from 'vue'   //引入vue
import Router from 'vue-router' //引入router
import Home from '@/pages/home/Home'  //引入两个组件
import List from '@/pages/list/List'


Vue.use(Router)  //调用Vue.use方法,把路由传进去当中间件

export default new Router({  //把路由实例暴露出去
    routes: [   //在这里配置相应的路由路径和要显示的组件
        {
            path: '/',
            name: 'Home',
            component: Home
        },
        {
            path: '/list',
            name: 'List',
            component: List
        }
    ]
})

最后一步要配置src目录下的mian.js文件:

import Vue from 'vue'
import App from './App.vue'
import router from './router'  //从router引入配置好的路由

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router     //挂载到vue实例中,这里key,value一致,省略value
}).$mount('#app')


这样就把路由配置好了,可以在网页中输入地址对应相应的组件

使用router-link进行页面跳转并携带参数

把router-link转化成li标签,使用tag

跳转使用:to,并且在跳转路径后面携带参数

 <ul>
      <router-link
        tag="li"   //渲染为li标签
        :to="'/detail/'+item.id" //要跳转的页面,并在后面加上携带的参数
        class="item border-bottom"
        v-for="item of recommendList"
        :key="item.id"
      >
        <img class="item-img" :src="item.imgUrl" alt />

        <div class="item-info">
          <p class="item-title">{{item.title}}</p>
          <p class="item-desc">{{item.desc}}</p>
          <button class="item-button">查看详情</button>
        </div>
      </router-link>
    </ul>

动态路由设置

路径后面携带了参数,要在路由里面也配置一下

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'


Vue.use(Router)

export default new Router({
    routes: [{
        path: '/',
        name: 'Home',
        component: Home
    },{
        path: '/detail/:id', //:id 表示接受动态路由
        name: 'detail',
        component: Detail
    }
]
})

接收动态参数:

methods: {
    getData() {
      console.log(this.$route.params.id); //这里可以拿到动态的id值
    }
  }

滚动行为

使用前端路由,当切换到新路由时,想要页面滚到顶部,或者是保持原先的滚动位置,就像重新加载页面那样。 vue-router 能做到,而且更好,它让你可以自定义路由切换时页面如何滚动。

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'


Vue.use(Router)

export default new Router({
    routes: [{
        path: '/',
        name: 'Home',
        component: Home
    }, {
        path: '/detail/:id',
        name: 'detail',
        component: Detail
    }],
    //关键代码=====================>
    scrollBehavior(to, from, savedPosition) {
        return { x: 0, y: 0 }
    }
})

项目初始化

如果要做移动化项目,先在public文件夹下的index.html修改meta标签

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

引入reset.css重置css

在assets文件夹下创建一个styles文件夹,里面创建一个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;
}html,
body,
div,
span,
applet,
object,
iframe,
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote,
pre,
a,
abbr,
acronym,
address,
big,
cite,
code,
del,
dfn,
em,
img,
ins,
kbd,
q,
s,
samp,
small,
strike,
strong,
sub,
sup,
tt,
var,
b,
u,
i,
center,
dl,
dt,
dd,
ol,
ul,
li,
fieldset,
form,
label,
legend,
table,
caption,
tbody,
tfoot,
thead,
tr,
th,
td,
article,
aside,
canvas,
details,
embed,
figure,
figcaption,
footer,
header,
hgroup,
menu,
nav,
output,
ruby,
section,
summary,
time,
mark,
audio,
video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}


/* HTML5 display-role reset for older browsers */

article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
menu,
nav,
section {
    display: block;
}

body {
    line-height: 1;
}

ol,
ul {
    list-style: none;
}

blockquote,
q {
    quotes: none;
}

blockquote:before,
blockquote:after,
q:before,
q:after {
    content: '';
    content: none;
}

table {
    border-collapse: collapse;
    border-spacing: 0;
}

然后在main.js中引入就可以生效了:

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

border.css文件

有一些手机写border:1px solid #ccc;会有显示问题,可以引入这个文件来解决,同reset.css一起引入就可以了

border.css:

@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);
    }
}

使用这个1px边框很简单:

<div class="border-bottom"></div>

只要在class加上一个border-xxx的类名就可使用了

解决移动端click事件延迟300毫秒的插件

fastclick可以解决这个问题

安装:

 yarn add fastclick

使用:

在mian.js中引入并使用

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles/reset.css'
import './assets/styles/border.css'
import fastClick from 'fastclick'  //引入fastClick
Vue.config.productionTip = false

fastClick.attach(document.body)  //绑定到body中,完成


new Vue({
    render: h => h(App),
    router
}).$mount('#app')



安装stylus方便写CSS

安装:

yarn add stylus
yarn add stylus-loader

使用:

<template>
    <div class="header">
        <div class="header-left">返回</div>
        <div class="header-input">输入城市/景点/游玩主题</div>
        <div class="header-right">城市</div>
    </div>
</template>


<script>
export default {
    name:'HomeHeader'
}
</script>
<style lang="stylus" scoped>  //在lang写入stylus,如果不想这里的样式影响到全局,那么就再加上scoped这个属性
    .header{
        line-height :.86rem;
        display :flex;
        background #00bcd4;
        color: #fff;
        .header-left{
            width:.64rem;
            float:left;
        }
        .header-input{
            flex:1;
            height : .64rem;
            margin-top:.12rem;
            margin-left: .2rem;
            background #fff;
            border-radius: .1rem;
            color :#ccc;
            line-height .64rem;
            height :.64rem;
        }
        .header-right{
            width :1.24rem;
            float:right
            text-align :center;
        }
    }
</style>

这样就可以使用stylus编译css了

使用stylus设置全局的CSS样式变量

第一步:在assets/styles文件夹中创建一个varibles.styl的文件,记得,后缀名是.styl

varibles.styl:

$bgColor = #00bcd4;    //定义这个变量为#00bcd4这个颜色

然后在使用的组件中引入:

HeaderHome.vue:

<style lang="stylus" scoped>
@import '../../../assets/styles/varibles.styl'  //引入
.header {
  line-height: 0.86rem;
  display: flex;
  background: $bgColor;  //使用这个变量
  color: #fff;

}
</style>

这样就可以使用变量的形式配置一些全局的样式了

引入的路径优化

使用@符号优化路径,@符号代表了src的根目录,所以,可以通过@符号来查找这些路径,不必通过../来查找

例如:

刚刚引入的CSS变量可以用@符号代替,因为它是CSS里面引入的,所有@符号前面要加上~@

@import '~@/assets/styles/varibles.styl'

vue轮播图插件

安装:

指定版本号安装

 yarn add vue-awesome-swiper@2.6.7

安装成功后要在mian.js引入才可以使用:

mian.js:

import VueAwesomeSwiper from 'vue-awesome-swiper'  //引入VueAwesomeSwiper
import 'swiper/dist/css/swiper.css'   //引入VueAwesomeSwiper的CSS

Vue.use(VueAwesomeSwiper)  //通过Vue.use配置一下

配置完成之后就可以开始使用了:

api参考:github.com/surmon-chin…

在component文件夹中创建一个Swiper.vue文件:

<template>
  <div class="wrapper">  //在最外层用div包裹住
    <swiper :options="swiperOption">  //用swipe包裹,里面是要轮播的每一项
      <swiper-slide v-for="item of swiperList" :key="item.id">
        <img
          class="swiper-img"
          :src="item.imgUrl"
          alt
        />
      </swiper-slide>
      <div class="swiper-pagination" slot="pagination"></div>  //轮播图的小圆圈
    </swiper>
  </div>
</template>
<script>
export default {
  name: "HomeSwiper",
  data() {
    return {
      swiperOption: {
          pagination:'.swiper-pagination', //配置使用轮播图的小圆圈
          loop:true   //配置使用无缝轮播
      },
      swiperList:[
          {id:'0001',imgUrl:'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/20193/b23a39921e8b78f38b61412d691d93ea.jpg_750x200_942ed7bd.jpg'},
          {id:'0002',imgUrl:'http://mp-piao-admincp.qunarzz.com/mp_piao_admin_mp_piao_admin/admin/201911/2369a7bacf810913c1ea839d03f9466f.jpg_750x200_b65db1d3.jpg'},

      ]
    };
  }
};
</script>
<style lang="stylus" scoped>
 .wrapper >>> .swiper-pagination-bullet-active{  //这段代码的意思是穿透了scoped的限制,可以改变
        background : #fff;						//全局的样式,如果没写 >>> 这个,就不能穿透
    }
.wrapper {
  width: 100%;
  height: 0;
  overflow: hidden;
 padding-bottom: 31.25%;
//   background #eee;
   
  .swiper-img {
    width: 100%;
  }
}
</style>


vue-awesome-swiper具体配置

vue-awesome-swiper是根据Swiper这个插件封装的Vue插件,所以可以根据Swiper官网里面的api来配置这个插件

地址:www.swiper.com.cn/

这是一个封装好的画廊插件,只要传递一个数组,数组里面是图片的路径就可以使用这个插件了

<template>
  <div class="container" @click="handlGallaryClick">
    <div class="wrapper">
      <swiper :options="swiperOption">
        <swiper-slide v-for="(item,index) of imgs" :key="index">
          <img
            class="gallary-img"
            :src="item"
            alt
          />
        </swiper-slide>
       
        <div class="swiper-pagination" slot="pagination"></div>
      </swiper>
    </div>
  </div>
</template>

<script>
export default {
  name: "CommonGallary",
  props: {
    imgs: {
      type: Array,
      default() {
        return [];
      }
    }
  },
  data() {
    return {
      swiperOption: {
        pagination: ".swiper-pagination",
        paginationType: "fraction",
        observeParents:true,
        observer:true
      }
    };
  },
  methods:{
      handlGallaryClick(){
          this.$emit('close')
      }
  }
};
</script>

<style lang='stylus' scoped>
.container >>> .swiper-container {
  overflow: inherit;
}

.container {
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: fixed;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  background: #000;
  z-index: 99;

  .wrapper {
    width: 100%;
    height: 0;
    padding-bottom: 100%;

    .gallary-img {
      width: 100%;
    }

    .swiper-pagination {
      color: #fff;
      bottom: -1rem;
      z-index: 999;
    }
  }
}
</style>

通过ajax获取数据

使用axios获取请求

yarn add axios

使用:

<template>
  <div>
    <home-header></home-header>
    <HomeSwiper></HomeSwiper>
    <HomeIcons></HomeIcons>
    <HomeRecommend></HomeRecommend>
    <HomeWeekend></HomeWeekend>
  </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
  },
  mounted() {
    this.getHomeInfo(); //在生命周期中调用 
   
  },
  methods: {
    getHomeInfo() {
     axios.get("/src/pages/home/Home.vue").then(res=>{  //发送请求
       console.log(res)
     })
    },
  
  }
};
</script>
<style lang="">
</style>

better-scroll 页面滚动插件

类似于微信小程序的scroll-view组件,可以在页面滚动

安装

npm install better-scroll -S //第一个版本
或者

npm install better-scroll@next -S //第二个版本
或者
yarn add better-scroll  //用yarn安装

使用:

<template>
  <div class="list" ref='wrapper'> //用ref获取dom
      <div>
          <div class="item">
              <ul>
                  <li>1</li>
                  <li>2</li>
                  <li>3</li>
                  <li>4</li>
                  <li>5</li>
                  <li>6</li>
                  <li>7</li>
                  <li>8</li>
                  <li>9</li>
                  <li>0</li>
              </ul>
          </div>
      </div>
  </div>
</template>

<script>
import BScroll from '@better-scroll/core';  //引入
export default {
    name:'Test',
    mounted(){
     this.scroll = new BScroll(this.$refs.wrapper)  //创建实例并把dom传递进去,这样就可以使用了
    }
}
</script>

<style>

</style>

scrollToElement:让选中的元素显示在顶部

 watch:{
	letter(){
    	if(this.letter){
            const element = this.$refs[this.letter][0] //refs获取dom元素
            this.scroll.scrollToElement(element) //调用这个方法,并把dom传进去,这样就会把选中的元素显示在顶部
    	}
	}
}

vuex

简单使用:

第一步,在scr路径下创建一个store文件夹,然后在里面创建一个index.js

store->index.js:

import Vue from 'vue'   //引入vue
import Vuex from 'vuex'  //引入vuex

Vue.use(Vuex) //因为vuex是一个插件,所以要经过vue.use转化



export default new Vuex.Store({   //把vuex.store的实例暴露出去,并且在里面创建一个state对象
    state: {
        city: '广州'
    }
})

第二步,在mian.js中引入store

mian.js:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import './assets/styles/reset.css'
import './assets/styles/border.css'
import fastClick from 'fastclick'
import './assets/styles/iconfont.css'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import 'swiper/dist/css/swiper.css'
import store from './store'   //引入store


Vue.config.productionTip = false

fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)

new Vue({
    render: h => h(App),
    router,
    store,  //放在根组件中
}).$mount('#app')

第三步,子组件使用store:

子组件:

  <div class="header-right">
      {{this.$store.state.city}}   //在子组件中,可以使用this.$store获取store中的数据了
      <span class="iconfont arrow-icon">&#xe64a;</span>
    </div>

还可以调用dispatch方法,改变store中的数据

methods:{
    handleCityClick(){
      
    let data =  this.$store.state.city === '北京'? '广州':'北京' 
      this.$store.dispatch('changeCity',data) 
        //派发一个一个名字为changeCity的action
    }
  }

第四步,store中处理dispatch传递过来的参数,改变store中的数据

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

Vue.use(Vuex)



export default new Vuex.Store({
    state: {
        city: '广州'
    },
    actions: {  //在actions这个对象里面处理dispatch派发的方法
        changeCity(ctx, city) { //捕获到这个方法
            console.log(city) //第二个参数是传递过来的数据
            ctx.commit('changeCity', city) //如果组件调用了changeCity,这个方法,那么这里就调用mutations下面的changeCity方法
        }
    },
    mutations: {  //在mutations改变store
        changeCity(state, city) { //执行这个方法的时候有两个参数,第一个是state对象,一个是传递过来的数据
            state.city = city
        }
    }
})

这样就可以使用Vuex通信了

如果中间不涉及到异步操作的话,那么也可以在子组件中直接使用commit调用mutations里面的方法来改变store

使用:

子组件

 methods:{
    handlClick(){
      this.$store.commit('changeValue','你已经改变了我')//这里不再是dispatch,而是commit
    }
  }

store中:

 mutations: {
        changeValue(state, data) {  //同样可以执行这个方法,改变数据
            state.value = data
        }
    }

使用localStorage配合vuex实现刷新数据不丢失

使用try..catch是为了让低版本的浏览器不报错

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

Vue.use(Vuex)

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

export default new Vuex.Store({
    state: {
        city: defaultCity || '广州',
    },
    actions: {
        changeCity(ctx, city) {
            console.log(city)

            ctx.commit('changeCity', city)
        }
    },
    mutations: {
        changeCity(state, city) {
            state.city = city
            try {
                localStorage.city = city
            } catch (e) {}

        }

    }
})

拆分store

以下三个文件分别在store文件夹下创建:

state.js

let defaultCity = '上海'
try {
    if (localStorage.city) {
        defaultCity = localStorage.city
    }
} catch (e) { throw new Error('fuck') }


export default {
    city: defaultCity,
}

actions.js

export default {
    changeCity(ctx, city) {
        console.log(city)

        ctx.commit('changeCity', city)
    }
}

mutations.js

export default {
    changeCity(state, city) {
        state.city = city
        try {
            localStorage.city = city
        } catch (e) {
            throw new Error('fuck')
        }

    }
}

然后在index.js中引入即可:

index.js

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



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

使用mapState更方便获取store中的数据

之前要取得store中的数据,一直是用 {{this.$store.state.city}}这种方式来取的,其实vuex已经提供了更方便的取数据方法

在子组件的computed中使用:

<script>
import { mapState } from "vuex";  //引入这个方法
export default {
  name: "HomeHeader",
  computed: {   //在computed中使用
    ...mapState({     //里面可以是一个对象,这个对象的意思是把store中的city映射给currentCity
      currentCity: "city"   //这样我们就可以在子组件中使用currentCity渲染内容了
    })
  }
};
</script>

使用mapMutations调用store中的方法

使用这个方法可以将mutations中的方法映射到子组件中,这样就可以不用调用dispatch或者commit这两个方法都可以改变store中的数据了

import { mapState ,mapMutations} from "vuex";  //引入mapMutations
export default {
  name: "HomeHeader",
  methods: {
    handleCityClick() {
      let data = this.$store.state.city === "北京" ? "广州" : "北京";
     this.changeCity(data) //这样就可以直接调用这个方法,改变store中的数据了
    },
    ...mapMutations(['changeCity'])  //在methods展开这个方法并把changeCity映射到methods中
  },
  computed: {
    ...mapState({
      currentCity: "city"
    })
  }
};
</script>

vuex中的Getter

vuex中的Getter类似组件中的计算属性,当我们需要根据state中的数据算出新的数据的时候,我们就可以借助Getter这个工具来提供一些新的数据

定义:

store->index.js:

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



export default new Vuex.Store({
    state,
    actions,
    mutations,
    getters: {   //定义getters
        doubleCity(state) {  //调用的时候,会返回两份state.city
            return state.city + ' ' + state.city
        }
    }
})

使用: 子组件

import { mapState ,mapMutations,mapGetters} from "vuex"; //引入
export default {
  name: "HomeHeader",
  methods: {
    handleCityClick() {
      let data = this.$store.state.city === "北京" ? "广州" : "北京";
     this.changeCity(data)
    },
    ...mapMutations(['changeCity'])
    
  },
  computed: {
    ...mapState({
      currentCity: "city"
    }),
    ...mapGetters(['doubleCity'])  //在计算属性中调用这个方法, 映射到组件中
  }
};
</script>

​ template

<div class="header-right">
      {{this.doubleCity}}
      <span class="iconfont arrow-icon">&#xe64a;</span>
    </div>

使用Module拆分合并store

可以根据每一个大组件来分配store,这样拆分之后,代码显得没那么臃肿,拆分之后可以用Module来进行合并

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 的状态

使用keep-alive标签优化性能

在App组件中使用,包裹住所有的组件:

<template>
  <div id="app">
    <keep-alive>
      <!-- 使用keep-alive标签,在进入页面的时候会把加载的数据存到内存中,在第二次进入的时候就可以不用重新加载了 -->
      <router-view />
    </keep-alive>
  </div>
</template>

使用keep-alive标签,在进入页面的时候会把加载的数据存到内存中,就是缓存起来,在第二次进入的时候就可以不用重新加载了,而是使用缓存中的数据了

如果有一些页面不想使用缓存的数据,那么就可以使用activated这个生命周期函数来重新获取数据

keep-alive 的 exclude属性

如果你不想页面被缓存,那么,可使用exclude这个属性来实现,exclude=“不想被缓存的组件” 这样就可以实现了

<div id="app">
    <keep-alive exclude="Detail">
      <router-view />
    </keep-alive>
  </div>

activated生命周期函数

这个生命周期函数只有在使用了keep-alive这个标签的时候才会出现

使用了keep-alive这个标签之后,你在切换页面的时候会发现,mounted这个生命周期函数只会执行一次,而不会重新执行,这样对于需要刷新数据的页面来说是有缺陷的

activated就是来解决这个问题的,组件来回切换,activated都会重新执行,那么在需要刷新的页面调用这个函数来发送ajax请求就可以了

mounted() {
    this.lastCity = this.city  //拿到上一次的city
 	this.getHomeInfo() //发送请求
  },
  activated(){
      if(this.lastCity !== this.city){  //如果上一次的数据和这一次的数据不同,那么发送请求,重新获取数据
          this.lastCity = this.city
          this.getHomeInfo() 
      }
  
  },

deactivated生命周期函数

如果在activated这个生命周期函数里绑定了全局的事件,那么就需要在deactivated这里来解绑了

使用:

<script>

export default {
    name:'Detail',
    methods:{
      handleScroll(){
        let top = document.documentElement.scrollTop;
        console.log(top)
      }
    },
  activated(){
      window.addEventListener('scroll',this.handleScroll)  //在这里给window绑定了全局的事件
  },
  deactivated(){
    window.removeEventListener('scroll',this.handleScroll)//所以要在这里解绑
  }
}
</script>

递归组件的使用

父组件:

<template>
  <div>
    <detail-banner></detail-banner>
    <detail-list :list="list"></detail-list>
  </div>
</template>

<script>
import DetailBanner from "./components/Banner";
import DetailList from "./components/List";
export default {
  name: "Detail",
  components: {
    DetailBanner,
    DetailList
  },
  data() {
    return {
      list: [
        {
          title: "成人票",
          children: [{ title: "三管联票" }, { title: "五管联票" }]
        },
        {
          title: "儿童票", //一级
          children: [    //二级
            { title: "三管联票" },
            { title: "五管联票", 
             children: [  //三级
                {title:'儿童六六六联票'}
            ] 
            }
          ]
        },
        { title: "特惠票" },
        { title: "活动票" }
      ]
    };
  }
};
</script>

<style>
</style>

子组件:

<template>
  <div>
    <div class="item" v-for="(item,index) of list" :key="index">
      {{item.title}}
      <div v-if="item.children">  //在每一个item有children的情况下,就调用自己,渲染自己,递归组件完成
        <detail-list :list='item.children'></detail-list>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: "DetailList",
  props: {
    list: Array
  }
};
</script>

<style>
</style>

封装一个动画组件

FadeAnimation.vue:

<template>
  <transition>
    <slot></slot>
  </transition>
</template>

<script>
export default {
  name: "FadeAnimation"
};
</script>

<style lang="stylus" scoped>
.v-enter, .v-leave-to {
  opacity: 0;
}

.v-enter-active, .v-leave-active {
    transition: opacity 2s
}
</style>

其他组件使用:

<fade-animation>
    <common-gallary :imgs="imgs" v-show="showGallary" @close="handlGallaryClose"></common-gallary>
</fade-animation>
//用动画组件包裹住就可以实现动画的功能了

解决浏览器不支持Promise的问题

使用一个叫 babel-polyfill的插件就可以解决这个问题

安装:

yarn add babel-polyfill

使用:在mian.js中引入即可

mian.js:

import 'babel-polyfill'

实现异步加载(按需加载)

在router文件夹下的index.js中实现:

没实现异步加载的写法:

import Vue from 'vue'
import Router from 'vue-router'
import Home from '@/pages/home/Home'
import Detail from '@/pages/detail/Detail'


Vue.use(Router)

export default new Router({
    routes: [{
        path: '/',
        name: 'Home',
        component: Home
    }, {
        path: '/detail/:id',
        name: 'detail',
        component: Detail
    }],
    scrollBehavior() {
        return { x: 0, y: 0 }
    }
})

异步加载的写法:

import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

export default new Router({
    routes: [{
        path: '/',
        name: 'Home',
        component: () =>
            import ('@/pages/home/Home')
    }, {
        path: '/detail/:id',
        name: 'detail',
        component: () =>
            import ('@/pages/detail/Detail')
    }],
    scrollBehavior() {
        return { x: 0, y: 0 }
    }
})

不仅仅可以再路由中进行异步加载,还可以在组件中进行异步加载

例如:

export default {
  name: "Home",
  components: {
    HomeHeader:()=>import('./components/Header'),  //在注册组件的时候同样可以进行异步加载
    HomeSwiper,
    HomeIcons,
    HomeRecommend,
    HomeWeekend
  },
  mounted() {
     console.log('mounted')
   
  },
  activated(){
    console.log('activated')
  },
  methods: {
    
  
  }
}