全书代码:https://github.com/icarusion/vue-book

chapter1初识Vue.js

  1. MVVM模式:全称model-view-viewmodel,Vue在设计上也遵循MVVM模式。View和ViewModel之间通过双向绑定建立联系,当视图View发生变化,视图模型ViewModel也会变化,反之亦然。

chapter2.数据绑定和第一个Vue应用

1.Vue实例
  1. 通过构造函数Vue就可以创建一个Vue实例。
let app = new Vue({
    // 选项
    
    // 1.el:指定一个页面中已经存在的DOM元素来挂载Vue实例
    // 挂载成功后可以通过app.$el来访问该元素
    el: '#app',
    
})
2.Vue的数据绑定
  1. 单向数据绑定:当页面上的数据发生改变,data对象里的对应的属性值不会发生变化。
<body>
    <div id="app">
        <input type="text" v-bind:value='name'></input>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data() {
                return {
                    name: 'test',
                }
            }
        })
    </script>
</body>
  1. 双向数据绑定
<body>
    <div id="app">
        <input type="text" v-model:value='name'></input>
        <h1>Hello,{{name}}</h1>
    </div>
    <script>
        const app = new Vue({
            el: '#app',
            data() {
                return {
                    name: '',
                }
            }
        })
    </script>
</body>
3.生命周期
  1. Vue的常用生命周期钩子如下:
    1. created:实例创建完成后调用,这个阶段完成了数据的观测等,但是尚未挂载。
    2. mounted:el挂载到实例上后调用。此时$el可以使用
    3. beforeDestroy:实例销毁之前调用
<div id="app">

</div>
<script>
    new Vue({
        el: '#app',
        data() {
            return {
                a: 100,
            }
        },
        created() {
            // 100, 此时data中的数据完成观测
            console.log(this.a)
            // el尚未挂载到实例上,undefined
            console.log(this.$el)
        },
        mounted() {
            // 打印div元素对象
            console.log(this.$el)
        },
    })
</script>
4.插值语法
  1. 插值语法使用双大括号。
<body>
    <!-- 实时显示当前的时间示例 -->
    <div id="app">
        <h1>今天时间是{{date}}</h1>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    date: new Date()
                }
            },
            mounted() {
                let that = this
                this.timer = setInterval(function() {
                    that.date = new Date()
                }, 1000)
                
            },
            beforeDestroy() {
                if (this.timer) {
                    // 清除定时器
                    clearInterval(this.timer)
                }
            },
        })
    </script>
</body>
5.过滤器
  1. 功能:在插值表达式中添加一个管道符|对数据进行过滤。
  2. 通过filters选项来设置过滤器
  3. 过滤器可以接受参数
  4. 过滤器可以串联
<body>
    <!-- 实时显示当前的时间示例 -->
    <div id="app">
        <h1>今天时间是{{date | formateDate}} </h1>
    </div>
    <script>
        let vueTemp
        new Vue({
            el: '#app',
            data() {
                return {
                    date: new Date()
                }
            },
            mounted() {
                let that = this
                this.timer = setInterval(function() {
                    that.date = new Date()
                }, 1000)

            },
            beforeDestroy() {
                if (this.timer) {
                    // 清除定时器
                    clearInterval(this.timer)
                }
            },
            beforeCreate() {
                vueTemp = this
            },
            filters: {
                formateDate(value) {
                    // 过滤器中的this对象是window对象
                    console.log("this:", this)
                    const date = new Date(value)
                    let year = date.getFullYear()
                    let month = vueTemp.format(date.getMonth())
                    let day = vueTemp.format(date.getDay())
                    let hour = vueTemp.format(date.getHours())
                    let minutes = vueTemp.format(date.getMinutes())
                    let seconds = vueTemp.format(date.getSeconds())
                    return year + '-' + month + '-' + day + ' ' + hour + ':' + minutes + ':' + seconds
                }
            },
            methods: {
                format(value) {
                    return value < 10 ? '0' + value : value;
                }
            },
        })
    </script>
</body>
6.指令
  1. 指令带有前缀v-
  2. Vue中内置了许多实用的指令
    1. v-bind指令:动态更新HTML元素上的属性
    2. v-on指令用来绑定事件监听器
    <body>
        <div id="app">
            <p v-if='show'>这是一段文本</p>
            <button v-on:click='show = !show'>点击按钮隐藏/显示文本</button>
            <p>show的值为{{show}}</p>
        </div>
        <script>
            new Vue({
                el: '#app',
                data() {
                    return {
                        show: true
                    }
                }
            })
        </script>
    </body>
    
    1. v-html:输出HTML,而不是纯文本
    <body>
        <div id="app">
            <span v-html='link'></span>
        </div>
        <script>
            new Vue({
                el: '#app',
                data() {
                    return {
                        link: '<a href="#">这是一个a标签</a>'
                    }
                },
            })
        </script>
    </body>
    
    1. v-pre:跳过元素和它的子元素的编译过程
    <body>
        <div id="app">
            <div v-pre>
                <span>{{这里的内容不会被编译}},将原样输出</span>
            </div>
        </div>
        <script>
            new Vue({
                el: '#app',
                data() {
                    return {
                    }
                },
            })
        </script>
    </body>
    
