Vue全家桶入门

B站 - Vue 学习笔记

vue全家桶:https://www.bilibili.com/video/BV15741177Eh?from=search&seid=8341611554359525751

0 课程介绍


  • 邂逅Vue.js
  • Vue基础语法
  • 组件化开发
  • Vue CLI详解
  • vue-router
  • vuex详解
  • 网络封装
  • 项目实战

1 邂逅Vue.js


Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架

1.1 MVVM架构
  • MVVM架构

    • MVC与MVP

请添加图片描述

  • MVVM

请添加图片描述

  • Vue.js的定位

    • Vue.js在MVVM架构中的定位

    请添加图片描述
    请添加图片描述

    • Vue.js的功能定位

    请添加图片描述

  • Vue.js的核心思想与特点

    • Reactive Components for Modern Web Interfaces(数据驱动的组件,为现代的 Web 界面而生)
      • 数据驱动
      • 组件化
    • 特点:侵入性低、鼓励模块化、轻量、高性能
  • 数据驱动的 Web 开发

    • Vue.js 安装

      • 下载Vue.js,通过

请添加图片描述

1.2 Vue初体验
  • HelloVue.html

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>TestVue</title>
        </head>
        <body>
            <div id="app">{{message}}</div>
    
            <script src="../js/vue.js"></script>
            <script>
                // 一、普通js写法(编程范式:命令式编程)
                //1. 创建div元素,设置id属性
                //2. 定义一个变量叫message
                //3. 将message变量放在前面的div元素中显示
                //4. 修改message数据
                //5. 将message数据替换原数据
    
                // 二、Vue写法(编程范式:声明式编程)
                // let变量、const常量
                // new Vue()就是ViewModel
                const app = new Vue({
                    //el、data属于Vue的options
                    el: '#app', //挂载管理的元素
                    data: {  // 定义数据
                        message: 'zfcer'
                    }
                })
                // Vue可以直接在浏览器中修改message:app.message = 'hello'
            </script>
        </body>
    </html>
    
  • Vue列表展示

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <title>Vue列表展示</title>
        </head>
        <body>
            <!-- Vue 声明式 + 响应式 -->
            <!-- 可以在浏览器中直接app.movies.push('d'),向数组movies中添加元素d -->
            <div id="app">
                <ul>
                    <li v-for="item in movies">{{item}}</li>
                </ul>
            </div>
    
            <script src="../js/vue.js"></script>
            <script>
                const app = new Vue({
                    el: '#app',
                    data:{
                        movies: ['a', 'b', 'c']
                    }
                })
            </script>
        </body>
    </html>
    
  • Vue案例-计数器

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>计数器</title>
        </head>
        <body>
            <div id="app">
                <h1>当前计数:{{counter}}</h1>
                
                <!-- <button v-on:click="counter++">+</button>
    <button v-on:click="counter--">-</button> -->
    
                <button @click="add">+</button>
                <button @click="sub">-</button>
    
            </div>
    
            <script src="../js/vue.js"></script>
            <script>
                const app = new Vue({
                    el: '#app',
                    data: {
                        counter: 0
                    },
                    methods: {
                        add: function(){
                            console.log("add执行了")
                            // 要用this.
                            this.counter++;
                        },
                        sub: function(){
                            this.counter--;
                        }
                    }
                })
            </script>
        </body>
    </html>
    

请添加图片描述

1.3 Vue生命周期

在github中下载vue源码(tag2.6.14)后,用vscode打开src --> core --> index.js,发现import Vue from './instance/index'

打开./instance/index后就可以看到Vue函数,options表示new Vue({})传入的el、data、methods

import { initMixin } from './init'
function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}

进入./init后,发现生命周期

// expose real self
vm._self = vm
initLifecycle(vm)
initEvents(vm)
initRender(vm)
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
initState(vm)
initProvide(vm) // resolve provide after data/props
callHook(vm, 'created')
  • Vue官网生命周期流程

请添加图片描述

  • 理解Vue生命周期

请添加图片描述
请添加图片描述

2 Vue基础语法


  • vscode前端代码规范缩进设置为2

请添加图片描述
请添加图片描述

2.1 Mustache语法
  • Mustache中可以写简单的表达式,如果是复杂的表达式推荐使用computed计算属性。Mustache也是响应式的。Mustache作用在显示内容本身。
<div id="app">
    <h2>{{message}}</h2>

    <!-- Mystache语法适用于标签内容上,不使用与标签属性 -->
    <!-- Mustache语法中,不仅仅可以直接写变量,也可以写简单的表达式 -->
    <h2>{{firstName + lastName}}</h2>
    <h2>{{firstName + ' ' + lastName}}</h2>
    <h2>{{firstName}} {{lastName}}</h2>
    <h2>{{counter*2}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            firstName: "zfcer",
            lastName: "520",
            counter: 1,
        },
    });
</script>
2.2 v-once语法
  • v-once是非响应式的,不希望随意更改数据或界面。v-once作用在标签上。
<div id="app">
    <h2>{{message}}</h2>
    <!-- 使用v-once后,下面的h2标签不会随着message改变而改变 -- 非响应式 -->
    <!-- v-once直接使用 -->
    <h2 v-once>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
    });
</script>
2.3 v-html语法
  • v-html用来按照html格式进行解析显示,v-html作用在标签上。
  • 历史遗留问题: {{{}}}以前可以使用三大括号来处理html格式解析,现在不再支持了。
<div id="app">
    <!-- 以前可以使用{{{}}}来代替,但是由于跟{{}}容易弄混,现在使用v-html -->
    <!-- v-html后面跟url,作用在标签上 -->
    <h2 v-html="url"></h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            url: "<a href='http://ww.baidu.com'>百度一下</a>",
        },
    });
</script>
2.4 v-text语法
  • v-text与Mustache比较相似,但是v-text作用在标签上,并且会覆盖掉原来的内容。不推荐使用
<div id="app">
    <h2>{{message}}, zfcer</h2>
    <!-- 使用v-text会用message覆盖掉‘,zfcer’ -->
    <!-- 不推荐使用 -->
    <h2 v-text="message">, zfcer</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "hello Vue",
        },
    });
</script>
2.5 v-pre语法
  • v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法
<div id="app">
    <h2>{{message}}</h2>
    <!-- 使用v-pre最开始编译过程中会显示{{message}} -->
    <h2 v-pre>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "hello Vue",
        },
    });
</script>
2.6 v-cloak语法
  • 当Vue加载前,v-cloak存在;当Vue加载后,v-cloak被移除。
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>v-cloak语法</title>
        <style>
            /* 隐藏元素信息{{message}} */
            [v-cloak] {
                display: none;
            }
        </style>
    </head>
    <body>
        <div id="app" v-cloak>{{message}}</div>

        <script src="../js/vue.js"></script>
        <script>
            // 当Vue加载前,v-cloak存在
            // 当Vue加载后,v-cloak被移除
            setTimeout(
                // 延时加载
                function () {
                    const app = new Vue({
                        el: "#app",
                        data: {
                            message: "hello Vue",
                        },
                    });
                },
                2000
            );
        </script>
    </body>
</html>
2.7 v-bind语法
  • v-bind基本用法:作用于标签属性上,语法糖:
<div id="app">
    <!-- Mustache语法不适用于标签属性上 -->
    <a href="{{url}}">百度一下</a>
    <br />
    <!-- v-bind作用与标签属性上 -->
    <a v-bind:href="url">百度一下</a>
    <!-- v-bind语法糖: -->
    <a :href="url">百度一下</a>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            url: "http://www.baidu.com",
        },
    });
</script>
  • v-bind动态绑定class对象:通过v-bind:class绑定标签上的class属性
    • 在v-bind:class属性中传入对象{style1: boolean1, style2: boolean2},可以做到自动根据boolean来进行拼接
    • 在普通class上定义公共style,最终会实现将普通class与v-bind:class进行拼接
    • v-bind:class也可以通过函数的方式传入对象
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>v-bind动态绑定class(对象语法)</title>

        <style>
            .active {
                color: red;
            }
            .line {
                padding: 100px;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <!-- 用普通class定义固定样式,v-bind绑定的class定义动态样式对象(会根据boolean值自动选择对应的样式) -->
            <!-- 最终会将普通class与v-bind绑定的class拼接起来 -->
            <h2 class="title" :class="{active: isActive, line: isLine}">
                Hello Vue (对象)
            </h2>
            <!-- 补充: 绑定函数 -->
            <h2 class="title" :class="getClasses()">Hello Vue (对象)</h2>
            <button @click="btnClick">点击变色</button>
        </div>

        <script src="../js/vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data: {
                    isActive: true,
                    isLine: true,
                },
                methods: {
                    btnClick: function () {
                        this.isActive = !this.isActive;
                    },
                    getClasses: function () {
                        return { active: this.isActive, line: this.isLine };
                    },
                },
            });
        </script>
    </body>
</html>
  • v-bind绑定class数组:与v-bind绑定class对象类似
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>v-bind动态绑定class(数组语法) - 不推荐</title>

        <style>
            .active {
                color: red;
            }
            .line {
                padding: 100px;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <!-- 用普通class定义固定样式,v-bind绑定的class定义动态样式数组(会根据boolean值自动选择对应的样式) -->
            <!-- 最终会将普通class与v-bind绑定的class拼接起来 -->
            <!-- 不推荐 -->
            <h2 class="title" :class="[active, line]">Hello Vue (数组)</h2>
            <!-- 补充: 绑定函数 -->
            <h2 class="title" :class="getClasses()">Hello Vue (数组)</h2>
            <button @click="btnClick">点击变色</button>
        </div>

        <script src="../js/vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data: {
                    active: "active",
                    line: "line",
                },
                methods: {
                    btnClick: function () {
                        this.isActive = !this.isActive;
                    },
                    getClasses: function () {
                        return [this.active, this.line];
                    },
                },
            });
        </script>
    </body>
