IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> Vuejs学习笔记终 -> 正文阅读

[JavaScript知识库]Vuejs学习笔记终

Vuejs学习笔记 学习资料来源于哔哩哔哩网课

Vue学习

Vue Day 01

一. Vuejs

1.1. 认识Vuejs

  • 为什么学习Vuejs?

  • Vue的读音
    读音 /vju?/,类似于 view

  • Vue的渐进式
    渐进式意味着你可以将Vue作为你应用的一部分嵌入其中,带来更丰富的交互体验。即一点点的对项目进行重构,比如将项目里的jQuery一点一点去掉用vue替代。
    或者如果你希望将更多的业务逻辑使用Vue实现,那么Vue的核心库以及其生态系统。
    比如Core+Vue-router+Vuex,也可以满足你各种各样的需求。

  • Vue的特点
    (先简单了解,后面加深)
    解耦视图和数据
    可复用的组件
    前端路由技术
    状态管理
    虚拟DOM
    PS:用了vue一般都不用jQuery了,只有老项目还存在jQuery

1.2. 安装Vue

  • CDN引入
    为了快,不用随时调用,前期学习建议下载引入
<!-- 开发环境版本,包含了有帮助的命令行警告 --> 
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
  • 下载引入(官方网站—安装)
    开发环境 https://vuejs.org/js/vue.js
    生产环境 https://vuejs.org/js/vue.min.js

  • npm安装(主流)
    后续通过webpack和CLI的使用,我们使用该方式。

1.3. Vue的初体验

  • Hello Vuejs PPT*6
    • mustache -> 体验vue响应式
  • Vue列表展示 PPT*7
    • v-for
    • 后面给数组追加元素的时候, 新的元素也可以在界面中渲染出来
  • Vue计数器小案例 PPT*8
    • 事件监听: click -> methods

1.4. Vue中的MVVM

详细看 PPT*9
Model–view–viewmodel
通常我们学习一个概念,最好的方式是去看维基百科(千万别看成了百度百科)
https://zh.wikipedia.org/wiki/MVVM
维基百科的官方解释,这里不再赘述。

1.5. 创建Vue时, options可以放那些东西

option
详细解析: https://cn.vuejs.org/v2/api/#%E9%80%89%E9%A1%B9-%E6%95%B0%E6%8D%AE

  • 开发中什么时候称之为方法, 什么时候称之为函数?

    • 方法: method(类里的东西)
    • 函数: function
  • el:

    • 类型:string | HTMLElement
    • 作用:决定之后Vue实例会管理哪一个DOM。
  • data:

    • 类型:Object | Function (在组件当中data必须是一个函数)
    • 作用:Vue实例对应的数据对象。
  • methods:

    • 类型:{ [key: string]: Function }
    • 作用:定义属于Vue的一些方法,可以在其他地方调用,也可以在指令中使用。
  • 生命周期函数 PPT*12
    Vue源码:https://github.com/vuejs/vue/tree/v2.6.14
    开发过程中分两个版本:debug/release
    GitHub上下载release版本

代码规范

代码规范:缩进4个空格, 2个空格.
CLI -> 自动创建.editconfig用于规范代码 2个空格.

vue的template :vscode在用户片段里设置,详细看http://mtw.so/5TnpAu

二.插值语法

  • mustache语法
    • 就是双大括号
    • 翻译:胡子/胡须
<div id="app">
  <h2>{{message}}</h2>
  <h2>{{message}}, 李银河!</h2>

  <!--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: {
      message: '你好啊',
      firstName: 'kobe',
      lastName: 'bryant',
      counter: 100
    },
  })
</script>
  • v-once PPT*16
    • 该指令后面不需要跟任何表达式(比如之前的v-for后面是由跟表达式的)
    • 该指令表示元素和组件(组件后面才会学习)只渲染一次,不会随着数据的改变而改变。
      用console不会被渲染
      与v-for不一样
  • v-html
    • 如果我们直接通过{{}}来输出,会将HTML代码也一起输出。
    • 但是我们可能希望的是按照HTML格式进行解析,并且显示对应的内容。
<div id="app">
  <h2>{{url}}</h2>
  <h2 v-html="url"></h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      url: '<a href="http://www.baidu.com">百度一下</a>'
    }
  })
</script>
  • v-text
    • v-text作用和Mustache比较相似:都是用于将数据显示在界面中
    • v-text通常情况下,接受一个string类型
    • 不好用,拼接很麻烦,会覆盖
<div id="app">
  <h2>{{message}}, 李银河!</h2>
  <h2 v-text="message">, 李银河!</h2>
</div>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>
  • v-pre: {{}}
    • v-pre用于跳过这个元素和它子元素的编译过程,用于显示原本的Mustache语法。
<div id="app">
  <h2>{{message}}</h2>
  <h2 v-pre>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>
  • v-cloak: 斗篷
    • 在某些情况下,我们浏览器可能会直接显然出未编译的Mustache标签。
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    [v-cloak] {
      display: none;
    }
  </style>
</head>
<body>

<div id="app" v-cloak>
  <h2>{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  // 在vue解析之前, div中有一个属性v-cloak
  // 在vue解析之后, div中没有一个属性v-cloak
  setTimeout(function () {
    const app = new Vue({
      el: '#app',
      data: {
        message: '你好啊'
      }
    })
  }, 1000)
</script>

三. v-bind

3.1. v-bind绑定基本属性

除了内容需要动态来决定外,某些属性我们也希望动态来绑定。
比如动态绑定a元素的href属性
比如动态绑定img元素的src属性

  • v-bind
    • 作用:动态绑定属性
    • 缩写::
    • 预期:any (with argument) | Object (without argument)
    • 参数:attrOrProp (optional)
  • v-bind:sr
  • :href
    • 作用:动态绑定属性
    • 缩写::
    • 预期:any (with argument) | Object (without argument)
    • 参数:attrOrProp (optional)
  • v-bind语法糖
    • <img :src="imgURL" alt="">
<div id="app">
  <!-- 错误的做法: 这里不可以使用mustache语法-->
  <!--<img src="{{imgURL}}" alt="">-->
  <!-- 正确的做法: 使用v-bind指令 -->
  <img v-bind:src="imgURL" alt="">
  <a v-bind:href="aHref">百度一下</a>
  <!--<h2>{{}}</h2>-->

  <!--语法糖的写法-->
  <img :src="imgURL" alt="">
  <a :href="aHref">百度一下</a>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      imgURL: 'https://img11.360buyimg.com/mobilecms/s350x250_jfs/t1/20559/1/1424/73138/5c125595E3cbaa3c8/74fc2f84e53a9c23.jpg!q90!cc_350x250.webp',
      aHref: 'http://www.baidu.com'
    }
  })
</script>

3.2. v-bind动态绑定class

  • v-bind动态绑定class(对象)语法
    • 当数据为某个状态时,字体显示红色。
    • 当数据另一个状态时,字体显示黑色。
  • 对象语法: 作业 :class=’{类名: boolean}’
    • 用法一:直接通过{}绑定一个类
      <h2 :class="{'active': isActive}">Hello World</h2>

    • 用法二:也可以通过判断,传入多个值
      <h2 :class="{'active': isActive, 'line': isLine}">Hello World</h2>

    • 用法三:和普通的类同时存在,并不冲突
      注:如果isActive和isLine都为true,那么会有title/active/line三个类
      <h2 class="title" :class="{'active': isActive, 'line': isLine}">Hello World</h2>

    • 用法四:如果过于复杂,可以放在一个methods或者computed中
      注:classes是一个计算属性
      <h2 class="title" :class="classes">Hello World</h2>

<head>
  <meta charset="UTF-8">
  <title>Title</title>

  <style>
    .active {
      color: red;
    }
  </style>
</head>
<body>

<div id="app">
  <!--<h2 class="active">{{message}}</h2>-->
  <!--<h2 :class="active">{{message}}</h2>-->

  <!-- 对象里可以放键值对 -->
  <!--<h2 v-bind:class="{key1: value1, key2: value2}">{{message}}</h2>-->
  <!--<h2 v-bind:class="{类名1: true, 类名2: boolean}">{{message}}</h2>-->
  <!-- class和动态绑定,最后会一起合并 -->
  <h2 class="title" v-bind:class="{active: isActive, line: isLine}">{{message}}</h2>
  <h2 class="title" v-bind:class="getClasses()">{{message}}</h2>
  <button v-on:click="btnClick">按钮</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      //默认为true
      isActive: true,
      isLine: true
    },
    methods: {
      btnClick: function () {
        //每次取反
        this.isActive = !this.isActive
      },
      getClasses: function () {
        return {active: this.isActive, line: this.isLine}
      }
    }
  })
</script>
  • 数组语法:
    • 用法一:直接通过{}绑定一个类
      <h2 :class="['active']">Hello World</h2>

    • 用法二:也可以传入多个值
      <h2 :class=“[‘active’, 'line']">Hello World</h2>

    • 用法三:和普通的类同时存在,并不冲突
      注:会有title/active/line三个类
      <h2 class="title" :class=“[‘active’, 'line']">Hello World</h2>

    • 用法四:如果过于复杂,可以放在一个methods或者computed中
      注:classes是一个计算属性
      <h2 class="title" :class="classes">Hello World</h2>

<div id="app">
  <h2 class="title" :class="[active, line]">{{message}}</h2>
  <h2 class="title" :class="getClasses()">{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      active: 'aaaaaa',
      line: 'bbbbbbb'
    },
    methods: {
      getClasses: function () {
        return [this.active, this.line]
      }
    }
  })
</script>

3.3. v-bind动态绑定style

我们可以利用v-bind:style来绑定一些CSS内联样式。
我们可以使用驼峰式 (camelCase) fontSize
或短横线分隔 (kebab-case,记得用单引号括起来) ‘font-size’

  • 绑定方式一:对象语法
    :style="{color: currentColor, fontSize: fontSize + 'px'}"
    • style后面跟的是一个对象类型
    • 对象的key是CSS属性名称
    • 对象的value是具体赋的值,值可以来自于data中的属性
  • 对象语法:
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .title {
      font-size: 50px;
      color: red;
    }
  </style>
</head>
<body>

<div id="app">
  <!--<h2 :style="{key(属性名): value(属性值)}">{{message}}</h2>-->

  <!--'50px'必须加上单引号, 否则是当做一个变量去解析-->
  <!--<h2 :style="{fontSize: '50px'}">{{message}}</h2>-->

  <!--finalSize当成一个变量使用-->
  <!--<h2 :style="{fontSize: finalSize}">{{message}}</h2>-->
  <h2 :style="{fontSize: finalSize + 'px', backgroundColor: finalColor}">{{message}}</h2>
  <h2 :style="getStyles()">{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      finalSize: 100,
      finalColor: 'red',
    },
    methods: {
      getStyles: function () {
        return {fontSize: this.finalSize + 'px', backgroundColor: this.finalColor}
      }
    }
  })
</script>
  • 数组语法:
  • 绑定方式二:数组语法
    <div v-bind:style="[baseStyles, overridingStyles]"></div>
    • style后面跟的是一个数组类型
    • 多个值以,分割即可
<div id="app">
  <h2 :style="[baseStyle, baseStyle1]">{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      baseStyle: {backgroundColor: 'red'},
      baseStyle1: {fontSize: '100px'},
    }
  })
</script>

四. 计算属性

  • 在模板中可以直接通过插值语法显示一些data中的数据。
  • 但是在某些情况,我们可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示
    • 比如我们有firstName和lastName两个变量,我们需要显示完整的名称。
  • 但是如果多个地方都需要显示完整的名称,我们就需要写多个{{firstName}} {{lastName}}
  • 我们可以将上面的代码换成计算属性:
    • 而计算属性是写在实例的computed选项中的。

  • 案例一: firstName+lastName
<div id="app">
  <h2>{{firstName + ' ' + lastName}}</h2>
  <h2>{{firstName}} {{lastName}}</h2>

  <h2>{{getFullName()}}</h2>

  <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      firstName: 'Lebron',
      lastName: 'James'
    },
    // computed: 计算属性()
    computed: {
      fullName: function () {
        return this.firstName + ' ' + this.lastName
      }
    },
    methods: {
      getFullName() {
        return this.firstName + ' ' + this.lastName
      }
    }
  })
</script>

  • 案例二: books -> price
<body>

<div id="app">
  <h2>总价格: {{totalPrice}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      books: [
        {id: 110, name: 'Unix编程艺术', price: 119},
        {id: 111, name: '代码大全', price: 105},
        {id: 112, name: '深入理解计算机原理', price: 98},
        {id: 113, name: '现代操作系统', price: 87},
      ]
    },
    computed: {
      totalPrice: function () {
        let result = 0
        for (let i=0; i < this.books.length; i++) {
          result += this.books[i].price
        }
        return result

        // for (let i in this.books) {
        //   this.books[i]
        // }
        //
        // for (let book of this.books) {
        //
        // }
      }
    }
  })
</script>
</body>

计算属性的setter和getter

  • 每个计算属性都包含一个getter和一个setter
  • 一般情况不写get,默认只有setter
    具体代码:
<body>

<div id="app">
  <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      firstName: 'Kobe',
      lastName: 'Bryant'
    },
    computed: {
      // fullName: function () {
      //   return this.firstName + ' ' + this.lastName
      // }
      // name: 'leng'
      // 计算属性一般是没有set方法, 只读属性.
      fullName: {
        //可用于赋值方法
        set: function(newValue) {
          // console.log('-----', newValue);
          //截取,对字符串进行封装
          const names = newValue.split(' ');
          this.firstName = names[0];
          this.lastName = names[1];
        },
        get: function () {
          return this.firstName + ' ' + this.lastName
        }
      },
      //写下面就可以替代上面的,只是一个get
      // fullName: function () {
      //   return this.firstName + ' ' + this.lastName
      // }
    }
  })
</script>

</body>

计算属性的缓存

  • 与method的对比
    • 计算属性会进行缓存,如果多次使用时,计算属性只会调用一次。

代码如下:

<body>

<div id="app">
  <!--1.直接拼接: 语法过于繁琐-->
  <h2>{{firstName}} {{lastName}}</h2>

  <!--2.通过定义methods-->
  <!--<h2>{{getFullName()}}</h2>-->
  <!--<h2>{{getFullName()}}</h2>-->
  <!--<h2>{{getFullName()}}</h2>-->
  <!--<h2>{{getFullName()}}</h2>-->

  <!--3.通过computed-->
  <h2>{{fullName}}</h2>
  <h2>{{fullName}}</h2>
  <h2>{{fullName}}</h2>
  <h2>{{fullName}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  // angular -> google
  // TypeScript(microsoft) -> ts(类型检测)
  // flow(facebook) ->
  const app = new Vue({
    el: '#app',
    data: {
      firstName: 'Kobe',
      lastName: 'Bryant'
    },
    methods: {
      getFullName: function () {
        console.log('getFullName');
        return this.firstName + ' ' + this.lastName
      }
    },
    computed: {
      fullName: function () {
        console.log('fullName');
        return this.firstName + ' ' + this.lastName
      }
    }
  })

</script>

</body>

Vue Day 02

五. 事件监听

5.1 v-on

  • 监听用户发生的时间,比如点击、拖拽、键盘事件等等
  • v表示Vue
    • 作用:绑定事件监听器
    • 缩写:@
    • 预期:Function | Inline Statement | Object
    • 参数:event

代码如下:

<body>

<div id="app">
  <h2>{{counter}}</h2>
  <!--<h2 v-bind:title></h2>-->
  <!--<h2 :title></h2>-->
  <!--<button v-on:click="counter++">+</button>-->
  <!--<button v-on:click="counter&#45;&#45;">-</button>-->
  <!--<button v-on:click="increment">+</button>-->
  <!--<button v-on:click="decrement">-</button>-->
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      counter: 0
    },
    methods: {
      increment() {
        this.counter++
      },
      decrement() {
        this.counter--
      }
    }
  })
</script>

</body>

5.1.1 v-on也有对应的语法糖

  • v-on:click可以写成@click
    • 情况一:如果该方法不需要额外参数,那么方法后的()可以不添加。

    但是注意:如果方法本身中有一个参数,那么会默认将原生事件event参数传递进去

    • 情况二:如果需要同时传入某个参数,同时需要event时,可以通过$event传入事件。
<body>
<div id="app">
  <!--1.事件调用的方法没有参数,加不加扩号都一样-->
  <button @click="btn1Click()">按钮1</button>
  <button @click="btn1Click">按钮1</button>

  <!--2.在事件定义时, 写方法时省略了小括号, 但是方法本身是需要一个参数的, 
  此时 Vue会默认将浏览器生产的event事件对象作为参数传入到方法-->
  <!--<button @click="btn2Click(123)">按钮2</button>//undefined-->
  <!--<button @click="btn2Click()">按钮2</button>-->
  <button @click="btn2Click">按钮2</button>

  <!--3.方法定义时, 我们需要event对象, 同时又需要其他参数-->
  <!-- 在调用方式, 如何手动的获取到浏览器产生的event对象: $event-->
  <button @click="btn3Click(abc, $event)">按钮3</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      abc: 123
    },
    methods: {
      btn1Click() {
        console.log("btn1Click");
      },
      btn2Click(event) {
        console.log('--------', event);
      },
      btn3Click(abc, event) {
        console.log('++++++++', abc, event);
      }
    }
  })

  // 如果函数需要参数,但是没有传入, 那么函数的形参为undefined
  // function abc(name) {
  //   console.log(name);
  // }
  //
  // abc()
</script>

</body>

5.1.2 v-on修饰符

  • 在某些情况下,拿到event的目的可能是进行一些事件处理。
  • Vue提供了修饰符来帮助我们方便的处理一些事件:
    • .stop - 调用 event.stopPropagation()。
    • .prevent - 调用 event.preventDefault()。
    • .{keyCode | keyAlias} - 只当事件是从特定键触发时才触发回调。
    • .native - 监听组件根元素的原生事件。
    • .once - 只触发一次回调。

代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <!--1. .stop修饰符的使用:停止冒泡,即分别冒泡-->
  <div @click="divClick">
    aaaaaaa
    <button @click.stop="btnClick">按钮</button>
  </div>

  <!--2. .prevent修饰符的使用:阻止默认行为-->
  <br>
  <form action="baidu">
    <input type="submit" value="提交" @click.prevent="submitClick">
  </form>

  <!--3. .监听某个键盘的键帽-->
  <input type="text" @keyup.enter="keyUp">

  <!--4. .once修饰符的使用。点击回调只触发一次-->
  <button @click.once="btn2Click">按钮2</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    methods: {
      btnClick() {
        console.log("btnClick");
      },
      divClick() {
        console.log("divClick");
      },
      submitClick() {
        console.log('submitClick');
      },
      keyUp() {
        console.log('keyUp');
      },
      btn2Click() {
        console.log('btn2Click');
      }
    }
  })
</script>
</body>
</html>

5.2 条件判断 v-if、v-else-if、v-else

  • 这三个指令与JavaScript的条件语句if、else、else if类似。
  • Vue的条件指令可以根据表达式的值在DOM中渲染或销毁元素或组件
  • v-if后面的条件为false时,对应的元素以及其子元素不会渲染。
    • 也就是根本没有不会有对应的标签出现在DOM中。

代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <h2 v-if="score>=90">优秀</h2>
  <h2 v-else-if="score>=80">良好</h2>
  <h2 v-else-if="score>=60">及格</h2>
  <h2 v-else>不及格</h2>

  <h1>{{result}}</h1>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      score: 99
    },
    //条件很多的时候,建议使用计算属性
    computed: {
      result() {
        let showMessage = '';
        if (this.score >= 90) {
          showMessage = '优秀'
        } else if (this.score >= 80) {
          showMessage = '良好'
        }
        // ...
        return showMessage
      }
    }
  })
</script>

</body>
</html>

5.3 条件渲染案例*用户登录切换的案例

  • 用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
    用户登录切换的案例
  • 用户再登录时,可以切换使用用户账号登录还是邮箱地址登录。
<body>
//span作为其的一个父级元素
<div id="app">
  <span v-if="isUser">
    <label for="username">用户账号</label>
    <input type="text" id="username" placeholder="用户账号">
  </span>
  <span v-else>
    <label for="email">用户邮箱</label>
    <input type="text" id="email" placeholder="用户邮箱">
  </span>
  <button @click="isUser = !isUser">切换类型</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      isUser: true
    }
  })
</script>

</body>