7.语法糖
  1. v-bind:可以简写成:
  2. v-on:可以简写成@

chapter3.计算属性和监视属性

1.计算属性
  1. 计算属性以函数的形式写在Vue实例内的computed选项内,最终返回计算的结果。
  2. 每一个计算属性都包含一个getter和一个setter。当读取计算属性的值时就会执行getter方法,当修改计算属性的值时就会执行setter方法。
<body>
    <div id="app">
        你好:<input type="text" v-model='fullName'></input>
        <h1>姓:{{firstName}}</h1>
        <h1>名:{{lastName}}</h1>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    firstName: '张',
                    lastName: '小云',
                }
            },
            computed: {
                fullName: {
                    get() {
                        return this.firstName + ',' + this.lastName
                    },
                    set(newValue, oldValue) {
                        nameArr = newValue.split(',')
                        this.firstName = nameArr[0]
                        this.lastName = nameArr[nameArr.length - 1]

                    }
                }
            }
        })
    </script>
</body>
  1. 计算属性可以缓存:通常使用计算属性实现的功能使用methods选项中的方法也可以实现。计算属性可以缓存,一个计算属性所依赖的数据发生变化时他才会重新执行get方法取值。所以只要依赖的数据不发生变化,计算属性就不会更新。
2.监视属性
  1. 监视属性:当被监视的属性发生变化时,回调函数自动被调用,进行相关操作。
  2. 监视属性的两种写法
    1. new Vue({})时传入watch选项
    2. 通过$watch方法监视
  3. watch监听的属性的回调函数有两个参数可以使用:第一个是新值,第二个是旧值。
watch: {
    isShow: {
        // 初始化时让handler调用一下
        immediate:true,
        // 当监视的isShow属性发生变化时调用handler
        handler(newValue, oldValue) {
            
        },
        // Vue中的watch默认不监视对象内部值的改变,只检测对象自身的改变
        // 配置deep:true就可以检测对象内部值的改变
        deep: true
    }
}

// 监视属性的简写形式
// 当只有handler一个配置项时可以简写
watch: {
    isShow(newValue, oldValue) {
        
    }
}

chapter4.v-bind及class与style的绑定

1.绑定class的几种方式
  1. 对象语法
<body>
    <div id="app">
        <!-- 渲染后的结果为<div class="active"></div> -->
        <div :class="{'active': isActive}"></div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    isActive: true,
                }
            }
        })
    </script>
</body>
  1. 数组语法
<body>
    <div id="app">
        <!-- 渲染后的结果为<div class="width-height bg-color"></div> -->
        <div :class="[wh, bgcolor]"></div>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    wh: 'width-height',
                    bgcolor: 'bg-color',
                }
            }
        })
    </script>
</body>
  1. 数组语法中使用对象语法
2.绑定内联样式
  1. 对象语法:注意CSS属性名使用驼峰命名或者短横分割命名
<body>
    <div id="app">
        <!-- <p style="font-size: 24px; color: red;">测试文本</p> -->
        <p :style="{'fontSize': fontSize + 'px', 'color': color}">测试文本</p>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    fontSize: 24,
                    color: 'red'
                }
            }
        })
    </script>
</body>

<!--上述示例不方便阅读和维护-->
<body>
    <div id="app">
        <!-- <p style="font-size: 24px; color: red;">测试文本</p> -->
        <p :style="styles">测试文本</p>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    styles: {
                        fontSize: 24 + 'px',
                        color: 'red'
                    }
                }
            }
        })
    </script>
</body>
  1. 数组语法

chapters5.内置指令

1.基本指令
  1. v-cloak:解决初始化慢导致页面闪动的问题,和display:none;一起使用。比如说网速较慢,在页面上显示{{xxx}},直到Vue创建实例、编译模板时DOM才会被替换,这个过程是有闪动的。
<style>
    [v-cloak] {
        display: none;
    }
</style>
<body>
    <div id="app">
        <!--意外情况不会显示{{message}}-->
        <h1 v-cloak>{{message}}</h1>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    message: 'Hello',
                }
            }
        })
    </script>
</body>
  1. v-once指令:定义v-once指令的元素或者组件只渲染一次,包括元素或者组件的所有子节点。首次渲染后,不再随数据的变化重新渲染,将被视为静态内容。
<body>
    <div id="app">
        <h1 v-once>{{message}}</h1>
        <!-- 点击按钮,message的值不会发生变化,因为h1元素不再重新渲染 -->
        <button @click="message = 'Hi'">点击改变message的值</button>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    message: 'Hello',
                }
            }
        })
    </script>
</body>
2.条件渲染指令
  1. v-if、v-else、v-else-if

<body>
    <div id="root">
        <template v-if="type === 'userName'">
            <label>用户名:</label>
            <!-- 增加key属性表示不复用input元素 -->
            <input placeholder="输入用户名" key="name-input"></input>
        </template>
        <template v-else="type === 'email'">
            <label>邮箱:</label>
            <input placeholder="输入邮箱" key="email-input"></input>
        </template>
        <button @click='changeType'>切换输入类型</button></button>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    type: 'userName',
                }
            },
            methods: {
                changeType() {
                    this.type = this.type === 'userName' ? 'email' : 'userName'
                }
            },
        })
    </script>