</html>
  • 练习:v-for与v-bind结合
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>作业 - v-for和v-bind</title>
        <style>
            .active {
                color: red;
            }
        </style>
    </head>
    <body>
        <div id="app">
            <!-- 方案一 -->
            <ul>
                <li :class="{active: isActive[index]}" v-for="(m, index) in movies">
                    {{index}} - {{m}} <button @click="btnClick(index)">点击</button>
                </li>
            </ul>

            <br>
            <br>
            <br>
            <!-- 方案二:推荐 -->
            <ul>
                <li :class="{active: index == curIdx }" v-for="(m, index) in movies" @click="liClick(index)">
                    {{index}} - {{m}} 
                </li>
            </ul>
        </div>

        <script src="../js/vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data: {
                    movies: ["aaa", "bbb", "ccc", "ddd"],
                    isActive: [true, false, false, false],
                    preIdx: 0,
                    curIdx: 0
                },
                methods: {
                    btnClick: function (index) {
                        console.log("点击了" + index);
                        this.isActive.splice(this.preIdx, 1, false)
                        this.preIdx = index;
                        Vue.set(this.isActive, index, true)
                    },
                    liClick(index){
                        this.curIdx = index
                    }
                },
            });
        </script>
    </body>
</html>
  • v-bind动态绑定style对象:使用驼峰命名
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>v-bind动态绑定style(对象语法)</title>
  </head>
  <body>
    <div id="app">
      <!-- 使用驼峰命名:fontSize -- font-size -->
      <h2 :style="{fontSize: finalSize+'px', color: finalColor}">
        Hello Vue (对象)
      </h2>
      <!-- 补充: 绑定函数 -->
      <h2 class="title" :style="getStyles()">Hello Vue (对象)</h2>
    </div>

    <script src="../js/vue.js"></script>
    <script>
      const app = new Vue({
        el: "#app",
        data: {
          finalSize: 100,
          finalColor: "red",
        },
        methods: {
          getStyles: function () {
            return {
              fontSize: this.finalSize + "px",
              color: this.finalColor,
            };
          },
        },
      });
    </script>
  </body>
</html>
  • v-bind动态绑定style数组
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>v-bind动态绑定style(数组语法)</title>
    </head>
    <body>
        <div id="app">
            <!-- 使用驼峰命名:fontSize -- font-size -->
            <h2 :style="[baseStyle, bgcStyle]">Hello Vue (对象)</h2>
            <!-- 补充: 绑定函数 -->
            <h2 class="title" :style="getStyles()">Hello Vue (对象)</h2>
        </div>

        <script src="../js/vue.js"></script>
        <script>
            const app = new Vue({
                el: "#app",
                data: {
                    baseStyle: { fontSize: "100px", color: "red" },
                    bgcStyle: { backgroundColor: "blue" },
                },
                methods: {
                    getStyles: function () {
                        return [this.baseStyle, this.bgcStyle];
                    },
                },
            });
        </script>
    </body>
</html>
2.8 computed属性
  • computed基本语法
<div id="app">
    <!-- 直接拼接,过于繁琐 -->
    <h2>{{firstName +' '+ lastName}}</h2>
    <h2>{{firstName}} {{lastName}}</h2>
    <!-- Mustache语法中支持解析方法 -->
    <h2>{{getFullName()}}</h2>
    <!-- computed计算属性 -->
    <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            firstName: "zfcer",
            lastName: "best",
        },
        // 计算属性:在变量没有修改情况下,第一次执行后有缓存
        computed: {
            fullName: function () {
                return this.firstName + " " + this.lastName;
            },
        },
        // 方法options:每次执行都要调用方法
        methods: {
            getFullName: function () {
                return this.firstName + " " + this.lastName;
            },
        },
    });
</script>
  • computed复杂操作
<div id="app">总价格为:{{totalPrice}}</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            books: [
                { id: 100, name: "aaa", price: 119 },
                { id: 101, name: "bbb", price: 120 },
                { id: 102, name: "ccc", price: 101 },
                { id: 103, name: "ddd", price: 99 },
            ],
        },
        computed: {
            totalPrice: function () {
                let finalPrices = 0;
                // for(let i=0; i<this.books.length; i++){
                //   finalPrices += this.books[i].price;
                // }

                for (let book of this.books) {
                    finalPrices += book.price;
                }
                return finalPrices;
            },
        },
    });
</script>
  • computed的setter和getter
<div id="app">
    <!-- computed计算属性 -->
    <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            firstName: "zfcer",
            lastName: "best",
        },
        // 计算属性:在变量没有修改情况下,第一次执行后有缓存
        computed: {
            // 写法1: 简写
            // fullName: function () {
            //   return this.firstName + " " + this.lastName;
            // },

            // 写法2
            // 第一种写法是第二种写法的简写
            fullName: {
                get: function () {
                    // 获取返回值:由vue自身来调用get方法
                    return this.firstName + " " + this.lastName;
                },
                set: function (newValue) {
                    // 一般computed只读,所以setter方法可以省略
                    let names = newValue.split(" ");
                    this.firstName = names[0];
                    this.lastName = names[1];
                },
            },
        },
    });
</script>~~~

- computed与methods属性对比:computed有缓存,methods没有缓存

~~~html
<div id="app">
    <!-- Mustache语法中支持解析方法 -->
    <!-- 会执行6次,无缓存。 -->
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>
    <h2>{{getFullName()}}</h2>

    <!-- computed计算属性 -->
    <!-- 会执行1次,有缓存。只有当firstName或lastName发生更改时,才会重新执行 -->
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
    <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            firstName: "zfcer",
            lastName: "best",
        },
        // 计算属性:在变量没有修改情况下,第一次执行后有缓存
        computed: {
            fullName: function () {
                console.log("fullName");
                return this.firstName + " " + this.lastName;
            },
        },
        // 方法options:每次执行都要调用方法
        methods: {
            getFullName: function () {
                console.log("getFullName");
                return this.firstName + " " + this.lastName;
            },
        },
    });
</script>
2.9 v-on语法
  • v-on时间监听基本语法:在事件监听时,如果方法没有参数,可以省略(); 但是在Mustache语法中是不能省略的。v-on语法糖@
<div id="app">
    <h1>当前计数:{{counter}}</h1>
    <button v-on:click="counter++">+</button>
    <button v-on:click="counter--">-</button>
    <br />
    <br />
    <!-- v-on的语法糖@ -->
    <!-- 注意:在事件监听时,如果方法没有参数,可以省略(); 但是在Mustache语法中是不能省略的 -->
    <button @click="add">+</button>
    <button @click="sub()">-</button>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            counter: 0,
        },
        methods: {
            add() {
                console.log("add执行了");
                // 要用this.
                this.counter++;
            },
            sub() {
                this.counter--;
            },
        },
    });
</script>
  • v-on时间监听参数问题:浏览器参数可以通过$event获得;当函数只需要event一个参数时,可以通过省略()的方式获得浏览器产生的event
<div id="app">
    <!-- 当监听事件没有参数时,可以省略() -->
    <button @click="btn1Click">按钮1</button>

    <!-- 当监听事件需要一个参数,但是省略小括号时,Vue会默认将浏览器产生的event时间对象作为参数传入到方法中 -->
    <!-- 不省略()会出现undefined -->
    <button @click="btn2Click()">按钮2-不省略()</button>
    <!-- 省略()会获取浏览器event对象 -->
    <button @click="btn2Click">按钮2-省略()</button>

    <!-- 当监听事件需要包含event对象在内的多个参数时,需要借助$event传入event对象 -->
    <button @click="btn3Click('zfcer', $event)">按钮3-直接传参</button>
    <button @click="btn3Click(name, $event)">按钮3-vue data 传参</button>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            counter: 0,
            name: "best",
        },
        methods: {
            btn1Click() {
                console.log("btn1Click ...");
            },
            btn2Click(event) {
                console.log("btn2Click ...", event);
            },
            btn3Click(name, event) {
                console.log("btn2Click ...", name, event);
            },
        },
    });
</script>
  • v-on事件监听修饰符
    • .stop - 调用 event.stopPropagation()。
    • .prevent - 调用 event.preventDefault()。
    • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
    • .native - 监听组件根元素的原生事件。
    • .once - 只触发一次回调。
<div id="app">
    <!-- 1. .stop修饰符阻止冒泡 -->
    <div @click="divClick">
        aaaaa <br />
        <button @click.stop="btn1Click">按钮1</button>
    </div>

    <br />
    <br />
    <!-- 2. .prevent修饰符阻止默认提交 -->
    <form action="www.baidu.com">
        <input type="submit" value="提交" @click.prevent="submitClick" />
    </form>

    <br />
    <br />
    <!-- 3. 监听键盘的键帽:当回车键还原时触发 -->
    <input type="text" @keyup.enter="keyUp" />

    <br />
    <br />
    <!-- 4. 按钮只触发一次: 了解 -->
    <button @click.once="btn2Click">按钮2</button>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            counter: 0,
            name: "best",
        },
        methods: {
            divClick() {
                console.log("divClick");
            },
            btn1Click() {
                console.log("btn1Click");
            },
            submitClick() {
                console.log("submitClick");
            },
            keyUp() {
                console.log("keyUp");
            },
            btn2Click() {
                console.log("btn2Click");
            },
        },
    });
</script>
2.10 v-if/v-else-if/v-else语法
  • v-if与v-else使用:v-if与v-else作用在标签上
<div id="app">
    <h2 v-if="isShow">
        <span>aaa</span>
        <span>bbb</span>
        <span>ccc</span>
        {{message}}
    </h2>
    <h2 v-else>isShow为false,不显示...</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
            isShow: true,
        },
    });
</script>
  • v-else-if
<div id="app">
    <!-- 一般复杂的逻辑是通过computed计算属性来进行处理的 -->
    <h2 v-if="score>=90">优秀</h2>
    <h2 v-else-if="score>=80">良好</h2>
    <h2 v-else-if="score>=60">及格</h2>
    <h2 v-else>不及格</h2>

    <h2>成绩表现:{{result}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
            score: 92,
        },
        computed: {
            result() {
                let resultMessage = "";
                if (this.score >= 90) resultMessage = "优秀";
                else if (this.score >= 80) resultMessage = "良好";
                else if (this.score >= 60) resultMessage = "及格";
                else resultMessage = "不及格";
                return resultMessage;
            },
        },
    });