5.3.1用户登录切换的案例的小问题

  • 如果我们在有输入内容的情况下,切换了类型,我们会发现文字依然显示之前的输入的内容。
  • 但是按道理讲,我们应该切换到另外一个input元素中了。
  • 在另一个input元素中,我们并没有输入内容。
    • 为什么会出现这个问题呢?
    • 问题解答:
      这是因为Vue在进行DOM渲染时,出于性能考虑,会尽可能的复用已经存在的元素,而不是重新创建新的元素。
  • 在上面的案例中,Vue内部会发现原来的input元素不再使用,直接作为else中的input来使用了。
  • 解决方案:
    • 如果我们不希望Vue出现类似重复利用的问题,可以给对应的input添加key
    • 并且我们需要保证key的不同
<body>
<div id="app">
  <span v-if="isUser">
    <label for="username">用户账号</label>
    <input type="text" id="username" placeholder="用户账号" key="username">
  </span>
  <span v-else>
    <label for="email">用户邮箱</label>
    <input type="text" id="email" placeholder="用户邮箱" key="email">
  </span>
  <button @click="isUser = !isUser">切换类型</button>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      isUser: true
    }
  })
</script>

</body>

5.4 v-show

  • v-show的用法和v-if非常相似,也用于决定一个元素是否渲染:
  • v-if和v-show对比
  • v-if和v-show都可以决定一个元素是否渲染,那么开发中我们如何选择呢?
    • v-if当条件为false时,压根不会有对应的元素在DOM中。
    • v-show当条件为false时,仅仅是将元素的display属性设置为none而已。
  • 开发中如何选择呢?
    • 当需要在显示与隐藏之间切片很频繁时,使用v-show
    • 当只有一次切换时,通过使用v-if
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<div id="app">
  <!--v-if: 当条件为false, 包含v-if指令的元素, 根本就不会存在dom中-->
  <h2 v-if="isShow" id="aaa">{{message}}</h2>

  <!--v-show: 当条件为false, v-show只是给我们的元素添加一个行内样式: display: none-->
  <h2 v-show="isShow" id="bbb">{{message}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isShow: true
    }
  })
</script>

</body>
</html>

六. 循环遍历操作

6.1 v-for遍历数组

<body>
<div id="app">
  <!--1.在遍历的过程中,没有使用索引值(下标值)-->
  <ul>
    <li v-for="item in names">{{item}}</li>
  </ul>

  <!--2.在遍历的过程中, 获取索引值index-->
  <ul>
    <li v-for="(item, index) in names">
      {{index+1}}.{{item}}
    </li>
  </ul>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      names: ['why', 'kobe', 'james', 'curry']
    }
  })
</script>
</body>

6.2 v-for遍历对象

  • v-for的语法类似于JavaScript中的for循环。
  • 格式如下:item in items的形式。
<body>
<div id="app">
  <!--1.在遍历对象的过程中, 如果只是获取一个值, 那么获取到的是value-->
  <ul>
    <li v-for="item in info">{{item}}</li>
  </ul>


  <!--2.获取key和value 格式: (value, key) -->
  <ul>
    <li v-for="(value, key) in info">{{value}}-{{key}}</li>
  </ul>


  <!--3.获取key和value和index 格式: (value, key, index) -->
  <ul>
    <li v-for="(value, key, index) in info">{{value}}-{{key}}-{{index}}</li>
  </ul>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      }
    }
  })
</script>
</body>

6.2.1 组件key的属性

  • 官方推荐在使用v-for时,给对应的元素或组件添加上一个:key属性。
  • key的作用主要是为了高效的更新虚拟DOM。
    添加元素:
    • push是在最后加元素
      即:app.letters.push(‘F’)
    • splice() 方法向/从数组中添加/删除项目,然后返回被删除的项目。
      注释:该方法会改变原始数组。包含index,就是替换后重新标号index性能比较低。
      使用key进行绑定则
      即:app.letters.splice();
      app.letters(2,0,‘F’)//添加F
      arrayObject.splice(index,howmany,item1,…,itemX)
参数描述
index必需。整数,规定添加/删除项目的位置,使用负数可从数组结尾处规定位置。
howmany必需。要删除的项目数量。如果设置为 0,则不会删除项目。
item1, …, itemX可选。向数组添加的新项目。
<body>
<div id="app">
  <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', 'E']
    }
  })
</script>
</body>

6.3 哪些数组方法是响应式

  • Vue中包含了一组观察数组编译的方法,使用它们改变数组也会触发视图的更新。
    • push()
    • pop()
    • shift()
    • unshift()
    • splice()
    • sort()
    • reverse()
<body>

<div id="app">
  <ul>
    <li v-for="item in letters">{{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() {
        // 1.push方法
        // this.letters.push('aaa')
        // this.letters.push('aaaa', 'bbbb', 'cccc')

        // 2.pop(): 删除数组中的最后一个元素
        // this.letters.pop();

        // 3.shift(): 删除数组中的第一个元素
        // this.letters.shift();

        // 4.unshift(): 在数组最前面添加元素
        // this.letters.unshift()
        // this.letters.unshift('aaa', 'bbb', 'ccc')

        // 5.splice作用: 删除元素/插入元素/替换元素
        // 删除元素: 第二个参数传入你要删除几个元素(如果没有传,就删除后面所有的元素)
        // 替换元素: 第二个参数, 表示我们要替换几个元素, 后面是用于替换前面的元素
        // 插入元素: 第二个参数, 传入0, 并且后面跟上要插入的元素
        // splice(start)
        // splice(start):
        this.letters.splice(1, 3, 'm', 'n', 'l', 'x')
        // this.letters.splice(1, 0, 'x', 'y', 'z')

        // 5.sort()
        // this.letters.sort()

        // 6.reverse()//将数组内容进行反转:app.letters.reverse()
        // this.letters.reverse()

        // 注意: 通过索引值修改数组中的元素,非响应式的
        // this.letters[0] = 'bbbbbb';非响应式。
        // this.letters.splice(0, 1, 'bbbbbb')
        // set(要修改的对象, 索引值, 修改后的值)
        // Vue.set(this.letters, 0, 'bbbbbb')
      }
    }
  })


  // function sum(num1, num2) {
  //   return num1 + num2
  // }
  //
  // function sum(num1, num2, num3) {
  //   return num1 + num2 + num3
  // }
  // function sum(...num) {
  //...bum是一个可变参数,就可以加入无数数字
  //   console.log(num);
  // }
  // sum(20, 30, 40, 50, 601, 111, 122, 33)

</script>
</body>

6.4作业完成

作业一

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <style>
    .active {
      color: red;
    }
  </style>
</head>
<body>

<div id="app">
  <ul>
    <li v-for="(item, index) in movies"
        :class="{active: currentIndex === index}"
        @click="liClick(index)">
      {{index}}.{{item}}
    </li>

    <!--<li :class="{active: 0===currentIndex}"></li>-->
    <!--<li :class="{active: 1===currentIndex}"></li>-->
    <!--<li :class="{active: 2===currentIndex}"></li>-->
    <!--<li :class="{active: 3===currentIndex}"></li>-->
  </ul>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      movies: ['海王', '海贼王', '加勒比海盗', '海尔兄弟'],
      currentIndex: 0
    },
    methods: {
      liClick(index) {
        this.currentIndex = index
      }
    }
  })
</script>

</body>
</html>

作业二 (书记购物车案例)

  • 取值
    • toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。
    • NumberObject.toFixed(num)
      • num必需。规定小数的位数,是 0 ~ 20 之间的值,包括 0 和 20,有些实现可以支持更大的数值范围。
      • 如果省略了该参数,将用 0 代替。
  • 过滤器的使用
    • totalPrice | 过滤器(filters)
      使用filters:{}
  • button属性
属性描述
disabled返回是否禁用按钮
accessKey设置或返回访问某个按钮的快捷键。
disabled设置或返回是否禁用按钮。
form返回对包含按钮的表单的引用。
id设置或返回按钮的 id。
name设置或返回按钮的名称。
tabIndex设置或返回按钮的 Tab 键控制次序。
type返回按钮的表单类型。
value设置或返回显示在按钮上的文本。
  • 计算属性 computed

  • // for (let i in/of this.books)

  • 高阶函数// reduce

    • 函数式编程
    • 编程范式:命令式编程/声明式编程
    • 编程范式:面向对象编程/函数式编程

index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>

<div id="app">
  <div v-if="books.length">
    <table>
      <thead>
      <tr>
        <th></th>
        <th>书籍名称</th>
        <th>出版日期</th>
        <th>价格</th>
        <th>购买数量</th>
        <th>操作</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(item, index) in books">
        <td>{{item.id}}</td>
        <td>{{item.name}}</td>
        <td>{{item.date}}</td>
        <td>{{item.price | showPrice}}</td>
        <td>
          <button @click="decrement(index)" v-bind:disabled="item.count <= 1">-</button>
          {{item.count}}
          <button @click="increment(index)">+</button>
        </td>
        <td><button @click="removeHandle(index)">移除</button></td>
      </tr>
      </tbody>
    </table>
	<!--过滤器totalPrice | 过滤器(filters)-->
    <h2>总价格: {{totalPrice | showPrice}}</h2>
  </div>
  <h2 v-else>购物车为空</h2>
</div>

<script src="../js/vue.js"></script>
<script src="main.js"></script>
<script>
</script>
</body>
</html>

main.js

const app = new Vue({
  el: '#app',
  data: {
    books: [
      {
        id: 1,
        name: '《算法导论》',
        date: '2006-9',
        price: 85.00,
        count: 1
      },
      {
        id: 2,
        name: '《UNIX编程艺术》',
        date: '2006-2',
        price: 59.00,
        count: 1
      },
      {
        id: 3,
        name: '《编程珠玑》',
        date: '2008-10',
        price: 39.00,
        count: 1
      },
      {
        id: 4,
        name: '《代码大全》',
        date: '2006-3',
        price: 128.00,
        count: 1
      },
    ]
  },
  methods: {
    // getFinalPrice(price) {
    //   return '¥' + price.toFixed(2)
    // }
    increment(index) {
      this.books[index].count++
    },
    decrement(index) {
      this.books[index].count--
    },
    removeHandle(index) {
      this.books.splice(index, 1)
    }
  },
  computed: {
    totalPrice() {
      //let totalPrice = 0
	  //return this.books.reduce(function (preValue, book) {
      //  return preValue + book.price * book.count
      //}, 0)
//1.
      for (let i = 0; i < this.books.length; i++) {
        totalPrice += this.books[i].price * this.books[i].count
      }
      return totalPrice
// 2.for (let i in this.books)
      // let totalPrice = 0
      // for (let i in this.books) {
      //   const book = this.books[i]
      //   totalPrice += book.price * book.count
      // }
      //
      // return totalPrice

// 3.for (let i of this.books)
      // let totalPrice = 0
      // for (let item of this.books) {
      //   totalPrice += item.price * item.count
      // }
      // return totalPrice
      // for (let i in/of this.books)
      // reduce
    }
  },
  filters: {
    showPrice(price) {
      return '¥' + price.toFixed(2)
    }
  }
})

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

清楚缓存快捷键:Ctrl+Shift+Del

七.JavaScript高阶函数的使用

  • 高阶函数// reduce
    • 函数式编程
    • 编程范式:命令式编程/声明式编程
    • 编程范式:面向对象编程/函数式编程
    • filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
      • array.filter(function(currentValue,index,arr), thisValue)
      • 注意: filter() 不会对空数组进行检测。
      • 注意: filter() 不会改变原始数组。
    • map() 方法创建一个新数组,其结果是该数组中的每个元素是调用一次提供的函数后的返回值。
    • reduce作用对数组中所有的内容进行汇总(相加)
      • array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
// 编程范式: 面向对象编程(第一公民:对象)/函数式编程(第一公民:函数)
// filter/map/reduce
// filter中的回调函数有一个要求: 必须返回一个boolean值
// true: 当返回true时, 函数内部会自动将这次回调的n加入到新的数组中
// false: 当返回false时, 函数内部会过滤掉这次的n

const nums = [10, 20, 111, 222, 444, 40, 50]
//一行代码解决问题
// let total = nums.filter(n => n < 100).map(n => n * 2).reduce((pre, n) => pre + n);
// console.log(total);

let total = nums.filter(function (n) {
  return n < 100
}).map(function (n) {
  return n * 2
}).reduce(function (prevValue, n) {
  return prevValue + n
}, 0)
console.log(total);

// 1.filter函数的使用
// // 10, 20, 40, 50
// let newNums = nums.filter(function (n) {
//   return n < 100
// })
// // console.log(newNums);
//
// // 2.map函数的使用
// // 20, 40, 80, 100
// let new2Nums = newNums.map(function (n) { // 20
//   return n * 2
// })
// console.log(new2Nums);
//
// // 3.reduce函数的使用
// // reduce作用对数组中所有的内容进行汇总
// let total = new2Nums.reduce(function (preValue, n) {
//   return preValue + n
// }, 0)
// console.log(total);

// 第一次: preValue 0 n 20
// 第二次: preValue 20 n 40
// 第二次: preValue 60 n 80
// 第二次: preValue 140 n 100
// 240

// // 1.需求: 取出所有小于100的数字
// let newNums = []
// for (let n of nums) {
//   if (n < 100) {
//     newNums.push(n)
//   }
// }
//
// // 2.需求:将所有小于100的数字进行转化: 全部*2
// let new2Nums = []
// for (let n of newNums) {
//   new2Nums.push(n * 2)
// }
//
// console.log(new2Nums);
//
//
// // 3.需求:将所有new2Nums数字相加,得到最终的记过
// let total = 0
// for (let n of new2Nums) {
//   total += n
// }
//
// console.log(total);

八.表单绑定 v-model使用

8.1 v-model的基本使用

  • Vue中使用v-model指令来实现表单元素和数据的双向绑定。
  • v-model用于textarea元素
<body>

<div id="app">
  <input type="text" v-model="message">
  {{message}}
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

</body>

8.2 v-model的原理

  • @input用于监听用户是否在输入
  • event
    • event.target
    • $event.target.value 获取event对象
  • v-model其实是一个语法糖,它的背后本质上是包含两个操作:
    • 1.v-bind绑定一个value属性
    • 2.v-on指令给当前元素绑定input事件
    • 即:
      <input type="text" v-model="message">
      等同于
      <input type="text" v-bind:value="message" v-on:input="message = $event.target.value">
<body>
<div id="app">
  <!--<input type="text" v-model="message">-->
  <!--<input type="text" :value="message" @input="valueChange">-->
  <!-- @input用于监听用户是否在输入-->
  <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: '你好啊'
    },
    methods: {
      valueChange(event) {
        this.message = event.target.value;
      }
    }
  })
</script>
</body>

8.3 v-model结合radio类型

  • name属性一样即互斥
  • v-model绑定同一个变量也是互斥的
  • 给默认值则:在data中sex:'女'
<body>

<div id="app">
  <label for="male">
    <input type="radio" id="male" value="男" v-model="sex"></label>
  <label for="female">
    <input type="radio" id="female" value="女" v-model="sex"></label>
  <h2>您选择的性别是: {{sex}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      sex: '女'
    }
  })
</script>

</body>

8.4 v-model结合checkbox类型

  • 单个勾选框:
    • v-model即为布尔值。
    • 此时input的value并不影响v-model的值。
  • 多个复选框:
    • 当是多个复选框时,因为可以选中多个,所以对应的data中属性是一个数组。
    • 当选中某一个时,就会将input的value添加到数组中。
<body>

<div id="app">
  <!--1.checkbox单选框-->
  <!--<label for="agree">-->
    <!--<input type="checkbox" id="agree" v-model="isAgree">同意协议-->
  <!--</label>-->
  <!--<h2>您选择的是: {{isAgree}}</h2>-->
  <!--<button :disabled="!isAgree">下一步</button>-->

  <!--2.checkbox多选框-->
  <input type="checkbox" value="篮球" v-model="hobbies">篮球
  <input type="checkbox" value="足球" v-model="hobbies">足球
  <input type="checkbox" value="乒乓球" v-model="hobbies">乒乓球
  <input type="checkbox" value="羽毛球" v-model="hobbies">羽毛球
  <h2>您的爱好是: {{hobbies}}</h2>

  <label v-for="item in originHobbies" :for="item">
    <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
  </label>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isAgree: false, // 单选框
      hobbies: [], // 多选框,
	  //值绑定
      originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
    }
  })
</script>

</body>

8.5 v-model结合select类型

  • 单选:只能选中一个值。
    • v-model绑定的是一个值。
    • 当我们选中option中的一个时,会将它对应的value赋值到mySelect中
  • 多选:可以选中多个值。
    • v-model绑定的是一个数组。
    • 当选中多个值时,就会将选中的option对应的value添加到数组mySelects中
<body>
<div id="app">
  <!--1.选择一个-->
  <select name="abc" v-model="fruit">
    <option value="苹果">苹果</option>
    <option value="香蕉">香蕉</option>
    <option value="榴莲">榴莲</option>
    <option value="葡萄">葡萄</option>
  </select>
  <h2>您选择的水果是: {{fruit}}</h2>

  <!--2.选择多个-->
  <select name="abc" v-model="fruits" multiple>
    <option value="苹果">苹果</option>
    <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: {
      message: '你好啊',
      fruit: '香蕉',
      fruits: []
    }
  })
</script>

</body>

8.6 值绑定

  • 动态的给value赋值
<div id="app">
  <label v-for="item in originHobbies" :for="item">
    <input type="checkbox" :value="item" :id="item" v-model="hobbies">{{item}}
  </label>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isAgree: false, // 单选框
      hobbies: [], // 多选框,
	  //值绑定
      originHobbies: ['篮球', '足球', '乒乓球', '羽毛球', '台球', '高尔夫球']
    }
  })
</script>

</body>

8.7 v-model修饰符的使用

  • lazy修饰符:
    • 默认情况下,v-model默认是在input事件中同步输入框的数据的。
    • 也就是说,一旦有数据发生改变对应的data中的数据就会自动发生改变。
    • lazy修饰符可以让数据在失去焦点或者回车时才会更新:
  • number修饰符:
    • 默认情况下,在输入框中无论我们输入的是字母还是数字,都会被当做字符串类型进行处理。
    • 但是如果我们希望处理的是数字类型,那么最好直接将内容当做数字处理。
    • number修饰符可以让在输入框中输入的内容自动转成数字类型:
  • trim修饰符:
    • 如果输入的内容首尾有很多空格,通常我们希望将其去除
    • trim修饰符可以过滤内容左右两边的空格
<body>

<div id="app">
  <!--1.修饰符: lazy-->
  <input type="text" v-model.lazy="message">
  <h2>{{message}}</h2>


  <!--2.修饰符: number-->
  <input type="number" v-model.number="age">
  <h2>{{age}}-{{typeof age}}</h2>

  <!--3.修饰符: trim-->
  <input type="text" v-model.trim="name">
  <h2>您输入的名字:{{name}}</h2>
</div>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      age: 0,
      name: ''
    }
  })

  var age = 0
  age = '1111'
  age = '222'
</script>

Vue Day 03

九. 组件化开发

如果我们将一个页面中所有的处理逻辑全部放在一起,处理起来就会变得非常复杂,而且不利于后续的管理以及扩展。
但如果,我们讲一个页面拆分成一个个小的功能块,每个功能块完成属于自己这部分独立的功能,那么之后整个页面的管理和维护就变得非常容易了。

  • 我们将一个完整的页面分成很多个组件。
  • 每个组件都用于实现页面的一个功能块。
  • 而每一个组件又可以进行细分。

它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用。
任何的应用都会被抽象成一颗组件树。

  • 组件化思想的应用:
    • 有了组件化的思想,我们在之后的开发中就要充分的利用它。
    • 尽可能的将页面拆分成一个个小的、可复用的组件。
    • 这样让我们的代码更加方便组织和管理,并且扩展性也更强。

9.1 组件化开发的基本使用

组件的使用分成三个步骤:

  • 创建组件构造器 Vue.extend()
  • 注册组件 Vue.component()
  • 使用组件

查看运行结果:
和直接使用一个div看起来并没有什么区别。
但是我们可以设想,如果很多地方都要显示这样的信息,我们是不是就可以直接使用来完成呢?

<body>

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

  <div>
    <div>
      <my-cpn></my-cpn>
    </div>
  </div>
</div>

<my-cpn></my-cpn>

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

  // 2.注册组件
  Vue.component('my-cpn', cpnC)

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

</body>