</body>
  1. v-show指令:改变元素的css属性display。
<body>
    <div id="root">
        <!--渲染后的结果如下-->
        <!-- <h1 style="display: none;">Hello,测试v-show</h1> -->
        <h1 v-show='isShow'>Hello,测试v-show</h1>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    isShow: false
                }
            }
        })
    </script>
</body>
  1. v-if和v-show的选择: v-if适用于条件不经常改变的场景,因为它切换开销相对大。v-show适用于频繁切换条件。
3.列表渲染指令v-for
  1. v-for和v-if一样,可以用在内置标签template上,对多个元素进行渲染。

<body>
    <div id="root">
        <ul>
            <template v-for="(book, index) in books">
                <li>索引:{{index}}</li>
                <li>书名:{{book.name}}</li>
                <li>作者:{{book.author}}</li>
            </template>
        </ul>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    books: [{
                        name: '唐生川籍',
                        author: '张三'
                    }, {
                        name: '武林秘籍',
                        author: '李四'
                    }, {
                        name: '大话西游',
                        author: '王五'
                    }]
                }
            }
        })
    </script>
</body>
  1. 对象的属性和数组都可以使用v-for遍历。
4.数组更新
  1. Vue包含了一组观察数组变化的方法,使用它们改变数组可以触发试图更新。
  2. 通过以下方式变动数组,Vue是检测不到的,也不会触发视图更新。
    1. 通过索引直接对数组中的元素赋值。解决方式为使用Vue内置的set方法
    2. 修改数组长度:xxx.length = xxxx。解决方式为使用数组的splice方法。
5.过滤和排序

可以使用计算属性返回过滤或者排序后的数组。


<body>
    <div id="root">
        <ul>
            <!-- 遍历书名中包含秘籍的书 -->
            <template v-for="(book, index) in filterBooks">
                <li>索引:{{index}}</li>
                <li>书名:{{book.name}}</li>
                <li>作者:{{book.author}}</li>
            </template>
        </ul>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    books: [{
                        name: '唐生秘籍',
                        author: '张三'
                    }, {
                        name: '武林秘籍',
                        author: '李四'
                    }, {
                        name: '大话西游',
                        author: '王五'
                    }, {
                        name: '魔域秘籍',
                        author: '赵六'
                    }]
                }
            },
            computed: {
                filterBooks() {
                    return this.books.filter(function(book) {
                        // return book.name.indexOf("秘籍") != -1
                        return book.name.match(/秘籍/)
                    })
                }
            }
        })
    </script>
</body>
6.事件与事件处理方法
  1. 通过@事件名给元素绑定事件,事件处理方法定义在methods选项中。
<body>
    <div id="root">
        <h1>按钮点击的次数:{{count}}</h1>
        <button @click="addOne()">不带参的事件处理方法</button>
        <!-- Vue中的特殊变量$event用于访问原生DOM事件 -->
        <button @click="addTen(10, $event)">带参的事件处理方法, 默认会将原生事件对象传入</button>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    count: 0
                }
            },
            methods: {
                addOne() {
                    this.count++
                },
                addTen(count, event) {
                    // PointEvent事件对象
                    console.log("event:", event)
                    this.count += count
                }
            },
        })
    </script>
</body>
  1. 事件修饰符:修饰符可以串联,一个事件上使用多个修饰符
    1. .stop:阻止事件冒泡
    2. .prevent:阻止事件的默认行为
    3. .capture:添加事件侦听器时使用事件捕获模式。改变事件捕获的方向
    4. .self:只当事件在该元素本身而不是子元素触发时才会触发事件回调
    5. .once:事件处理回调只触发一次,组件同样适用
    6. .native:表示监听地是原生DOM事件
    7. @keyup.13:按键修饰符,@keyup表示监听键盘事件,只要当按下的键的keyCode为13时才调用对应的事件回调方法。

chapters6.表单与v-model

1.基本用法
  1. Vue提供了v-model指令,用于在表单类元素上双向绑定数据。表单类元素例如单选、多选、下拉选择、输入框等。
  2. 单选按钮的组合使用:
<div id="root">
    <input type="radio" value="html-lang" id="html" v-model="pickedOption"></input>
    <!-- <label> 标签的 for 属性应当与相关元素的 id 属性相同。 -->
    <label for="html">HTML</label>
    <input type="radio" value="css-lang" id="css" v-model="pickedOption"></input>
    <label for="css">CSS</label>
    <input type="radio" value="js-lang" id="js" v-model="pickedOption"></input>
    <label for="css">JS</label>
    <h1>你的选择是:{{pickedOption}}</h1>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                pickedOption: ''
            }
        }
    })
</script>
  1. 复选框的组合使用
<div id="root">
    <input type="checkbox" value="html-lang" v-model:value='checkedOption' id="html"></input>
    <label for="html">HTML</label>
    <input type="checkbox" value="js-lang" v-model:value='checkedOption' id="js"></input>
    <label for="js">JS</label>
    <input type="checkbox" value="css-lang" v-model:value='checkedOption' id="css"></input>
    <label for="css">CSS</label>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                checkedOption: ['html-lang', 'css-lang']
            }
        }
    })