</script>
  • 练习:用户登录方式切换。
    • 注意:==由于虚拟DOM的存在,导致两个input控件会复用。==这里需要特别使用key来做区分,保证登录方式切换后,input不被复用(密码清空)
<div id="app">
    <!-- 注意:由于虚拟DOM的存在,导致两个input控件会复用。
这里需要特别使用key来做区分,保证登录方式切换后,input不被复用(密码清空) -->
    <span v-if="isUser">
        <label for="username">用户登录:</label>
        <input
               type="text"
               id="username"
               key="username-input"
               placeholder="输入用户密码"
               />
    </span>
    <span v-else>
        <label for="email">邮箱登录:</label>
        <input
               type="text"
               id="email"
               key="email-input"
               placeholder="输入邮箱密码"
               />
    </span>
    <button @click="btnClick">切换登录方式</button>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            isUser: true,
        },
        methods: {
            btnClick() {
                this.isUser = !this.isUser;
            },
        },
    });
</script>
  • v-if与v-show的区别

    • v-if是真正把标签元素移除了、
    • v-show只是加了个display: none

    注意:当元素在显示与隐藏之间频繁切换时,推荐使用v-show当元素就是一次切换,推荐使用v-if

<div id="app">
    <!-- 注意:当元素在显示与隐藏之间频繁切换时,推荐使用v-show
当元素就是一次切换,推荐使用v-if   -->

    <!-- v-if当为false时,会使v-if修饰的元素不在DOM中 -->
    <h2 v-if="isShow">{{message}}</h2>

    <!-- v-show当为false时, 只是简单的加了display:none 样式 -->
    <h2 v-show="isShow">{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
            isShow: true,
        }
    });
</script>
2.11 v-for语法
  • v-for遍历数组&对象
<div id="app">
    <!-- 数组遍历 -->
    <ul>
        <li v-for="item in movies">{{item}}</li>
    </ul>
    <ul>
        <li v-for="(item, index) in movies">{{index}} -> {{item}}</li>
    </ul>

    <br>
    <!-- 对象遍历 -->
    <ul>
        <li v-for="value in info">{{value}}</li>
    </ul>
    <ul>
        <li v-for="(value, key) in info">{{key}} -> {{value}}</li>
    </ul>
    <ul>
        <li v-for="(value, key, index) in info">{{index}} -> {{key}} -> {{value}}</li>
    </ul>
</div>


<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: {
            movies: ['aaa', 'bbb', 'ccc', 'ddd'],
            info: {
                name: 'zfcer',
                age: 23,
                height: 17.8
            }
        }
    })
</script>
  • v-for中绑定key:绑定唯一的key后让v-for操作更加高效—DIff算法
<div id="app">
    <!-- 加入key的主要作用是为了高效更新DOM。但是key一定要能唯一代表一个item(index显然不行,假如在BC之间插入E,index就会发生改变)
	使用key前:在BC之间插入E,先把C更新为E,D更新为C,最后插入D --- 默认Diff算法
	使用key后:直接在BC之间插入E,高效很多 --- 有唯一标识的Diff算法 -->
    
    <!-- 浏览器输入:app.letters.splice(2, 0, 'E')表示在第二个位置后,删除0个元素,插入元素E -->
    <ul>
        <li v-for="item in letters" :key="item">{{item}}</li>
    </ul>

</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: { 
            letters: ['A', 'B', 'C', 'D'],
        }
    })
</script>
2.12 数组的响应式操作
  • 数组响应式函数:push、pop、shift、unshift、splice、sort、reverse
    • 注意:直接根据数组下标修改数组元素不是响应式的
    • 非响应式操作改进手段:用splice/set代替
<div id="app">
    <ul>
        <li v-for="item in letters" :key="item">{{item}}</li>
    </ul>
    <button @click="btnClick">按钮</button>

</div>


<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: '#app',
        data: { 
            letters: ['A', 'B', 'C', 'D'],

        },
        methods: {
            btnClick(){
                // 响应式操作:push、pop、shift、unshift、splice、sort、reverse
                // 1. push
                // this.letters.push('aaa', 'bbb', 'ccc')
                // 2. pop
                // 3. shift 删除第一个元素
                // this.letters.shift()
                // 4. unshift 从头插入元素
                // this.letters.unshift('aaa', 'bbb', 'ccc')
                // 5. splice 删除元素/插入元素/替换元素
                // this.letters.splice(1, 1, 'aa', 'bb') //可以同时插入多个元素
                // 6. sort
                // 7. reverse

                // 非响应式操作
                // this.letters[0] = 'bbbb'
                // 解决方法:用splice/set代替
                // this.letters.splice(0, 1, 'bbbb')
                Vue.set(this.letters, 0, 'bbbb')
            }
        }
    })

</script>
2.13 书籍购物车案例
  • style.css
table {
    border: 1px solid #e9e9e9;
    border-collapse: collapse;
    border-spacing: 0;
}

th,
td {
    padding: 8px, 16px;
    border: 1px solid #e9e9e9;
    text-align: left;
}

th {
    background-color: #f7f7f7;
    color: #5c6b77;
    font-weight: 600;
}
  • main.js:过滤器属性filters
const app = new Vue({
    el: '#app',
    data: {
        books: [
            {
                id: 1,
                name: 'java study',
                date: '2021-7-15',
                count: 1,
                price: 127.00
            },
            {
                id: 2,
                name: 'java study',
                date: '2021-7-15',
                count: 1,
                price: 100.00
            },
            {
                id: 3,
                name: 'java study',
                date: '2021-7-15',
                count: 2,
                price: 152.00
            },
            {
                id: 4,
                name: 'java study',
                date: '2021-7-15',
                count: 3,
                price: 59.00
            }
        ]
    },
    computed: {
        totalPrice(){
            let allPrices = 0;
            // for循环的三种写法
            // for(let book of this.books)
            //   allPrices += book.price * book.count;

            // for(let i = 0; i<this.books.length; i++){
            //   allPrices += this.books[i].price * this.books[i].count
            // }

            // for(let i in this.books){
            //   // i为索引
            //   allPrices += this.books[i].price * this.books[i].count
            // }


            // 使用reduce高阶函数求price总和: 箭头函数
            allPrices = this.books.reduce((preValue, book) => preValue + book.price * book.count, 0)
            return allPrices;
        }
    },
    methods: {
        showFixedPrice(price){
            return '¥'+price.toFixed(2); //toFixed(2)保留两位小数
        },
        decrement(index){
            if(this.books[index].count == 1)
                return;
            this.books[index].count--;
        },
        increment(index){
            this.books[index].count++;
        },
        removeBook(index){
            this.books.splice(index, 1);
        }
    },
    //  过滤器:注意它的使用 {{value | showPrice}}
    filters: {
        showPrice(price){
            return '¥'+price.toFixed(2); //toFixed(2)保留两位小数
        }
    }
})
  • index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <title>书籍购物车案例</title>
        <link rel="stylesheet" href="style.css" />
    </head>
    <body>
        <div id="app">
            <table>
                <thead v-if="books.length">
                    <tr>
                        <th>编号</th>
                        <th>书籍名</th>
                        <th>出版日期</th>
                        <th>价格</th>
                        <th>数量</th>
                        <th>操作</th>
                    </tr>
                </thead>

                <tbody>
                    <tr v-for="(book, index) in books" :key="book.id">
                        <td>{{book.id}}</td>
                        <td>{{book.name}}</td>
                        <td>{{book.date}}</td>
                        <!-- 函数方式处理价格保留两位小数 -->
                        <!-- <td>{{showFixedPrice(book.price)}}</td> -->
                        <!-- 过滤器方式处理价格保留两位小数 -->
                        <td>{{book.price | showPrice}}</td>
                        <td>
                            <!-- 绑定disabled属性 -->
                          <button @click="decrement(index)" :disabled="book.count <= 1"> - </button>
                            {{book.count}}
                            <button @click="increment(index)">+</button>
                        </td>
                        <td><button @click="removeBook(index)">移除</button></td>
                    </tr>
                </tbody>
            </table>

            <div v-if="books.length">
                <!-- 复用过滤器 -->
                <h2>总价格为:{{totalPrice | showPrice}}</h2>
            </div>
            <div v-else><h2>购物车为空</h2></div>
        </div>

        <script src="../../js/vue.js"></script>
        <script src="main.js"></script>
    </body>
</html>
2.14 v-model语法
  • 表单双向绑定
<div id="app">
    <!-- 在输入框上使用v-model就是实现双向绑定 -->
    <input type="text" v-model="message" />
    <textarea name="" id="" cols="30" rows="10" v-model="message"></textarea>
    {{message}}
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
    });
</script>
  • v-model原理:v-bind 与 v-on
<div id="app">
    <!-- v-model原理:v-bind 与 v-on -->
    <input type="text" v-model="message" />
    <input type="text" :value="message" @input="valueChange($event)" />
    <!-- 当valueChange不传入参数时,默认会把$event传入 -->
    <input type="text" :value="message" @input="valueChange" />
    <input
           type="text"
           :value="message"
           @input="message = $event.target.value"
           />

    <h2>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
        methods: {
            valueChange(event) {
                console.log(event);
                this.message = event.target.value;
            },
        },
    });
</script>
  • v-model与radio结合使用
<div id="app">
    <!-- 在不使用v-model的情况下,一定要加name属性来保证radio互斥 -->
    <label for="male">
        <input type="radio" id="male" name="sex" value="" v-model="sex" /></label>
    <label for="female">
        <input type="radio" id="female" name="sex" value="" v-model="sex" /></label>

    <h2>{{sex}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            sex: "女",
        },
    });
</script>
  • v-model与checkbox结合使用