9.2 全局组件和局部组件

  • 调用Vue.component()注册组件时,组件的注册是全局的
  • 注册的组件是挂载在某个实例中, 那么是一个局部组件
<body>
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<div id="app2">
  <cpn></cpn>
</div>

<script src="../js/vue.js"></script>
<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: '你好啊'
    },
    components: {
      // cpn使用组件时的标签名
	  //局部组件
      cpn: cpnC
    }
  })

  const app2 = new Vue({
    el: '#app2'
  })
</script>
</body>

9.3 父组件和子组件

组件和组件之间存在层级关系

<body>

<div id="app">
  <cpn2></cpn2>
  <!--<cpn1></cpn1>-->
</div>

<script src="../js/vue.js"></script>
<script>
  // 1.创建第一个组件构造器(子组件)
  const cpnC1 = Vue.extend({
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })


  // 2.创建第二个组件构造器(父组件)
  const cpnC2 = Vue.extend({
    template: `
      <div>
        <h2>我是标题2</h2>
        <p>我是内容, 呵呵呵呵</p>
        <cpn1></cpn1>
      </div>
    `,
	//组件构造器里加入了components注册属性,作用cpn1可以在cpnC2里使用
    components: {
      cpn1: cpnC1
    }
  })

  // root组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn2: cpnC2
    }
  })
</script>

</body>

9.4 组件的语法糖

省去了调用Vue.extend()的步骤,而是可以直接使用一个对象来代替。

<body>
<div id="app">
  <cpn1></cpn1>
  <cpn2></cpn2>
</div>