</script>
  1. 下拉框
    1. 单选
    <div id="root">
        <select v-model="selected">
            <option value="html">HTML</option>
            <option>CSS</option>
            <option>JS</option>
        </select>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    selected: 'CSS'
                }
            }
        })
    </script>
    
    1. 多选
    <div id="root">
        <select v-model="selected" multiple>
            <option value="html">HTML</option>
            <option>CSS</option>
            <option>JS</option>
        </select>
    </div>
    <script>
        new Vue({
            el: '#root',
            data() {
                return {
                    selected: ['CSS', 'JS']
                }
            }
        })
    </script>
    
2.绑定值
3.v-model的修饰符
  1. .lazy:让数据在失去焦点或者回车时才会更新。
<div id="root">
    <!-- 当输入框离开焦点或者按下回车,才会更新value -->
    <input type="text" v-model:value.lazy='value' @change='printValue'></input>
    <span>{{value}}</span>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                value: '',
            }
        },
        methods: {
            printValue() {
                console.log("value:", this.value)
            }
        },
    })
</script>
  1. .number:使用这个修饰符可以将输入转化为Number类型。
<div id="root">
    <input type="number" v-model='value1'></input>
    <!-- string -->
    <h1>{{typeof(value1)}}</h1>
    <input type="number" v-model.number='value2'></input>
    <!-- number -->
    <h1>{{typeof(value2)}}</h1>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                value1: '12',
                value2: '12'
            }
        },
    })
</script>
  1. .trim:自动过滤输入的首尾空格
<div id="root">
    <input type="text" v-model.trim='message' @change='printMess'></input>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                message: ''
            }
        },
        methods: {
            printMess() {
                // 输入'   1234',不过滤首尾空格的话原样输出
                console.log("mess:", this.message)
            }
        },
    })
</script>

chapters7.组件详解

1.组件的注册
  1. 全局注册
Vue.component("my-component", {
    template: "<div>子组件的内容</div>",
});
<!--在一个父组件中使用这个子组件-->
 <div id="app">
    <my-component></my-component>
</div>
  1. 局部注册:使用components选项局部注册组件
<div id="root">
        <my-component></my-component>
</div>
<script>
    let childComponent = {
        template: '<div>这是局部注册的子组件的内容</div>',
        // data、name、methods等选项
    }
    new Vue({
        el: '#root',
        components: {
            'my-component': childComponent,
        }
    })
</script>
2.使用props配置项传递数据
  1. props选项一般用于父组件向子组件传递数据,可作为组件间通信的一种方式之一。
  2. 数组写法:注意当在props配置项中属性名称使用驼峰命名,则在DOM模板中使用时,应该使用短横线分隔命名。
<div id="root">
    <!-- 父组件向子组件传递数据 -->
    <my-component :first-name='firstName' :last-name='lastName'></my-component>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                firstName: '张',
                lastName: '小三'
            }
        },
        components: {
            'my-component': {
                template: '<div>{{firstName}}-{{lastName}}</div>',
                // 接收从父组件中传递过来的数据
                props: ['firstName', 'lastName']
            },

        }
    })
</script>
  1. 对象写法:对象写法可以对props中的属性进行验证。
<div id="root">
    <my-component :first-name='firstName' :last-name='lastName'></my-component>
    <!-- 不传递则使用默认值 -->
    <my-component></my-component>
</div>
<script>
    new Vue({
        el: '#root',
        data() {
            return {
                firstName: '张',
                lastName: '小三'
            }
        },
        components: {
            'my-component': {
                template: '<div>{{firstName}}-{{lastName}}</div>',
                props: {
                    firstName: {
                        required: true,
                        default: '李',
                        type: String
                    },
                    lastName: {
                        required: true,
                        default: '二狗',
                        type: String
                    }
                }

            }
        }
    })
</script>
  1. props中接收数组类型的数据
3.单向数据流
  1. 父组件传递初始值给子组件,子组件的data中再声明一个数据将其保存起来,然后就可以再自己的作用域内随意使用和修改。
  2. props作为需要被转变的值传入,使用计算属性。这样在子组件内使用计算属性,不直接使用props。