<div id="app">
    <!-- 单选框:boolean类型 -->
    <label for="agree">
        <input
               type="checkbox"
               name="agree"
               value="isAgree"
               v-model="isAgree"
               />同意协议
    </label>
    <button :disabled="!isAgree">下一步</button>
    <h2>{{isAgree}}</h2>

    <br />
    <br />
    <!-- 复选框:数组类型 -->
    <input type="checkbox" value="篮球" v-model="hobbits" />篮球
    <input type="checkbox" value="足球" v-model="hobbits" />足球
    <input type="checkbox" value="乒乓球" v-model="hobbits" />乒乓球
    <input type="checkbox" value="羽毛球" v-model="hobbits" />羽毛球
    <h2>{{hobbits}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            isAgree: false,
            hobbits: [],
        },
    });
</script>
  • v-model与select结合使用
<div id="app">
    <!-- 单选框:boolean类型 -->
    <select name="aaa" v-model="fruit">
        <option value="苹果">苹果</option>
        <option value="橘子">橘子</option>
        <option value="香蕉">香蕉</option>
    </select>
    <h2>{{fruit}}</h2>

    <br />
    <br />
    <br />
    <!-- 复选框:数组类型 -->
    <!-- 使用multiple变为多选框 -->
    <select name="bbb" v-model="fruits" multiple>
        <option value="苹果">苹果</option>
        <option value="橘子">橘子</option>
        <option value="香蕉">香蕉</option>
    </select>
    <h2>{{fruits}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            fruit: "香蕉",
            fruits: [],
        },
    });
</script>
  • v-model与v-bind值绑定:就是动态的给value赋值,如果不使用v-bind:value就会导致value=“ f ”值就是f字符串,而不是fruit
<div id="app">
    <!-- 复选框:数组类型 -->
    <!-- 通过绑定label for 和 input id完成label与input关联 -->
    <label :for="f" v-for="f in orginalFruits">
        <input type="checkbox" :id="f" :value="f" v-model="fruits" />{{f}}
    </label>

    <br />
    <br />
    <br />
    <!-- 复选框:数组类型 -->
    <!-- 使用multiple变为多选框 -->
    <select v-model="fruits" multiple>
        <!-- value属性一定要使用v-bind:value -->
        <option v-for="f in orginalFruits" :value="f">{{f}}</option>
    </select>
    <h2>{{fruits}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            fruit: "香蕉",
            fruits: [],
            orginalFruits: ["苹果", "香蕉", "橘子", "葡萄"],
        },
    });
</script>
  • v-model修饰符的使用
    • lazy懒加载
    • number修饰符
    • trim修饰符
<div id="app">
    <!-- 1. lazy懒加载:只在input回车/移出焦点下才触发双向绑定 -->
    <input type="text" v-model.lazy="message" /> {{message}}

    <br />
    <br />
    <br />
    <!-- 2. number,限制input中类型为number类型 -->
    <!-- 默认情况下,输入框会被转化为string处理,即使我们在vue中age: 23,更改age后还是会转为string -->
    <!-- 修饰符可以叠加使用 -->
    <!-- typeof(value)用来展示value的类型 -->
    <input type="text" v-model.number.lazy="age" /> {{age +' age的类型为:'+
    typeof(age)}}

    <br />
    <br />
    <br />
    <!-- 3. trim,去除多余首尾空格 -->
    <input type="text" v-model.trim="message" /> {{message}}
</div>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
            age: 23,
        },
    });
</script>

3 Vue组件化开发


组件化是Vue.js中的重要思想:组件树

请添加图片描述

3.1 组件化基本使用
  • 组件化使用步骤:1 创建组件构造器对象; 2 注册组件; 3 使用组件

请添加图片描述

<div id="app">
    <!-- 3. 使用组件 -->
    <my-cpn></my-cpn>
    <my-cpn></my-cpn>
</div>

<script src="../js/vue.js"></script>
<script>
    // 组件化的三步操作
    // 1. 创建组件构造器对象
    const cpnC = Vue.extend({
        template: `
            <div>
                <h2>我是标题</h2>
                <p>我是内容</p>
            </div>
`,
    });

    // 2. 注册组件(全局组件)
    Vue.component("my-cpn", cpnC);

    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
    });
</script>
3.2 全局组件&局部组件
<script>
    // 组件化的三步操作
    // 1. 创建组件构造器对象
    const cpnC = Vue.extend({
        template: `
            <div>
                <h2>我是标题</h2>
                <p>我是内容</p>
    		</div>
`,
    });

    // 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
    // Vue.component("cpn", cpnC);

    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
        components: {
            // 这里注册的是局部组件:作用范围在div#app标签下
            cpn: cpnC,
        },
    });
</script>
3.3 父组件与子组件
<div id="app">
    <cpn1></cpn1>
    <!-- 子组件因为既没有在全局组件中注册,也没有在Vue实例中注册,故无效! -->
    <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
    // 1. 两个组件构造器
    // 注意:父组件一定要写在子组件后面,不然就会报找不到子组件异常(父组件需要注册子组件)
    // 子组件
    const cpnC2 = Vue.extend({
        template: `
<div>
<h2>标题2</h2>
<p>这是内容部分2</p>
    </div>
`,
    });
    // 父组件
    const cpnC1 = Vue.extend({
        template: `
<div>
<h2>标题1</h2>
<p>这是内容部分1</p>
<!-- 使用子组件 -->
<cpn2></cpn2>
    </div>
`,
        components: {
            // 注册子组件
            cpn2: cpnC2,
        },
    });

    // 2. 注册局部组件
    const app = new Vue({
        el: "#app",
        components: {
            // 注册局部组件
            cpn1: cpnC1,
        },
    });
</script>
3.4 组件注册语法糖
  • 将Vue.extend({})省略掉,直接在注册组件时传入template
<div id="app">
    <!-- 3. 使用组件 -->
    <!-- 这种语法糖的注册方式,本质还是进行了Vue.extend({})操作 -->
    <cpn1></cpn1>
    <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
    // 组件化的三步操作
    // 1. 创建组件构造器对象:省略构建器对象
    const cpnC = Vue.extend({
        template: `
<div>
<h2>我是标题</h2>
<p>我是内容</p>
    </div>
`,
    });

    // 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
    Vue.component("cpn1", {
        template: `
<div>
<h2>我是全局组件</h2>
<p>我是内容</p>
    </div>
`,
    });

    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
        components: {
            // 这里注册的是局部组件
            cpn2: {
                template: `
<div>
<h2>我是局部组件</h2>
<p>我是内容</p>
    </div>
`,
            },
        },
    });
</script>
3.5 组件模板的分离写法
  • 推荐使用模板分离template的方式进行模板分离
<div id="app">
    <!-- 3. 使用组件 -->
    <cpn1></cpn1>
    <cpn2></cpn2>
</div>

<!-- 1. 模板分离script方法 -->
<script type="text/x-template" id="cpn1">
      <div>
        <h2>我是标题</h2>
        <p>我是内容</p>
    </div>
</script>

<!-- 2. 模板分离template -- 推荐 -->
<template id="cpn2">
    <div>
        <h2>我是标题</h2>
        <p>我是内容</p>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    // 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
    Vue.component("cpn1", {
        template: "#cpn1",
    });

    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
        },
        components: {
            // 这里注册的是局部组件
            cpn2: {
                template: "#cpn2",
            },
        },
    });
</script>
3.6 组件中数据存放问题
  • 组件中数据使用函数方式存放:避免多个组件实例之间数据共享导致混乱
<div id="app">
    <!-- 3. 使用组件 -->
    <!-- data使用函数的作用:划分作用域,当使用多个组件实例时,如果不用data函数,非常容易混淆counter变量,导致全局counter出现 -->
    <cpn1></cpn1>
    <cpn1></cpn1>
    <cpn1></cpn1>
    <!-- <cpn2></cpn2> -->
</div>

<template id="cpn1">
    <div>
        <!-- 在模板中使用的数据,必须是在组件中data函数中定义的,如果只是在Vue实例data中注册是读取不到的 -->
        <h2>当前计数为:{{counter}}</h2>
        <button @click="decrement" :disabled="counter <= 0">-</button>
        <button @click="increment">+</button>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    // 定义全局对象
    let obj = {
        counter: 0,
    };

    // 2. 注册组件(这里注册的是全局组件,可以在多个Vue实例中使用)
    Vue.component("cpn1", {
        template: "#cpn1",
        data() {
            return {
                // 在组件中定义counter变量
                counter: 0,
            };
            // return obj; //此时使用的是同一个obj对象,显然不符合要求
        },
        methods: {
            decrement() {
                if (this.counter <= 0) return;
                this.counter--;
            },
            increment() {
                this.counter++;
            },
        },
    });

    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer123",
        },
        components: {
            // 这里注册的是局部组件
            cpn2: {
                template: "#cpn1",
                data() {
                    return {
                        message: "zfcer best",
                    };
                },
            },
        },
    });
</script>
3.7 父子组件通信
  • 父传子(props)
<div id="app">
    <!-- 完成父传子:v-bind绑定子属性chobbits、cmessage
如果不使用v-bind绑定子属性就会把hobbits、message识别为字符串 -->
    <cpn :chobbits="hobbits" :cmessage="message"></cpn>
</div>