<script src="../js/vue.js"></script>
<script>
  // 1.全局组件注册的语法糖
  // 1.创建组件构造器
  // const cpn1 = Vue.extend()

  // 2.注册组件
  Vue.component('cpn1', {
    template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容, 哈哈哈哈</p>
      </div>
    `
  })

  // 2.注册局部组件的语法糖
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      'cpn2': {
        template: `
          <div>
            <h2>我是标题2</h2>
            <p>我是内容, 呵呵呵</p>
          </div>
    `
      }
    }
  })
</script>

</body>

9.5 组件模板的分离写法

  • 将组件和html分离出来
  • Vue提供了两种方案来定义HTML模块内容:
    • 使用<script>标签
    • 使用<template>标签
<body>

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

<!--1.script标签, 注意:类型必须是text/x-template-->
<!--<script type="text/x-template" id="cpn">-->
<!--<div>-->
  <!--<h2>我是标题</h2>-->
  <!--<p>我是内容,哈哈哈</p>-->
<!--</div>-->
<!--</script>-->

<!--2.template标签-->
<template id="cpn">
  <div>
    <h2>我是标题</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>

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

  // 1.注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn'
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

</body>

9.6 组件中数据存放问题

  • 组件是一个单独功能模块的封装:
  • 这个模块有属于自己的HTML模板,也应该有属性自己的数据data。
    • 组件对象也有一个data属性(也可以有methods等属性)
    • 只是这个data属性必须是一个函数,不是对象类型
    • 而且这个函数返回一个对象,对象内部保存着数据
  • 组件内部是不能直接访问Vue实例里的数据
<body>
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<!--1.script标签, 注意:类型必须是text/x-template-->
<!--<script type="text/x-template" id="cpn">-->
<!--<div>-->
  <!--<h2>我是标题</h2>-->
  <!--<p>我是内容,哈哈哈</p>-->
<!--</div>-->
<!--</script>-->

<!--2.template标签-->
<template id="cpn">
  <div>
    <h2>{{title}}</h2>
    <p>我是内容,呵呵呵</p>
  </div>
</template>

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

  // 1.注册一个全局组件
  Vue.component('cpn', {
    template: '#cpn',
	//函数类型,而非对象类型且必须返回一个对象实例
    data() {
      return {
        title: 'abc'
      }
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      // title: '我是标题'
    }
  })
</script>
</body>

9.7 组件中的data为什么是函数

  • 首先,如果不是一个函数,Vue直接就会报错。
  • 其次,原因是在于Vue让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响。
<body>

<!--组件实例对象-->
<div id="app">
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>当前计数: {{counter}}</h2>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  // 1.注册组件
  const obj = {
    counter: 0
  }
  Vue.component('cpn', {
    template: '#cpn',
    // data() {
    //   return {
    //     counter: 0
    //   }
    // },
    data() {
      return obj
    },
    methods: {
      increment() {
        this.counter++
      },
      decrement() {
        this.counter--
      }
    }
  })

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    }
  })
</script>

<script>
  // const obj = {
  //   name: 'why',
  //   age: 18
  // }
  //
  // function abc() {
  //   return obj
  // }
  //
  // let obj1 = abc()
  // let obj2 = abc()
  // let obj3 = abc()
  //
  // obj1.name = 'kobe'
  // console.log(obj2);
  // console.log(obj3);


</script>

</body>

9.7 组件通信

子组件是不能引用父组件或者Vue实例的数据
但是可以又上层向下层传递数据来进行展示

  • 组件请求数据步骤
    • 向服务器请求,请求列表数据
    • 最外层请求的数据,传到内组件

9.7.1 父组件向子组件传递数据

  • 通过props向子组件传递数据

  • 通过事件向父组件发送消息

  • Vue实例和子组件的通信和父组件和子组件的通信过程是一样的

  • props的值有两种方式:

    • 方式一:字符串数组,数组中的字符串就是传递时的名称。
    • 方式二:对象,对象可以设置传递时的类型,也可以设置默认值等。

  • 需要对props进行类型等验证时,就需要对象写法
  • 验证都支持哪些数据类型呢?
    • String
    • Number
    • Boolean
    • Array
    • Object
    • Date
    • Function
    • Symbol
  • 当我们有自定义构造函数时,验证也支持自定义的类型

<body>

<div id="app">
  <!--<cpn v-bind:cmovies="movies"></cpn>-->
  <!--<cpn cmovies="movies" cmessage="message"></cpn>-->

  <cpn :cmessage="message" :cmovies="movies"></cpn>
</div>



<template id="cpn">
  <div>
    <ul>
      <li v-for="item in cmovies">{{item}}</li>
    </ul>
    <h2>{{cmessage}}</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  // 父传子: props
  const cpn = {
    template: '#cpn',
    // props: ['cmovies', 'cmessage'],
    props: {
      // 1.类型限制
      // cmovies: Array,
      // cmessage: String,

      // 2.提供一些默认值, 以及必传值
      cmessage: {
        type: String,
        default: 'aaaaaaaa',
        //必传值
        required: true
      },
      // 类型是对象或者数组时, 默认值必须是一个函数
      cmovies: {
        type: Array,
        default() {
          return []
        }
      }
    },
    data() {
      return {}
    },
    methods: {

    }
  }

  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      movies: ['海王', '海贼王', '海尔兄弟']
    },
    components: {
      cpn
    }
  })
</script>

</body>

9.7.2 父传子(props中的驼峰标识)

使用驼峰标识的话,绑定时需要-来进行绑定
子组件里有很多的mustcahe需要一个确切的根(即外层的东西,如

<body>

<div id="app">
  <cpn :c-info="info" :child-my-message="message" v-bind:class></cpn>
</div>

<template id="cpn">
  <div>
    <h2>{{cInfo}}</h2>
    <h2>{{childMyMessage}}</h2>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const cpn = {
    template: '#cpn',
    props: {
      cInfo: {
        type: Object,
        default() {
          return {}
        }
      },
      childMyMessage: {
        type: String,
        default: ''
      }
    }
  }

  const app = new Vue({
    el: '#app',
    data: {
      info: {
        name: 'why',
        age: 18,
        height: 1.88
      },
      message: 'aaaaaa'
    },
    components: {
      cpn
    }
  })
</script>

</body>

9.8 子传父(自定义事件)

  • 当子组件需要向父组件传递数据时,就要用到自定义事件了。
  • v-on不仅仅可以用于监听DOM事件,也可以用于组件间的自定义事件。
  • 自定义事件的流程:
    • 在子组件中,通过$emit()来触发事件。
    • 在父组件中,通过v-on来监听子组件事件。
//整个操作的过程还是在子组件中完成,但是之后的展示交给父组件。
//需要将子组件中的counter,传给父组件的某个属性,比如total。

<body>

<!--父组件模板-->
<div id="app">
  <cpn @item-click="cpnClick"></cpn>
</div>

<!--子组件模板-->
<template id="cpn">
  <div>
    <button v-for="item in categories"
            @click="btnClick(item)">
      {{item.name}}
    </button>
  </div>
</template>

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

  // 1.子组件
  const cpn = {
    template: '#cpn',
    data() {
      return {
        categories: [
          {id: 'aaa', name: '热门推荐'},
          {id: 'bbb', name: '手机数码'},
          {id: 'ccc', name: '家用家电'},
          {id: 'ddd', name: '电脑办公'},
        ]
      }
    },
    methods: {
      btnClick(item) {
        // 发射事件: 自定义事件
        this.$emit('item-click', item)
      }
    }
  }

  // 2.父组件
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn
    },
    methods: {
      cpnClick(item) {
        console.log('cpnClick', item);
      }
    }
  })
</script>

</body>

9.9 父子组件通信案例(Day04 部分)

9.9.1 父子组件的双向绑定

<body>

<div id="app">
  <cpn :number1="num1"
       :number2="num2"
       @num1change="num1change"
       @num2change="num2change"/>
</div>

<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <!--<input type="text" v-model="dnumber1">-->
    <input type="text" :value="dnumber1" @input="num1Input">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <!--<input type="text" v-model="dnumber2">-->
    <input type="text" :value="dnumber2" @input="num2Input">
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value) {
        this.num1 = parseFloat(value)
      },
      num2change(value) {
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          number1: Number,
          number2: Number
        },
        data() {
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        methods: {
          num1Input(event) {
            // 1.将input中的value赋值到dnumber中
            this.dnumber1 = event.target.value;

            // 2.为了让父组件可以修改值, 发出一个事件
            this.$emit('num1change', this.dnumber1)

            // 3.同时修饰dnumber2的值
            this.dnumber2 = this.dnumber1 * 100;
            this.$emit('num2change', this.dnumber2);
          },
          num2Input(event) {
            this.dnumber2 = event.target.value;
            this.$emit('num2change', this.dnumber2)

            // 同时修饰dnumber2的值
            this.dnumber1 = this.dnumber2 / 100;
            this.$emit('num1change', this.dnumber1);
          }
        }
      }
    }
  })
</script>

</body>

9.9.2 父子组件的双向绑定(watch实现)

<body>

<div id="app">
  <cpn :number1="num1"
       :number2="num2"
       @num1change="num1change"
       @num2change="num2change"/>
</div>

<template id="cpn">
  <div>
    <h2>props:{{number1}}</h2>
    <h2>data:{{dnumber1}}</h2>
    <input type="text" v-model="dnumber1">
    <h2>props:{{number2}}</h2>
    <h2>data:{{dnumber2}}</h2>
    <input type="text" v-model="dnumber2">
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      num1: 1,
      num2: 0
    },
    methods: {
      num1change(value) {
        this.num1 = parseFloat(value)
      },
      num2change(value) {
        this.num2 = parseFloat(value)
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        props: {
          number1: Number,
          number2: Number,
          name: ''
        },
        data() {
          return {
            dnumber1: this.number1,
            dnumber2: this.number2
          }
        },
        watch: {
          dnumber1(newValue) {
            this.dnumber2 = newValue * 100;
            this.$emit('num1change', newValue);
          },
          dnumber2(newValue) {
            this.number1 = newValue / 100;
            this.$emit('num2change', newValue);
          }
        }
      }
    }
  })
</script>

</body>

9.10 父子组件的访问方式

9.10.1 父访问子 children-refs

  • 父组件访问子组件:使用 c h i l d r e n 或 children或 childrenrefs

    • this.$children是一个数组类型,它包含所有子组件对象。
  • 子组件访问父组件:使用$parent

  • $children的缺陷:

    • 通过$children访问子组件时,是一个数组类型,访问其中的子组件必须通过索引值。
    • 但是当子组件过多,我们需要拿到其中一个时,往往不能确定它的索引值,甚至还可能会发生变化。
    • 有时候,我们想明确获取其中一个特定的组件,这个时候就可以使用$refs
  • $refs的使用:

    • $refs和ref指令通常是一起使用的。
    • 首先,我们通过ref给某一个子组件绑定一个特定的ID。
    • 其次,通过this.$refs.ID就可以访问到该组件了。
<child-cpn1 ref="chi1d1"></child-cpn1>
<child-cpn2 ref="chi1d2"></chi1d-cpn2>
<button @click="showRefscpn">通过refs访问子组件</button>
showRefsCpn() {
console.1og(this. $refs.chi1d1.message);
console.log(this. $refs.chi1d2.message);
}

实例

<body>

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

  <my-cpn></my-cpn>
  <y-cpn></y-cpn>

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

<template id="cpn">
  <div>我是子组件</div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    methods: {
      btnClick() {
        // 1.$children
        // console.log(this.$children);
        // for (let c of this.$children) {
        //   console.log(c.name);
        //   c.showMessage();
        // }
        // console.log(this.$children[3].name);

        // 2.$refs => 对象类型, 默认是一个空的对象 ref='bbb'
        console.log(this.$refs.aaa.name);
      }
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是子组件的name'
          }
        },
        methods: {
          showMessage() {
            console.log('showMessage');
          }
        }
      },
    }
  })
</script>

</body>

9.10.2 子访问父 parent-root

<body>

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

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

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

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            name: '我是cpn组件的name'
          }
        },
        components: {
          ccpn: {
            template: '#ccpn',
            methods: {
              btnClick() {
                // 1.访问父组件$parent
                // console.log(this.$parent);
                // console.log(this.$parent.name);

                // 2.访问根组件$root
                console.log(this.$root);
                console.log(this.$root.message);
              }
            }
          }
        }
      }
    }
  })
</script>

</body>

十. 组件化高级

10.1 slot 插槽的基本使用

  • 组件的插槽也是为了让我们封装的组件更加具有扩展性。
  • 让使用者可以决定组件内部的一些内容到底展示什么。
    • 移动开发中,几乎每个页面都有导航栏。
    • 导航栏我们必然会封装成一个插件,比如nav-bar组件。
    • 一旦有了这个组件,我们就可以在多个页面中复用了。
  • 最好的封装方式就是将共性抽取到组件中,将不同暴露为插槽。
<body>

<!--
1.插槽的基本使用 <slot></slot>
2.插槽的默认值 <slot>button</slot>
3.如果有多个值, 同时放入到组件进行替换时, 一起作为替换元素
-->

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

  <cpn><span>哈哈哈</span></cpn>
  <cpn><i>呵呵呵</i></cpn>
  <cpn>
    <i>呵呵呵</i>
    <div>我是div元素</div>
    <p>我是p元素</p>
  </cpn>

  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
  <cpn></cpn>
</div>

<template id="cpn">
  <div>
    <h2>我是组件</h2>
    <p>我是组件, 哈哈哈</p>
    <!-- 设置默认值-->
    <slot><button>按钮</button></slot>
    <!--<button>按钮</button>-->
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>
</body>

10.2 slot 具名插槽的基本使用

  • 给slot元素一个name属性即可
    <slot name='myslot'></slot>
<body>

<div id="app">
  <cpn><span slot="center">标题</span></cpn>
  <cpn><button slot="left">返回</button></cpn>
</div>

<template id="cpn">
  <div>
    <slot name="left"><span>左边</span></slot>
    <slot name="center"><span>中间</span></slot>
    <slot name="right"><span>右边</span></slot>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn'
      }
    }
  })
</script>
</body>

10.3 编译的作用域

  • 在使用变量时会看变量的模板在哪里,就在哪里查找。
  • 父组件模板的所有东西都会在父级作用域内编译;子组件模板的所有东西都会在子级作用域内编译。
<body>
<div id="app">
  <cpn v-show="isShow"></cpn><!--实例里的isShow-->
  <cpn v-for="item in names"></cpn>
</div>

<template id="cpn">
  <div>
    <h2>我是子组件</h2>
    <p>我是内容, 哈哈哈</p>
    <button v-show="isShow">按钮</button>
  </div>
</template>

<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊',
      isShow: true
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            isShow: false
          }
        }
      },
    }
  })
</script>
</body>

10.4 作用域插槽的案例

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

  • 在父组件使用我们的子组件时,从子组件中拿到数据:
  • 我们通过<template slot-scope="slotProps">获取到slotProps属性
<body>
  <!-- 内容在子组件,希望父组件告诉我们如何展示
  利用slot作用域插槽就可以了
   -->
<div id="app">
  <cpn></cpn>

  <cpn>
    <!--目的是获取子组件中的pLanguages-->
    <template slot-scope="slot">
      <!--<span v-for="item in slot.data"> - {{item}}</span>-->
      <span>{{slot.data.join(' - ')}}</span>
    </template>
  </cpn>

  <cpn>
    <!--目的是获取子组件中的pLanguages-->
    <template slot-scope="slot">
      <!--<span v-for="item in slot.data">{{item}} * </span>-->
      <span>{{slot.data.join(' * ')}}</span>
    </template>
  </cpn>
  <!--<cpn></cpn>-->
</div>

<template id="cpn">
  <div>
    <slot :data="pLanguages">
      <ul>
        <li v-for="item in pLanguages">{{item}}</li>
      </ul>
    </slot>
  </div>
</template>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      message: '你好啊'
    },
    components: {
      cpn: {
        template: '#cpn',
        data() {
          return {
            pLanguages: ['JavaScript', 'C++', 'Java', 'C#', 'Python', 'Go', 'Swift']
          }
        }
      }
    }
  })
</script>


</body>

Vue Day 04

十一. 前端模块化

10.1 为什么要用模块化-匿名函数

  • 因为js代码和文件越来越多。
  • 全局变量重复使用,造成bug等

  • 使用匿名函数来解决方面的重名问题
  • 模拟块级作用域,减少全局变量。
  • 执行完匿名函数,存储在内存中相对应的变量会被销毁,从而节省内存。
  • 在大型多人开发的项目中,使用块级作用域,会大大降低命名冲突的问题,从而避免产生灾难性的后果。
  • 自此开发者再也不必担心搞乱全局作用域了。
(function(){
  var flag = true;
})()

但是代码的复用性降低

但是我们不使用以下的模块化去使用代码
我们采用的是别人已经规范的模块进行代码

bbb.js

var moduleB = (function () {
  var obj = {}

  var name = '小红'
  var flag = false

  console.log(name);

  obj.flag = flag

  return obj
})()

aaa.js

var moduleA = (function () {
  // 导出的对象
  var obj = {}

  // 小明
  var name = '小明'
  var age = 22

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

  var flag = true

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


  obj.flag = flag;
  obj.sum = sum;

  return obj
})()


mmm.js

;(function () {
  // 1.想使用flag
  if (moduleA.flag) {
    console.log('小明是天才, 哈哈哈');
  }

  // 2.使用sum函数
  console.log(moduleA.sum(40, 50));
})()

10.2 常见的模块化规范

  • 常见的模块化规范:
  • CommonJS、AMD、CMD,也有ES6的Modules
    • CommonJS(了解)
    • 模块化有两个核心:导出和导入

10.2.1 CommonJS

a.js

//小明
var name = '小明'
var age = 22

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

var flag = true

if (flag) {
  console.log(sum(10, 20));
}
//必须有webpack低沉来解析这个代码
module.exports = {
  flag: flag,
  sum: sum
}

b.js

//导入
var aaa = require('./aaa.js')
var flag = aaa.flag;
var sum = aaa.sum;
//上面三步等于下面一步
var {flag, sum} = require('./aaa.js')

sum(20, 30)

10.2.2 ES6模块化-export/import

  • import
    • 通过*可以导入模块中所有的export变量
    • 但是通常情况下我们需要给*起一个别名,方便后续的使用(代码如下)
// 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 addr from "./aaa.js";
addr('你好啊');

// 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);

  • export
    • 当在index.html<script src="aaa.js" type="module"></script> 设置type="module"则定义aaa.js为一个模块(闭包)。
    • 想用其它模块的变量则可以使用export
  • export default
    • 某些情况下,一个模块中包含某个的功能,我们并不希望给这个功能命名,而且让导入者可以自己来命名

aaa.js

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('在奔跑');
  }
}

// 5.export default
// const address = '北京市'
// export {
//   address
// }
// export const address = '北京市'

//只能有一个default*2*
// const address = '北京市'
// export default address

export default function (argument) {
  console.log(argument);
}

bbb.js

import {sum} from './aaa.js'

var name = '小红'
var flag = false

console.log(sum(100, 200));

Vue Day 05

十一. Webpack

11.1 Webpack和grunt/gulp的对比

  • grunt/gulp更加强调的是前端流程的自动化,模块化不是它的核心。
  • webpack更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能。

问:webpack node npm 的联系

11.2 Webpack安装相关知识

  • npm init用于创建package.json文件
  • --save-dev是开发时依赖,项目打包后不需要继续使用的(本地的webpack,局部的)
  • 为什么全局安装后,还需要局部安装呢?
    • 在终端直接执行webpack命令,使用的全局安装的webpack
    • 当在package.json中定义了scripts时,其中包含了webpack命令,那么使用的是局部webpack

  • 文件和文件夹解析:
    • dist文件夹:用于存放之后打包的文件(distribution)
    • src文件夹:用于存放我们写的源文件
    • main.js:项目的入口文件。具体内容查看下面详情。
    • mathUtils.js:定义了一些数学工具函数,可以在其他地方引用,并且使用。具体内容查看下面的详情。
    • index.html:浏览器打开展示的首页html
    • package.json:通过npm init生成的,npm包管理的文件(暂时没有用上,后面才会用上)

一个项目往往依赖特定的webpack版本,全局的版本可能很这个项目的webpack版本不一致,导出打包出现问题,所以通常一个项目,都有自己局部的webpack。

  • package.json中的script中定义"build":"webpack"就可以使用npm run build相当于在终端使用webpack
    • npm run server同理
  • 在任何终端执行webpack都是全局的,但如果在package.json中配置脚本script则优先在本地找webpack
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  • 直接用webpack 则是命令./node_module/.bin/webpack 名来执行
  • 安装本地固定版本生产依赖命令:npm install webpack@版本号 --save-dev

11.3 Webpack中核心之一:loader

11.3.1 打包css、less、js、image等图片需要下载相应的loader和手动配置

  • 在对图片处理中,options中添加上如下选项:
    • img:文件要打包到的文件夹
    • name:获取图片原来的名字,放在该位置
    • hash:8:为了防止图片名称冲突,依然使用hash,但是我们只保留8位
    • ext:使用图片原来的扩展名
{
        test: /\.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: 'url-loader',
            options: {
              // 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
              // 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
              limit: 13000,
              name: 'img/[name].[hash:8].[ext]'
            },
          }
        ]
      },

11.3.2 ES6语法的处理

将ES6转化为ES5:因为很多浏览器不能识别ES6

  • webpack中,我们直接使用babel对应的loader
    npm install --save-dev babel-loader@7 babel-core babel-preset-es2015
  • 配置文件
module: {
  rules: [
    {
      test: /\.js$/,
      //排除
      exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
        options: {
          presets: ['es2015']
        }
      }
    }
  ]
}

11.4 使用webpack配置Vue

  • npm i vue --save
    • 不加-dev是因为整个项目都需要用Vue,运行时也需要依赖,故不需要开发依赖
    • 注:因为我们后续是在实际项目中也会使用vue的,所以并不是开发时依赖

  • npm run build出现Error
    • ERROR:[Vue warn]: You are using the runtime-only build of Vue where the template compiler is rotavailable. Either pre-compile the templates into render functions,or use the compiler-includc( found in <Root>)

    • 原因:打包Vue会出现两个情况:

      • 一、runtime-only->代码中,不可以又任何的template,挂载的app的vue实例<div id = 'app'></div>相当于template
      • 二、runtime-complier->代码中可以又template,因为有compiler可以用于编译template。
        解决方案:
        修改webpack配置
resolve: {
  //alias:别名的意思
  alias: {
    //指向一个具体的文件夹vue.esm.js报含runtime-complier
    'vue$ ': 'vue/dist/vue.esm.js'
    }
},

11.5 创建Vue时template和el的关系

1.新的形式

import Vue from 'vue'
//这个形式也可以,不用 const app =new Vue(...)
new Vue({
  el: '#app',
  data:{
    message:'hello'
  }
  
})

2.template
需求:

  • 如果希望将data中的数据显示在界面中,就必须是修改index.html
  • 如果自定义了的件,也必须修改index.html来使用组件
  • 但是html模板在之后的开发中,我并不希望手动的来频繁修改,是否可以做到呢?
    • 使用template
  • 在前面的Vue实例中,我们定义了el属性,用于和index.html中的#app进行绑定,让Vue实例之后可以管理它其中的内容
  • 这里,我们可以将html中的div元素中的{{message}}内容删掉,只保留一个基本的id为div的元素
  • 但是如果我依然希望在其中显示{{message}}的内容,应该怎么处理呢?
  • 我们可以再定义一个template属性,代码如下:
new Vue({
  el: '#app' ,
  template: '<div id="app">{message}</div>',
  data: {
    message: 'leng'
    }
})

1.el用于指定Vue要管理的DOM,可以帮助解析其中的指令、事件监听等等,而如果Vue实例中同时指定了template,那么template模板的内容会替换掉挂载的对应el的模板。
2.将template模板中的内容进行抽离,会分成三部分书写:template、script、style,结构变得非常清晰。

11.6 Vue的终极使用方案

  • 方案一:
const App = {
  template:  `
  <div>
    <h2>{{message}}</h2>
    <button @click="btnClick">按钮</button>
    <h2>{{name}}</h2>
  </div>
  `,
  data() {
    return {
      message: 'Hello Webpack',
      name: 'leng'
    }
  },
  methods: {
    btnClick() {

    }
  }
}
new Vue({
  el: '#app',
  template: '<App/>',
  components: {
    App
  }
})

  • 方案二:
    创建app.js
export default {
  template:  `
  <div>
    <h2>{{message}}</h2>
    <button @click="btnClick">按钮</button>
    <h2>{{name}}</h2>
  </div>
  `,
  data() {
    return {
      message: 'Hello Webpack',
      name: 'leng'
    }
  },
  methods: {
    btnClick() {

    }
  }

main.js

//引用vue文件下的app.js
import App from './vue/app.js'
  • 但是模板和js代码并没有分离
    改为以下代码
    创建文件App.vue
<template>
  <div>
    <h2 class="title">{{message}}</h2>
    <button @click="btnClick">按钮</button>
    <h2>{{name}}</h2>
    <Cpn/>
  </div>
</template>

<script>
  import Cpn from './Cpn'

  export default {
    name: "App",
    components: {
      Cpn
    },
    data() {
      return {
        message: 'Hello Webpack',
        name: 'leng'
      }
    },
    methods: {
      btnClick() {

      }
    }
  }
</script>
<!--可以定义样式-->
<style scoped>
  .title {
    color: green;
  }
</style>

main.js

// import App from './vue/app'
import App from './vue/App.vue'
  • 配置对应loader
    npm install vue-loader vue-template-compiler --save-dev
//webpack.config.js
{
        test: /\.vue$/,
        use: ['vue-loader']
      }
  • 会出现ERROR:plugin缺少插件:在package.json中配置"vue-loader":^13.0.0到指定版本13.0.0
  • package.json配置改变要重新加载:npm install

  • 组件化开发:
    • 创建Cpn.vue
  • import Cpn from './Cpn.js'
    • 如果想省略.js需要更改webpack.config.js的配置,如果使用脚手架就不用这种操作
resolve: {
    // alias: 别名
    //如果想省略`.js`需要更改webpack.config.js的配置
    extensions: ['.js', '.css', '.vue'],
    //配置路径与该内容点无关
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  }

Cpn.vue

<template>
  <div>
    <h2>我是cpn组件的标题</h2>
    <p>我是cpn组件的内容, 哈哈哈</p>
    <h2>{{name}}</h2>
  </div>
</template>

<script>
  export default {
    name: "Cpn",
    data() {
      return {
        name: 'CPN组件的name'
      }
    }
  }
</script>

<style scoped>

</style>

App.vue

<template>
  <div>
    <h2 class="title">{{message}}</h2>
    <button @click="btnClick">按钮</button>
    <h2>{{name}}</h2>
    <Cpn/>
  </div>
</template>

<script>
  import Cpn from './Cpn'

  export default {
    name: "App",
    components: {
      Cpn
    },
    data() {
      return {
        message: 'Hello Webpack',
        name: 'leng'
      }
    },
    methods: {
      btnClick() {

      }
    }
  }
</script>
<!--可以定义样式-->
<style scoped>
  .title {
    color: green;
  }
</style>

SPA(simple page web application)单页面富应用

11.6 webpack-plugin

  • loader和plugin区别
    • loader主要用于转换某些类型的模块,它是一个转换器。
    • plugin是插件,它是对webpack本身的扩展,是一个扩展器。
  • plugin的使用过程:
    • 步骤一:通过npm安装需要使用的plugins(某些webpack已经内置的插件不需要安装)
    • 步骤二:在webpack.config.js中的plugins中配置插件。
  1. 添加版权协议
  • 插件名字叫BannerPlugin,属于webpack自带的插件。
  • 不用安装其它
const path = require( ' path')
const webpack = require( 'webpack')
module.exports = {
  //...
  plugins: [
    new webpack.BannerPlugin('最终版权归***所有')
]
  • 重新打包程序
  1. 打包html的plugin
  • 自动生成一个index.html文件(可以指定模板来生成)
  • 将打包的js文件,自动通过script标签插入到body中
  • npm install html-webpack-plugin --save-dev(@3.2.0)
  • 配置文件
  • 重新打包
  • 使用插件,修改webpack.config.js文件中plugins部分的内容如下:
    • 这里的template表示根据什么模板来生成index.html
    • 另外,我们需要删除之前在output中添加的publicPath属性
    • 否则插入的script标签中的src可能会有问题
plugins: [
      new webpack.BannerPlugin('最终版权归***所有'),
      new HtmlWebpackPlugin({
        template: 'index.html'
      }),
      
  ]
  1. js压缩的Plugin
  • 在项目发布之前,我们必然需要对js等文件进行压缩处理
  • 这里,我们就对打包的js文件进行压缩
  • 我们使用一个第三方的插件uglifyjs-webpack-plugin,并且版本号指定1.1.1,和CLI2保持一致
  • npm install uglifyjs-webpack-plugin@1.1.1 --save-dev
const path = require( ' path ')
const webpack = require( 'webpack ' )
const uglifyJsPlugin = require( 'uglifyjs-webpack-plugin ')
module.exports = {
  plugins: [
    new webpack.BannerPlugin('最终版权归***所有')
    //开发阶段一般不需要丑化,运行阶段才需要
    new uglifyJsPlugin()
]
}
  1. 搭建本地服务器
  • 本地服务器基于node.js搭建,内部使用express框架,可以实现我们想要的让浏览器自动刷新显示我们修改后的结果。
    • npm install --save-dev webpack-dev-server@2.9.1(对应脚手架2使用的版本)
    • npm install --save-dev webpack-dev-server@3.10.3(对应webpack@4.42.0使用的版本)
  • devserver也是作为webpack中的一个选项,选项本身可以设置如下属性:
    • contentBase:为哪一个文件夹提供本地服务,默认是根文件夹,我们这里要填写./dist
    • port:端口号
    • inline:页面实时刷新
    • historyApiFallback:在SPA页面中,依赖HTML5的history模式
  • 可以再配置另外一个scripts:--open参数表示直接打开浏览器
    • 在脚本中配置"dev" : "webpack-dev-server --open"
  • 终端:webpack-dev-server跑不起来,是因为server是局部安装的,这条命令是访问全局
    • ./node_modules/.bin/webpack-dev-server
    • 也可以在脚本中配置"dev":"webpack-dev-server"
  plugins: [
      new webpack.BannerPlugin('最终版权归aaa所有'),
      new HtmlWebpackPlugin({
        template: 'index.html'
      }),
      new UglifyjsWebpackPlugin()
  ],
  devServer: {
    //用于服务的当前文件夹
    contentBase: './dist',
    inline: true
  }

11.7 webpack配置文件分离

  • webpack.config.js中有些配置是开发环境需要,有些配置是运行环境需要,所以我们可以进行配置分离,高效使用代码。
  • 创建好base.config.js``dev.config.js``prod.config.js文件后需要安装npm install webpack-merge --save-devbase.config.jsprod.config.js合并在一起
    • 报错下载webpack-merge@4.1.5
  • 创建base.config.js文件

    • webpack.config.js中的文件全部复制到base.config.js文件
    • 将基本文件(公共文件)保留,注释掉运行环境所需要的东西
    const path = require('path')
    const webpack = require('webpack')
    const HtmlWebpackPlugin = require('html-webpack-plugin')
    const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
    
    module.exports = {
      entry: './src/main.js',
      output: {
        path: path.resolve(__dirname, '../dist'),
        filename: 'bundle.js',
        // publicPath: 'dist/'
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            // css-loader只负责将css文件进行加载
            // style-loader负责将样式添加到DOM中
            // 使用多个loader时, 是从右向左
            use: [ 'style-loader', 'css-loader' ]
          },
          {
            test: /\.less$/,
            use: [{
              loader: "style-loader", // creates style nodes from JS strings
            }, {
              loader: "css-loader" // translates CSS into CommonJS
            }, {
              loader: "less-loader", // compiles Less to CSS
            }]
          },
          {
            test: /\.(png|jpg|gif|jpeg)$/,
            use: [
              {
                loader: 'url-loader',
                options: {
                  // 当加载的图片, 小于limit时, 会将图片编译成base64字符串形式.
                  // 当加载的图片, 大于limit时, 需要使用file-loader模块进行加载.
                  limit: 13000,
                  name: 'img/[name].[hash:8].[ext]'
                },
              }
            ]
          },
          {
            test: /\.js$/,
            // exclude: 排除
            // include: 包含
            exclude: /(node_modules|bower_components)/,
            use: {
              loader: 'babel-loader',
              options: {
                presets: ['es2015']
              }
            }
          },
          {
            test: /\.vue$/,
            use: ['vue-loader']
          }
        ]
      },
      resolve: {
        // alias: 别名
        extensions: ['.js', '.css', '.vue'],
        alias: {
          'vue$': 'vue/dist/vue.esm.js'
        }
      },
      plugins: [
        new webpack.BannerPlugin('最终版权归aaa所有'),
        new HtmlWebpackPlugin({
          template: 'index.html'
        })
      ]
    }
    
  • 创建dev.config.js文件

    • 开发模式 模块的文件
    const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
    const webpackMerge = require('webpack-merge')
    const baseConfig = require('./base.config')
    
    module.exports = webpackMerge(baseConfig, {
      plugins: [
        new UglifyjsWebpackPlugin()
      ]
    })
    
  • 创建prod.config.js文件

    const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin')
    const webpackMerge = require('webpack-merge')
    //导入
    const baseConfig = require('./base.config')
    
    //将base 和 prod进行了合并
    module.exports = webpackMerge(baseConfig, {
      plugins: [
        new UglifyjsWebpackPlugin()
      ]
    })
    
  • 需要配置文件

    • 创建好base.config.js``dev.config.js``prod.config.js文件后需要安装npm install webpack-merge --save-devbase.config.jsprod.config.js合并在一起。
    • 配置代码const webpackMerge = require('webpack-merge')
  • ERROR_01:

    • 运行npm run build 报错
    • 未找到配置文件webpack.config.js
    • 解决办法:配置脚本文件
    "scripts": {
      "test": "echo \"Error: no test specified\" && exit 1",
      //增添 --config ./文件路径
      "build": "webpack --config ./build/prod.config.js",
      "dev": "webpack-dev-server --open --config ./build/dev.config.js"
    },
    
  • ERROR_02:

    • 打包成功后没有打包到dist文件夹下,而在build
    • 原因:path:path.resolve(__dirname,'dist')指的是当前所在目录的dist文件夹,base在build,故就在build文件夹中创建了打包文件dist文件
    • 解决办法:path:path.resolve(__dirname,'../dist')指当前的上一层目录进行创建
  • 以上内容,脚手架会自动配置

Vue Day 06

十二. Vue CLI - 脚手架

12.1 什么是Vue CLI-安装 Vue CLI

  • 使用Vue.js开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情。

  • 如果每个项目都要手动完成这些工作,那无以效率比较低效,所以通常我们会使用一些脚手架工具来帮助完成这些事情。

  • 什么是CLI?

    • CLI是Command-Line Interface, 翻译为命令行界面, 但是俗称脚手架.
    • Vue CLI是一个官方发布 vue.js 项目脚手架
    • 使用 vue-cli 可以快速搭建Vue开发环境以及对应的webpack配置.
  • 使用CLI的前提

    • 安装 NodeJS
    • 安装NPM(是Node Package Manager)
      • cnpm安装

    由于国内直接使用 npm 的官方镜像是非常慢的,这里推荐使用淘宝 NPM 镜像。
    你可以使用淘宝定制的 cnpm (gzip 压缩支持) 命令行工具代替默认的 npm:
    npm install -g cnpm --registry=https://registry.npm.taobao.org
    这样就可以使用 cnpm 命令来安装模块了:cnpm install [name]

    也可以使用代理registry:npm config set registry https://registry.npm.taobao.org后可以直接使用npm

    • 检查 Node和npm版本:node -v; npm -v;
    • Webpack的全局安装:npm install webpack -g
    • 安装Vue脚手架npm install -g @vue/cli
      • vue --version检查是否安装成功
      • 注意:安装的是Vue CLI3的版本,如果需要想按照Vue CLI2的方式初始化项目时不可以的。
      • Vue CLI2初始化项目
        vue init webpack my-project
      • Vue CLI3初始化项目
        vue create my-project
  • 拉取 VueCLI 2.x 模板 (如果要用旧版本就需要npm以下内容)

    npm install -g @vue/cli-init
    // `vue init` 的运行效果将会跟 `vue-cli@2.x` 相同
    vue init webpack my-project
    
  • Vue/cli安装不成功

C:\Users\12987\AppData\Roaming\npm-cache删除npm-cache
或者cmd:npm clean cache -force
然后重新安装npm

12.2 VueCLI2 初始化项目过程

  • vue init webpack 项目名称=>vue init webpack vuecli2test
    • vue全家桶:VueCore+Vue-router+Vuex
    • ? Author Noerrorist <1298711060@qq.com>默认选取git中的信息,可以在gitconfig中更改
    • ESLint:ES-Lint就是js代码进行限制:如果代码编写不规范,编译器就会报错
      • 如果? Use ESLint to lint your code? No 你选择为yes
      • Pick an ESLint preset:
        Standard
        Airbnb
        none
    • unit test (单元测试)、No
    • Setup e2e tests with Nightwatch? (Y/n)
      • e2e表示 end to end 即端到端测试
      • Nightwatch

      安装Nightwatch,是一个利用selenium或webdriver或phantomjs等进行自动化测试的框架

      • 软件测试工程师
  • VueCLI2的文件的目录结构
    • 读文件:build和config
    • 先读package.json
    "scripts": {--progress监听进度
      //
    "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "build": "node build/build.js"
    },
    
    • npm run build 执行的是哪个文件再去找。
    • node是c++开发的,内含V8引擎所以执行解析代码快,直接跳过字节码转换到二进制代码
  • 如何关掉ESlint:
    • config->index.js->useESLint:false

12.3 runtime-compiler 和 runtime-only 的区别

  • 如果在之后的开发中,依然使用template,就需要选择Runtime-Compiler
  • 如果之后的开发中,使用的是.vue文件夹开发,那么可以选择Runtime-only

将runtime-compiler中的main.js改掉一样可以变成runtime-only

import Vue from 'vue'
import App from './App'

Vue.config.productionTip = false

/* eslint-disable no-new */
// const cpn = {
//   template: '<div>{{message}}</div>',
//   data() {
//     return {
//       message: '我是组件message'
//     }
//   }
// }

new Vue({
  el: '#app',
  render: function (createElement) {
    // 1.普通用法: createElement('标签', {标签的属性}, [''])
    //将el:'#app'给替换了,即也将index.html中的div替换成了h2标签
    // return createElement('h2',
    //   {class: 'box'},
    //   ['Hello World', createElement('button', ['按钮'])])

    // 2.传入组件对象:
    return createElement(App)
  }
})


// runtime-compiler(v1)
// template -> ast -> render -> vdom -> UI

// runtime-only(v2)(1.性能更高 2.下面的代码量更少)
// render -> vdom -> UI

12.4 npm run build 和 npm run dev 底层原理

  • npm run build 底层原理
  • npm run dev 底层原理

12.5 修改配置webpack.base.conf.js 起别名

resolve: {
  extensions: ['.js ', '.vue ', '.json ' ],
  alias: {
    '@': resolve ( 'src '),
    'pages' : resolve( 'src/pages '),' common' : resolve( 'src / common '),
    'components': resolve( 'src/ components ' ),
    'network ': resolve( 'src/network ')
    }
},

12.6 VueCLI3 初始化项目过程

  • vue-cli 3 与 2 版本有很大区别

    • 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中
  • ? Where do you prefer placing config for Babel, ESLint,etc.? (Use arrow keys).

    • In dedicated config files //独立的配置文件(建议)
    • In package.json //放在package.json
  • ? Save this as a preset for future projects? (y/N) //是否新建一个选项作为工程

    • 如何删除:更改目录C:\Users\12987下的.vuerc文件部分内容注释掉
    {
      {
    "useTaobaoRegistry": false,
    "presets": {
    //  "leng": {
    //    "useConfigFiles": true,
    //    "plugins": {
    //      "@vue/cli-plugin-babel": {}
    //    },
    //    "vueVersion": "3"
       }
      }
    }
    

Git 存储库“d:\Desktop\My Files\RecentlyStudy\VUE\Vue.js\demo\Day06\ClassCode\LearnVuejs06\testvuecli3”中存在大量活动更改,将仅启用部分 Git 功能。 是否要将“node_modules”添加到 .gitignore?

package-lock.json: For performance reasons, document symbols have been limited to 5000 items.
Use setting ‘json.maxItemsComputed’ to configure the limit.

12.6.1 配置文件的查看和修改

  1. 隐藏的webpack配置在哪
  • 更改配置: vue.config.js
    • 文件被标绿原因:修改了文件 且没有 git
    • 解决方案:
    1. git status
    2. git add .
    3. git status
    4. git commit -m ‘修改配置文件’
  1. 启动服务器配置UI: vue ui

12.7 箭头函数的使用和

箭头函数的基本使用

<body>
<script>

  // 箭头函数: 也是一种定义函数的方式
  // 1.定义函数的方式: function
  const aaa = function () {

  }

  // 2.对象字面量中定义函数
  const obj = {
    bbb() {

    }
  }

  // 3.ES6中的箭头函数
  // const ccc = (参数列表) => {
  //
  // }
  const ccc = () => {

  }
</script>
</body>

箭头函数的返回值

<body>
<script>
  // 1.参数问题:
  // 1.1.放入两个参数
  const sum = (num1, num2) => {
    return num1 + num2
  }

  // 1.2.放入一个参数
  const power = num => {
    return num * num
  }

  // 2.函数中
  // 2.1.函数代码块中有多行代码时
  const test = () => {
    // 1.打印Hello World
    console.log('Hello World');

    // 2.打印Hello Vuejs
    console.log('Hello Vuejs');
  }

  // 2.2.函数代码块中只有一行代码
  // const mul = (num1, num2) => {
  //   return num1 + num2
  // }
  const mul = (num1, num2) => num1 * num2
  console.log(mul(20, 30));

  // const demo = () => {
  //   console.log('Hello Demo');
  // }
  const demo = () => console.log('Hello Demo')
  console.log(demo()); // undefined
</script>
</body>

12.7.1 箭头函数this的指向

  • 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。
  • this对象的指向是可变的,但在箭头函数中,它是固定的
//箭头函数中的this总时指向函数定义生效时所在的对象({id:52})
//console输出函数为52
function foo(){
  setTimeout(()=>{
    console.log('id',this,id);
  },100)
}
var id=21;
foo.call({id:52});

箭头函数中this的使用

<body>

<script>
  // 什么时候使用箭头
  // setTimeout(function () {
  //   console.log(this);
  // }, 1000)
  //
  // setTimeout(() => {
  //   console.log(this);
  // }, 1000)
  //以上打印出都是window


  // 问题: 箭头函数中的this是如何查找?
  // 答案: 向外层作用域中, 一层层查找this, 直到有this的定义.
  // const obj = {
  //   aaa() {
  //     setTimeout(function () {
  //       console.log(this); // window
  //     })
  //
  //     setTimeout(() => {
  //       console.log(this); // obj对象
  //     })
  //   }
  // }
  //
  // obj.aaa()


  const obj = {
    aaa() {
      setTimeout(function () {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // window
        })
      })

      setTimeout(() => {
        setTimeout(function () {
          console.log(this); // window
        })

        setTimeout(() => {
          console.log(this); // obj
        })
      })
    }
  }

  obj.aaa()
</script>
</body>

12.8 Vue-Router

  • 使用脚手架 Vue CLI2

  • 路由(routing)就是通过互联的网络把信息从源地址传输到目的地址的活动

  • 面试:什么是前端渲染,什么是后端渲染?

    • 1.后端路由阶段 -> 2.前后端分离阶段 -> 3.前后端分离+ 前端路由阶段
  1. 后端路由阶段
    • URL会发送到服务器, 服务器会通过正则对该URL进行匹配, 并且最后交给一个Controller进行处理.
    • Controller进行各种处理, 最终生成HTML或者数据, 返回给前端.
    • 这就完成了一个IO操作.
      • 优点:有利于SEO优化
      • 缺点:通常情况下HTML代码和数据以及对应的逻辑会混在一起, 编写和维护都是非常困难的
  1. 前后端分离阶段
    • 随着Ajax的出现, 有了前后端分离的开发模式.
    • 后端只提供API来返回数据, 前端通过Ajax获取数据, 并且可以通过JavaScript将数据渲染到页面中.
      • 优点:前后端责任的清晰, 后端专注于数据上, 前端专注于交互和可视化上.
      • 并且当移动端(iOS/Android)出现后, 后端不需要进行任何处理, 依然使用之前的一套API即可.
      • 目前很多的网站依然采用这种模式开发.
  1. 前后端分离+ 前端路由阶段
    • 单页面富应用阶段:
    • 其实SPA最主要的特点就是在前后端分离的基础上加了一层前端路由.
    • 也就是前端来维护一套路由规则.

12.8.1 url的hash和HTML5的history

目的:改变url 但是页面不刷新
可以在控制台进行验证

  1. hash
    URL的hash
    URL的hash也就是锚点(#), 本质上是改变window.location的href属性.
    可以通过直接赋值location.hash来改变href, 但是页面不发生刷新.
    eg:location.hash=‘aaa’

  2. HTML5的history模式:pushState 和 history.replaceState() 和 go
    history接口是HTML5新增的, 它有五种模式改变URL而不刷新页面.
    history.pushState()
    eg:history.pushState({},’’,‘home’)//入栈
    history.back() //弹栈
    history.replaceState()
    eg:history.replaceState({},’’,‘home’)//是替换,不能返回
    history.go()
    history.back() 等价于 history.go(-1)
    history.forward() 则等价于 history.go(1)
    这三个接口等同于浏览器界面的前进后退。

12.8.2 Vue-Router基础

  • 访问其官方网站对其进行学习: https://router.vuejs.org/zh/
  1. 安装 Vue-Router

    • 步骤一: 安装vue-router
      npm install vue-router --save
    • 步骤二: 在模块化工程中使用它(因为是一个插件, 所以可以通过Vue.use()来安装路由功能)
      • 第一步:导入路由对象,并且调用 Vue.use(VueRouter)
      • 第二步:创建路由实例,并且传入路由映射配置
      • 第三步:在Vue实例中挂载创建的路由实例
  2. 使用 vue-router的步骤:

    • 第一步: 创建路由组件
    • 第二步: 配置路由映射: 组件和路径映射关系
    • 第三步: 使用路由: 通过<router-link><router-view>
  3. 在没有配置Vue-Router的情况下配置路由

    • 在当前路径下创建路由文件./src/router

    • 在当前路径下创建index文件./src/router/index.js

      • ./src/router/index.js配置路由相关信息

      ./src/router/index.js

      
      // 配置路由相关的信息
      //VueRouter就是一个插件
      import VueRouter from 'vue-router'
      import Vue from 'vue'
      import Home from '../components/Home'
      import About from '../components/About'
      // 1.通过Vue.use(插件), 安装插件
      Vue.use(VueRouter)
      // 2.创建VueRouter对象
      const routes = [
        {
          path: '',
          // redirect重定向
          redirect: '/home'
        },
        {
          path: '/home',
          component: Home
        },
        {
          path: '/about',
          component: About
        }
      ]
      const router = new VueRouter({
        // 配置路由和组件之间的映射关系
        routes,
        mode: 'history',
        //用于<router-link>处理active-class
        linkActiveClass: 'active'
      })
      // 3.将router对象传入到Vue实例(导出)
      export default router
      

      main.js

      //挂载到Vue实例中
      import Vue from 'vue'
      import App from './App'
      //import router from './router/index.js'自动导入index.js
      import router from './router'
      
      Vue.config.productionTip = false
      
      new Vue({
        el: '#app',
        router,
        render: h => h(App)
      })
      
      • ./src/components配置路由相关信息,创建路由实例 vscode模板快捷键在vue后缀文件中输入vue

      Home.vue

      <template>
        <div>
          <h2>我是首页</h2>
        </div>
      </template>
      <script>
      export default {
        name: "Home"
      
      }
      </script>
      <style>
      </style>
      

      About.vue

      <template>
        <div>
          <h2>我是关于</h2>
        </div>
      </template>
      <script>
      export default {
        name:"About"
      
      }
      </script>
      <style>
      </style>
      
      • App.vue配置路由相关信息

      App.vue

      <template>
      <div id="app">
        <!-- <router-view></router-view> -->
        <!-- //vuerouter 定义 注册的组件,是全局的,被渲染成<a> -->
        <router-link to="/home">首页</router-link>
        <router-link to="/about">关于</router-link>
        <!-- //router-view路劲发生改变 进行渲染 相当于占位 在上方也可以 -->
        <router-view></router-view>
      </div>
      
    
    
  • <router-link>: 该标签是一个vue-router中已经内置的组件, 它会被渲染成一个<a>标签.

  • <router-view>: 该标签会根据当前的路径, 动态渲染出不同的组件.

  • 网页的其他内容, 比如顶部的标题/导航, 或者底部的一些版权信息等会和<router-view>处于同一个等级.

  • 在路由切换时, 切换的是<router-view>挂载的组件, 其他内容不会发生改变.

  • 如何可以让路径默认跳到到首页, 并且<router-view>渲染首页组件

    • 需要配置多配置一个映射就可以了:
    const routes = [
            {
              path: '',
              // redirect重定向
              redirect: '/home'
            },
    
  • 默认是hash模式

    • 更改模式HTML5:http://localhost:8080/home
    • 默认情况下, 路径的改变使用的URL的hash:http://localhost:8080/#home
const router = new VueRouter({
        // 配置路由和组件之间的映射关系
        routes,
        mode: 'history',
        linkActiveClass: 'active'
      })
  • <router-link>补充

    • tag: tag可以指定<router-link>之后渲染成什么组件, 比如上面的代码会被渲染成一个<li>元素, 而不是<a>

      • eg:<router-link to='/home' tag='button'>
    • replace: replace不会留下history记录, 所以指定replace的情况下, 后退键返回不能返回到上一个页面中

    • active-class: 当<router-link>对应的路由匹配成功时, 会自动给当前元素设置一个router-link-activeclass, 设置active-class可以修改默认的名称.

    • exact-active-class

      • 类似于active-class, 只是在精准匹配下才会出现的class.
      • 后面看到嵌套路由时, 我们再看下这个属性.
       const router = new VueRouter({
          // 配置路由和组件之间的映射关系
          routes,
          mode: 'history',
          //用于<router-link>处理active-class
          linkActiveClass: 'active'
        })
      
    • 在进行高亮显示的导航菜单或者底部tabbar时, 会使用到该类.

    • 但是通常不会修改类的属性, 会直接使用默认的router-link-active即可.

  • 通过代码跳转路由
    App.vue

    <template>
    <div id="app">
      <!-- <router-view></router-view> -->
      <!-- //vue-router 定义 注册的组件,是全局的,被渲染成<a> -->
      <!-- <router-link to="/home">首页</router-link>
      <router-link to="/about">关于</router-link> -->
      <!-- //router-view路劲发生改变 进行渲染 相当于占位 在上方也可以 -->
      <router-view></router-view>
      <button @click="homeClick">首页</button>
      <button @click="aboutClick">关于</button>
    </div>
    </template>
    
    <script>
    export default{
      name:'App',
      methods:{
        homeClick(){
          // //相当于绕过了vue-router去做,不可取
          // history.pushState();
          // this.$router来自vuerouter源码中的
          this.$router.push('/home');
            // this.$router.repalce('/home');
          console.log('homeClick');
        },
        aboutClick(){
          this.$router.push('/about');
          // this.$router.repalce('/about');
          console.log('aboutClick');
        }
      }
    }
    </script>
    

12.8.3 vue-router动态路由的使用

  • 动态路由

    • 在某些情况下,一个页面的path路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:
    • /user/aaaa或/user/bbbb
    • 除了有前面的/user之外,后面还跟上了用户的ID
    • 这种path和Component的匹配关系,称之为动态路由(也是路由传递数据的一种方式)。
  • 动态路由创建步骤

    • 创建一个组件./src/components/User.vue
    • 配置./src/router/index.js
      import User from '../components/User.vue'
      {
              path: '/user/:userID',
              component: User
            }
      
    • 配置App.vue
      <router-link to = "/user/zhangsan">用户</router-link>
      <router-view></router-view>
      
  • 动态路由 动态绑定创建步骤

    • 创建一个组件./src/components/User.vue
    • 配置./src/router/index.js
      import User from '../components/User.vue'
      {
              path: '/user/:userID',
              component: User
            }
      
    • 配置App.vue
      <template>
        <div id="app">
        
          <router-link to = "/home">首页 </router-link>
          <router-link to = "/about">关于 </router-link>
          <router-link v-bind:to = "'/user/'+userID">用户</router-link>
          <router-view></router-view>
        </div>
      </template>
      
      <script>
      
      export default{
        name:'App',
        data(){
          return{
            userID:'lisi',
          }
        
        }
      }
      </script>
      <style>
      </style>
      
  • 在动态绑定后,点击及展现userID的信息

    • 配置User.vue
    <template>
    <div>
      <h2>我是用户界面</h2>
      <h2>{{userID}}</h2>
      <h2>{{$route.params.userID}}</h2>
    </div>
    </template>
    
    <script>
    export default {
      name:"User",
      computed:{
        userID(){
          // .$route表示当前哪个路由处于活跃状态就是那一个路由
          //userID指的是/component/index.js配置的userID
          return this.$route.params.userID
        }
      }
    }
    </script>
    <style>
    </style>
    

12.8.4 vue-router 打包文件的解析 路由的懒加载

  • vue-router 打包文件的解析

  • 路由的懒加载

    • 当打包构建应用时,Javascript 包会变得非常大,影响页面加载。
    • 如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了
    • 路由懒加载的主要作用就是将路由对应的组件打包成一个个的js代码块.
    • 只有在这个路由被访问到的时候, 才加载对应的组件
    const routes = [
      {
            path: '/home',
            component:() => import( '../components/Home')
          },
          {
            path: '/about',
            component:() => import('../components/About.vue')
          },
    ];
    
    • 打包后效果:
      • cli4是默认效果
      • cli4以下:
  • 懒加载的三种方式

    • 方式一: 结合Vue的异步组件和Webpack的代码分析.
    const Home = resolve => { require.ensure(['../components/Home.vue'], 
    () => { resolve(require('../components/Home.vue')) })};
    
    • 方式二: AMD写法
    const About = resolve => require(['../components/About.vue'], resolve);
    
    • 方式三: 在ES6中, 我们可以有更加简单的写法来组织Vue异步组件和Webpack的代码分割.
    const Home = () => import('../components/Home.vue')
    
    • 重构配置./src/router/index.js:(建议使用这种方式)
    // 配置路由相关的信息
        //VueRouter就是一个插件
        import VueRouter from 'vue-router'
        import Vue from 'vue'
    
        // import Home from '../components/Home'
        // import About from '../components/About'
        // import User from '../components/User.vue'
        const Home=()=>import('../components/Home.vue')
        const About=()=>import('../components/About.vue')
        const User=()=>import('../components/User.vue')
        // 1.通过Vue.use(插件), 安装插件
        Vue.use(VueRouter)
        // 2.创建VueRouter对象
        const routes = [
          //配置路由映射
          {
            path: '',
            // redirect重定向
            redirect: '/home'
          },
          {
            path: '/home',
            component:Home
          },
          {
            path: '/about',
            component:About
          },
          {
            path: '/user/:userID',
            component: User
          }
        ]
        const router = new VueRouter({
          // 配置路由和组件之间的映射关系
          routes,
          mode: 'history',
          linkActiveClass: 'active'
        })
        // 3.将router对象传入到Vue实例(导出)
        export default router
    
    文件中的map文件只用来输出错误信息(追踪错误),不要此文件可以修改 `config/index.js` 中的`productionSourceMap:false`

12.8.5 路由的嵌套以及子路由默认路径

  • 目标:通过/home/news和/home/message访问一些内容.
  • 一个路径映射一个组件, 访问这两个路径也会分别渲染两个组件.
  • 实现嵌套路由有两个步骤:
    • 创建对应的子组件, 并且在路由映射中配置对应的子路由.
    • 在组件内部使用标签.
  • 具体实现
    • 创建./src/components/HomeNews.vue组件
    • 创建./src/components/HomeMessage.vue组件
    • 配置./router/index.js
          const Home=() => import('../components/Home.vue')
          const About=() => import('../components/About.vue')
          const HomeMessage=() => import('../components/HomeMessage.vue')
          const HomeNews=() => import('../components/HomeNews.vue')
      //在之前的基础上添加children属性进行配置
      {
              path: '/home',
              component:Home,
              children:[
                {
                  //路由默认路径
                    path: '',
                    // redirect重定向
                    redirect: 'news'
                },
                {
                  //子路由不用加/=》'/news'
                  path:'news',
                  component:HomeNews
                },
                {
                  path:'message',
                  component:HomeMessage
                }
              ]
            },
      
    • 配置.src/components/Home.vue
      <template>
      <div>
        <h2>我是首页</h2>
        <!-- 用于子组件路由嵌套:router-view -->
        <router-link to="/home/news">HomeNews</router-link>
        <router-link to="/home/message">HomeMessage</router-link>
        <router-view></router-view>
      </div>
      </template>
      

12.8.6 路由vue-router的参数传递

  • 创建一个组件, 并且将其配置好 Profile相当于用户档案:= Mine

    • 第一步: 创建新的组件Profile.vue
    • 第二步: 配置路由映射
    • 第三步: 添加跳转的
  • 传递参数主要有两种类型: params和query

    • params的类型:
      • 配置路由格式: /router/:id
      • 传递的方式: 在path后面跟上对应的值
      • 传递后形成的路径: /router/123, /router/abc
    • query的类型:
      • 配置路由格式: /router, 也就是普通配置
      • 传递的方式: 对象中使用query的key作为传递方式
      • 传递后形成的路径: /router?id=123, /router?id=abc
      • 创建组件Profile.vue
      <template>
      <div>
      <h2>Profile组件</h2>
      <h2>{{$route.query}}</h2>
      <h2>{{$route.query.name}}</h2>
      </div>
      </template>
      
      • 配置index.js
        正常配置就行
      • 配置App.vue
      <router-link :to = "{path:'/profile',
                           query:{name:'why'}}"
      >档案</router-link>
      
  • <router-link>改为<button>且重构<router-link v-bind:to = "'/user/'+userID">用户</router-link>

  • 使用代码进行跳转

    • 建立动态绑定进行拼接
    <template>
    <div id="app">
      <!-- <router-view></router-view> -->
      <!-- //vuerouter 定义 注册的组件,是全局的,被渲染成<a> -->
      <!-- <router-link to="/home">首页</router-link>
      <router-link to="/about">关于</router-link> -->
      <!-- //router-view路劲发生改变 进行渲染 相当于占位 在上方也可以 -->
      <!-- <router-view></router-view> -->
      <!-- <button @click="homeClick">首页</button>
      <button @click="aboutClick">关于</button> -->
    
      <router-link to = "/home">首页 </router-link>
      <router-link to = "/about">关于 </router-link>
      <!-- <router-link v-bind:to = "'/user/'+userID">用户</router-link> -->
    
      <!-- <router-link to = "/profile">档案</router-link> -->
      <!-- to="{}"输出的是字符串{},故要加上动态绑定":" -->
    
      <!-- <router-link :to = "{path:'/profile',query:{name:'why'}}">档案</router-link> -->
      <button @click="userClick">用户</button>
      <button @click="profileClick">档案</button>
    
       <router-view></router-view>
    </div>
    </template>
    
    <script>
    
    export default{
      name:'App',
      data(){
        return{
          userID:'lisi',
        }
      },
      methods:{
        homeClick(){
          // //相当于绕过了vue-router去做,不可取
          // history.pushState();
          // this.$router来自vuerouter源码中的
          this.$router.push('/home');
            // this.$router.repalce('/home');
          console.log('homeClick');
        },
        aboutClick(){
          this.$router.push('/about');
          // this.$router.repalce('/about');
          console.log('aboutClick');
        },
        userClick(){
          this.$router,push('/user'+this.userID)
        },
        profileClick(){
          this.$router,push({
            path:'/profile',
            query:{
              name:'kobe',
              age:19,
              height:1.87
            }
          })
        }
      }
    }
    </script>
    <style>
    </style>
    
  • r o u t e 和 route和 routerouter的区别

  • 前者用于当前活跃的路由,后者用于全局

  • r o u t e r 为 V u e R o u t e r 实 例 , 想 要 导 航 到 不 同 U R L , 则 使 用 router为VueRouter实例,想要导航到不同URL,则使用 routerVueRouterURL使router.push方法

  • r o u t e 为 当 前 r o u t e r 跳 转 对 象 里 面 可 以 获 取 n a m e 、 p a t h 、 q u e r y 、 p a r a m s 等 < i m a g e s r c = " . / i m a g M d / route为当前router跳转对象里面可以获取name、path、query、params等 <image src="./imagMd/ routerouternamepathqueryparams<imagesrc="./imagMd/route和$router区别.png">

12.8.7 导航守卫

需求:
网页标题是通过<title>来显示的, 但是SPA只有一个固定的HTML, 切换不同的页面时, 标题并不会改变.
但是我们可以通过JavaScript来修改<title>的内容.window.document.title = '新的标题'

  • 方法一:改变每一个文件的配置
    home.vue为例创建document.title='首页';

    <script>
    export default {
      name: "Home",
      data(){
        return{
        message:"你好"
        }
      },
      created(){
        console.log("created");
        document.title='首页';
      },
      //挂载在DOM上回回调
      mounted(){
        console.log("mounted");
      },
      updated(){
        console.log("updated");
      }
    }
    </script>
    
  • 方法二:监听每个路由跳转的过程 使用全局导航守卫

    • 导航钩子beforeEach的三个参数解析:

    • to: 即将要进入的目标的路由对象.

    • from: 当前导航即将要离开的路由对象.

    • next: 调用该方法后, 才能进入下一个钩子.

    • 修改/router/index.js创建meta属性

    • 创建函数并配置router.beforeEach()

    • 嵌套路由:document.title=to.meta.title;改为document.title=to.matched[0].meta.title;

          const routes = [
            ...
            {
              path: '/about',
              component:About,
              meta:{
                title:'首页'
              }
            },
            {
              path: '/user/:userID',
              component: User,
              meta:{
                title:'用户'
              }
            },
            {
              path:'/profile',
              component:Profile,
              meta:{
                title:'档案'
              }
            }
          ]
          const router = new VueRouter({
            // 配置路由和组件之间的映射关系
            routes,
            mode: 'history',
            linkActiveClass: 'active'
          })
    
          router.beforeEach((to,from,next)=>{
            //document.title=to.meta.title;
            document.title=to.matched[0].meta.title;
            //to: 即将要进入的目标的路由对象.
            console.log(to);
            next()
          })
          // 3.将router对象传入到Vue实例(导出)
          export default router
    
  • 导航守卫补充

    • beforeEach(前置钩子)
    • afterEach (后置钩子)不需要主动调用next()函数.
    • beforeEach\afterEach被称之为全局守卫.
  • 路由独享的守卫.

  • 组件内的守卫.
    更多查看:

  • 网址:https://router.vuejs.org/zh/guide/advanced/navigation-guards.html#%E8%B7%AF%E7%94%B1%E7%8B%AC%E4%BA%AB%E7%9A%84%E5%AE%88%E5%8D%AB

12.9 keep-alive & vue-router

  • keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。
  • 有两个非常重要的属性:
    • include - 字符串或正则表达,只有匹配的组件会被缓存<keep-alive exclude="name1,name2,..."></keep-alive>
    • exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存
  • router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:
    <keep-alive>
    <router-view>
    <! --所有路径匹配到的视图组件都会被缓存!-->
    </router-view>
    </keep-alive>
    
    • 注释/router/index.js中的部分配置
    children:[
                // {
                //     path: '',
                //     // redirect重定向
                //     redirect: 'news'
                  
                // },
                ...
    ]
    
    • 配置home.vue
    <script>
    export default {
      name: "Home",
      data(){
        return{
        message:"你好",
        path:"/home/news"
        }
      },
      created(){
        console.log("home created");
        // document.title='首页';
      },
      destroyed(){
        console.log("home destroyed");
      },
      // 这两个函数, 只有该组件被保持了状态使用了keep-alive时, 才是有效的
        activated() {
          this.$router.push(this.path);
          console.log('activated');
        },
        deactivated() {
          console.log('deactivated');
        },
        beforeRouteLeave (to, from, next) {
          console.log(this.$route.path);
          this.path = this.$route.path;
          next();
        }
      // //挂载在DOM上回回调
      // mounted(){
      //   console.log("mounted");
      // },
      // updated(){
      //   console.log("updated");
      // }
    }
    

activated/deactivated在keep-alive中使用

Vue Day 07

十三. TabBar实现思路

13.1 基本结构的搭建

  1. 如果在下方有一个单独的TabBar组件,你如何封装
    自定义TabBar组件,在APP中使用
    让TabBar出于底部,并且设置相关的样式
  2. TabBar中显示的内容由外界决定
    定义插槽
    flex布局平分TabBar

13.2 TabBar和TabBarItem的组件封装

  1. 自定义TabBarItem,可以传入 图片和文字
    定义TabBarItem,并且定义两个插槽:图片、文字。
    给两个插槽外层包装div,用于设置样式。
    填充插槽,实现底部TabBar的效果
    App.vue
<template>
  <div id="app">
    <tab-bar> 
      <tab-bar-item>
        <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
        <div slot="item-text">首页</div>
      </tab-bar-item>
      <tab-bar-item>
        <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="">
        <div slot="item-text">分类</div>
      </tab-bar-item>
      <tab-bar-item>
        <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="">
        <div slot="item-text">购物车</div>
      </tab-bar-item>
      <tab-bar-item>
        <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="">
        <div slot="item-text">我的</div>
      </tab-bar-item>
    </tab-bar>
  </div>
</template>

<script>
import TabBar from './components/tabbar/TabBar.vue'
import TabBarItem from './components/tabbar/TabBarItem.vue'

export default {
  name: 'App',
  components:{
    TabBar,
    TabBarItem
  }
}
</script>

<style>
@import url('./assets/css/base.css');

</style>

TarBar.vue

<template>
  <div id="tab-bar">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name:"TabBar"
}
</script>

<style>
#tab-bar{
  display: flex;
  background-color: #f6f6f6;

  position: fixed;
  left: 0px;
  right: 0px;
  bottom: 0px;

  box-shadow: 0px -2px 1px rgba(100,100,100,.1);
}
</style>

TabBatItem.vue

<template>
  <div class="tab-bar-item">
    <slot name="item-icon"></slot>
    <slot name="item-text"></slot>
    <!-- <img src="../../assets/img/tabbar/home.svg" alt="">
    <div>首页</div> -->
  </div>
</template>

<script>
export default {

}
</script>

<style>
.tab-bar-item{
  flex: 1;
  text-align: center;
  height: 49px;
  font-size: 12px;
}
.tab-bar-item img{
  margin-top: 3px;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
</style>

13.3 TabBarItem传入active图片

  1. 传入 高亮图片
    定义另外一个插槽,插入active-icon的数据
    定义一个变量isActive,通过v-show来决定是否显示对应的icon

TabBatItem.vue

<template>
  <div class="tab-bar-item">
    <slot v-if="!isActive" name="item-icon"></slot>
    <slot v-else  name="item-icon-active"></slot>
    <div :class="{active:isActive} "><slot  name="item-text"></slot></div>    
    <!-- <img src="../../assets/img/tabbar/home.svg" alt="">
    <div>首页</div> -->
  </div>
</template>

<script>
export default {
  name:"TabBarItem",
  data(){
    return{
      isActive:true
    }
  }

}
</script>

<style>
.tab-bar-item{
  flex: 1;
  text-align: center;
  height: 49px;
  font-size: 12px;
}
.tab-bar-item img{
  margin-top: 3px;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
.active{
  color: red;
}
</style>

13.4 TabBarItem 和 路由结合效果

  1. TabBarItem绑定路由数据
    安装路由:npm install vue-router —save
    完成router/index.js的内容,以及创建对应的组件
    main.js中注册router
    APP中加入<router-view>组件
  2. 点击item跳转到对应路由,并且动态决定isActive
    监听item的点击,通过this.$router.replace()替换路由路径

TabBatItem.vue

<template>
  <div class="tab-bar-item" @click="itemClick">
    <slot v-if="!isActive" name="item-icon" ></slot>
    <slot v-else  name="item-icon-active"></slot>
    <div :class="{active:isActive} "><slot  name="item-text"></slot></div>
    
    <!-- <img src="../../assets/img/tabbar/home.svg" alt="">
    <div>首页</div> -->
  </div>
</template>
<script>
export default {
  name:"TabBarItem",
  props:{
    path:String
  },
  data(){
    return{
      isActive:true
    }
  },
  methods:{
    itemClick(){
      // console.log("yes");
      this.$router.replace(this.path);
    }

  }

}

App.vue

<template>
  <div id="app">
    <tab-bar> 
      <tab-bar-item path="/home">
        <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="">
        <div slot="item-text">首页</div>
      </tab-bar-item>
      <tab-bar-item path="/category">
        <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="">
        <div slot="item-text">分类</div>
      </tab-bar-item>
      <tab-bar-item path="/cart">
        <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar//shopcart_active.svg" alt="">
        <div slot="item-text">购物车</div>
      </tab-bar-item>
      <tab-bar-item path="/profile">
        <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="">
        <div slot="item-text">我的</div>
      </tab-bar-item>
    </tab-bar>
    <router-view></router-view>
  </div>
</template>

<script>
import TabBar from './components/tabbar/TabBar.vue'
import TabBarItem from './components/tabbar/TabBarItem.vue'

export default {
  name: 'App',
  components:{
    TabBar,
    TabBarItem
  }
}
</script>

<style>
@import url('./assets/css/base.css');
</style>

13.5 TabBarItem 的颜色动态控制

  1. 图标响应式变色
    通过this.$route.path.indexOf(this.link) !== -1来判断是否是active
    TabBatItem.vue
<script>
export default {
  name:"TabBarItem",
  props:{
    path:String
  },
  data(){
    return{
      // isActive:true
    }
  },
  computed:{
    isActive(){
      //拿到处于活跃路由的路径
      return this.$route.path.indexOf(this.path)!==-1;
    }
  },
  methods:{
    itemClick(){
      // console.log("yes");
      this.$router.replace(this.path);
    }
  }
}
</script>
  1. 动态计算active样式
    封装新的计算属性:this.isActive ? {‘color’: ‘red’} : {}
    TabBatItem.vue
<template>
  <div class="tab-bar-item" @click="itemClick">
    <slot v-if="!isActive" name="item-icon" ></slot>
    <slot v-else  name="item-icon-active"></slot>
    <div :style="activeStyle"><slot  name="item-text"></slot></div>
    
    <!-- <img src="../../assets/img/tabbar/home.svg" alt="">
    <div>首页</div> -->
  </div>
</template>

<script>
export default {
  name:"TabBarItem",
  props:{
    path:String,
    activeColor:{
      type:String,
      default:'red'
    }
  },
  data(){
    return{
      // isActive:true
    }
  },
  computed:{
    isActive(){
      //拿到处于活跃路由的路径
      return this.$route.path.indexOf(this.path)!==-1;
    },
    activeStyle(){
      //如果不处于活跃状态就返回一个空对象
      return this.isActive ? {color:this.activeColor} : {}
    }
  },
  methods:{
    itemClick(){
      // console.log("yes");
      this.$router.replace(this.path);
    }
  }

}
</script>

<style>
.tab-bar-item{
  flex: 1;
  text-align: center;
  height: 49px;
  font-size: 12px;
}
.tab-bar-item img{
  margin-top: 3px;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
/* .active{
  color: red;
} */
</style>

App.vue

<template>
  <div id="app">
    <tab-bar> 
      <tab-bar-item path="/home" activeColor="blue">
        <img slot="item-icon" src="./assets/img/tabbar/home.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/home_active.svg" alt="">
        <div slot="item-text">首页</div>
      </tab-bar-item>
      <tab-bar-item path="/category" activeColor="blue">
        <img slot="item-icon" src="./assets/img/tabbar/category.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/category_active.svg" alt="">
        <div slot="item-text">分类</div>
      </tab-bar-item>
      <tab-bar-item path="/cart" activeColor="blue">
        <img slot="item-icon" src="./assets/img/tabbar/shopcart.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar//shopcart_active.svg" alt="">
        <div slot="item-text">购物车</div>
      </tab-bar-item>
      <tab-bar-item path="/profile" activeColor="blue">
        <img slot="item-icon" src="./assets/img/tabbar/profile.svg" alt="">
        <img slot="item-icon-active" src="./assets/img/tabbar/profile_active.svg" alt="">
        <div slot="item-text">我的</div>
      </tab-bar-item>
    </tab-bar>
    <router-view></router-view>
  </div>
</template>

<script>
import TabBar from './components/tabbar/TabBar.vue'
import TabBarItem from './components/tabbar/TabBarItem.vue'

export default {
  name: 'App',
  components:{
    TabBar,
    TabBarItem
  }
}
</script>

<style>
@import url('./assets/css/base.css');

</style>

13.6 TabBar路由格式终版

App.vue

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

<script>
import MainTabBar from "./components/MainTabBar.vue"

export default {
  name: 'App',
  components:{
    MainTabBar
  }
}
</script>

<style>
@import url('./assets/css/base.css');

</style>

MainTabBar.vue

<template>
      <tab-bar> 
      <tab-bar-item path="/home" activeColor="blue">
        <img slot="item-icon" src="../assets/img/tabbar/home.svg" alt="">
        <img slot="item-icon-active" src="../assets/img/tabbar/home_active.svg" alt="">
        <div slot="item-text">首页</div>
      </tab-bar-item>
      <tab-bar-item path="/category" activeColor="blue">
        <img slot="item-icon" src="../assets/img/tabbar/category.svg" alt="">
        <img slot="item-icon-active" src="../assets/img/tabbar/category_active.svg" alt="">
        <div slot="item-text">分类</div>
      </tab-bar-item>
      <tab-bar-item path="/cart" activeColor="blue">
        <img slot="item-icon" src="../assets/img/tabbar/shopcart.svg" alt="">
        <img slot="item-icon-active" src="../assets/img/tabbar//shopcart_active.svg" alt="">
        <div slot="item-text">购物车</div>
      </tab-bar-item>
      <tab-bar-item path="/profile" activeColor="blue">
        <img slot="item-icon" src="../assets/img/tabbar/profile.svg" alt="">
        <img slot="item-icon-active" src="../assets/img/tabbar/profile_active.svg" alt="">
        <div slot="item-text">我的</div>
      </tab-bar-item>
    </tab-bar>
</template>

<script>
import TabBar from '../components/tabbar/TabBar.vue'
import TabBarItem from '../components/tabbar/TabBarItem.vue'
export default {
components: {
  TabBar,
  TabBarItem
}
}
</script>

<style>

</style>

TabBarItem.vue

<template>
  <div class="tab-bar-item" @click="itemClick">
    <slot v-if="!isActive" name="item-icon" ></slot>
    <slot v-else  name="item-icon-active"></slot>
    <div :style="activeStyle"><slot  name="item-text"></slot></div>
    
    <!-- <img src="../../assets/img/tabbar/home.svg" alt="">
    <div>首页</div> -->
    
    

  </div>
</template>

<script>
export default {
  name:"TabBarItem",
  props:{
    path:String,
    activeColor:{
      type:String,
      default:'red'
    }
  },
  data(){
    return{
      // isActive:true
    }
  },
  computed:{
    isActive(){
      //拿到处于活跃路由的路径
      return this.$route.path.indexOf(this.path)!==-1;
    },
    activeStyle(){
      //如果不处于活跃状态就返回一个空对象
      return this.isActive ? {color:this.activeColor} : {}
    }
  },
  methods:{
    itemClick(){
      // console.log("yes");
      this.$router.replace(this.path);
    }
  }

}
</script>

<style>
.tab-bar-item{
  flex: 1;
  text-align: center;
  height: 49px;
  font-size: 12px;
}
.tab-bar-item img{
  margin-top: 3px;
  width: 24px;
  height: 24px;
  vertical-align: middle;
}
/* .active{
  color: red;
} */
</style>

TabBar.vue

<template>
  <div id="tab-bar">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name:"TabBar"
}
</script>

<style>
#tab-bar{
  display: flex;
  background-color: #f6f6f6;

  position: fixed;
  left: 0px;
  right: 0px;
  bottom: 0px;

  box-shadow: 0px -2px 1px rgba(100,100,100,.1);
}

</style>

./router/index.js

import Vue from 'vue'
import Router from 'vue-router'
const Home=()=>import('../views/home/Home.vue')
const Category=()=>import('../views/category/Category.vue')
const Profile=()=>import('../views/profile/Profile.vue')
const Cart=()=>import('../views/cart/Cart.vue')


Vue.use(Router)

export default new Router({
  routes: [
    {
      path: '',
      redirect:'/home'
    },
    {
      path:'/category',
      component:Category
    },
    {
      path:'/profile',
      component:Profile
    },
    {
      path:'/cart',
      component:Cart
    },
    {
      path:'/home',
      component:Home
    }
  ],
  mode:'history'
})

13.7 TabBar 文件路径引用问题

  • ../../../这种很不方便
  • 为路径起别名 是 webpack的配置
    • webpack.config.js
    resolve: {
      extensions: ['.js', '.vue', '.json'],
      alias: {
        '@': resolve('src'),
        'assets':resolve('src/assets'),
        'components':resolve('src/components'),
        'views':resolve('src/views')
      }
    },
    
    • import TabBar from '../components/tabbar/TabBar.vue'
      就可以改为import TabBar from 'components/tabbar/TabBar.vue'
    • 但如果是src路径 前面必须加~符号
      eg:<img slot="item-icon" src="~assets/img/tabbar/shopcart.svg" alt="">

Vue Day 08

十四. Promise

14.1 Promise 基础

  • Promise是异步编程的一种解决方案
    一种很常见的场景应该就是网络请求。
    我们封装一个网络请求的函数,因为不能立即拿到结果,所以不能像简单的3+4=7一样将结果返回。
    所以往往我们会传入另外一个函数,在数据请求成功时,将数据通过传入的函数回调出去。
    如果只是一个简单的网络请求,那么这种方案不会给我们带来很大的麻烦。

  • 回调地狱
    考虑下面的场景:

    • 我们需要通过一个url1从服务器加载一个数据data1,data1中包含了下一个请求的url2
    • 我们需要通过data1取出url2,从服务器加载数据data2,data2中包含了下一个请求的url3
    • 我们需要通过data2取出url3,从服务器加载数据data3,data3中包含了下一个请求的url4
    • 发送网络请求url4,获取最终的数据data4

    如果每一步都有跟多的代码那么同步操作会消耗大量时间,且代码难看,很难维护

    $.ajax( 'url1' , function (data1) {
      $.ajax(data1 ['url2 '], function (data2) {
        $.ajax(data2 ['url3'], function (data3) {
          $.ajax(data3 [ 'url4'], function (data4){
            console.log(data4);
          })
        })
      })
    })
    
  • Promise的模拟使用

  • Promise 对异步操作进行封装

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Title</title>
    </head>
    <body>
    
    <script>
      // 1.使用setTimeout
      // setTimeout(() => {
      //   console.log('Hello World');
      // }, 1000)
    
      // 什么情况下会用到Promise?
      // 一般情况下是有异步操作时,使用Promise对这个异步操作进行封装
      // new -> 构造函数(1.保存了一些状态信息  2.执行传入的函数)
      // 在执行传入的回调函数时, 会传入两个参数, resolve, reject.本身又是函数
      new Promise((resolve, reject) => {
        setTimeout(() => {
          // 成功的时候调用resolve,来处理结果
          // resolve('Hello World')
    
          // 失败的时候调用reject
          reject('error message')
        }, 1000)
      }).then((data) => {
        // 1.100行的处理代码
        console.log(data);
        console.log(data);
        console.log(data);
        console.log(data);
        console.log(data);
      }).catch((err) => {
        console.log(err);
      })
    </script>
    </body>
    </html>
    

14.2 Promise 的三种状态

  • pending:等待状态,比如正在进行网络请求,或者定时器没有到时间。
  • fulfill:满足状态,当我们主动回调了resolve时,就处于该状态,并且会回调.then()
  • reject:拒绝状态,当我们主动回调了reject时,就处于该状态,并且会回调.catch()
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  new Promise((resolve, reject) => {
    setTimeout(() => {
      // resolve('Hello Vuejs')
      reject('error message')
    }, 1000)
  }).then(data => {
    console.log(data);
  }, err => {
    console.log(err);
  })
</script>
</body>
</html>

14.3 Promise 的链式调用

链式调动(一)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>


<script>


  // 参数 -> 函数(resolve, reject)
  // resolve, reject本身它们又是函数
  // 链式编程
  new Promise((resolve, reject) => {

    // 第一次网络请求的代码
    setTimeout(() => {
      resolve()
    }, 1000)

  }).then(() => {
    // 第一次拿到结果的处理代码
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');

    return new Promise((resolve, reject) => {

      // 第二次网络请求的代码
      setTimeout(() => {
        resolve()
      }, 1000)
    })
  }).then(() => {

    // 第二次处理的代码
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');

    return new Promise((resolve, reject) => {

      // 第三次网络请求的代码
      setTimeout(() => {
        resolve()
      })
    })
  }).then(() => {

    // 第三处理的代码
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
  })
</script>
</body>
</html>

链式调动(二)(Promise的第二种处理形式)

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  // 参数 -> 函数(resolve, reject)
  // resolve, reject本身它们又是函数
  // 链式编程
  new Promise((resolve, reject) => {

    // 第一次网络请求的代码
    setTimeout(() => {
      resolve()
    }, 1000)

  }).then(() => {
    // 第一次拿到结果的处理代码
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');
    console.log('Hello World');

    return new Promise((resolve, reject) => {

      // 第二次网络请求的代码
      setTimeout(() => {
        resolve()
      }, 1000)
    })
  }).then(() => {

    // 第二次处理的代码
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');
    console.log('Hello Vuejs');

    return new Promise((resolve, reject) => {

      // 第三次网络请求的代码
      setTimeout(() => {
        resolve();
        reject();
      })
    })
    //第二种处理形式
  }).then((data) => {

    // 第三处理的代码
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
    console.log('Hello Python');
  },err=>{
    console.loh("error");
  })
</script>
</body>
</html>

需求:网络请求->aaa ,自己处理10行 -> 处理aaa后面加111 , 自己处理10行 -> 处理aaa111后面加222 , 自己处理10行代码 ->…

  • 链式调用简写:
<script>
  // wrapped into
  // 网络请求: aaa -> 自己处理(10行)
  // 处理: aaa111 -> 自己处理(10行)
  // 处理: aaa111222 -> 自己处理
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    // 1.自己处理10行代码
    console.log(res, '第一层的10行处理代码');
  
    // 2.对结果进行第一次处理
    return new Promise((resolve, reject) => {
      // resolve(res + '111')
      reject('err')
    })
  }).then(res => {
    console.log(res, '第二层的10行处理代码');
  
    return new Promise(resolve => {
      resolve(res + '222')
    })
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  }).catch(err => {
    console.log(err);
  })


    // new Promise(resolve => resolve(结果))简写
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    // 1.自己处理10行代码
    console.log(res, '第一层的10行处理代码');

    // 2.对结果进行第一次处理
    return Promise.resolve(res + '111')
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return Promise.resolve(res + '222')
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  })

  // 省略掉Promise.resolve
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    // 1.自己处理10行代码
    console.log(res, '第一层的10行处理代码');

    // 2.对结果进行第一次处理,内部对“res + '111'”进行了Promise包装
    return res + '111'
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return res + '222'
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  })

  //省略 return res + '111'
  new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('aaa')
    }, 1000)
  }).then(res => {
    // 1.自己处理10行代码
    console.log(res, '第一层的10行处理代码');

    // 2.对结果进行第一次处理
    // return Promise.reject('error message')
    throw 'error message'
  }).then(res => {
    console.log(res, '第二层的10行处理代码');

    return Promise.resolve(res + '222')
  }).then(res => {
    console.log(res, '第三层的10行处理代码');
  }).catch(err => {
    console.log(err);
  })
</script>
  • Promise的all方法的使用
  • 需求:两个请求同时到位才可以进行下一步
  1. 正常写法:
    链式调动(三)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>
  // 请求一:
  let isResult1 = false
  let isResult2 = false
  $ajax({
    url: '',
    success: function () {
      console.log('结果1');
      isResult1 = true
      handleResult()
    }
  })

  // 请求二:
  $ajax({
    url: '',
    success: function () {
      console.log('结果2');
      isResult2 = true
      handleResult()
    }
  })

  function handleResult() {
    if (isResult1 && isResult2) {
      //
    }
  }
</script>
</body>
</html>
  1. Promise异步请求
  • Promise包装异步请求写法
    链式调动(四)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Title</title>
</head>
<body>

<script>

  //Promise.all([iterator])可迭代的
  Promise.all([
      // new Promise((resolve, reject) => {
      //   $.ajax({
      //     url: 'url1',
      //     success: function (data) {
      //       resolve(data)
      //     }
      //   })
      // }),
      // new Promise((resolve, reject) => {
      //   $.ajax({
      //     url: 'url2',
      //     success: function (data) {
      //       resolve(data)
      //     }
      //   })
      // })

    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);
  })
  //result[0]输出结果
  //result[1]输出结果
</script>
</body>
</html>

十五. Vuex

  • xcode/iphonex/xml
  • Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
  • 它采用 集中式存储管理 应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。
  • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。
    • 把需要多个组件共享的变量全部存储在一个对象里面。
    • 然后,将这个对象放在顶层的Vue实例中,让其他组件可以使用,它是具有响应式的

token令牌请求数据

  • 有什么状态时需要在多个组件间共享的呢?
    • 如果做过大型开放,一定遇到过多个状态,在多个界面间的共享问题。
    • 比如用户的登录状态、用户名称、头像、地理位置信息等等。
    • 比如商品的收藏、购物车中的物品等等。
    • 这些状态信息,都可以放在统一的地方,对它进行保存和管理,而且它们还是响应式的。

15.1 vuex单界面到多界面状态管理切换

State: 状态
View: 视图层
Actions:主要是用户的各种操作:点击、输入等等,会导致状态的改变。

  • 共享状态

  • 单界面:方法一:父子组件的传递

    App.vue

    <template>
    <div id="app">
      <h2>{{message}}</h2>
      <h2>{{counter}}</h2>
      <button @click="counterUp()">+</button>
      <button @click="counterDown()">-</button>
      <!--方法一:父子组件的传递 -->
      <hello-vuex :counter="counter"></hello-vuex>
    </div>
    </template>
    
    <script>
    import HelloVuex from './components/HelloVuex.vue'
    
    export default {
      name: 'App',
      data() {
        return{
          message: "我是App组件",
          counter:0
        }
      },
      components:{
        HelloVuex
      },
      methods:{
        counterUp(){
          this.counter ++;
          // return counter
        },
        counterDown(){
        this.counter --;
          // return counter
        }
      }
    }
    </script>
    <style>
    </style>
    

    HelloVuex.vue

    <template>
    <div>
      <h1>{{counter}}</h1>
    </div>
    </template>
    
    <script>
    export default {
      props:{
        counter:Number
      }
    
    }
    </script>
    
    <style>
    
    </style>
    
  • 多界面:方法二:使用Vuex传递

    • vuex相当于一个插件
    • npm install vuex --save
    • 在创建文件夹./src/store存放包管理工具,创建index.js文件
      index.js
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    //1.安装插肩
    Vue.use(Vuex)
    
    //2.创建对象
    const store =new Vuex.Store({
      //状态
      state:{
        counter:1000
      },
      mutations:{
      },
      actions:{
      },
      getters:{
      },
      modules:{
    
      }
    })
    export default store;
    
    • main.js中引入vuex
      main.js
    import Vue from 'vue'
    import App from './App'
    import store from './store'
    
    Vue.config.productionTip = false
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      store,
      render: h => h(App)
    })
    
    • 将文件HelloVuex.vueApp.vue中的
    • {{messagge}}替换为{{$store.state.counter}}

  • Vuex状态管理图例

15.2 devtools 和 Mutations (Vuex基本的使用)

  1. 提取出一个公共的store对象,用于保存在多个组件中共享的状态
  2. 将store对象放置在new Vue对象中,这样可以保证在所有的组件中都可以使用到
  3. 在其他组件中使用store对象中保存的状态即可
  • 通过this.$store.state.属性的方式来访问状态
  • 通过this.$store.commit(‘mutation中方法’)来修改状态
  • 注意事项:
    我们通过提交mutation的方式,而非直接改变store.state.count。
    这是因为Vuex可以更明确的追踪状态的变化,所以不要直接改变store.state.count的值。

index.js

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

//1.安装插肩
Vue.use(Vuex)

//2.创建对象
const store =new Vuex.Store({
  //状态
  state:{
    counter:1000
  },
  mutations:{
    //方法,state指的就是上方的state
    increment(state){
      state.counter++
    },
    decrement(state){
      state.counter--
    }
  },
  actions:{

  },
  getters:{

  },
  modules:{

  }
})

export default store;

App.vue

<template>
  <div id="app">
    <h2>{{message}}</h2>
    <h2>{{$store.state.counter}}</h2>
    <button @click="counterUp()">+</button>
    <button @click="counterDown()">-</button>
    <hello-vuex></hello-vuex>
  </div>
</template>

<script>
import HelloVuex from './components/HelloVuex.vue'

export default {
  name: 'App',
  data() {
    return{
      message: "我是App组件",
    
    }
  },
  components:{
    HelloVuex
  },
  methods:{
    counterUp(){
      this.$store.commit('increment')
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    }
  }
}
</script>

<style>

</style>

15.3 Vuex核心概念

15.3.1 单一状态树的理解

  • Single Source of Truth,也可以翻译成单一数据源。
  • Vuex也使用了单一状态树来管理应用层级的全部状态。

单一状态树能够让我们最直接的方式找到某个状态的片段,而且在之后的维护和调试过程中,也可以非常方便的管理和维护。

即使你有很多状态,但还是只使用放在一个 store里。

15.3.2 getter的使用

  1. 相当于计算属性:computed

./src/store/index.js

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

//1.安装插肩
Vue.use(Vuex)

//2.创建对象
const store =new Vuex.Store({
  //状态
  state:{
    counter:1000,
    students: [
      {id: 110, name: 'why', age: 18},
      {id: 111, name: 'kobe', age: 24},
      {id: 112, name: 'james', age: 30},
      {id: 113, name: 'curry', age: 10}
    ]
  },
  mutations:{
    //方法,state指的就是上方的state
    increment(state){
      state.counter++
    },
    decrement(state){
      state.counter--
    }
  },
  actions:{

  },
  getters:{
    powerCounter(state){
      return state.counter * state.counter
    },
    more20stu(state){
      return state.students.filter(s=>s.age>20)
    }
  },
  modules:{

  }

})

export default store;

App.vue

<template>
  <div id="app">
    <h2>------------App内容----------------</h2>
    <h2>{{message}}</h2>
    <h2>{{$store.state.counter}}</h2>
    <button @click="counterUp()">+</button>
    <button @click="counterDown()">-</button>
    <h2>------------App内容:getters相关信息----------------</h2>
    <h2>{{$store.state.counter * $store.state.counter}}</h2>
    <h2>{{$store.getters.powerCounter}}</h2>
    <h2>{{$store.getters.more20stu}}</h2>
    <hello-vuex></hello-vuex>
  </div>
</template>

<script>
import HelloVuex from './components/HelloVuex.vue'

export default {
  name: 'App',
  data() {
    return{
      message: "我是App组件",
    
    }
  },
  components:{
    HelloVuex
  },
  methods:{
    counterUp(){
      this.$store.commit('increment')
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    }
  }
}
</script>

<style>

</style>

  1. Getters作为参数和传递参数
  • 需求: 输出大于20岁的个数
    ./src/store/index.js
  getters:{
    powerCounter(state){
      return state.counter * state.counter
    },
    more20stu(state){
      return state.students.filter(s=>s.age>20)
    },
    more20stulength(state,getters){
      return getters.more20stu.length
    }

  },
  • 需求:获取年龄大于age(此时的age是用户传进来。动态的获取age)
    App.vue
<h2>{{$store.getters.moreagestu(25)}}</h2>

./src/store/index.js

getters:{
    powerCounter(state){
      return state.counter * state.counter
    },
    more20stu(state){
      return state.students.filter(s=>s.age>20)
    },
    more20stulength(state,getters){
      return getters.more20stu.length
    },
    moreagestu(state){
      // return function(age){
      //   return state.students.filter(s=>s.age>age)
      // }
      return age = > {
        return state.students.filter(s=>s.age>age)
      // }
      }
    }
  },

15.3.3 mutations的携带参数

  • Vuex的store状态的更新唯一方式:提交Mutation

  • Mutation主要包括两部分:

    • 字符串的事件类型(type)
    • 一个回调函数(handler),该回调函数的第一个参数就是state。
  • 在通过mutation更新数据的时候, 有可能我们希望携带一些额外的参数

  • 参数被称为是mutation的载荷(Payload)

如果参数不是一个呢?

  • 比如很多参数需要传递.
  • 通常会以对象的形式传递, 也就是payload是一个对象.
  • 这个时候可以再从对象中取出相关的信息.

需求:添加两个个按钮:+5,+10

    <button @click="addFive">+5</button>
    <button @click="addTen">+10</button>
    //如果这样操作又要重新定义两个方法

可以变为
App.vue

export default {
  name: 'App',
  data() {
    return{
      message: "我是App组件",
    
    }
  },
  components:{
    HelloVuex
  },
  methods:{
    counterUp(){
      this.$store.commit('increment')
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    },
    addCount(count){
      //payload: 负载
      this.$store.commit('incrementCount',count)
    }
  }
}
</script>

./src/store/index.js

  mutations:{
    //方法,state指的就是上方的state
    increment(state){
      state.counter++
    },
    decrement(state){
      state.counter--
    },
    incrementCount(state,count){
      state.counter+=count
    }
  },

15.3.4 mutations的提交风格

App.vue

  methods:{
    counterUp(){
      this.$store.commit('increment')
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    },
    addCount(count){
      // 1.普通的提交风格
      this.$store.commit('incrementCount',count)
      //2.特殊的提交风格
      this.$store.commit({
        type:'incrementCount',
        count:count//此时传出的是对象,故要改变index.js中的传入参数
      })
  }
}

./src/store/index.js

  mutations:{
    //方法,state指的就是上方的state
    increment(state){
      state.counter++
    },
    decrement(state){
      state.counter--
    },
    //将这里改掉
    incrementCount(state,payload){
      state.counter+=payload.count
    }
  },

15.3.5 mutations的响应规则

一旦定义,
这些属性都会被加入到响应式系统中,而响应式系统会监听属性的变化当属性发生变化时,
会通知所有界面中用到该属性的地方,让界面发生刷新。

  • 响应式规则:
    • 提前在store中初始化好所需的属性.
    • 当给state中的对象添加新属性时, 使用下面的方式:
    • 方式一: 使用Vue.set(obj, ‘newProp’, 123)
      Vue.set(state.info,'address','洛杉矶')
      Vue.delete(state.info,'age')
    • 方式二: 用心对象给旧对象重新赋值
      state.info = {...state.info,'height':payload.height}
      ``
    updateInfo(state){
          state.info.name="leng"
          //address未做到响应式,因为address未定义
          //现在vuex3 cli4可以是响应式的
          //添加属性
          //state.info['address']='洛杉矶'
    
          //删除属性 //非响应式
          //delete state.info.age
          //响应式
          Vue.delete(state.info,'age')
        }
    

    现在Vuex3 已经可以做到响应式了

15.3.6 mutations的常量类型

  1. mutations的常量类型

在mutation中, 我们定义了很多事件类型(也就是其中的方法名称).
当我们的项目增大时, Vuex管理的状态越来越多, 需要更新状态的情况越来越多, 那么意味着Mutation中的方法越来越多.
方法过多, 使用者需要花费大量的经历去记住这些方法, 甚至是多个文件间来回切换, 查看方法名称, 甚至如果不是复制的时候, 可能还会出现写错的情况.
解决方案:我们可以创建一个文件:mutation-types.js, 并且在其中定义我们的常量.
定义常量时, 我们可以使用ES2015中的风格, 使用一个常量来作为函数的名称.

创建一个文件./src/store/mutations-types.js

15.3.7 vuex-actions的使用详解

  1. mutations同步函数 actions
  • 通常情况下, Vuex要求我们Mutation中的方法必须是同步方法.
    • 主要的原因是当我们使用devtools时, 可以devtools可以帮助我们捕捉mutation的快照.
    • 但是如果是异步操作, 那么devtools将不能很好的追踪这个操作什么时候会被完成.

如果直接使用异步,vue devtool不会有记录(但我使用时,是会有记录的)

  • context是和store对象具有相同方法和属性的对象.

  • 也就是说, 我们可以通过context去进行commit相关的操作, 也可以获取context.state等.

  • 在mutations中不要有异步操作,有异步操作是就需要使用action函数

  • action传递参数
    例:
    ./src/store/index.js

mutations:{
    //方法,state指的就是上方的state
    [INCREMENT](state){
      state.counter++
    },
    decrement(state){
      state.counter--
    },
    incrementCount(state,payload){
      state.counter+=payload.count
    },
    updateInfo(state){
      state.info.name="leng"
      // state.info['address']='洛杉矶'

      //非响应式
      // delete state.info.age
      // Vue.delete(state.info,'age')

      // setTimeout(()=>{
      //   state.info.name="leng"
      // },1000)
    }
  },
  actions:{
    //context此时理解成store
    aUpdateInfo(context,payload){
      setTimeout(()=>{
        context.commit('updateInfo');
        console.log(payload);
      },1000)
    }
  },

App.vue

methods:{
  updateInfo(){
    // this.$store.commit('updateInfo')
    this.$store.dispatch('aUpdateInfo','我是payload')

  }
}
  1. 需求:修改成功后:输出修改成功
    App.vue
updateInfo(){
    // this.$store.commit('updateInfo')
    this.$store.dispatch('aUpdateInfo',()=>{
      console.log('里面已经完成了');
    })

  }

./src/store/index.js

actions:{
    //context此时理解成store
    aUpdateInfo(context,payload){
      setTimeout(()=>{
        context.commit('updateInfo')
        payload()
      },1000)
    }
  },

弊端:无法再传其它参数

改进:
App.vue

  updateInfo(){
    // this.$store.commit('updateInfo')
    this.$store.dispatch('aUpdateInfo',{
      payload:"我是携带的信息",
      success:()=>{
        console.log("里面已经完成了")
      }
    })
  }

./src/store/index.js

actions:{
    //context此时理解成store
    aUpdateInfo(context,payload){
      setTimeout(()=>{
        context.commit('updateInfo')
        console.log(payload.message);
        payload.success()
      },1000)
    }
  },
  1. 但是改进的方法不够简洁明了,使用Promise

App.vue

  updateInfo(){
    this.$store
    .dispatch('aUpdateInfo',"我是携带的信息")
    .then(res=>{
      console.log('里面的任务完成了');
      console.log(res);
    })
  }

./src/store/index.js

 actions:{
    //简介的做法:
    aUpdateInfo(context,payload){
      return new Promise((resolve,reject)=>{
        setTimeout(()=>{
          context.commit('updateInfo')
          console.log(payload);
          resolve('111')
        },1000)
      })
    }
  },

15.3.8 vuex-modules的使用详解

Vue使用单一状态树,那么也意味着很多状态都会交给Vuex来管理.
当应用变得非常复杂时,store对象就有可能变得相当臃肿.
为了解决这个问题, Vuex允许我们将store分割成模块(Module),
而每个模块拥有自己的state、mutations、actions、getters等

例:

modules:{
    a:{
      state:{},
      mutations:{},
      actions:{},
      getters:{}
    },
    b:{
      state:{},
      mutations:{},
      actions:{},
      getters:{}
    }
    //...
  }

  • 包含此次vuex的全部内容
  • 增加了modules的运用

App.vue

<template>
  <div id="app">
    <h1>4.modules内容</h1>
    <h2>{{$store.state.a.name}}</h2>
    <button @click="updateName">修改名字</button>
    <h2>{{$store.getters.fullname}}</h2>
    <h2>{{$store.getters.fullname2}}</h2>
    <h2>{{$store.getters.fullname3}}</h2>
    <button @click="asyncUpdateName">异步修改名字</button>



    <h1>1.App内容</h1>
    <h2>{{message}}</h2>
    <h2>{{$store.state.counter}}</h2>
    <button @click="counterUp()">+</button>
    <button @click="counterDown()">-</button>
    <button @click="addCount(5)">+5</button>
    <button @click="addCount(10)">+10</button>

    <h1>2.App内容:getters相关信息</h1>
    <h2>{{$store.state.counter * $store.state.counter}}</h2>
    <h2>{{$store.getters.powerCounter}}</h2>
    <h2>{{$store.getters.more20stu}}</h2>
    <h2>{{$store.getters.more20stulength}}</h2>
    <h2>{{$store.getters.moreagestu(25)}}</h2>
    <hello-vuex></hello-vuex>

    <h1>3.展示info的内容是否时响应式的</h1>
    <h2>{{$store.state.info}}</h2>
    <button @click="updateInfo">修改信息</button>

   
  </div>
</template>

<script>
import HelloVuex from './components/HelloVuex.vue'
import {INCREMENT} from './store/mutations-types'
export default {
  name: 'App',
  data() {
    return{
      message: "我是App组件",
    
    }
  },
  components:{
    HelloVuex
  },
  methods:{
    counterUp(){
      this.$store.commit(INCREMENT)
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    },
    addCount(count){
      // 1.普通的提交风格
      // this.$store.commit('incrementCount',count)
      //2.特殊的提交风格
      this.$store.commit({
        type:'incrementCount',
        count:count//此是传出的是对象
      })
  },
  updateInfo(){
    // this.$store.commit('updateInfo')
    // this.$store.dispatch('aUpdateInfo',{
    //   message:"我是携带的信息",
    //   success:()=>{
    //     console.log("里面已经完成了")
    //   }
    // })
    this.$store
    .dispatch('aUpdateInfo',"我是携带的信息")
    .then(res=>{
      console.log('里面的任务完成了');
      console.log(res);
    })
  },
  updateName(){
    this.$store.commit('updateName','222')
  },
  asyncUpdateName(){
    this.$store.dispatch('aUpdateName')
  }
}
}
</script>

<style>

</style>

./src/store/index.js

<template>
  <div id="app">
    <h1>4.modules内容</h1>
    <h2>{{$store.state.a.name}}</h2>
    <button @click="updateName">修改名字</button>
    <h2>{{$store.getters.fullname}}</h2>
    <h2>{{$store.getters.fullname2}}</h2>
    <h2>{{$store.getters.fullname3}}</h2>
    <button @click="asyncUpdateName">异步修改名字</button>



    <h1>1.App内容</h1>
    <h2>{{message}}</h2>
    <h2>{{$store.state.counter}}</h2>
    <button @click="counterUp()">+</button>
    <button @click="counterDown()">-</button>
    <button @click="addCount(5)">+5</button>
    <button @click="addCount(10)">+10</button>

    <h1>2.App内容:getters相关信息</h1>
    <h2>{{$store.state.counter * $store.state.counter}}</h2>
    <h2>{{$store.getters.powerCounter}}</h2>
    <h2>{{$store.getters.more20stu}}</h2>
    <h2>{{$store.getters.more20stulength}}</h2>
    <h2>{{$store.getters.moreagestu(25)}}</h2>
    <hello-vuex></hello-vuex>

    <h1>3.展示info的内容是否时响应式的</h1>
    <h2>{{$store.state.info}}</h2>
    <button @click="updateInfo">修改信息</button>

   
  </div>
</template>

<script>
import HelloVuex from './components/HelloVuex.vue'
import {INCREMENT} from './store/mutations-types'
export default {
  name: 'App',
  data() {
    return{
      message: "我是App组件",
    
    }
  },
  components:{
    HelloVuex
  },
  methods:{
    counterUp(){
      this.$store.commit(INCREMENT)
    },
    counterDown(){
     this.$store.commit('decrement')
      // return counter
    },
    addCount(count){
      // 1.普通的提交风格
      // this.$store.commit('incrementCount',count)
      //2.特殊的提交风格
      this.$store.commit({
        type:'incrementCount',
        count:count//此是传出的是对象
      })
  },
  updateInfo(){
    // this.$store.commit('updateInfo')
    // this.$store.dispatch('aUpdateInfo',{
    //   message:"我是携带的信息",
    //   success:()=>{
    //     console.log("里面已经完成了")
    //   }
    // })
    this.$store
    .dispatch('aUpdateInfo',"我是携带的信息")
    .then(res=>{
      console.log('里面的任务完成了');
      console.log(res);
    })
  },
  updateName(){
    this.$store.commit('updateName','222')
  },
  asyncUpdateName(){
    this.$store.dispatch('aUpdateName')
  }
}
}
</script>

<style>

</style>

15.4 vuex的目录结构

index.js原始

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

import {INCREMENT} from './mutations-types'
//1.安装插肩
Vue.use(Vuex)

//2.创建对象
const moduleA= {
  state:{
    name:'hua'
  },
  mutations:{
    updateName(state,payload){
      state.name=payload
    }
  },
  actions:{
    //此时的context不再是store
    aUpdateName(context){
      setTimeout(()=>{
        context.commit('updateName','leng1')
      },1000)
    }
  },
  getters:{
    fullname(state){
      return state.name+'232323'
    },
    fullname2(state,getters){
      return getters.fullname+'222333'
    },
    //模块里有rootState
    fullname3(state,getters,rootState){
      return getters.fullname2+ rootState.counter
    }
  }
}
const store =new Vuex.Store({
  //状态
  state:{
    counter:1000,
    students: [
      {id: 110, name: 'why', age: 18},
      {id: 111, name: 'kobe', age: 24},
      {id: 112, name: 'james', age: 30},
      {id: 113, name: 'curry', age: 10}
    ],
    info:{
      name:'kobe',
      age:40,
      height:1.98
    }
  },
  mutations:{
    //方法,state指的就是上方的state
    [INCREMENT](state){
      state.counter++
    },
    decrement(state){
      state.counter--
    },
    incrementCount(state,payload){
      state.counter+=payload.count
    },
    updateInfo(state){
      state.info.name="leng"
      // state.info['address']='洛杉矶'

      //非响应式
      // delete state.info.age
      // Vue.delete(state.info,'age')
      // setTimeout(()=>{
      //   state.info.name="leng"
      // },1000)
    }
  },
  actions:{
    //context此时理解成store
    // aUpdateInfo(context,payload){
    //   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('111')
        },1000)
      })
    }
  },
  getters:{
    powerCounter(state){
      return state.counter * state.counter
    },
    more20stu(state){
      return state.students.filter(s=>s.age>20)
    },
    more20stulength(state,getters){
      return getters.more20stu.length
    },
    moreagestu(state){
      return function(age){
        return state.students.filter(s=>s.age>age)
      }
    }

  },
  modules:{
    a: moduleA
  }

})

export default store;

./src/store/index.js

import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutations'
import actions from './actions'
import moduleA from './modules/moduleA'
import getters from './getters'
// import {INCREMENT} from './mutations-types'
//1.安装插肩
Vue.use(Vuex)

//2.创建对象

const state={
  counter:1000,
  students: [
    {id: 110, name: 'why', age: 18},
    {id: 111, name: 'kobe', age: 24},
    {id: 112, name: 'james', age: 30},
    {id: 113, name: 'curry', age: 10}
  ],
  info:{
    name:'kobe',
    age:40,
    height:1.98
  }
}
const store =new Vuex.Store({
  //状态
  state:state,
  mutations:mutations,
  actions:actions,
  getters,
  modules:{
    a: moduleA
  }

})

export default store;

./store/getter.js

export default{
  
    powerCounter(state){
      return state.counter * state.counter
    },
    more20stu(state){
      return state.students.filter(s=>s.age>20)
    },
    more20stulength(state,getters){
      return getters.more20stu.length
    },
    moreagestu(state){
      return function(age){
        return state.students.filter(s=>s.age>age)
      }
    }

  
}

./store/actions.js

export default{
  //context此时理解成store
  // aUpdateInfo(context,payload){
  //   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('111')
      },1000)
    })
  }
}

./store/mutations.js

import {INCREMENT} from './mutations-types'
export default{
  //方法,state指的就是上方的state
  [INCREMENT](state){
    state.counter++
  },
  decrement(state){
    state.counter--
  },
  incrementCount(state,payload){
    state.counter+=payload.count
  },
  updateInfo(state){
    state.info.name="leng"
    // state.info['address']='洛杉矶'

    //非响应式
    // delete state.info.age
    // Vue.delete(state.info,'age')
    // setTimeout(()=>{
    //   state.info.name="leng"
    // },1000)
  }
}

./store/module/moduleA.js

export default{
  state:{
    name:'hua'
  },
  mutations:{
    updateName(state,payload){
      state.name=payload
    }
  },
  actions:{
    //此时的context不再是store
    aUpdateName(context){
      setTimeout(()=>{
        context.commit('updateName','leng1')
      },1000)
    }
  },
  getters:{
    fullname(state){
      return state.name+'232323'
    },
    fullname2(state,getters){
      return getters.fullname+'222333'
    },
    //模块里有rootState
    fullname3(state,getters,rootState){
      return getters.fullname2+ rootState.counter
    }
  }
}

./store/mutations-type.js

export const INCREMENT = 'increment'

十六. 网络请求封装(axios)

  1. 主要内容
  • 常见的网络请求模块,以及优缺点对比。
    • JSONP的原理和封装
    • JSONP原理回顾
    • JSONP请求封装
    • axios的内容详解
  • 认识axios网络模块
    • 发送基本请求
    • axios创建实例
    • axios拦截器的使用

2.选择什么网络模块

  • Vue中发送网络请求有非常多的方式

选择一: 传统的Ajax是基于XMLHttpRequest(XHR)

为什么不用它呢?
配置和调用方式等非常混乱.
编码起来看起来就非常蛋疼.
所以真实开发中很少直接使用, 而是使用jQuery-Ajax

选择二: 在前面的学习中, 我们经常会使用jQuery-Ajax

相对于传统的Ajax非常好用.
为什么不选择它呢?
首先, 我们先明确一点: 在Vue的整个开发中都是不需要使用jQuery了.
那么, 就意味着为了方便我们进行一个网络请求, 特意引用一个jQuery, 你觉得合理吗?
jQuery的代码1w+行.
Vue的代码才1w+行.
完全没有必要为了用网络请求就引用这个重量级的框架.

选择三: 官方在Vue1.x的时候, 推出了Vue-resource.

Vue-resource的体积相对于jQuery小很多.
另外Vue-resource是官方推出的.
为什么不选择它呢?
在Vue2.0退出后, Vue作者就在GitHub的Issues中说明了去掉vue-resource, 并且以后也不会再更新.
那么意味着以后vue-reource不再支持新的版本时, 也不会再继续更新和维护.
对以后的项目开发和维护都存在很大的隐患.

选择四: 在说明不再继续更新和维护vue-resource的同时, 作者还推荐了一个框架: axios

为什么不用它呢?
axios有非常多的优点, 并且用起来也非常方便.
稍后, 我们对他详细学习.

16.1 JSONP

16.2 axios的特点

  • 功能特点:
    • 在浏览器中发送 XMLHttpRequests 请求
    • 在 node.js 中发送 http请求
    • 支持 Promise API
    • 拦截请求和响应
    • 转换请求和响应数据

16.3 axios框架的基本使用

  • 安装框架npm install axios --save

main.js

import Vue from 'vue'
import App from './App'
import router from './router'

import axios from 'axios'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})

//方法1.
//axios(config),config可以是对象类型
//默认情况是get请求
axios({
  //这个网址用于网络封装的模拟,url:'http://httpbin,org/'
  //http://123.207.32.32/home 学习项目接口域名
  // 向http://123.207.32.32:8000/home/multidata 请求数据
  url:'http://123.207.32.32:8000/home/multidata',
  //不设置methods,默认情况是get请求 需设置post
  methods:'get'
})
//axios本身会返回一个Promise,在内部拿到数据后,会调用内部的resolve,然后执行then方法
.then(res=>{
  console.log(res);
})

axios({
  url:'http://123.207.32.32:8000/home/data?typr=sell&page=3'
})
.then(res=>{
  console.log(res);
})


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


//接口可能出现跨域 所以设计的时候也支持jsonp
//http://123.207.32.32:8000/home/multidata?callback=json123


//方法2.
//axios.post()

16.3.1 axios发送并发请求

  • 使用axios.all, 可以放入多个请求的数组.
  • axios.all([])返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为res1, res2
    main.js
import Vue from 'vue'
import App from './App'
import router from './router'

import axios from 'axios'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})
//一。 axios的并发请求使用


//1.
// axios.all([axios({
//   url:'http://123.207.32.32:8000/home/multidata'
// }),axios({
//   url:'http://123.207.32.32:8000/home/data?typr=sell&page=3'
// })])
//   .then(results=>{
//     console.log(results);
//     console.log(results[0]);
//     console.log(results[1]);
//   })


//2.
axios.all([axios({
  url:'http://123.207.32.32:8000/home/multidata'
}),axios({
  url:'http://123.207.32.32:8000/home/data',
  params:{
    type:'sell',
    page:5
  }
})])
  .then(axios.spread((res1,res2) => {
    console.log(res1);
    console.log(res2);

  }))
//  3.对象的解构
// 数组解构
// const names=['yhy','my','se']
// const name1 = names[0];
// const name2 = names[1];
// const name3 = names[2];

// const [name1,name2,name3]=names;

16.4 axios配置信息相关

  1. 全局配置
    上面的示例中, 我们的BaseURL是固定的
    事实上, 在开发中可能很多参数都是固定的.
    这个时候我们可以进行一些抽取, 也可以利用axiox的全局配置
    axios.defaults.baseURL = '123.207.32.32:8000'
    axios.defaults.headers.post[‘Content-Type’] = 'application/x-www-form-urlencoded';

main.js

axios.defaults.baseURL='http://123.207.32.32:8000'
axios.defaults.timeout= 5000

axios.all([axios({
  url:'/home/multidata'
}),axios({
  url:'/home/data',
  params:{
    type:'sell',
    page:5
  }
})])
  .then(axios.spread((res1,res2) => {
    console.log(res1);
    console.log(res2);

  }))
  1. 常见的配置选项
    请求地址
    url: ‘/user’,
    请求类型
    method: ‘get’,
    请根路径
    baseURL: ‘http://www.mt.com/api’,
    请求前的数据处理
    transformRequest:[function(data){}],
    请求后的数据处理
    transformResponse: [function(data){}],
    自定义的请求头
    headers:{‘x-Requested-With’:‘XMLHttpRequest’},
    URL查询对象
    params:{ id: 12 },
    查询对象序列化函数
    paramsSerializer: function(params){ }
    request body
    data: { key: ‘aa’},
    超时设置s
    timeout: 1000,
    跨域是否带Token
    withCredentials: false,
    自定义请求处理
    adapter: function(resolve, reject, config){},
    身份验证信息
    auth: { uname: ‘’, pwd: ‘12’},
    响应的数据格式 json / blob /document /arraybuffer / text / stream
    responseType: ‘json’,

16.5 axios的实例和模块封装

  1. 为什么要创建axios的实例呢?
    当我们从axios模块中导入对象时, 使用的实例是默认的实例.
    当给该实例设置一些默认配置时, 这些配置就被固定下来了.
    但是后续开发中, 某些配置可能会不太一样.
    比如某些请求需要使用特定的baseURL或者timeout或者content-Type等.
    这个时候, 我们就可以创建新的实例, 并且传入属于该实例的配置信息.

main.js

// 5.实例封装配置 创建对应的axios实例
//第一个实例
// const instance1 = axios.create({
//   baseURL:'http://152.136.185.210:7878/api/m5',
//   timeout:5000
// })

// instance1({
//   url:'/home/multidata'
// })
// .then(res=>{
//   console.log(res);
// })

// instance1({
//   url:'/home/data',
//   params:{
//     type:'pop',
//     page:1
//   }
// })
// .then(res=>{
//   console.log(res);
// })

// //第二个实例
// const instance2 = axios.create({
//   baseURL:'http://baidu.com',
//   timeout:5000
// })

//6.封装request模块
import { request } from './network/request'
request({
  url:'/home/multidata'
},res=>{
  console.log(res);
},err=>{
  console.log(err);
})

//6.1.
// request({
//   baseConfig:{

//   },
//   success:function(res){

//   },
//   failure:function(err){

//   }
// })

// 6.2和6.3
request({
  url:'/home/multidata'
}).then(res=>{
  console.log(res);
}).catch(err=>{
  console.log(err);
})

./src/network/request

import axios from 'axios'

//6.3
export function request(config){
    
      // 1.创建axios实例
      const instance = axios.create({
        baseURL:'http://152.136.185.210:7878/api/m5',
        timeout:5000
      })
      //2.发送真正的网络请求
      //返回的就是Promise
      return instance(config)
      
    

}
// 6.2
// export function request(config){
//   return new Promise((resolve,reject)=>{
    
//       // 1.创建axios实例
//       const instance = axios.create({
//         baseURL:'http://152.136.185.210:7878/api/m5',
//         timeout:5000
//       })
//       //2.发送真正的网络请求
//       instance(config)
//       .then(res=>{
//         // console.log(res);
//         // config.success(res);
//         resolve(res);
//       })
//       .catch(err=>{
//         // console.log(err);
//         // config.failure(err);
//         reject(err);
//       })
    
    
    
//   })

// }


//6.1
// export function request(config){
//   // 1.创建axios实例
//   const instance = axios.create({
//     baseURL:'http://152.136.185.210:7878/api/m5',
//     timeout:5000
//   })
//   //2.发送真正的网络请求
//   instance(config.baseConfig)
//   .then(res=>{
//     // console.log(res);
//     config.success(res);
//   })
//   .catch(err=>{
//     // console.log(err);
//     config.failure(err);
//   })

// }

// 6.
// export function request(config,success,failure){
//   // 1.创建axios实例
//   const instance = axios.create({
//     baseURL:'http://152.136.185.210:7878/api/m5',
//     timeout:5000
//   })
//   //2.发送真正的网络请求
//   instance(config)
//   .then(res=>{
//     // console.log(res);
//     success(res);
//   })
//   .catch(err=>{
//     // console.log(err);
//     failure(err);
//   })

// }

16.7 axios拦截器的使用

  • axios提供了拦截器,用于我们在发送每次请求或者得到相应后,进行对应的处理。
  • 请求成功/失败,响应成功/失败都是可以拦截的
    main.js
import Vue from 'vue'
import App from './App'
import router from './router'

import axios from 'axios'

Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  router,
  render: h => h(App)
})


import { request } from './network/request'

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



./src/network/request.js

import axios from 'axios'

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

      //2.axios拦截器
      //全局拦截axios.interceptors
      // instance.interceptors.request.use(config=>{
      //   console.log(config);
      //   /* 
      //   * 2.1请求拦截的作用
      //   * 比如config中的一些信息不符合服务器的要求
      //   * 比如每次发送网络请求时,都希望在界面中显示一个请求的图标,先show,请求成功后隐藏show
      //   * 某些网络请求(比如登录(token)),想要进入目的地,必须携带一些特殊信息,未携带着提示用户登录
      //   * 等待
      //   */
      //   //拦截了还要还回去
      //   return config;
      // },err=>{
      //   console.log(err);
      // })
        //2.2响应拦截
      instance.interceptors.response.use(res=>{
        // console.log(res);
        return res.data
      },err=>{
        console.log(err);
      })

      //3.发送真正的网络请求
      //返回的就是Promise
      return instance(config)
}

Vue Day 09

十七. 项目开发

17.1 GitHub托管

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-04 17:24:58  更:2021-09-04 17:26:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 20:42:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计