4.组件间通信
  1. 子组件给父组件传递数据一般使用自定义事件。子组件使用$emit()来触发事件,父组件使用$on()来监听子组件上的事件。
    1. 实现方式1
    <!--子组件给父组件传递数据的案例-->
    
    <!--父组件中-->
    <template>
        <div id="app">
            <!--父组件中监听increase事件-->
            <my-component @increase="handleIncrease"/>
            <h1>total:{{total}}</h1>
        </div>
    </template>
    <script>
        import MyComponent from '@/views/MyComponent'
        export default {
            name: 'App',
            components: {
                MyComponent
            },
            data() {
                return {
                    total: 0
                }
            },
            methods: {
                // count即为子组件向父组件传递的数据
                handleIncrease(count) {
                    console.log("count:", count)
                    this.total = count
                }
            }
        }
    </script>
    
    <!--子组件中-->
    <template>
        <div>
            <button @click="increaseCount">增加10</button>
        </div>
    </template>
    <script>
    export default {
        name: 'MyComponent',
        data() {
            return {
                count: 0
            }
        },
        methods: {
            increaseCount() {
                this.count += 10
                // 子组件通过事件给父组件传递数据count
                this.$emit('increase', this.count)
            }
        }
    }
    </script>
    
    1. 上述案例可以使用v-model实现
    <template>
    <div id="app">
        <my-component v-model='total'/>
        <h1>total:{{total}}</h1>
    </div>
    </template>
    <script>
        import MyComponent from '@/views/MyComponent'
        export default {
            name: 'App',
            components: {
                MyComponent
            },
            data() {
                return {
                    total: 0
                }
            },
            methods: {
                // count即为子组件向父组件传递的数据
                handleIncrease(count) {
                    console.log("count:", count)
                    this.total = count
                }
            }
        }
    </script>
    
    
    <template>
    <div>
        <button @click="increaseCount">增加10</button>
    </div>
    </template>
    <script>
        export default {
            name: 'MyComponent',
            data() {
                return {
                    count: 0
                }
            },
            methods: {
                increaseCount() {
                    this.count += 10
                    // 子组件通过事件给父组件传递数据count
                    this.$emit('input', this.count)
                }
            }
        }
    </script>
    
    
  2. 通过中央事件总线实现任意组件间的通信,包括父子组件、兄弟组件、跨级组件。
    1. 安装全局事件总线
    <!--在main.js中安装-->
    new Vue({
        router,
        store,
        render: h => h(App),
        beforeCreate() {
            // 全局事件总线就是一个Vue实例
            Vue.prototype.$bus = this
        },
    
    }).$mount('#app')
    
    1. 提供数据使用this.$bus.$emit('事件名',数据)
    2. 接收数据:A组件想接收数据,则在A组件中给$bus绑定自定义事件,事件的回调留在A组件自身。
    this.$bus.$on('事件名', 回调)
    
    1. 事件的解绑:this.$off('事件名',回调),一般在beforeDestroy钩子中调用。
    2. 完整示例
    <template>
        <div id="app">
            <h1>来自另一个组件的内容:{{message}}</h1>
            <my-component/>
        </div>
    </template>
    <script>
    
    import MyComponent from '@/views/MyComponent'
    export default {
        name: "App",
        data() {
            return {
                message: "",
            };
        },
        components: {
            MyComponent,
        },
        mounted() {
            // 监听passMess事件,接收数据
            this.$bus.$on('passMess', (msg) => {
                this.message = msg
            })
        }
    };
    </script>
    
    <!--另一个组件中-->
    <template>
        <div>
            <button @click="handleClick">传递事件</button>
        </div>
    </template>
    <script>
    export default {
        name: 'MyComponent',
        methods: {
            handleClick() {
                // 发送数据
                this.$bus.$emit('passMess', '一个组件向另一个组件传递的数据')
            }
        }
    }
    </script>
    
  3. 通过父链可以实现祖辈、子孙组件间的通信。避免使用这种方式通信,因为父子组件间紧耦合。
    1. 通过this.$children访问子组件,并且可以通过递归来访问子组件下的子组件
    2. 通过this.$parent访问父组件,并且可以通过递归来访问父组件下的父组件
    // 例如在子组件中直接修改父组件中的数据message
    this.$parent.message = "来自子组件的数据"
    
  4. 通过子组件索引可以方便实现父子组件间通信
    1. 用属性ref为子组件指定一个索引名称
    2. 父组件中使用this.$refs来访问指定名称的子组件。
<!--父组件-->
<template>
    <div id="app">
        <button @click="handleRef">获取子组件中的数据</button>
        <h1>来自另一个组件的内容:{{message}}</h1>
        <my-component ref="child"/>
    </div>
</template>
<script>

import MyComponent from '@/views/MyComponent'
export default {
    name: "App",
    data() {
        return {
            message: "",
        };
    },
    components: {
        MyComponent,
    },
    methods: {
        handleRef() {
            this.message = this.$refs.child.message
        }
    }
    
};
</script>

<!--子组件-->
<template>
    <div>
        
    </div>
</template>
<script>
export default {
    name: 'MyComponent',
    data() {
        return {
            message: '子组件中的数据'
        }
    }
}
</script>
  1. 通过插槽可以实现父子组件间通信
5.使用slot(插槽)分发内容
  1. 作用域:父组件模板的内容是在父组件作用域内编译,子组件模板的内容是在子组件作用域内编译。