<template id="cpn">
    <!-- template模板里面的内容要用div包裹一下 -->
    <div>
        <ul>
            <li v-for="hobbit in chobbits">{{hobbit}}</li>
        </ul>
        {{cmessage}}
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    // 自定义构造函数PersonName
    function PersonName(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // 子组件: prop -- 父传子
    const cpn = {
        template: "#cpn",
        // 1. 直接传值
        // props: {
        //   cmessage: "",
        //   chobbits: [],
        // },

        // 2. 加入类型检测, 默认值,required
        props: {
            cmessage: {
                // 验证type: String、Number、Boolean、Array、object、Date、Function、Symbol
                // 当我们有自定义构造函数时,验证也支持自定义类型
                type: [String, Number, PersonName],
                default: "aaaa",
                required: true, //表示必须加上这个属性
            },
            chobbits: {
                type: Array,
                // default: [], //vue2.5以上,当type是Array/Object时不能直接给数组赋默认值 [] {}
                default() {
                    return [];
                },
            },
        },
        data() {
            // 如果写data,必须要加上return,否则就会报错
            return {};
        },
    };

    // 把Vue实例看成Root组件
    const app = new Vue({
        el: "#app",
        data: {
            message: "zfcer",
            hobbits: ["篮球", "足球", "羽毛球"],
        },
        // 子组件
        components: {
            // cpn: cpn,
            // 增强写法
            cpn,
        },
    });
</script>
  • 父传子的驼峰标识问题:v-bind:props属性时,必须把props属性改为驼峰命名
<div id="app">
    <!-- 注意:在父传子的时候,因为v-bind不支持驼峰命名,这里的cInfo必须转为c-info -->
    <!-- <cpn :cInfo="info"></cpn> -->
    <cpn :c-info="info"></cpn>
</div>

<template id="cpn">
    <!-- template中尽量用div包裹 -->
    <div>
        <h2>{{cInfo}}</h2>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: "#cpn",
        props: {
            // 在标签上使用
            cInfo: {
                type: Object,
                defalut() {
                    return {};
                },
                required: true,
            },
        },
    };

    const app = new Vue({
        el: "#app",
        data: {
            info: {
                name: "zfcer",
                age: 23,
                height: 1.87,
            },
        },
        components: {
            cpn,
        },
    });
</script>
  • 子传父
    • 子组件emit发射出数据
    • 子组件标签中通过属性将数据传递给父组件
<div id="app">
    <!-- 利用v-on根据子组件emit出的名字来接受category-click,避免使用驼峰命名
没有传入参数是因为可以默认接受发射出来的category对象: 与接受浏览器默认event类似 -->
    <cpn @category-click="recive"></cpn>
    <div :style="{fontSize:'20px'}">{{result}}</div>
</div>

<template id="cpn">
    <div>
        <button v-for="category in categories" @click="btnClick(category)">
            {{category.name}}
        </button>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    // 子组件
    const cpn = {
        template: "#cpn",
        data() {
            return {
                categories: [
                    { id: "aaa", name: "热门推荐" },
                    { id: "bbb", name: "手机数码" },
                    { id: "ccc", name: "家电家用" },
                    { id: "ddd", name: "电脑办公" },
                ],
            };
        },
        methods: {
            btnClick(category) {
                // 1. 子组件发射出event,带名字category-click, 这里不要用驼峰命名
                // 可以发送多个信息,接收时按顺序接受即可
                this.$emit("category-click", category, "abc");
            },
        },
    };

    const app = new Vue({
        el: "#app",
        data: {
            result: null,
        },
        components: {
            cpn,
        },
        methods: {
            recive(category, name) {
                // 父组件接收子组件发射来的category
                // 也可以接收多个参数(按顺序接收)
                console.log(category);
                console.log(name);
                this.result = category;
            },
        },
    });
</script>
  • 父子通信案例:通过props和emit实现版
<!--------props 和 emit 实现版--------->
<div id="app">
    <cpn
         :cnum1="num1"
         :cnum2="num2"
         @num1change="num1Recive"
         @num2change="num2Recive"
         ></cpn>
</div>

<template id="cpn">
    <div>
        cnum1: {{cnum1}}
        <br />
        dnum1: {{dnum1}}
        <br />
        <input type="text" :value="dnum1" @input="dnum1Input" />
        <br />
        <br />
        cnum2: {{cnum2}}
        <br />
        dnum2: {{dnum2}}
        <input type="text" :value="dnum2" @input="dnum2Input" />
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: "#cpn",
        data() {
            return {
                dnum1: this.cnum1,
                dnum2: this.cnum2,
            };
        },
        props: {
            cnum1: {
                type: Number,
                default: 0,
            },
            cnum2: {
                type: Number,
                default: 0,
            },
        },
        methods: {
            dnum1Input(event) {
                this.dnum1 = parseFloat(event.target.value);
                if (event.target.value == null || event.target.value == "")
                    this.dnum1 = 0;
                this.$emit("num1change", this.dnum1);
                this.$emit("num2change", this.dnum1 * 100);
            },
            dnum2Input(event) {
                this.dnum2 = parseFloat(event.target.value);
                if (event.target.value == null || event.target.value == "")
                    this.dnum2 = 0;
                this.$emit("num2change", this.dnum2);
                this.$emit("num1change", this.dnum2 / 100);
            },
        },
    };

    const app = new Vue({
        el: "#app",
        data: {
            num1: 1,
            num2: 2,
        },
        components: {
            cpn,
        },
        methods: {
            num1Recive(dnum1) {
                console.log(typeof dnum1);
                this.num1 = parseFloat(dnum1);
            },
            num2Recive(dnum2) {
                console.log(typeof dnum2);
                this.num2 = parseFloat(dnum2);
            },
        },
    });
</script>
  • 父子通信案例:通过watch实现版
<div id="app">
    <cpn
         :cnum1="num1"
         :cnum2="num2"
         @num1change="num1Recive"
         @num2change="num2Recive"
         ></cpn>
</div>

<template id="cpn">
    <div>
        cnum1: {{cnum1}}
        <br />
        dnum1: {{dnum1}}
        <br />
        <input type="text" v-model.lazy="dnum1" />
        <br />
        <br />
        cnum2: {{cnum2}}
        <br />
        dnum2: {{dnum2}}
        <input type="text" v-model.lazy="dnum2" />
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    const cpn = {
        template: "#cpn",
        data() {
            return {
                dnum1: this.cnum1,
                dnum2: this.cnum2,
            };
        },
        props: {
            cnum1: {
                type: Number,
                default: 0,
            },
            cnum2: {
                type: Number,
                default: 0,
            },
        },
        methods: {
            // dnum1Input(event) {
            //   this.dnum1 = parseFloat(event.target.value);
            //   if (event.target.value == null || event.target.value == "")
            //     this.dnum1 = 0;
            //   this.$emit("num1change", this.dnum1);
            //   this.$emit("num2change", this.dnum1 * 100);
            // },
            // dnum2Input(event) {
            //   this.dnum2 = parseFloat(event.target.value);
            //   if (event.target.value == null || event.target.value == "")
            //     this.dnum2 = 0;
            //   this.$emit("num2change", this.dnum2);
            //   this.$emit("num1change", this.dnum2 / 100);
            // },
        },
        watch: {
            dnum1(newValue, oldValue) { //函数名与监听的变量名同名,newValue表示监听的新值,oldValue表示监听的旧值
                console.log(oldValue)
                this.dnum2 = newValue * 100; //dnum2的改变也会被watch监听到
                this.$emit("num1change", newValue);
            },
            dnum2(newValue) {
                this.dnum1 = newValue / 100; //dnum1的改变也会被watch监听到
                this.$emit("num2change", newValue);
            },
        },
    };

    const app = new Vue({
        el: "#app",
        data: {
            num1: 1,
            num2: 2,
        },
        components: {
            cpn,
        },
        methods: {
            num1Recive(dnum1) {
                console.log(typeof dnum1);
                this.num1 = parseFloat(dnum1);
            },
            num2Recive(dnum2) {
                console.log(typeof dnum2);
                this.num2 = parseFloat(dnum2);
            },
        },
    });
</script>
3.8 组件访问
  • 父访问子($childern、$refs)
    • 在组件标签上设置ref属性名后,就可以通过$childern、$refs完成对子组件数据的访问。 推荐使用$refs
<div id="app">
    <cpn></cpn>
    <cpn></cpn>
    <!-- 用ref属性标识,这样$refs.aaa就可以直接取出这个组件实例 -->
    <cpn ref="aaa"></cpn>

    <button @click="btnClick">按钮</button>
</div>

<template id="cpn">
    <div>
        <h2>这是组件</h2>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>

    const app = new Vue({
        el: '#app',
        components: {
            cpn: {
                template: '#cpn',
                data(){
                    return {
                        name: '我是cpn组件'
                    }
                }
            }
        },
        methods: {
            btnClick(){
                // 1. $childern利用索引下标从数组中取vueComponent元素 -- vueComponent元素表示组件实例
                // this.$children不推荐使用,因为当插入新元素后,数组索引下标后改变
                // console.log(this.$children[0].name)

                // 2. this.$refs:推荐使用
                console.log(this.$refs.aaa.name)
                this.$refs.aaa.name = "hahahaahaha"; //修改
                console.log('按钮执行了')
            }
        }
    })
</script>
  • 子访问父($parent、$root)
<div id="app">
    <cpn></cpn>
</div>

<template id="cpn">
    <div>
        <h2>这是cpn组件</h2>
        <ccpn></ccpn>
    </div>
</template>

<template id="ccpn">
    <div>
        <h2>这是ccpn组件</h2>
        <button @click="btnClick">按钮</button>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>

    const app = new Vue({
        el: '#app',
        data: {
            message: 'zfcer root'
        },
        components: {
            cpn: {
                template: '#cpn',
                data(){
                    return {
                        name: '我是cpn组件'
                    }
                },
                // 在cpn下
                components: {
                    ccpn: {
                        template: '#ccpn',
                        data(){
                            return {
                                name: '我是ccpn组件'
                            }
                        },
                        methods: {
                            btnClick(){
                                // 1. this.$parent: 获取当前vueComponent元素的父组件
                                // 不推荐使用,一般组件要尽量减少与其他组件的联系
                                // console.log(this.$parent)
                                // console.log(this.$parent.name)

                                // 2. this.$root: 直接获取root组件 -- vue实例
                                console.log(this.$root)
                                console.log(this.$root.message)
                                console.log('按钮执行了')
                            }
                        }
                    }
                },
            }
        },

    })
</script>
3.9 slot插槽
  • slot基本使用
<div id="app">
    <!-- 使用默认标签 -->
    <cpn></cpn>

    <cpn><span>haha</span></cpn>
    <cpn><span>hehe</span></cpn>

    <cpn>
        <h2>插槽标签1</h2>
        <span>插槽标签2</span>
        <button>插槽按钮</button>
    </cpn>
    <cpn></cpn>