<!--message就是一个slot,他绑定的是父组件中的数据-->
<my-component>{{message}}</my-component>
  1. slot的用法
    1. 默认插槽(单个slot):在父组件模板里,插入在子组件标签内的所有内容将替代子组件的标签及它的内容。
    <!--父组件-->
    <template>
        <div id="app">
            <my-component>
                <p>这段文本将插入在子组件内</p>
                <p>替换掉子组件中的slot标签及它的内容</p>
            </my-component>
        </div>
    </template>
    <script>
    
    import MyComponent from '@/views/MyComponent'
    export default {
        name: "App",
        
        components: {
            MyComponent,
        },
    };
    </script>
    
    <!--子组件-->
    <template>
        <div>
            <slot>
                <p>如果父组件中没有插入内容,则默认出现这段文本</p>
            </slot>
        </div>
    </template>
    <script>
    export default {
        name: 'MyComponent',
        
    }
    </script>
    
    1. 具名插槽:给slot元素指定name属性
    <!--父组件-->
    <template>
        <div id="app">
            <my-component>
                <p slot="namedSlot">test1</p>
                <p>test2</p>
            </my-component>
        </div>
    </template>
    <script>
    
    import MyComponent from '@/views/MyComponent'
    export default {
        name: "App",
        
        components: {
            MyComponent,
        },
    };
    </script>
    
    <!--子组件-->
    <template>
        <div>
            <div class="box1">
                <!-- 具名插槽 -->
                <slot name="namedSlot"> </slot>
            </div>
            <div class="box2">
                <slot></slot>
            </div>
        </div>
    </template>
    <script>
    export default {
        name: "MyComponent",
    };
    </script>
    
    <!--渲染后的部分结果为:-->
    <div><div class="box1"><p>test1</p></div><div class="box2"><p>test2</p></div></div>
    
    1. 作用域插槽:使用一个可以复用的模板替换已经渲染的元素。可以用来父子组件间通信
    <!--子组件给父组件传递数据示例-->
    
    <!--父组件部分代码-->
     <my-component>
        <!-- template标签内可以通过临时变量props访问子组件插槽上的数据 -->
        <template scope="props">
            <p >{{props.msg}}</p>
        </template>
        
    </my-component>
    
    <!--子组件部分代码-->
    <template>
        <div>
            <div class="box1">
                <slot  :msg='msg' ></slot>
            </div>
            
        </div>
    </template>
    
  2. slot的访问:通过$slots访问
6.组件的高级用法
  1. 递归组件:给组件设置name选项,组件在他的模板内就可以递归调用他自己。
  2. 内联模板:组件的模板一般都是在template选项内定义的。内联模板的功能就是在使用组件时,给组件标签使用inline-template特性,组件就会把它的内容当作模板,而不会当作内容分发。
  3. 动态组件:component元素用来动态的挂载不同的组件,is属性选择要挂载的组件。
<!--动态改变currentView的值就可以动态挂载组件-->
<template>
    <div id="app">
        <component :is="currentView"></component>
        <button @click="changeToView('A')">切换到组件A</button>
        <button @click="changeToView('B')">切换到组件B</button>
        <button @click="changeToView('C')">切换到组件C</button>
    </div>
</template>
<script>


export default {
    name: "App",
    
    components: {
        componentsA: {
            template: '<div>组件A渲染显示</div>'
        },
        componentsB: {
            template: '<div>组件B渲染显示</div>'
        },
        componentsC: {
            template: '<div>组件C渲染显示</div>'
        },        
    },
    data() {
        return {
            currentView: 'componentsB',
        }
    },
    methods: {
        changeToView(viewName) {
            this.currentView = 'components' + viewName
        }
    }
};
</script>

  1. 异步组件
  2. $nextTick: 在下一次DOM更新结束后执行其指定的回调。
    1. 语法:this.$nextTick(回调函数)
    2. 当改变数据后,要基于更新后的新DOM进行某些操作时,要在nextTick所指定的回调函数中执行操作。
<template>
    <div id="app">
        <!-- 获取div的文本内容 -->
        <div id="div" v-if="showDiv">div的文本内容</div>
        <button @click="getContent">获取div的文本内容</button>
    </div>
</template>
<script>
export default {
    name: "App",
    data() {
        return {
            showDiv: false,
        };
    },
    methods: {
        getContent() {
            this.showDiv = true;
            // 等DOM更新完成再获取Div的文本
            this.$nextTick(function () {
                let text = document.getElementById("div").innerHTML;
                console.log(text);
            });
        },
    },
};
</script>
  1. 手动挂载实例
    1. 使用Vue.extend()方法和$mount()方法

chapters8. 自定义指令

  1. 自定义指令的注册方法
    1. 全局注册
    Vue.directive('指令名', {
        指令选项
    })
    
    1. 局部注册
    const app = new Vue({
        el: '#app',
        directives: {
            指令名: {
                // 指令选项
            }
        }
    })
    
  2. 自定义指令的选项:就是一些钩子函数
    1. bind:只调用一次,指令第一次绑定到元素时调用,用这个钩子函数可以定义一个在绑定时执行一次的初始化动作。
    2. inserted: 被绑定元素插入父节点时调用
    3. update:被绑定元素所在的模板更新时调用,而不论绑定值是否变化。通过比较更新前后的绑定值,可以忽略不必要的模板更新。
    4. componentUpdated:被绑定元素所在模板完成一次更新周期时调用。
    5. unbind:只调用一次,指令与元素解绑时调用。
  3. 钩子函数中常用的参数
    1. el:指令所绑定的元素,可以用来直接操作DOM
    2. binding:一个对象,包含以下属性。
      1. name:指令名,不包含v-前缀
      2. value:指令的绑定值
      3. oldValue:指令绑定的前一个值
      4. expression:指令绑定值的字符串形式
      5. arg:传给指令的参数。例如v-on:click,arg的值就是click
      6. modifiers:一个包含指令修饰符的对象
    3. vnode:Vue编译生成的虚拟节点
    4. oldVnode:上一个虚拟节点
  4. 示例:自定义指令v-focus,页面挂载完毕让input输入框自动获得焦点
<template>
    <div id="app">
        <input type="text" v-focus:once.prevent="1 + 1"></input>       
    </div>
</template> 
<script>
export default {
    name: "App",
    directives: {
        focus: {
            inserted(el, binding, vnode) {
                // 和指令绑定的input元素
                console.log("el:", el)
                // 指令名focus
                console.log("name:", binding.name)
                // 2,指令的绑定值
                console.log("value:", binding.value)
                // 1 + 1,指令绑定值的字符串形式
                console.log("expression:", binding.expression)
                // once,传给指令的参数
                console.log("arg:", binding.arg)
                // {prevent:true},一个包含指令修饰符的对象
                console.log("modifiers:", binding.modifiers)
                
                el.focus()
            }
        }
    }
};
</script>

chapters9.Render函数

vue1.x和vue2.x的区别是:vue2.x使用了虚拟DOM来更新DOM节点,提升渲染性能。

1.虚拟DOM
  1. 虚拟DOM是一个轻量级的JS对象,在状态发生变化时,虚拟DOM会进行Diff运算,来更新只需要被替换的DOM,而不是全部重绘。
  2. 虚拟DOM通过一种VNode类(虚拟节点)来表示的。每个DOM元素或者组件都对应一个VNode对象。
  3. 虚拟节点VNode的主要类型:
    1. TextVNode:文本节点
    2. ElementVNode:普通元素节点
    3. ComponentVNode:组件节点
    4. EmptyVNode:没有内容的注释节点
    5. CloneVNode:克隆节点,可以是以上任意类型的节点,唯一的区别就是isCloned属性为true
2.Render函数

Render函数通过createElement参数来创建虚拟DOM

new Vue({
    router,
    store,
    // h就是createElement参数
    render: (h) => return h(App),
}).$mount('#app')
3.createElement参数
  1. createElement构成了虚拟DOM的模板,他有三个参数:
    1. 一个HTML标签、组件选项、或者一个函数,String/Object/Function类型。这个参数必选
    2. 一个对应属性的数据对象,Object(对象)类型。这个参数可选
    3. 子节点,String或者Array类型。这个参数可选

chapters10.使用webpack

webpack是一个模块打包工具。

1.webpack基础配置
  1. 安装webpack
npm install webpack --save-dev
  1. 安装webpack-dev-server
npm install webpack-dev-server --save-dev
  1. webpack本质就是一个js文件,例如webpack.config.js

chapters11.插件

1.插件的注册
  1. 注册插件需要使用install方法,第一个参数是Vue构造器,第二个参数是一个可选的选项对象。
  2. 使用插件:Vue.use(插件名)
2.前端路由
  1. 前端路由:即由前端维护一个路由规则。其实现方式有两种:
    1. 利用url的hash:url中包含有#
    2. History模式:url中没有#,以/分割。这种模式需要服务端支持。
3.vue-router的基本用法
  1. 安装vue-router插件
npm install --save vue-router
  1. 加载插件:在main.js中
import Router from 'vue-router'
Vue.use(Router)
  1. 路由跳转:路由跳转有两种方式。
    1. 使用<router-link></router-link>标签,它会被渲染成一个标签。其常用的属性如下:
      1. 标签的to属性指定需要跳转的路径。
      2. tag属性:使用replace属性不会留下History记录,所以导航后不能使用后退键返回上一个页面。
    <router-link to="/test">test</router-link>
    
    1. 编程式跳转:使用router实例的方法
      1. this.$router.push()方法
      <template>
        <div>
            <button @click="changeView">点击按钮跳转到Test</button>
            <router-view/>
        </div>
        
      </template>
      <script>
      export default {
          name: 'App',
          methods: {
              changeView() {
                  this.$router.push('/test')
              }
          },
      }
      </script>
      
      1. this.$router.replace()方法:类似于router-link标签的replace功能,他不会向history添加新记录,而是替换掉当前的history。
      2. this.$router.go()方法
4.导航钩子的使用
  1. vue-router中的导航钩子beforeEach和afterEach,分别在路由即将改变前和改变后触发,设置网页的标题可以在beforeEach钩子中完成。
    1. 网页标题的修改
    // to:即将进入的目标的路由对象(一个route对象)
    // from:当前导航即将离开的路由对象
    // next:调用这个方法,才能进入下一个钩子
    router.beforeEach((to, from, next) => {
        window.document.title = to.meta.title
        // 放行
        next()
    })
    
    1. 某些页面需要校验是否登录,如果登录了就可以访问,否则跳转到登录页
    router.beforeEach((to, from, next) => {
        if (window.localStorage.getItem("token")) {
            // 放行
            next()
        } else {
            // 跳转到登录页
            next('/login')
        }
    })
    