</div>

<template id="cpn">
    <div>
        <h2>组件标题</h2>
        <span>组件内容</span>
        <!-- 定义插槽 -->
        <!-- 
1. 使用空插槽
2. 插槽中可以设置默认标签 button
3. 插槽中可以插入多个标签
-->
        <slot><button>组件按钮</button></slot>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>

    const cpn = {
        template: '#cpn',
    }

    const app = new Vue({
        el: '#app',
        data: {
            message: 'zfer'
        },
        components: {
            cpn
        }
    })
</script>
  • slot具名插槽
<div id="app">
    <!-- 使用默认标签 -->
    <cpn></cpn>

    <!-- 只能替换没有设置name的插槽 -->
    <cpn><span>haha</span></cpn>

    <!-- 替换指定插槽 -->
    <cpn>
        <span slot="left">left</span>
        <span slot="center">center</span>
        <span slot="right">right</span>
    </cpn>
</div>

<template id="cpn">
    <div>
        <!-- 当有多个插槽时,可以给slot设置name属性,来指定slot插入
一旦slot设置了name属性,在使用的时候标签中必须使用slot属性指定插槽的name -->
        <!-- 默认插槽 -->
        <slot><button>组件按钮</button></slot>

        <slot name="left"><button>返回</button></slot>
        <slot name="center"><input type="text" value="请输入搜索内容"></slot>
        <slot name="right"><button>搜索</button></slot>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>

    const cpn = {
        template: '#cpn',
    }

    const app = new Vue({
        el: '#app',
        data: {
            message: 'zfer'
        },
        components: {
            cpn
        }
    })
</script>
  • 作用域插槽
    • 编译的作用域:父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译
    • 作用域插槽的使用:1 template标签上绑定数据languages; 2 组件标签上使用slot-scope获取slotProps
<!-- 父组件替换插槽的标签,但是内容由子组件来提供 -->
<div id="app">
    <cpn></cpn>
    <cpn>
        <!-- 2. 在使用组件时,最好在template标签下使用slot-scope属性,来获取组件传来的值 -->
        <!-- vue2.5.x版本以前必须使用template标签包裹 -->
        <template slot-scope="slot">
            <!-- join函数 -->
            <span>{{slot.languages.join(' * ')}}</span>
        </template>
    </cpn>
</div>

<template id="cpn">
    <div>
        <!-- 1. 在模板slot上绑定data --- 命名是随意的但是必须使用v-bind -->
        <slot :languages="languages">
            <ul>
                <li v-for="language in languages">{{language}}</li>
            </ul>
        </slot>
    </div>
</template>

<script src="../js/vue.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        components: {
            cpn: {
                template: "#cpn",
                data() {
                    return {
                        languages: ["java", "vue", "javascript", "c++"],
                    };
                },
            },
        },
    });
</script>

4 Vue CLI详解


4.1 ES6模块化实现
  • 导入方式
// 1.导入的{}中定义的变量
import {flag, sum} from "./aaa.js";

if (flag) {
    console.log('小明是天才, 哈哈哈');
    console.log(sum(20, 30));
}

// 2.直接导入export定义的变量
import {num1, height} from "./aaa.js";

console.log(num1);
console.log(height);

// 3.导入 export的function/class
import {mul, Person} from "./aaa.js";

console.log(mul(30, 50));

const p = new Person();
p.run()

// 4.导入 export default中的内容, 可以省略{}
import defaultname from "./aaa.js";

defaultname('你好啊');

// 5.统一全部导入
// import {flag, num, num1, height, Person, mul, sum} from "./aaa.js";
import * as aaa from './aaa.js'
console.log(aaa.flag);
console.log(aaa.height);
  • 导出方式
var name = '小明'
var age = 18
var flag = true

function sum(num1, num2) {
  return num1 + num2
}

if (flag) {
  console.log(sum(20, 30));
}

// 1.导出方式一:
export {
  flag, sum
}

// 2.导出方式二:
export var num1 = 1000;
export var height = 1.88


// 3.导出函数/类
export function mul(num1, num2) {
  return num1 * num2
}

export class Person {
  run() {
    console.log('在奔跑');
  }
}

// 4.export default
// 一个模块内只能有一个export default,不然导出的时候就乱套了
const address = '北京市'
export default address

export default function (argument) {
  console.log(argument);
}
4.2 webpack使用
  • webpack起步
    • 安装webpack,需要node.js的环境
      1. node -v #查看node版本
      2. npm install webpack@3.6.0 -g #全局安装webpack3.6.0
      3. npm install webpack@3.6.0 --save-dev #局部安装webpack3.6.0
    • 注意:在终端直接执行webpack命令,使用的全局安装的webpack;当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack。
    • webpack打包指令:webpack src/main.js dist/bundle.js
//1. webpack导入方式
const {add, mul} = require('./mathUtils.js')


//2. webpack导出方式
function add(num1, num2){
  return num1+num2
}
function mul(num1, num2){
  return num1*num2
}
// webpack方式导出
module.exports = {
  add,
  mul
}
  • webpack配置

    • 通过npm init生成package.json文件, 并配置脚本

    {
    “name”: “meetwebpack”,
    “version”: “1.0.0”,
    “main”: “index.js”,
    “scripts”: {
    “test”: “echo “Error: no test specified” && exit 1”,
    “build”: “webpack”
    },
    “author”: “”,
    “license”: “ISC”,
    “devDependencies”: {
    “babel-core”: “^6.26.3”,
    “babel-loader”: “^7.1.5”,
    “babel-preset-es2015”: “^6.24.1”,
    “css-loader”: “^2.0.2”,
    “file-loader”: “^3.0.1”,
    “less”: “^3.9.0”,
    “less-loader”: “^4.1.0”,
    “style-loader”: “^0.23.1”,
    “url-loader”: “^1.1.2”,
    “webpack”: “^3.6.0”
    },
    “description”: “”
    }

    
    - 创建`webpack.config.js`文件, 配置入口、出口
    
    ~~~js
    // 这里导入的path是node.js中的path: 通过npm init获取node中的path环境
    

const path = require(‘path’)

module.exports = {
entry: ‘./src/main.js’,
output: {
// __dirname的下划线是两个
path: path.resolve(__dirname, ‘dist’),
filename: ‘bundle.js’
}
}

// 通过npm install webpack@3.6.0 --save-dev 安装局部开发webpack,
// 最后会生成package.json、package-lock.json文件
~~~

  • webpack的loader:在开发中我们不仅仅有基本的js代码处理,我们也需要加载css、图片,也包括一些高级的将ES6转成ES5代码,将TypeScript转成ES5代码,将scss、less转成css,将.jsx、.vue文件转成js文件等等。给webpack扩展对应的loader就可以实现上述的功能。

    • loader使用过程:1 npm安装对应的loader(loader可以在webpack官网上找到);2在webpack.config.js中的modules关键字下进行配置;
    • 安装的loader:css-loader、style-loader、less-loader、url-loader、file-loader、ES6语法处理loader(babel-loader、babel-core、babel-preset-es2015)
/package.json中的devDependencies
"devDependencies": {
    "babel-core": "^6.26.3",
    "babel-loader": "^7.1.5",
    "babel-preset-es2015": "^6.24.1",
    "css-loader": "^2.0.2",
    "file-loader": "^3.0.1",
    "less": "^3.9.0",
    "less-loader": "^4.1.0",
    "style-loader": "^0.23.1",
    "url-loader": "^1.1.2",
    "webpack": "^3.6.0"
  },

      
// webpack.config.js中的moudle
module: {
    rules: [
        {
            test: /\.css$/i,
            // css-loader只负责将css文件进行加载
            // style-loader负责将样式添加到DOM中
            // 使用多个loader时,是从右往左加载 -- 注意先后顺序
            use: ["style-loader", "css-loader"],
        },
        {
            test: /\.less$/i,
            loader: [
                // compiles Less to CSS
                "style-loader",
                "css-loader",
                "less-loader",
            ],
        },
        {
            test: /\.(png|jpg|gif)$/i,
            use: [
                {
                    loader: 'url-loader',
                    options: {
                        // 当加载的图片,小于limit时,会将图片编译为base64字符串形式 -- 不需要打包到dist中
                        // 当加载的图片,大于limit时,需要使用file-loader模块进行加载 -- 需要打包到dist中
                        limit: 5632,
                        // 人为设置打包到dist文件夹下的图片路径, hash:8标识截取hash的前8位做图片名;ext是图片后缀
                        name: 'img/[name].[hash:8].[ext]'
                    },

                },
            ],
        },
        {
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            use: {
                // 利用babel将es6转为es5,
                // 安装命令如下:npm install --save-dev babel-loader@7.1.5 babel-core@6.26.3 babel-preset-es2015@6.24.1
                loader: 'babel-loader',
                options: {
                    presets: ['es2015']
                }
            }
        }
    ],
},
    

//main.js中导入css、less
// 导入base.css文件
require('./css/base.css')
// 导入.less文件
require('./css/special.less')
  • webpack配置vue

    1. 安装vue:npm install vue --save

    2. runtime-only问题:在webpack.config.js中配置

// 添加vue:runtime-compiler环境
resolve: {
alias: {
‘vue$’: ‘vue/dist/vue.esm.js’ // 用 webpack 时需用’vue/dist/vue.common.js’
}
}


  3. .vue文件封装处理::安装vue-loader、vue-template-compiler

- webpack配置plugin:loader主要用于转换某些类型的模块,它是一个转换器;plugin是插件,它是对webpack本身的扩展,是一个扩展器。

  - plugin的使用过程:1通过npm安装需要使用的plugins;2在webpack.config.js中的plugins中配置插件

~~~js
//webpack.config.js
// 引用插件
plugins: [
    new HtmlWebpackPlugin({
        template: 'index.html' //为了在dist index.html中配置index.html中的div#app
    }),
    new UglifyJsPlugin(), //压缩dist中的bundle.js
    new webpack.BannerPlugin('最终版权归zfcer所有'),
]
  • webpack-server配置

    • 安装webpack-server: npm install --save-dev webpack-dev-server@2.9.1

    • 在webpack.config.js中配置

    devServer: {
    contentBase: ‘./dist’,
    inline: true //inline表示页面实时刷新
    }

    
    
  • 配置分离

    • 配置base.config.js,在dev.config.js和pro.config.js中导入基本配置