5.状态管理和vuex
  1. 状态管理和使用场景:在实际业务中有跨组件共享数据的需求。vuex就是用来统一管理组件状态的,它定义了一系列规范来使用和操作数据。
  2. vuex的基本用法
    1. 安装vuex:npm install --save vuex
    2. 加载插件:
    import Vuex from 'vuex'
    Vue.use(Vuex)
    
    1. 数据保存在vuex的state属性中,任意组件内可以通过$store.state.xxx来获取。
    2. 在组件内,来自store的数据只能读取,不能手动改变,改变store中数据的唯一方式就是显示的提交mutations。
    <template>
        <div>
            <h1>count的值为:{{count}}</h1>
            <button @click="addCount">加1</button>
            <button @click="decreaseCount">减1</button>
            <button @click="changeCount">改变count的值</button>        
        </div>
    </template>
    <script>
        export default {
            name: 'Test',
            data() {
                return {
                    message: 'Test'
                }
            },
            methods: {
                addCount() {
                    // 执行store下mutations中的方法
                    this.$store.commit('increment')
                },
                decreaseCount() {
                    this.$store.commit('decrement')
                },
                changeCount() {
                    this.$store.commit({
                        type: 'changeCount',
                        operator: '-',
                        count: 10
                    })
                }
            },
            computed: {
                count(){
                    return this.$store.state.count
                }
            }
        }
    </script>
    
    <!--store-->
    export default createStore({
        state: {
            count: 0
        },
        getters: {},
        mutations: {
            increment(state) {
                console.log("state:", state)
                state.count++
            },
            decrement(state) {
                state.count--
            },
            changeCount(state, params) {
                if (params.operator === '+') {
                    state.count += params.count
                } else if (params.operator === '-') {
                    state.count -= params.count
                }
            }
        },
        actions: {},
        modules: {}
    })
    
    1. getters:当state中的数据需要经过加工后再使用时,可以使用getters加工
    <!--store-->
    state: {
        list: [1, 3, 6, 4, 8, 0]
    },
    getters: {
        filterList: (state) => {
            return state.list.filter((item) => {
                return item < 6
            })
        }
    },
    <!--其他组件中-->
    computed: {
        
        list() {
            return this.$store.getters.filterList
        }
    }
    
    1. actions:提交mutation,并且可以异步操作业务逻辑。 actions在组件内通过$store.dispatch触发。
    <!--store配置-->
    import { createStore } from 'vuex'
    
    export default createStore({
        state: {
            count: 0,
        },
        getters: {
        },
        mutations: {
            increment(state) {
                console.log("state:", state)
                state.count++
            },
        },
        actions: {
            asncIncrease(context) {
                return new Promise((resolve) => {
                    setTimeout(() => {
                        context.commit('increment')
                        resolve()
                    }, 1000)
                })
            }
        },
        modules: {}
    })
    
    <!--组件中-->
    <template>
        <div>
            <h1>count的值为:{{count}}</h1>
            <button @click="asncIncrease">加1</button>
        </div>
    </template>
    <script>
        export default {
            name: 'Test',
            computed: {
                count() {
                    return this.$store.state.count
                }
            },
            methods: {
                asncIncrease() {
                    this.$store.dispatch('asncIncrease').then(() => {
                        console.log(this.$store.state.count)
                    })
                }
            },
            
        }
    </script>
    
    1. modules:将store分割到不同的模块,每个模块都拥有自己的state、getters、mutations、actions
    2. 总结:涉及到改变数据的,使用mutations。存在业务逻辑的,使用actions。
6.中央事件总线插件的开发

环境:vue2

  1. src目录下新建vue-bus目录,vue-bus目录下新建index.js,其内容如下:
const install = function(Vue) {
    const bus = new Vue({
        methods: {
            emit(event, ...args) {
                this.$emit(event, ...args)
            },
            on(event, callback) {
                this.$on(event, callback)
            },
            off(event, callback) {
                this.$off(event, callback)
            }
        },
    })
    Vue.prototype.$bus = bus
}
export default install
  1. main.js中加载插件
import VueBus from './vue-bus'
Vue.use(VueBus)
  1. 测试
    1. 父组件中
    <template>
        <div id="app">
            <Counter :number="number"></Counter>        
        </div>
    </template> 
    <script>
    import Counter from '@/views/Counter'
    export default {
        name: "App",
        data() {
            return {
                number: 0,
            }
        },
        methods: {
            handleAdd(value) {
                this.number += value
            }
        },
        components: {
            Counter
        },
        created() {
            // 监听add事件,事件回调为handleAdd
            this.$bus.on('add', this.handleAdd)
        },
        beforeDestroy() {
            // 事件解绑, 指定了事件和回调表示只移除这个回调的监听器
            this.$bus.off('add', this.handleAdd)
        }
    };
    </script>
    
    1. 子组件中
    <template>
        <div>
            <!-- number的初始值来自父组件 -->
            <h1>number的值:{{number}}</h1>
            <button @click="addByOne">增加number的值</button>
        </div>
    </template>
    <script>
    export default {
        name: 'Counter',
        props: {
            number: {
                type: Number,
                required: true
            }
        },
        methods: {
            addByOne() {
                // 通过事件总线的方式,子组件给父组件传递数据
                this.$bus.emit('add', 10)
            },
        }
    
    }
    </script>