请添加图片描述

  • 在package.json中使用配置

“scripts”: {
“test”: “echo “Error: no test specified” && exit 1”,
“build”: “webpack --config ./build/pro.config.js”,
“dev”: “webpack-dev-server --open --config ./build/dev.config.js”
},
~~~

4.3 Vue CLI2
  • 安装Vue CLI: npm install -g @vue/cli@3.0.4npm install -g @vue/cli-init按照Vue CLI2的方式初始化项目

  • 初始化项目:vue init webpack my-project

请添加图片描述

  • 目录结构

请添加图片描述

  • Runtime-Compiler和Runtime-only的区别

请添加图片描述

  • Vue程序运行过程: template --> ast --> render --> VDOM --> UI
    请添加图片描述

当我们在使用.vue文件时,.vue中的template是由vue-template-compiler来处理的,所以可以直接使用render来渲染。runtime only ,比runtime compiler少6k空间 (效率更高,空间更小)。

  • npm run build

请添加图片描述

  • npm run dev

请添加图片描述

  • webpack.base.conf.js起别名

请添加图片描述

4.4 Vue CLI3
  • Vue CLI3 与 Vue CLI2区别

    • vue-cli 3 是基于 webpack 4 打造,vue-cli 2 还是 webapck 3
    • vue-cli 3 的设计原则是“0配置”,移除的配置文件根目录下的,build和config等目录
    • vue-cli 3 提供了 vue ui 命令,提供了可视化配置,更加人性化
    • 移除了static文件夹,新增了public文件夹,并且index.html移动到public中
  • 创建Vue CLI3项目: npm create my-project

请添加图片描述

  • 目录结构

请添加图片描述

  • vue ui上查看配置:一大堆配置文件被放在node_modules/@vue/cli-service/lib/Service.js文件下

请添加图片描述

  • 起别名,在项目目录下创建vue.config.js文件

请添加图片描述

5 vue-router


5.1 vue-router的使用
  • 后端路由&前端路由

    • 后端路由:早期的网站开发整个HTML页面是由服务器来渲染的(JSP)。当我们页面中需要请求不同的路径内容时, 交给服务器来进行处理, 服务器渲染好整个页面, 并且将页面返回给客户端.p这种情况下渲染好的页面, 不需要单独加载任何的js和css, 可以直接交给浏览器展示, 这样也有利于SEO的优化.
    • 前端路由:
      1. 前后端分离阶段:后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中。(根据Ajax请求从静态服务器中获取对应的静态资源)
      2. 单页面富应用SPA阶段:SPA最主要的特点就是在前后端分离的基础上加 了一层前端路由。(根据axios路由请求一次性把所有静态资源获取(路由lazy加载))
  • URL变更

    • location.href来修改url
    • history.pushState({}, ‘’, ‘/foo’)相当于压栈,在浏览器中可以点击返回按钮
    • history.replaceState({}, ‘’, ‘/foo’), 在浏览器中不能点击返回按钮
    • history.go(-1)弹栈一个元素,等价于history.back() (并不是真正弹栈)
    • history.forward() 则等价于 history.go(1)
  • 安装vue-router:npm install vue-router --save

  • 使用vue-router:1创建路由组件;2配置路由映射:组件与路径映射关系;3使用路由:通过和

    • 路由嵌套:children: [ ]
    • 参数传递:动态url,’/user/:userId’传递参数userId。在组件中通过this.$route.params.userId来获取
    • 导航守卫:通过在beforeEach中利用to来获取meta来传递导航栏标题, 通过to.matched[0].meta.title来获取meta参数
    • kepp-alive:keep-alive包裹router-view设置访问缓存, 这样可以让组件created只执行一次(缓存)。组件内的 activated 和 deactived 生命周期函数必须在设置keep-alive标签的包裹下来生效
      • : 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个标签.
      • : 该标签会根据当前的路径, 动态渲染出不同的组件.
    //index.js
    import Vue from 'vue'
    import Router from 'vue-router'
    
    // 路由懒加载: 不要直接注册组件
    const Home = () => import('@/components/Home')
    const HomeMessages = () => import('@/components/HomeMessages')
    const HomeNews = () => import('@/components/HomeNews')
    const About = () => import('@/components/About')
    const User = () => import('@/components/User')
    const Profile = () => import('@/components/Profile')
    
    // 1. 通过Vue.use(插件),安装插件
    Vue.use(Router)
    // 源码分析:Vue.use(Router)的内部操作是install(Vue类), 在install内部全局注册了RouterView 和 RouterLink
    
    const routes = [
       {
          path: '/',
          redirect: '/home',
          meta: {
            title: '首页'
          },
        },
        {
          path: '/home',
          component: Home,
          meta: {
            title: '首页'
          },
          children: [
            {
              path: '',
              component: HomeNews
            },
            {
              path: 'news', //children中的path一定不能加'/', routes中的path可以加'/'
              component: HomeNews
            },
            {
              path: 'messages', 
              component: HomeMessages
            }
          ]
        },
        {
          path: '/about',
          component: About,
          meta: {
            title: '关于'
          },
        },
        {
          path: '/user/:userId', //注册动态url
          component: User,
          meta: {
            title: '用户'
          },
        },
        {
          path: '/profile',
          component: Profile,
          meta: {
            title: '档案'
          },
        }
    ]
    
    // 2. 创建Router对象, 并导出Router
    const router =  new Router({
      routes,
      mode: 'history', //默认使用的是hash, 这里替换为history方式没有#
      linkActiveClass: 'active', //linkActiveClass在路由上设置默认激活样式; active是App.vue中定义的style
    })
    
    router.beforeEach((to, from, next) => {
      document.title = to.matched[0].meta.title //获取meta.title
      next() //释放router,类似filter效果
      console.log(to) // 当存在children时,直接取meta是没有值的。通过打印发现,在matched数组中有要的meta.title
    })
    
    // 3. 将router对象传入的Vue实例中
    export default router
    
    


~~~js
 //main.js
 import Vue from 'vue'
 import App from './App'
 import router from './router' //自动找router/index.js
 
 Vue.config.productionTip = false
 
 /* eslint-disable no-new */
 new Vue({
   el: '#app',
   router, //将router传给Vue类中的$router, 所以router 与 $router是同一个对象
   render: h => h(App)
 })
<template>
<div id="app">
    <!-- router-link请求path -->
    <!-- router-link属性: 
to表示映射的path;
tag表示渲染成其他标签;
replace表示使用replaceStatus,让浏览器无法记录栈,而不能操作返回按钮;
active-class当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-active的class
router-link-active是router-link标签中被选中的元素默认加上的class -->
    <!-- <router-link to="/home" tag="button" replace active-class="active">Home</router-link>
<router-link to="/about" replace active-class="active">About</router-link> -->
    <!-- <router-link to="/home" tag="button" replace>Home</router-link>
<router-link to="/about" replace>About</router-link> -->

    <br>
    <br>
    <br>
    <button @click="homeClick">Home</button>
    <button @click="aboutClick">About</button>
    <!-- <router-link :to="'/user/'+userId">User</router-link>
<router-link :to="{path: '/profile', query: {name: 'zfcer', age: 23, height: 1.87}}">Profile</router-link> -->
    <button @click="userClick">User</button>
    <button @click="profileClick">Profile</button>
    <!-- router-view占位展示 -->
    <!-- keep-alive包裹router-view设置访问缓存, 这样可以让组件created只执行一次(缓存) -->
    <!-- exclude可以用来取消组件缓存,exclude本身是正则表达式,所以不能有空格 -->
    <keep-alive exclude="User">
        <router-view/>
    </keep-alive>

    </div>
</template>

<script>
    export default {
        name: 'App',
        data(){
            return {
                userId: '1206682'
            }
        },
        methods: {
            homeClick(){
                this.$router.push('/home')
                console.log('homeClick run')
            },
            aboutClick(){
                this.$router.push('/about')
                console.log('aboutClick run')
            },
            userClick(){
                this.$router.push('/user/' + this.userId)
            },
            profileClick(){
                this.$router.push({
                    path: '/profile',
                    query: {
                        name: 'best',
                        age: 18,
                        height: 1.87
                    }
                })
            }
        }
    }

    // 1. $router 和 $route 是Vue类的原型中定义的:
    //     源码中的install.js中通过object.defineProperty(Vue.prototype, '$router', ...)完成$router 和 $route的注册
    // 2. 所有自己创建的组件都继承自Vue类,所以都有$router 和 $route
    // 3. 将router (main.js中定义的)传给Vue类中的$router, 所以router 与 $router是同一个对象
    // 4. $route 表示当前被激活的route
    // $router 与 $route 都来自Vue类中的this._routerRoot对象
</script>

<style>
    /* router-link-active是vue默认加上的class */
    .active{
        color: #f00;
    }
</style>
  • $router 和 $route 说明

    1. $router 和 r o u t e 是 V u e 类 的 原 型 中 定 义 的 : 源 码 中 的 i n s t a l l . j s 中 通 过 ‘ o b j e c t . d e f i n e P r o p e r t y ( V u e . p r o t o t y p e , ′ route 是Vue类的原型中定义的:源码中的install.js中通过`object.defineProperty(Vue.prototype, ' routeVueinstall.jsobject.defineProperty(Vue.prototype,router’, …)`完成​$router 和 $route的注册

    2. 所有自己创建的组件都继承自Vue类,所以都有$router 和 $route

    3. 将router (main.js中定义的)传给Vue类中的$router, 所以router 与 $router是同一个对象

    4. $route 表示当前被激活的route

    5. $router 与 $route 都来自Vue类中的this._routerRoot对象

5.2 案例tabbar

学会封装组件

请添加图片描述

6 Vuex详解


6.1 Promise语法
// Promise基本使用
new Promise((resolve, reject) => {
    setTimeout(() => {
        // 成功的时候调用resolve
        resolve("Hello World");

        // 失败的时候调用reject
        // reject("error message");
    }, 1000);
})
    .then((data) => {
    // data是resolve传过来的数据
    // 1.100行的处理代码
    console.log(data);
    console.log(data);
    console.log(data);
    console.log(data);
    console.log(data);
})
    .catch((err) => {
    // err是reject传过来的数据
    console.log(err);
});

// Promise all 实现两个请求同时完成后再进行下一步操作
Promise.all([
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ name: "why", age: 18 });
        }, 2000);
    }),
    new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve({ name: "kobe", age: 19 });
        }, 1000);
    }),
]).then((results) => {
    console.log(results);
});
6.2 Vuex使用

Vuex就是为了提供这样一个在多个组件间共享状态的插件,Vuex扮演着大管家的角色(全局单例模式)

请添加图片描述

  • vuex安装:npm install vuex --save

  • vuex使用步骤:1注册插件; 2创建对象并导出store; 3使用store

    //index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    import {INCREMENT} from '@/store/mutations-types.js'
    
    const moduleA = {
        state: {
          // 模块中的state调用:this.$store.state.a.name
          name: 'zfcer'
        },
        mutations: {
          // 模块中的mutations调用: this.$store.commit('mutations名字'), 调用方式不变
          updateName(state, payload){
            state.name = payload
          }
        },
        actions: {
          aUpdateName(context) {
            setTimeout(() => {
              console.log('模块内actions执行了')
              // 模块内的actions只能调用模块内的mutations
              context.commit('updateName', '模块内的actions调用了模块内的mutations')
            })
          }
        },
        getters: {
          // 模块中的getters调用: this.$store.getters.fullName 调用方式不变。所以方法名不要与模块外的方法名重复
          fullName(state){
            return state.name + '111'
          },
          fullName2(state, getters){
            return getters.name + '222'
          },
          fullName3(state, getters, rootState){
            // rootState获取Root的state
            return getters.fullName2 + rootState.counter
          }
        }, 
        modules: {
          
        }
    }
    
    // 1. 注册插件
    Vue.use(Vuex)
    
    // 2. 创建对象, 并导出
    export default new Vuex.Store({
      state: {
        // 共享变量
        counter: 1000,
        students: [
          {id: 110, name: 'zfcer', age: 23},
          {id: 111, name: 'best', age: 26},
          {id: 112, name: 'one', age: 18},
        ],
        info: {
          name: 'zfcer',
          age: 23,
          height: 1.87
        }
      },
      mutations: {
        // Vuex的store状态的更新唯一方式:提交Mutations
        // 方法中是 默认传入state参数的
        // mutations中提交参数可以通过payload提交对象
        [INCREMENT](state){
          state.counter++
        },
        decrement(state){
          state.counter--
        },
        incrementCountOne(state, payload){
          console.log(payload) //payload是一个对象
        },
        incrementCount(state, payload){
          console.log(payload.cnt1 + payload.cnt2)
          state.counter += payload.cnt1
        },
        addStudent(state, stu){
          state.students.push(stu)
        },
        updateInfo(state){
          // 直接修改info中已有的属性是响应式的,这是因为Vue对属性做了监听可以做到响应式
          state.info.name = 'zfcer best'
    
          // 直接在info中添加新属性不是响应式的
          // state.info['address'] = '苏州'
          // Vue.set(state.info, 'address', '苏州') //利用Vue.set实现响应式
    
          // 直接删除info中属性不是响应式的
          // delete state.info.age
          // Vue.delete(state.info, 'age') //利用Vue.delete实现响应式
        }
      },
      actions: {
        // actions用来处理异步操作
    
        // 方法一
        // context上下文,可以理解为就是store对象
        // payload传递参数
        // aUpdateInfo(context, payload){
        //   console.log("---------------")
        //   setTimeout(() => {
        //     context.commit('updateInfo')
        //     console.log(payload.message)
        //     payload.success() //执行回调函数
        //   }, 1000)
        // }
    
        // 方法二
        aUpdateInfo(context, payload){
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              context.commit('updateInfo')
              console.log(payload)
              
              resolve('内部执行成功') //传递信息可以被外部then获取
            }, 1000)
          })
        }
      },
      getters: {
        // 与计算属性类似
        powerCounter(state){
          return state.counter * state.counter
        },
        more20Stus(state){
          return state.students.filter(s => s.age >= 20)
        },
        more20StusLength(state, getters){
          // 在getters中可以定义getters参数来获得getters中其他函数
          return getters.more20Stus.length
        },
        moreAgeStus(state){ 
          // 通过内部创建函数来获得moreAgeStus传过来的参数
          // return function(age){
          //   return state.students.filter(s => s.age >= age)
          // }
          return age => state.students.filter(s => s.age >= age)
        }
      },
      modules: {
        // 模块最终会被加载到state中,还是一个实体
        a: moduleA
      }
    })
    
    
    // 对象的解构
    const obj = {
      name: 'zfcer',
      age: 18,
      height: 1.87
    }
    const {name, height, age} = obj
    
    // 数组解构
    const names = ['zfcer', 'best', 'one']
    const [name1, name2, name3] = names
    
    // main.js
    import Vue from 'vue'
    import App from './App'
    import router from './router'
    import store from './store'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      store, //3. 使用store
      render: h => h(App)
    })
    
  • vuex项目结构

请添加图片描述

7 axios


  • 全局Axios
// 一、直接用axios是用的全局Axios
// axios全局配置
axios.defaults.baseURL = 'http://152.136.185.210:7878/api/m5'
axios.defaults,headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.timeout = 5000

// 1. axios基本使用
// 指定method的请求,默认是get请求
axios({
  // 局部配置
  baseURL: 'http://152.136.185.210:7878/api/m5',
  url: '/home/multidata',
  method: 'get'
}).then(res => console.log(res))

// get请求
axios.get('/home/multidata')

// 带参数的get请求
axios({
  url: '/home/data',
  // 针对get请求的参数拼接
  params: {
    type: 'pop',
    page: 1
  }
}).then(res => console.log(res))

// 2. axios并发使用
// then中获取结果集
axios.all([axios({
  url: '/home/multidata',
}), axios({
  url: '/home/data',
  // 针对get请求的参数拼接
  params: {
    type: 'sell',
    page: 5
  }
})])
.then(res => {
  //合并后的结果
  console.log(res)
})

// then中获取分别的结果
axios.all([axios({
  url: 'http://123.207.32.32:8000/home/multidata',
}), axios({
  url: 'http://152.136.185.210:7878/api/m5/home/data',
  // 针对get请求的参数拼接
  params: {
    type: 'sell',
    page: 5
  }
})])
.then(axios.spread((res1, res2) => {
  //两个异步请求分别的结果
  console.log(res1)
  console.log(res2)
}))
  • 局部Axios
//  二、axios实例创建与使用 
const instance = axios.create({
  baseURL: 'http://152.136.185.210:7878/api/m5',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
instance({
  url: '/home/data',
  method: 'get'
}).then(res => {
  console.log(res)
}).catch(err => {
  console.log(err)
})
  • 封装axios:创建network文件夹–>request.js工具类,在main.js中调用封装的request
//--------------------request.js---------------------------
import axios from 'axios'

export function request1(config, success, failure){
  // 1.创建axios实例
  const instance = axios.create({
    baseURL: 'http://152.136.185.210:7878/api/m5',
    timeout: 5000
  })


  // 2.1.axios拦截器
  instance.interceptors.request.use(config => {
    console.log(config)
    // (1)config中一些信息不符合服务器要求需要拦截
    // (2)每次发送网络请求时,都希望在界面中显示一个请求的图标
    // (3)某些网络请求(比如登录token),必须携带一些特殊信息
    return config //释放拦截,如果不释放拦截就无法继续执行
  }, err => {
    console.log(err)
  }) 
  // 2.2.axios响应拦截
  instance.interceptors.response.use(res => {
    console.log(res)
    // 做一些响应处理拦截
    return res.data //只需要把data信息返回即可
  }, err => {
    console.log(err)
  })


  // 3.发送真正的网络请求
  instance(config)
  .then(res => {
    // console.log(res);
    success(res)
  })
  .catch(err => {
    // console.log(err)
    failure(err)
  })
}

export function request2(config){
  // 创建axios实例
  const instance = axios.create({
    baseURL: 'http://152.136.185.210:7878/api/m5',
    timeout: 5000
  })

  // 发送真正的网络请求
  instance(config.baseConfig)
  .then(res => {
    // console.log(res);
    config.success(res)
  })
  .catch(err => {
    // console.log(err)
    config.failure(err)
  })
}


export function request3(config) {
  return new Promise((resolve, reject) => {
    // 创建axios实例
    const isntance = axios.create({
      baseURL: 'http://152.136.185.210:7878/api/m5',
      timeout: 5000
    })

    // 发送真正的网络请求
    instance(config)
    .then(res => {
      resolve(res)
    })
    .catch(err => {
      reject(err)
    })
  })
}

export function request4(config) {
 // 创建axios实例
  const instance = axios.create({
    baseURL: 'http://152.136.185.210:7878/api/m5',
    timeout: 5000
  })

  // 发送真正的网络请求
  return instance(config) //因为axios实例本身就是Promise
}


//-------------------main.js----------------------------
// 三、axios封装
import {request1, request2, request3, request4} from '@/network/request.js'

request1({
  url: '/home/multidata'
}, res => {
  console.log(res)
}, err => { 
  console.log(err)
})

request2({
  baseConfig: '/home/multidata',
  success: function(res){
    console.log(res)
  },
  failure: function(err){
    console.log(err)
  }
})

request3({
  url: '/home/multidata'
}).then(res => console.log(res))
.catch(err => console.log(err))

request4({
  url: '/home/multidata'
}).then(res => console.log(res))
.catch(err => console.log(err))

8 项目实战


项目还在学习ing

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值