Vue.js 笔记  

144 阅读34分钟

                        

by aoziao

Email:youxi0928@qq.com

一、邂逅Vuejs

1.遇见Vuejs

Vuejs的特点Vue不支持 IE8及以下版本,因为 Vue 使用了 IE8 无法模拟的 ECMAScript 5 特性。但它支持所有兼容 ECMAScript 5 的浏览器

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。

Vue有很多他点和web开发中的常见的高级功能

    Exp:解耦视图和数据、可复用组件、前端路由技术、状态管理、虚拟DOM

2.安装Vuejs

  2.1 安装

Vue有三种方式安装,使用开发环境版本,

  1. 第一:直接在CDN引入
  2. 第二:下载引入
  3. 第三:NPM安装

  2.2 NPM安装

 npm install vue

3.体验Vuejs

  3.1 Hello Vuejs


<div class="div">{{message}}</div>
<script src="./js/vue.js"></script>

<script>
  const app = new Vue({
    el: '.div',
    data: {
      message: 'Hello Vue!'
    }
  });
</script>

 

  3.2 Vue显示列表

<div id="app">
      <h1>{{message}}</h1>
      <ul><li v-for="item in movies">{{item}}</li></ul>
    </div>

    <script>
      const obj = {
        message"nihao",
        movies: ["星际穿越""奥兹奥""夏洛特烦恼""速度与激情"]
      };

      var vm = new Vue({
        el"#app",
        data: obj
      });
    </script>

 

  3.3实现计时器

    <div id="app">
      <h2>当前计数:{{counter}}</h2>
      <!-- <button v-on:click="counter++">+</button>
      <button v-on:click="counter--">-</button> -->
      <button @click="add">+</button>
      <button @click="sub">-</button>
    </div>
    
    <script>
      //创建Vue实例,得到 ViewModel
      const app = new Vue({
        el"#app",
        data: {
          counter0
        },
        methods: {
          addfunction() {
            console.log("add被执行");
            this.counter++;
          },

          subfunction() {
            console.log("sub被执行"); //console首字母不可以大写
            this.counter--;
          }
        }
      });
    </script>
 

 

4.MVVM架构

  4.1 Vue中的MVVM

image.png

Model层:数据层,数据可能是固定死的数据,更多的来自于服务器,从网络上请求下来的数据。

View层:视图层,也叫Dom层,主要给用户展示各种信息

VueModel层:视图模型层,视图模型层是view和model沟通的桥梁,一方面实现数据绑定,将model的改变实时反映到view中,另一方面实现了DOM Listener也就是DOM监听,当DOM发生一些事件(点击、touch等)时,可以监听到,并在需要的情况下改变对于的data

 

二、基础语法

1. 插值语法

  1.1 Mustache语法

    <div id="app">
      <h2>{{message}}</h2>
      <h2>{{message}},aoziao!</h2>
      <h2>{{firstname}} {{lastname}}</h2>
      <h2>{{firstname + " " + lastname}}</h2>
      <h2>{{counter * 2}}</h2>
    </div>

    <script>
      //创建Vue实例,得到 ViewModel
      var app = new Vue({
        el"#app",
        data: {
          message"你好啊",
          firstname"kobe",
          lastname"bryant",
          counter100
        }
      });
    </script>

 

  1.2 v-once

在某些情况下,我们可能不希望界面中 Mustach 中的值随意的跟随改变,就可以使用一个 Vue 的指令:v-once ,该指令后面不需要跟任何和参数

该指令表示元素和组件只渲染一次,不会随着数据的改变而改变

    <h2 v-once>{{message}}</h2> <!-- 只会渲染一次 -->

 

  1.3 v-html

某些情况下,我们从服务器请求到的数据本身就是一个 HTML 代码,如果我们直接通过 {{}} 来输出,会将 HTML 代码也一起输出,但如果希望按照 HTML 格式进行解析,并且显示对应的内容,可以使用 v-html 指令

v-html 指令后面往往会跟上一个类型(String、url等),会将类型的 html 解析出来并且进行渲染

    <div id="app">
      <h2>{{message}}</h2>
      <h2 v-once>{{message}}</h2>
      <h2 v-html="url"></h2>
</div>

    <script>
      //创建Vue实例,得到 ViewModel
      var vm = new Vue({
        el"#app",
        data: {
          message"aoziao",
          url'<a href="http://www.baidu.com">百度一下</a>'
        }
      });
    </script>

 

  1.4 v-text

v-text作用和Mustache比较相似,都是用域将数据显示在界面中,通常情况下会接受一个String类型,

但是v-text会覆盖原有标签中渲染的内容

image.png

  1.5 v-pre

v-pre 用于跳过这个元素和它子元素的编译过程,显示原本的 Mustache 语法,将代码原封不动的解析出来

image.png

  1.6 v-block

将未解析出来的代码块进行隐藏,但基本不会用到

image.png

2.绑定属性

  2.1 v-bind的基本使用

Mustache 指令主要作用是将值插入到我们模板的内容当中,但是,除了内容需要动态来决定外,某些属性我们也希望动态来绑定,比如:

动态绑定a元素的href属性或者动态绑定img元素的src属性

这时,可以使用 v-bind 指令来动态绑定属性,v-bind 用于绑定一个或多个属性值,或者向另一个组件传递 props值

    <div id="app">
      <img :src="imgURL" alt="" />
      <a href="https://image.baidu.com/search/index?tn=baiduimage&ct=201326592&lm=-1&cl=2&ie=gb18030&word=%C3%C0%C5%AE&fr=ala&ala=1&alatpl=adress&pos=0&hs=2&xthttps=111111">美女</a>
    </div>
    <script>
      //创建Vue实例,得到 ViewModel
      var vm = new Vue({
        el"#app",
        data: {
          messagel"nihyaoa",
          imgURL:"https://ss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1480623082,1028728801&fm=26&gp=0.jpg"
        },
        methods: {}
      });

    </script>

  2.2 v-bind语法糖

v-bind 有一个对应的语法糖(简写方式),在开发中,通常会使用语法糖的形式,因为这样更加简洁

      <img :src="imgURL" alt="" />
       <img v-bind: src="imgURL" alt="" />  
       //二者意义相同

 

  2.3 动态绑定class

动态绑定class有两种方法:对象语法、数组语法

对象语法:

对象语法的含义是class后面跟的是一个对象

对象语法有下面这些用法:

用法一:直接通过{}绑定一个类

<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>
    <style>
      .active {color: red;}
      .a {font-size50px;}
    </style>
  </head>

  <body>
    <div id="app">

      <h2 class="a" v-bind:class="{active: isActive,line: isLine}">
        {{message}}
      </h2>
      <button @click="btnClick">按钮</button>
    </div>

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

    <script>
      //创建Vue实例,得到 ViewModel
      var vm = new Vue({
        el"#app",
        data: {
          message"aoziao",
          isActivetrue,
          isLinetrue
        },
        methods: {
          btnClickfunction() {
            this.isActive = !this.isActive;
          },
        }
      });
    </script>

通过点击按钮,触发btnClick事件,btnClick方法取反动态修改isActive的值,实现动态添加或删除class属性,此时class属性中为字体颜色样式,这样通过动态绑定class实现了对字体颜色的动态改变。

如果js<h2 class="a" v-bind:class="{active: isActive,line: isLine}">中的绑定class内容过多,可单独抽取出来放入methods成为一个方法。

数组语法:

数组语法的含义是class后面跟的是一个数组 数组语法有下面这些用法: 用法一:直接通过{}绑定一个类

<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>
<h2 class="title" :class="classes">Hello World</h2>

点击指定项改变颜色:

  <head>
    <style>
      .active {color: red;}
    </style>
  </head>
  
  <body>
    <div id="app">
      <ul>
        <li v-for="(item, index) in movies" v-on:click="changeColor(index)">
          <div v-bind:class="{active: currentIndex == index}">
            {{index}}-{{item}}
          </div>
        </li>
      </ul>
    </div>
    <script src="../node_modules/vue/dist/vue.js"></script>
    <script>
      const app = new Vue({
        el"#app",
        data: {
          message"奥兹奥",
          movies: ["海王""海尔兄弟""火影忍者""进击的巨人"],
          currentIndexNumber.MIN_VALUE
        },
        methods: {
          changeColorfunction(index) {
            this.currentIndex = index;
          }
        }
      });
    </script>

通过点击文字促发btnClick事件,同时传入文字的index,将传入的index赋值给currentIndex,通过判断传入的currentIndex是否就是点击currentIndex,如果是则为当前的div标签添加.active样式,通过.active的样式来改变点击文字的颜色。

  2.4 动态绑定样式

我们可以利用 v-bind:style 来绑定一些 CSS 内联样式,同样的,动态绑定样式也分为动态绑定数组和动态绑定对象两种语法。

  <body>
    <div id="app">
      <!-- 数组方法 -->
      <h2 :style="{fontSize:finalSize + 'px',background:finalColor}">{{message}}</h2>
      <!-- 对象方法 -->
      <h2 :style="finalStyle()">{{message}}</h2>
    </div>
    <script>
      var vm = new Vue({
        el"#app",
        data: {
          message"奥兹奥",
          finalColor"red",
          finalSize100
        },
        methods: {
          finalStylefunction() {
            return {
              fontSizethis.finalSize + "px",
              backgroundthis.finalColor
            };
          }
        }
      });
    </script>

对象语法:style后面跟的是一个对象类型,(key:value)对象的key值是css属性名称,value是具体的值 数组语法:style 后面跟的是一个数组类型,语法与上相同。  

3.计算属性

  3.1 computed的基本使用

什么时候使用计算属性:

在模板中可以直接通过插值语法显示一些 data 中的数据,但是在某些情况下,可能需要对数据进行一些转化后再显示,或者需要将多个数据结合起来进行显示,这时可以使用计算属性 computed。

    <div id="app">
      <h2>{{firstName}} {{lastName}}</h2>
      <h2>{{firstName +" "+lastName}}</h2>
      <h2>{{getFullName()}}</h2>
      <h2>{{fullName}}</h2>
    </div>
    <script>
      const app = new Vue({
        el"#app",
        data: {
          firstName"aoziao",
          lastName"Vuejs"
        },
        computed: {
          fullNamefunction() {
            return this.firstName + " " + this.lastName;
          }
        },
        methods: {
          getFullNamefunction() {
            return this.firstName + " " + this.lastName;
          }
        }
      });
    </script>

上述四种方法的结果一致

计算属性可以进行比较复杂的操作,如价格计算等

计算属性中也可以进行一些更加复杂的操作,比如下面计算图书价格的例子:

<div class="app">
  <h2>图书总价为:{{totalPrice}}</h2>
</div>
<script src="../js/vue.js"></script>
<script>
  const app = new Vue({
    el: '.app',
    data: {
      books: [
        {id: 1001,name: '计算机操作原理',price: 108},
        {id: 1002,name: 'JavaScript高级程序设计',price: 99},
        {id: 1003,name: '计算机网络',price: 28},
        {id: 1004,name: '数据结构',price: 46},
        {id: 1005,name: 'C语言',price: 48},
      ]
    },
    computed: {
      totalPrice: function() {
        let result = 0
        for (let i in this.books) {
          result += this.books[i].price
        }
        return result
      }
    },
  })
</script>

通过遍历数组对象的price值,进行累加操作计算出五本书的总价格。

  3.2 计算属性的setter和getter

每个计算属性都包含一个getter和一个setter,getter用来读取值,setter用来设置值(但setter不常用)

  <body>
    <div id="app">{{fullName}}</div>
    <script>
      const app = new Vue({
        el"#app",
        data: {
          firstName"Kobe",
          lastName"Bryant"
        },
        computed: {
          fullName: {
            setfunction(newValue) {
              const names = newValue.split(" ");
              this.firstName = names[0];
              this.lastName = names[1];
            },
            getfunction() {
              return this.firstName + " " + this.lastName;
            }
          }
        }
      });
    </script>

//通过浏览器调试模式中的console来修改值是调用computed的set函数

    fullName: {
      getfunction() {
         return this.firstName + " " + this.lastName;
      }
    }

直接简写为

     fullNamefunction() {
        return this.firstName + " " + this.lastName;
    }

 

  3.3 setter和getter的区别

问题:methods 和 computed 看起来都可以实现我们的功能,那么为什么还要多一个计算属性这个东西呢?

原因:计算属性会进行缓存,如果多次使用时,计算属性只会调用一次

computed 区别于 methods 的核心 

在官方文档中,强调了computed 区别于 methods 最重要的两点

1、computed 是属性调用,而 methods 是函数调用computed 带有缓存功能,而 methods 没有。computed 定义的方法,我们是以属性访问的形式调用的,{{computedTest}},但是 methods 定义的方法,我们必须要加上()来调用,如{{methodTest()}},我们可以将同一函数定义为一个方法而不是一个计算属性,两种方式的最终结果确实是完全相同的,然而,不同的是计算属性是基于它们的响应式依赖进行缓存的,只在相关响应式依赖发生改变时它们才会重新求值,这就意味着只要 text 还没有发生改变,多次访问 getText 计算属性会立即返回之前的计算结果,而不必再次执行函数,而方法只要页面中的属性发生改变就会重新执行。对于任何复杂逻辑,都应当使用计算属性

2、computed 依赖于 data 中的数据,只有在它的相关依赖数据发生改变时才会重新求值

 

 

4.事件监听

  4.1 v-on介绍

在前端开发中,我们需要经常和用户进行交互,这个时候,就必须监听用户发生的事件,比如点击、拖拽、键盘事件等,在 Vue 中监听事件使用 v-on 指令

  4.2 v-on基础

      <button v-on:click="counter--">-</button>
      <button @click="decrement">-</button>

 

  4.3 v-on参数

  <body>
    <div id="app">
    
      <!-- 1.事件调用的方法没有参数 -->
      <button @click="btn1Click()">按钮1</button>
      <button @click="btn1Click">按钮2</button>
    
      <!--2. 在事件定义时,写函数时省略了小括号,本身是需要一个参数的,这个时候,
             vue会默认将浏览器产生的event事件对象作为参数传入方法 -->
      <!-- 如果函数需要参数,但是没有传入,那么函数的形参为undefined -->
      <button @click="btn2Click()">按钮3</button@click="btnClick()">
      <button @click="btn2Click(123)">按钮4</button@click="btnClick()">

      <!--去掉括号时,则监听的是时间:mouseEvent,不去括号为空,则函数寻找变量,报undefined -->
      <button @click="btn3Click">按钮5</button@click="btnClick()"> 
      <button @click="btn3Click()">按钮6</button@click="btnClick()"> 

      <!-- 3.方法定义时,需要event对象,又需要其他参数 -->

      <!-- 在调用方式,如何手动的获取到浏览器的event对象:$event -->
      <button @click="btn4Click(123,$event)">按钮7</button@click="btnClick()"> 
    </div>

    <script>
      //创建Vue实例,得到 ViewModel
      const app = new Vue({
        el: "#app",
        data: {},
        methods: {
          btn1Click() {console.log("btn1Clock");},
          btn2Click(abc){console.log("--------",abc);},
          btn3Click(event){console.log("++++++++++",event);    },
          btn4Click(abc,event){console.log("***********",abc,event);}
        } 
      });
    </script>

  4.4 v-on修饰符

在某些情况下,我们拿到 event 的目的可能是进行一些事件处理,vue 提供了修饰符来帮助我们方便的处理一些事件:

.stop :调用 event.stopPropagation()

.prevent :调用 event.preventDefault()

.{keyCode | keyAlias} :只当事件是从特定键触发时才触发回调

.native :监听组件根元素的原生事件(重中之重)

.once :只触发一次回调

  <body>
    <div id="app">
      <!-- .stop修饰符 :阻止冒泡 -->
      <div @click="divClick">
        aaaaa
        <button @click.stop="btnClick">按钮</button>
      </div
      
      <!-- .prevent修饰符:阻止默认行为 -->
      <form action="baidu">
        <input type="submit" value="提交" @click.prevent="submitClick" />
      </form>
      
      <!--  键修饰符.键别名:监听某个键帽-->
      <!--监听所有按键 -->
      <input type="text" @keyup="keyUp" />

      <!-- 监听enter键  -->
      <input type="text" @keyup.13="keyEnterUp" />

      <!-- .once :点击回调值调用一次 -->
      <button @click.once="btn2Click">按钮2</button>
      
    </div>

    <script>
      const app = new Vue({
        el: "#app",
        data: {},
        methods: {
          divClick() {console.log("divClick");},
          btnClick() {console.log("btnClick");},
          submitClick() {console.log("submitClick");},
          keyUp() {console.log("keyUp");},
          keyEnterUp() {console.log("keyEnterUp");},
          btn2Click() {console.log("btn2Click");}
        }
      });

 

5.条件与循环

  5.1 条件渲染

通过v-if、v-else-if、v-else 这三个指令添加条件指令可以实现对 DOM 中渲染或销毁元素或组件。

v-if后面的条件为 false 时,对应的元素以及其子元素不会渲染,也就是不会有对应的标签出现在DOM 中

    <div id="app">
      <h2 v-if="isShow">
        <div>abc</div>
      </h2>
      <h2>{{messsage}}</h2>
    </div>
    
    <script>
      const app = new Vue({
        el"#app",
        data: {
          messsage"aoziao",
          isShowtrue
        },
      });
    </script>

  5.2 v-show指令

v-show 的用法和 v-if 非常相似,也用于决定一个元素是否渲染

  5.3 v-if和v-show的对比

1、v-if 是真正的条件渲染,会确保在切换过程中,条件块内的事件和子组件被销毁和重建(组件被重建将会调用created)

2、v-show 不论如何,都会被渲染在 DOM 中,当条件为真值时,将会修改条件的 css 样式

3、v-if 有更高的切换开销,v-show有更高的初始渲染开销,v-if 适合运营条件不大可能改变,v-show适合频繁切换

4、v-if 是动态的向DOM树内添加或者删除DOM元素,v-show是通过设置DOM元素的display样式属性控制显隐

5、v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件,v-show 只是简单的基于 css 切换

6、v-if 是惰性的,如果初始条件为假,则什么也不做,只有在条件第一次变为真时才开始局部编译,v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留

v-if 和 v-show 都可以决定一个元素是否渲染,那么开发中我们如何选择呢?

 v-if:当条件为False时,包含v-if指令的元素,根本就不会存在DOM中(从DOM中删除)

 v-show:当条件为False时,v-show只是给元素增加一个行内样式:dispaly:none

结论:当需要在显示与隐藏之间切换很频繁时,使用 v-show ,当只有一次切换时,通过使用 v-if

  5.4 v-for指令

当我们有一组数据需要进行渲染时,我们就可以使用 v-for 来完成

v-for 遍历数组:

无索引值:vue <li v-for="item in names">{{item}}</li>

使用索引值:vue <li v-for="(item,index) in names">{{index+1}}-----{{item}}</li>

v-for 遍历对象:

   vue <li v-for="(value,key,index) in info">{{key}}-----{{value}}-----{{index+1}}</li>

假设对象为 vue info:{name: “aoziao” ,age:18 } 在遍历获取对象中,获取key和value顺序依次为 value,key,index,

最后才是索引值。

遍历过程中获取key 值:

      vue  <li v-for="item in letter" :key="item">{{item}}</li>

具有响应式的数组方法

            //push():在数组末尾追加元素(可添加多个,与shift()一样)
            this.letter.push("aaaa");
            //
            pop():删除数组中的最后一个元素
            this.letter.pop();

            //shift():删除数组中的都一个元素
            this.letter.shift();

            //unshift():在数组最前面添加元素
            this.letter.unshift("aaaa", "bbbb", "cccc");

            //splice():删除元素/插入元素/替换元素
            //删除元素:第一个元素传入从第几个元素开始,第二个元素传入要
            //删除几个元素(如果没有传,则默认删除后面所有元素)
            this.letter.splice(2, 2);

           // 替换元素:第一个元素传入从第几个元素开始,第二个元素表示要替换几个元素,后面是用域替换前面的元素
            this.letter.splice(2, 2, "c", "d");
            
            //插入元素:第一个元素传入从第几个元素开始,第二个元素传入0,后面跟上要插入的元素
            this.letter.splice(1, 0, "b", "c", "d", "e");

            //sort():排序   注意:按照字符编码的顺序进行排序
            this.letter1.sort();

            //reverse():数组反转
            this.letter.reverse();
            
            //注:修改数组中的元素时:
            //不采用:this.letter[0]="bbbbb";
            //建议采用:this.letter.splice(0,1,"bbbb");
            
            //set(要修改的对象,索引组,修改后的值)
            set(this.letter,0,"bbbbb")

  5.5 用户登录案例

  <!-- 问题:从input元素切入另外一个input元素中去,输入的内容会随之切换带入另外一个input元素
       解决方案:给input添加key且保证key值不同 -->
  <body>
    <div id="app">
      <span v-if="isUser">
        <label for="username">用户账户</label>
        <input type="text" id="username" placeholder="********" key="1" />
      </span>
      
      <span v-else>
        <label for="userEmail">用户邮箱</label>
        <input type="text" id="userEmail" placeholder=<********@qq.com> key="2" />
      </span>
      <button @click="isUser = !isUser">切换登陆方式</button>
    </div>
    
    <script>
      const app = new Vue({
        el"#app",
        data: {
          isUsertrue
        },
        methods: {}
      });

  5.5 用户登录案例

image.png

Index.html:

  <body>
    <div id="app">
    
      <div v-if="books.length">
        <table>
          <thead>
            <th>&nbsp;</th>
            <th>书籍名称</th>
            <th>出版日期</th>
            <th>价格</th>
            <th>购买数量</th>
            <th>操作</th>
          </thead>
          <tbody>
            <tr v-for="(book, index) in books" :key="index">
              <td>{{index}}</td>
              <td>{{book.name}}</td>
              <td>{{book.beginDate}}</td>
              <td>{{book.price | showPrice}}</td>
              <td>
                <button @click="decrement(index)" :disabled="book.count<=1">-</button>
                {{book.count}}
                <button @click="increment(index)">+</button>
              </td>
              <td><button @click="remove">移除</button></td>
            </tr>
          </tbody>
        </table>
        
        <h3>总价:{{totalPrice | showPrice}}</h3>
      </div>
      
      <div v-else>
        <h2>购物车为空</h2>
      </div>
    </div>
  </body>

mian.js:

const app = new Vue({
  el"#app",
  data: {
    books: [
      {  name"《算法导论》",beginDate"2006-9",price85.0,count1},
      {  name"《UNIX编程艺术》",beginDate"2006-2",price59.0,count1},
      {  name"《编程大全》",beginDate"2008-10",price39.0,count1},
      {  name"《代码大全》",beginDate"2006-3",price128.0,count1}
    ]
  },
  computed: {
    totalPrice() {
      let total = 0;
      //1.普通for循环
      // for (let i = 0; i < this.books.length; i++) {
      //   total = total + this.books[i].price * this.books[i].count
      // }
      // 2.增强for循环
      // for (let i in this.books) {
      //   total = total + this.books[i].price * this.books[i].count
      // }
      // 3.for of
      // for (const book of this.books) {
      //   total = total + book.price * book.count
      // }
      // return total
  methods: {
    increment(index) { this.books[index].count++;},
    decrement(index) { this.books[index].count--;},
    remove(index) { this.books.splice(index, 1);}
  },
  filters: {
    //过滤器
    showPrice(price) {
      return "¥" + price.toFixed(2);
    }
  }
});

   1.filter过滤函数

const nums = [23517755100200];
//要求获取nums中大于50的数
//回调函数会遍历nums中每一个数,传入回调函数,在回调函数中写判断逻辑,返回true则会被数组接收,false会被拒绝
let newNums = nums.filter(function(num) {
  if (num > 50) {
    return true;
  }
  return false;
});
//可以使用箭头函数简写
//  let newNums = nums.filter(num => num >50)
console.log(newNums);

// 2.map高阶函数
// 要求将已经过滤的新数组每项乘以2
//map函数同样会遍历数组每一项,传入回调函数为参数,num是map遍历的每一项,回调函数function返回值会被添加到新数组中

let newNums2 = newNums.map(function(num) {
  return num * 2;
});

//简写
//  let newNums2 = newNums.map(num => num * 2)
    console.log(newNums2);
    
// 3.reduce高阶函数
//要求将newNums2的数组所有数累加

//reduce函数同样会遍历数组每一项,传入回调函数和‘0’为参数,0表示回调函数中preValue初始值为0,回调函数中参数preValue是每一次回调函数function返回的值,currentValue是当前值

//例如数组为[154, 110, 200, 400],则回调函数第一次返回值为0+154=154,第二次preValue为154,返回值为154+110=264,以此类推直到遍历完成

let newNum = newNums2.reduce(function(preValue, currentValue) {
  return preValue + currentValue;
}, 0);

//简写

// let newNum = newNums2.reduce((preValue,currentValue) => preValue + currentValue)

console.log(newNum);

 

//三个需求综合
let n = nums
  .filter(num => num > 50)
  .map(num => num * 2)
  .reduce((preValue, currentValue) => preValue + currentValue);
  
console.log(n);

style.css

table {
  border1px;
  border-collapse: collapse;
  border-spacing0;
}
th,
td {
  padding8px 16px;
  border: ipx solid #e9e9e9;
  text-align: left;
}
th {
  background-color#f7f7f7;
  color#5c6b77;
  font-weight600;
}

 

6.表单绑定

  6.1 基本使用

表单控件在实际开发中是非常常见的,特别是对于用户信息的提交,需要大量的表单,vue 中使用 v-model 指令来实现表单元素和数据的双向绑定

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

    

      const app = new Vue({

        el: "#app",

        data: {

          message: "你好啊"

        }

      });

    

当我们在输入框输入内容时,因为 input 中的 v-model 绑定了 message,所以会实时将输入的内容传递给 message,message 发生改变,当 message 发生改变时,因为使用了 Mustache 语法,所以将 message 的值插入到 DOM 中,所以 DOM 会发生响应的改变,所以通过 v-model 实现了双向绑定

  6.2 v-model基本原理

v-model 其实是一个语法糖,它的背后本质上是包含两个操作:

v-bind 绑定一个 value 属性 +  v-on 指令给当前元素绑定 input 事件

即:

<input type="text" v-model="message">

<input type="text" :value="message" @input="message = $event.target.value">
//二者相同

  6.3 其他类型

v-model 结合radio 类型

    <div id="app">
      <label for="male">
        <input type="radio" id="male" value="男" v-model="sex" />男
      </label>

      <label for="male">
        <input type="radio" id="female" value="女" v-model="sex" />女
      </label>
      
      <h2>您选择的性别是:{{sex}}</h2>
    </div>
    
    <script>
      const app = new Vue({
        el"#app",
        data: {
          message"奥兹奥",
          sex"男"
        },
        methods: {}
      });
    </script>

默认为男,点击那个单选项则改为单选项的value

v-model 结合checkbox 类型

  <body>
    <div id="app">
      <label v-for="item in originHobbies" :for="item">
        <input type="checkbox" :value="item" v-model="hobbies" :id="item"/>
        {{item}}
      </label>
      
      <h2>您选择的爱好是:{{hobbies}}</h2>
    </div>
    
    <script>
      const app = new Vue({
        el"#app",
        data: {
          hobbies: [],
          originHobbies: ["足球","篮球","乒乓球","羽毛球","台球","高尔夫球"]
        },
      });
    </script>

默认为空,点击哪个选项则添加哪个选项

v-model 结合select 类型

  <body>
    <div id="app">
      <select name="fruits" v-model="fruits" multiple>
     <!--  multiple控制下拉框的隐藏-->
        <option value="苹果">苹果</option>
        <option value="香蕉">香蕉</option>
        <option value="葡萄">葡萄</option>
        <option value="桃子">桃子</option>
      </select>
      <h2>您选择的水果是:{{fruits}}</h2>
    </div>

    <script>
      //创建Vue实例,得到 ViewModel
      const app = new Vue({
        el"#app",
        data: {
          fruit"香蕉",
          fruits: []
        },
      });
    </script>

Fruit默认有香蕉,点击哪个则添加哪个

  6.4 修饰符

lazy 修饰符:默认情况下,v-model 是在 input 事件中同步输入框的数据,也就是说,一旦有数据发生改变,对应的 data 中的数据就会自动发生改变

lazy 修饰符可以让数据在失去焦点或者回车时才会更新

使用方法: js <input type="text" v-model.lazy="message" />

number 修饰符

number 修饰符可以让在输入框中输入的内容自动转成数字类型

使用方法: js <input type="number" v-model.number="age" />

trim 修饰符

trim 修饰符可以过滤内容左右两边的空格

使用方法:js<input type="text" v-model.trim="message" />

 

三、组件发开发

1.认识组件化

1.1   什么是组件化

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

1.2   vue组件化思想

组件化是 Vue.js 中的重要思想 ,它提供了一种抽象,让我们可以开发出一个个独立可复用的小组件来构造我们的应用

任何的应用都会被抽象成一颗组件树:

image.png

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

 

2.组件化基础

  2.1注册组件

2.1.1 注册的基本步骤

组件的使用分成三个步骤:创建组件构造器 → 注册组件 → 使用组件 image.png

注意:以上代码方式创建的组件是全局组件,即可以在多个 vue 的实例下使用,要改为局部组件,需要将注册方法写到具体的某个实例中

2.1.2 全局组件和局部组件

当我们通过调用 Vue.component() 注册组件时,组件的注册是全局的 ,这意味着该组件可以在任意 Vue 实例下使用;如果我们注册的组件是挂载在某个实例中, 那么就是一个局部组件

image.png  

2.1.3 父组件和子组件

组件和组件之间存在层级关系,而其中一种非常重要的关系就是父子组件的关系

image.png

2.1.4 组测组件语法糖

在上面注册组件的方式,可能会有些繁琐,Vue为了简化这个过程,提供了注册的语法糖,主要是省去了调用Vue.extend() 的步骤,直接使用一个对象来代替

语法糖注册全局组件和局部组件:

    <div id="app">
      <cpn1></cpn1>
      <cpn2></cpn2>
    </div>
    <script>
      Vue.component("cpn1", {
        template: `
      <div>
        <h2>我是标题1</h2>
        <p>我是内容,哈哈哈哈哈哈</p>
      </div>
      `
      });
      
      const app = new Vue({
        el: "#app",
        data: {},
        methods: {},
        components: {
          cpn2: {
            template: `
              <div>
              <h2>我是标题2</h2>
              <p>我是内容,呵呵呵呵呵呵</p>
              </div>
              `
          }
        }
      });
    </script>

 

2.1.5 模块的分离写法

即使用 script 标签或 template 标签将模板内容从注册时的 template 中抽离出来

使用script 标签抽离:

image.png

使用template 标签抽离:(常用)

image.png           

2.1.6 组件的数据存放问题

Vue 组件有自己保存数据的地方,组件对象也有一个 data 属性,只是这个 data 属性必须是一个函数,而且这个函数返回一个对象,对象内部保存着数据。

image.png  

为什么 data 在组件中必须是一个函数呢?

1、首先,如果不是一个函数,Vue直接就会报错

2、其次,Vue 让每个组件对象都返回一个新的对象,因为如果是同一个对象的,组件在多次使用后会相互影响

image.png  

  2.2 数据传递

2.2.1 父级向子级传递 props

在组件中,使用选项 props 来声明需要从父级接收到的数据

props 的值有两种方式:

1. 字符串数组,数组中的字符串就是传递时的名称

image.png

2. 对象:可以设置传递时的类型,也可以设置默认值和类型等         

image.png

2.2.2 子级向父级传递

自定义事件流程:子组件中,通 $ emit()来触发事件 父组件中,通过v-on来监听子组件事件

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

    <script>
      //子组件
      const cpn = {
        template"#cpn",
        data() {
          return {
            categories: [
              { id"aaa"name"热门推荐" },{ id"bbb"name"手机数码" },
              { id"ccc"name"家用家电" },{ id"ddd"name"电脑办公" }
            ]
          };
        },
        methods: {
          btnclick(item) {
            //发射一个事件:自定义事件 
            this.$emit("itemclick", item);
          }
        }
      };
      
      const app = new Vue({
        el"#app",
        data: {},
        methods: {
          cpnclick(item) {
            console.log("cpnclick", item);
          }
        },
        components: {
          cpn
        }
      });
    </script>
  </body>

点击界面的buttom按钮触发子组件的btnClick事件同时传回对应的item值,btnClick事件携带item通过this. $ emit(‘item-click’,item)发送item-click事件,并且同时携带item,父组件通过@item-click接受事件同时触发cpnClick事件,携带的item值传给cpnClick事件,cpnClick事件通过item值找到对应的数据并打印。

**父子组件通信案例**
  <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>
      //创建Vue实例,得到 ViewModel
      const app = new Vue({
        el"#app",
        data: {
          num11,
          num20
        },
        methods: {
          num1change(value) {
            this.num1 = parseFloat(value);
          },
          num2change(value) {
            this.num2 = parseFloat(value);
          }
        },
        components: {
          cpn: {
            template"#cpn",
            props: {
              number1Number,
              number2Number
            },
            data() {
              return {
                dnumber1this.number1,
                dnumber2this.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);
                this.dnumber1 = this.dnumber1 / 100;
                this.$emit("num1change"this.dnumber1);
              }
            }
          }
        }
      });
    </script>
  </body>

image.png


2.2.3 父子组件的访问

父访问子:

    <div id="app">
      <cpn ref="reference"></cpn>
      <button @click="btnClick">按钮</button>
    </div>
    console.log(this.$refs.reference.name);==console.log(this.$children.name);

在父组件里 this.$refs.reference 通过对应子组件 ref="reference" 的进入子组件并获取相应数据

this.$refs this.$children要常用

子访问父:

通过This.$parent访问

2.2.4 非父子组件的通讯

注意:

尽管在 Vue 开发中,我们允许通过 $parent 来访问父组件,但是在真实开发中尽量不要这样做,因为这样耦合度太高了

子组件应该尽量避免直接访问父组件的数据,如果我们将子组件放在另外一个组件之内,很可能该父组件没有对应的属性,往往会引起问题

另外,通过 $parent 直接修改父组件的状态,那么父组件中的状态将变得飘忽不定,很不利于调试和维护

也不常用 $root 来访问根组件(即 vue 实例),因为根组件中一般只存放路由等重要数据,不存放其他信息

 

3.组件化高级

  3.1 slot插槽

3.1.1 编译作用域

父组件模板的所有东西,都只会在父级作用域内编译

子组件模板的所有东西,都只会在子级作用域内编译

image.png

3.1.2 为什么使用slot

组件的插槽,也是为了让我们封装的组件更加具有扩展性,让使用者可以决定组件内部的一些内容到底展示什么

3.1.3 slot的基本使用

在子组件中,使用特殊的元素 就可以为子组件开启一个插槽,该插槽插入什么内容取决于父组件如何使用

image.png  

3.1.4 slot的具名插槽

当子组件的功能复杂时,子组件的插槽可能并非是一个,比如我们封装一个导航栏的子组件,可能就需要三个插槽,分别代表左边、中间、右边

那么,外面在给插槽插入内容时,如何区分插入的是哪一个呢?这个时候,我们就需要给插槽起一个名字,这就是具名插槽

具名插槽的使用很简单,只要给 slot 元素一个 name 属性即可:,

使用时在对应标签内加入slot=”myslot”

3.1.5 slot作用域插槽

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

image.png

 

4.组件生命周期

beforeCreate

Vue对象用新方法实例化。它创建一个Vue类的对象来处理DOM元素。对象的这个生命阶段可以通过beforeCreated 挂钩来访问 。我们可以在这个钩子中插入我们的代码,在对象初始化之前执行。

created

在这个生命阶段,对象及其事件完全初始化。 created 是访问这个阶段并编写代码的钩子。

beforeMount

DOM未完成挂载,数据也初始化完成,但是数据的双向绑定还是显示{{}},这是因为Vue采用了Virtual DOM(虚拟Dom)技术。先占住了一个坑。这个钩子被调用 beforeMounted。在这个阶段,它检查是否有任何模板可用于要在DOM中呈现的对象。如果没有找到模板,那么它将所定义元素的外部HTML视为模板。

mounted

数据和DOM都完成挂载,在上一个周期占位的数据把值给渲染进去。可以在这边请求,不过created请求会更好一些。这个周期适合执行初始化需要操作DOM的方法。

beforeUpdate

只要是页面数据改变了都会触发,数据更新之前,页面数据还是原来的数据,当你请求赋值一个数据的时候会执行这个周期,如果没有数据改变不执行。

updated

只要是页面数据改变了都会触发,数据更新完毕,页面的数据是更新完成的。beforeUpdate和updated要谨慎使用,因为页面更新数据的时候都会触发,在这里操作数据很影响性能和容易死循环。

beforeDestroy

这个周期是在组件销毁之前执行, beforeDestroy无法阻止路由跳转,但是可以做一些路由离开的时候操作,因为这个周期里面还可以使用data和method。比如一个倒计时组件,如果在路由跳转的时候没有清除,这个定时器还是在的,这时候就可以在这个里面清除计时器。

Destroyed

销毁对象

image.png

四、Vue Cli详解

1.webPack

  1.1 什么是webpack

At its core, webpack is a static module bundler for modern JavaScript applications.

从本质上来讲,webpack是一个现代的JavaScript应用的静态模块打包工具。

image.png

前端模块化

在ES6之前,我们要想进行模块化开发,就必须借助于其他的工具,让我们可以进行模块化开发,并且在通过模块化开发完成了项目后,还需要处理模块间的各种依赖,并且将其进行整合打包,而 webpack 其中一个核心就是让我们可能进行模块化开发,并且会帮助我们处理模块间的依赖关系,而且不仅仅是 JavaScript 文件,我们的 CSS、图片、json 文件等,在 webpack 中都可以被当做模块来使用,这就是 webpack 中模块化的概念

打包

webpack 可以帮助我们进行模块化和处理模块间的各种复杂关系,而打包则是将 webpack 中的各种资源模块进行打包合并成一个或多个包(Bundle),并且在打包的过程中,还可以对资源进行处理,比如压缩图片,将 scss 转成 css,将 ES6 语法转成 ES5 语法,将TypeScript 转成 JavaScript 等操作

 

1.3   webpack和gulp的对比

grunt/gulp 的核心是 Task:我们可以配置一系列的 task,并且定义 task 要处理的事务(例如 ES6、ts 转化,图片压缩,scss 转成 css),之后让 grunt/gulp 来依次执行这些 task,而且让整个流程自动化,所以 grunt/gulp 也被称为前端自动化任务管理工具

如果你的工程模块依赖非常简单,甚至是没有用到模块化的概念,只需要进行简单的合并、压缩,就使用 grunt/gulp 即可。但是如果整个项目使用了模块化管理,而且相互依赖非常强,就可以使用更加强大的 webpack

grunt/gulp 和 webpack 有什么不同?

grunt/gulp:更加强调前端流程的自动化,模块化不是它的核心

webpack:更加强调模块化开发管理,而文件压缩合并、预处理等功能,是他附带的功能

 

1.4   手动配置webpack

1.创建一个 webpack.config.js 文件

image.png

  1. 安装局部 webpack

npm install webpack@3.6.0 -save-dev

1.4.1 Webpack配置loader

1.  css-loader 和style-loader 的使用

css-loader 解析 CSS 文件后,使用 import 加载,并且返回 CSS 代码。

style-loader 将模块的导出作为样式添加到 DOM 中

1.1安装命令

npm install style-loader css-loader --save-dev

  1. 2 在 webpack.config.js 中的 module关键字下添加如下进行配置
 rules: [
      {
        test/\.css$/,
        use: ["style-loader""css-loader"]
      },
      {
        test/\.less$/,
        use: [
          {
            loader"style-loader" // creates style nodes from JS strings
          },
          {
            loader"css-loader" // translates CSS into CommonJS
          },
        ]
      },
     ]

 

2. 安装配置 url-loader****

url-loader 像 file-loader 一样工作,但如果文件小于限制,可以返回 data URL

图片限制在8KB以下,也就是limit:8000;此时使用base64对图片进行编码

2.1 安装命令

npm install --save-dev url-loader

  1. 2 在 webpack.config.js 中的 module关键字下添加如下进行配置
      {
        test: /\.(png|jpg|gif|jpeg)$/,
        use: [
          {
            loader: "url-loader",
            options: {
              //当加载的图片小于limit时,会将图片编译成base64字符串形式。
              //当加载的图片大于limit时,需要使用file-loader模块进行加载
              limit: 8196,
              name: "img/[name].[hash:8].[ext]"
            }
          }

3. 安装配置 file-loader****

file-loader 将文件发送到输出文件夹,并返回(相对)URL,大于8KB的图片都需要使用file-loader进行处理。

3.1 安装命令

npm install --save-dev file-loader

3.2 在 webpack.config.js 中的 module关键字下添加如下进行配置

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

4. 安装配置babel-loader

将 ES6 的语法转成 ES5 ,避免一些还不支持ES6的浏览器没有办法很好的运行我们的代码

4.1 安装命令

npm install --save-dev babel-loader@7 babel-core babel-preset-es2015

  1. 2 在 webpack.config.js 中的 module关键字下添加如下进行配置
{
  test: /\.js$/,
    exclude: /(node_modules|bower_components)/,
      use: {
        loader: 'babel-loader',
          options: {
            presets: ['es2015']
          }
      }
}

 

1.4.2 Webpack配置Vue

  1. 安装命令

npm install vue -s

  2. 在 main.js 中引入 vue,创建 vue 实例

// 引用 vue

import Vue from 'vue'

// 创建 vue 实例

const vue = new Vue({
  el:'#app',
  data:{
    message:”aoziao”
  }
})

3.在 index.html 中挂载 vue 实例

<div id="app">
  <h3>{{message}}</h3>
</div>
  1. 重新打包,运行程序, 发现打包过程没有任何错误,但是运行程序,没有出现想要的效果,这是因为目前我们使用的 Vue 版本为:runtime-only,这里需要修改为:runtime-compiler修改方法就是在 webpack.config.js 中添加如下代码:
  resolve: {
    // alias: 别名
    extensions: [".js"".css"".vue"],
    alias: {
      vue$: "vue/dist/vue.esm.js"
    }
  }
  1. vue 文件封装处理

  5.1 安装 vue-loader 和 vue-template-compiler

npm install vue-loader@14.2.2 vue-template-compiler --save-dev

5.2 修改 webpack.config.js 的配置文件

  {
    test: /\.vue$/,
    use: ['vue-loader']
  }

5.3 vue文件夹建立App.vue文件,在mian.js引用App.vue文件

 

1.4.3 Webpack配置plugin

1.什么是plugin:

 plugin 是插件的意思,通常是用于对某个现有的架构进行扩展

 webpack 中的插件,就是对 webpack 现有功能的各种扩展,比如打包优化,文件压缩等

2.loader 和 plugin 区别****

loader 主要用于转换某些类型的模块,它是一个转换器

plugin 是插件,它是对 webpack 本身的扩展,是一个扩展器

3.plugin 的使用过程

通过 npm 安装需要使用的 plugins (某些 webpack 已经内置的插件不需要安装)

在 webpack.config.js 中的 plugins 中配置插件

4.添加版权的 plugin:BannerPlugin

BannerPlugin 属于 webpack 自带的插件,可以为打包的文件添加版权声明,使用方式如下:
    plugins: [ new webpack.BannerPlugin("最终版权归奥兹奥所有"), ],
  1. 打包 html 的 plugin:HtmlWebpackPlugin

         自动生成一个 index.html 文件(可以指定模板来生成),将打包的 js 文件,自动通过 script 标签插入到 body 中

  5.1 安装命令

npm install html-webpack-plugin --save-dev

  5.2修改 webpack.config.js 文件中 plugins

const HtmlWebpackPlugin require("html-webpack-plugin");

  plugins: [
    new HtmlWebpackPlugin({
      template"index.html"
    }),
  ],
  1. js 压缩的 Plugin:uglifyjs-webpack-plugin

在项目发布之前,我们必然需要对 js 等文件进行压缩处理,对打包的js文件进行压缩,可以使用一个第三方的插件 uglifyjs-webpack-plugin,并且版本号指定 1.1.1,和 CLI2 保持一致。

6.1 安装命令

npm install uglifyjs-webpack-plugin@1.1.1 --save-dev

6.2 修改 webpack.config.js 文件中 plugins

const UglifyjsWebpackPlugin = require("uglifyjs-webpack-plugin");
  plugins: [
    new UglifyjsWebpackPlugin()
  ]

 

1.4.4 Webpack搭建本地服务器

webpack 提供了一个可选的本地开发服务器,这个本地服务器基于 node.js 搭建,内部使用 express 框架,可以实现让浏览器自动刷新显示我们修改后的结果,不过它是一个单独的模块,在webpack中使用之前需要先安装它。

1.安装 webpack-dev-server

npm install --save-dev webpack-dev-server@2.9.1

  1. webpack.config.js 文件配置修改:在moudle.exports下配置如下代码
  devServer: {
    contentBase: "./dist",
    inlinetrue, //是否自动刷新
     port:8080 //端口号
 }
  1. package.json配置启动命令
  "scripts": {
    "dev": "webpack-dev-server --open"
  },

4.执行命令 npm run dev

 

1.4.5webpack 配置分离

由于我们目前所有的生产环境代码和发布环境代码都写在 webpack.config.json 中,这样当我们在编译的时候,会将生产环境代码和发布环境代码一起编译,但有时候我们希望将二者分开编译,比如:

生产环境时,我们希望在生成服务器的代码执行,但是不希望执行uglifyjs-webpack-plugin命令对项目进行压缩打包,这时候,将生产环境代码和发布环境代码抽离出来显得很有必要。

配置分离操作步骤:

1.一个文件夹 build,将所有编译环境代码放到里面,在 build 中新建三个文件:

image.png

其中 base.config.js为公共部分、dev.config.js为开发部分、prod.config.js为生产部分

  1. 安装对配置文件合并的插件:webpack-merge

npm install webpack-merge --save-dev

3.修改配置文件

dev.config.js

image.png

prod.config.js

image.png
  1. 删除原来的 webpack.config.js 文件

此时执行 npm run build 命令会报如下错误:

image.png

5.修改 package.json 文件

image.png

此时可以打包成功了,但是发现打包的 bundle.js 的位置是在 built/dist 文件夹中,而不是在原来的 dist 中,这时因为在 base.config.js 中的 output 中的文件拼接路径是 'dist',表示的是从当前路径下拼接,需要将其修改为从上一级目录开始拼接:

image.png 恭喜你,终于结束了webpack配置问题,准备上天吧!!!!!!!

2.Vue CLI

  2.1 Vue CLI是什么

         如果你只是简单写几个 Vue 的 Demo 程序, 那么你不需要 Vue CLI,但是如果你在开发大型项目, 那么你需要, 并且必然需要使用 Vue CLI

使用 Vue.js 开发大型应用时,我们需要考虑代码目录结构、项目结构和部署、热加载、代码单元测试等事情,如果每个项目都要手动完成这些工作,效率比较低,所以通常我们会使用一些脚手架工具来帮助完成这些事情。

CLI 是 Command-Line Interface , 翻译为命令行界面, 俗称脚手架

 

  2.2 Vue CLI依赖环境

Node 与 webpack ,确保电脑安装了这两个东西

 

  2.3 Vue CLI的安装

脚手架3安装命令: npm install -g @vue/cli 

安装完后脚手架2拉取命令: npm install -g @vue/cli-init 

3.Vue CLI2的使用

  3.1 Vue CLI配置过程

3.1.1 Vue CLI2配置过程

image.png

3.1.1 Vue CLI3配置过程

image.png

  3.2 Vue CLI目录结构

3.2.1 Vue CLI2目录结构

image.png

3.2.1 Vue CLI3目录结构

image.png

注意:Runtime-Compiler 和 Runtime-only 的区别

runtime-copmpiler : twmpalte -> ast -> render -> vdom ->UI

runtime-only(性能更高,代码量更少,且较上面小6KB) : render -> vdom ->UI


如果在之后的开发中,你依然使用 template ,就需要选择 Runtime-Compiler

如果你之后的开发中,使用的是 .vue 文件开发,那么可以选择 Runtime-only (使用!)

  3.3 入口文件详解

3.3.1 Vue渲染过程

image.png

当我们把template传给vue的时候,vue会做一个保存,将template保存到vm.options中,让后对template进行一个parse(解析)操作,此时tempalte被解析为ast结构(抽象语法树 abstract syntax tree),让后vue会将ast编译(compile)成一个render函数,通过render函数创建一些virtual point(虚拟节点),再有virtual point组成虚拟dom树(virtual dom),之后再将虚拟dom树渲染成真实dom即显示在界面的dom,或者说UI上面的东西。

twmpalte -> ast -> render -> virtual dom ->UI

 

3.3.2 render函数的使用

New Vue({

el:’#app’
render:function(createElement => h){
return createElement(“h2”,”{class :”box”}”,[”Hello World” ]) }
           createElement(“标签”,”{标签属性}”,[”内容”])
}
    
createElement中的h2标签会替换掉挂载的<div id= ” app ”></div>,使他变成""<h2></h2>,在内容里面除开 ” Hello World ”,还调入createElement(“button”,[‘按钮’]),此时内容会增加一个button。
    
createElement除开上述做法,还可以传入组件对象

New Vue({
el:’#app’
render:function(createElement => h){
return createElement(App);
    
此时没有template进行编译,直接给一个render函数,大大加快了编译速度

}

 

  3.4 webpack配置解析

npm run build

image.png

npm run dev

image.png

 

4.Vue CLI3的使用

  4.1 Vue CLI配置修改

4.1.1 Vue UI

在 Vue CLI 3 中,webpack 等相关配置文件被隐藏起来了,如果想查看或修改相关配置,所以引入原理Vue UI的概念

通过Vue UI启动本地服务器

4.1.2 配置存放路径

在UI界面里面找到项目配置,在输出目录项配置相关存放路径

4.13 添加新的别名

在 node_modules 中寻找

image.png

在项目中新建一个 vue.config.js 文件,将需要修改的配置代码写入其中

image.png

最终编译时会自动将我们添加的代码与隐藏的代码进行合并。

 

五、vue-router

1. 认识路由的概念

  1.1 认识路由

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

路由器提供了两种机制, 路由和转发

路由是决定数据包从来源到目的地的路径,转发将输入端的数据转移到合适的输出端

 

  1.2 后端路由时代

什么是后端路由?

早期的网站开发,整个 HTML 页面都是是由服务器来渲染的,服务器直接生产渲染好对应的 HTML 页面, 返回给客户端进行展示

 

服务器如何处理一个网站的诸多页面呢?

首先,一个页面会有自己对应的网址, 也就是 URL,客户端发生请求时,URL 会发送到服务器,服务器通过正则对该URL 进行匹配且最后交给 Controller 进行处理,Controller 进行各种处理后,最终生成 HTML 或者数据,返回给前端,这就完成了一个IO操作,这种操作, 就是后端路由

 

后端路由的优点

当页面中需要请求不同的路径内容时,交给服务器来进行处理, 服务器渲染好整个页面,并且将页面返回给客户端,这种情况下渲染好的页面,不需要单独加载任何的 js 和 css,可以直接交给浏览器展示,这样也有利于 SEO 的优化

 

后端路由的缺点

整个页面的模块都要由后端人员来编写和维护,工作量太大,前端开发人员如果要开发页面,需要通过 PHP 和 Java 等语言来编写页面代码,增加了额外的学习成本,HTML 代码和数据以及对应的逻辑混在一起,,不利于编写和维护

 

  1.3 前后端分离时代

随着 Ajax 的出现,有了前后端分离的开发模式:后端只提供 API 来返回数据,前端通过 Ajax 获取数据,并且可以通过 JavaScript 将数据渲染到页面中

 

优点:

前后端责任变得很清晰,后端专注于数据上, 前端专注于交互和可视化上

当移动端(iOS/Android)出现后,后端不需要进行任何处理, 依然使用之前的一套API即可

 

1.5   前端路由时代

前端路由的核心:改变URL,但是页面不进行整体的刷新

单页面富应用,即单页Web应用(single page web application,SPA),就是只有一张 Web 页面的应用,是加载单个 HTML 页面并在用户与应用程序交互时动态更新该页面的 Web 应用程序。简单理解:就是在前后端分离的基础上加了一层前端路由

SPA 的特点

速度:更好的用户体验,让用户在 web app 感受 native app 的速度和流畅

MVVM:经典 MVVM 开发模式,前后端各负其责

ajax:重前端,业务逻辑全部在本地操作,数据都需要通过AJAX同步、提交

路由:在 URL 中采用 # 号来作为当前视图的地址,改变 # 号后的参数,页面并不会重载

SPA 缺点

首屏渲染等待时长: 必须得加载完毕,才能渲染出首屏

seo不友好:爬虫只能拿到一个 div,认为页面是空的,不利于 seo

初次加载耗时多:为实现单页Web应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面可以在需要的时候加载,所以必须对 JavaScript 及 CSS 代码进行合并压缩处理,如果使用第三方库,建议使用一些大公司的 CDN,因此带宽的消耗是必然的

 

SPA 优点

良好的交互体验 :用户不需要重新刷新页面,获取数据也是通过 Ajax 异步获取,页面显示流畅

良好的前后端工作分离模式:单页 Web 应用可以和 RESTful 规约一起使用,通过 REST API 提供接口数据,并使用 Ajax 异步获取,这样有助于分离客户端和服务器端工作,更进一步,可以在客户端也可以分解为静态页面和页面交互两个部分

减轻服务器压力:服务器只用出数据就可以,不用管展示逻辑和页面合成,吞吐能力会提高几倍

共用一套后端程序代码 :不用修改后端程序代码就可以同时用于 Web 界面、手机、平板等多种客户端

 

2.前端路由的规则

  2.1 URL的hash

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

image.png

 

  2.2 HTML5的history

history 接口是 HTML5 新增的,它有五种模式改变 URL 而不刷新页面

 

2.2.1 pushState

Console输入history.pushatate({ }, ” ”,’home’)会改变当前界面的URL:localhost:8080/home,

压入的URL形成一个栈结构:先进先出。

2.2.2 repalceState

Console输入history.repalceState({ }, ” ”,’home’),替换之前的URL,无法保留之前的URL记录,也就是说,无法返回上一个URL,你会发现回退箭头按钮为灰色,无法点击。

2.2.3 go

history.go( n ),表示从栈顶一次性弹出n个URL,正值为弹出,赋值为压入

history.go( -1 ) =history.back()       history.go(1 )=history.forward()

3.router-view基础

  3.1 认识和安装vue-router

目前前端流行的三大框架,都有自己的路由实现: Angular:ngRouter

React:ReactRouter

Vue:vue-router

vue-router 是 Vue.js 官方的路由插件,它和 vue.js 是深度集成的,适合用于构建单页面应用。

vue-router 是基于路由和组件的,路由用于设定访问路径, 将路径和组件映射起来,在 vue-router 的单页面应用中, 页面路径的改变就是组件的切换。

 

  3.2 vue-router的基本使用

3.3.1 安装 vue-router

npm install vue-router --save

3.3.2 步骤一、创建路由组件

因为 vue-router 是一个插件,所以可以通过 Vue.use() 来安装路由功能

Import Vue from“vue”
Import VueRouter from “vue-router”
    
Vue.use(VueRouter);

const routes = [];
    
const router = new VueRouter({})

export default router;

之后在mian.js中挂载将路由组件挂载到Vue实例中

Import router from “./router”

New Vue({
  render:h =>h(App),
  router
}).$mount(‘#app’)

3.3.3 步骤二、配置组件和路由的映射关系

  1. 新建两个组件
image.png
  1. 为组件配置路由映射关系

image.png

3.3.4 步骤三、使用路由

通过和

<router-link>: 该标签是一个 vue-router 中已经内置的组件, 它默认会被渲染成一个 <a> 标签

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

image.png

  3.3 vu-router细节补充

3.3.1 路由的默认路径

默认情况下, 进入网站的首页,我们希望 渲染首页的内容,但是在上面的实现中,默认没有显示首页组件,必须让用户点击才可以

image.png

在 routes 中又配置了一个映射:

path:根路径 /

redirect:重定向,也就是将根路径重定向到 /home 的路径下

 

3.3.2 HTML5的History模式

默认情况下,Vue 路径的改变使用的是 URL 的 hash,这样显示出的页面的地址中有一个 # 号,不太美观,可以使用 HTML5 的 history 模式来进行改变,进行如下配置即可:

image.png

3.3.3 router-link补充

属性:

to:用于指定跳转的路径

tag:tag 可以指定 之后渲染成什么组件,默认是渲染为 标签

replace: replace 不会留下 history 记录,所以指定 replace 的情况下, 后退键返回不能返回到上一个页面中

active-class: 当对应的路由匹配成功时,会自动给当前元素设置一个 router-link-active 的class,该 class 具体的名称也可以通过 router 实例的属性进行修改,但是通常不会修改类的属性, 会直接使用默认的 router-link-active 即可

<router-link to="/home" tag="button" replace active-class="active">首页</router-link>

image.png

3.3.4 路由代码跳转

有时候,页面的跳转可能需要执行对应的 JavaScript 代码,这个时候,就可以使用第二种跳转方式了

比如,我们将代码修改如下:

image.png

注:

动态路由

在某些情况下,一个页面的 path 路径可能是不确定的,比如我们进入用户界面时,希望是如下的路径:/user/aaaa 或 /user/bbbb,除了有前面的 /user 之外,后面还跟上了用户的 ID

这种 path 和 Component 的匹配关系,称之为动态路由(也是路由传递数据的一种方式)

在 vue-router 的路由路径中使用“动态路径参数”(dynamic segment) :

{
   path :”/user/:id”
   component : User
}

在组件中手动绑定一个用户 ID:

image.png

更新 User 的模板,输出当前用户的 ID:

image.png

4.路由懒加载

  4.1 什么是路由懒加载

路由懒加载的主要作用就是将路由对应的组件打包成一个个的 js 代码块,只有在这个路由被访问到的时候,才加载对应的组件

  4.2 懒加载和非懒加载打包区别

当打包构建应用时,Javascript 包会变得非常大,影响页面加载,如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了

为了实现这种效果,我们可以使用路由的懒加载

 

  4.3 懒加载的方式

4.3.1 方式一:结合Vue的异步组件和webpack的代码分析

const Home = resolve => { require.ensure(['../components/Home.vue'], ()=>

{ resolve(require('../components/Home.vue')) })};

 

4.3.2 方式二:ADM写法

const About = resolve => require(['../components/About.vue'], resolve);

4.3.3 方式三:在ES6中,有更简单的写法来组织Vue异步组件和Webpack的代码分割

[const Home = () => import('../components/Home.vue')]()

5.路由嵌套的使用

  5.1 认识嵌套路由

嵌套路由是一个很常见的功能 ,比如在 home 页面中,我们希望通过 /home/news 和 /home/message 访问一些内容,一个路径映射一个组件,访问这两个路径也会分别渲染两个组件。

路径和组件的关系如下:

image.png

实现嵌套路由有两个步骤:

`、创建对应的子组件,并且在路由映射中配置对应的子路由 -> 定义两个子组件

image.png   2、配置子组件的路由

image.png  

  5.3 嵌套默认路径

在父组件内部显示子组件

image.png image.png

6.路由传递参数

  6.1 准备工作

6.1.1 第一步:创建新的组件Profile.vue

image.png

6.1.2 第二步:配置路由映射

image.png

6.1.3第三步:添加跳转的

image.png

  6.2 传递参数的方式:params方式

配置路由格式:/router/:id****

传递的方式:在path后面跟上对应的值

传递后形成的路径:/router/123,/router/abc

  6.3传递参数的方式: query方式

配置路由格式:/router 也就是普通配置

传递的方式:对象中使用query的key作为传递方式

传递后形成的路径:/router?id=123,/router?id=abc

image.png

image.png

  6.4 获取参数

获取参数的方式:$route对象获取****

在使用vu-route的应用中,路由对象会被注入每个组件中,赋值为this.$sroute,并且当路由切换时,路由对象会被更新

  6.5 routeroute和router的区别

routerVueRouter 实例,想要导航到不同 URL,则使用 router.push 方法

$route 为当前 router 跳转对象,里面可以获取 name、path、query、params 等

 

7.路由导航守卫

  7.1什么是导航守卫

导航守卫主要用来监听路由的进入和离开,vue-router 提供了 beforeEach 和 afterEach 的钩子函数,它们会在路由即将改变前和改变后触发

  7.2钩子函数

7.1.1 beforeEach

image.png

7.1.2 afterEach

image.png

  7.3守卫应用

7.2.1 动态改变页面标题

见上beforeEarch使用方法

  7.3参数解析

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

from:当导航将要离开的路由对象

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

8.keep-alive

  8.1 认识keep-alive       

keep-alive 是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染

  router-view 也是一个组件,如果直接被包在 keep-alive 里面,所有路径匹配到的视图组件都会被缓存:

<keep-alive>
       <router-view>
          <!-- 所有路径匹配到的试图组件都会被缓存! -->
  </router-view>
</keep-alive>

 

keep-alive 还有两个非常重要的属性:

include - 字符串或正则表达,只有匹配的组件会被缓存

exclude - 字符串或正则表达式,任何匹配的组件都不会被缓存

 

让部分组件不缓存:

<keep-alive exclude="Profile,User"><router-view/></keep-alive>

六、Vuex详解

1.为什么使用Vuex

  1.1 Vuex是做什么的

1.1.1 Vuex解释

    Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。

Vuex 采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了诸如零配置的 time-travel 调试、状态快照导入导出等高级调试功能。

1.1.2 状态管理

把需要多个组件共享的变量全部存储在一个对象里面,然后将这个对象放在顶层的 Vue 实例中,让其他组件可以使用,共享这个对象中的所有变量属性,并且是响应式的。

 

1.1.3 有什么状态时需要我们在多个组件间共享?

用户的登录状态、用户名称、头像、地理位置信息等

商品的收藏、购物车中的物品等

  1.2 单页面状态管理

在单个组件中进行状态管理是一件非常简单的事情,如图:

 

State:状态(姑且可以当做是 data 中的属性)

View:视图层,可以针对 State 的变化,显示不同的信息

Actions:用户的各种操作:点击、输入等,会导致状态的改变

image.png

在这个案例中:

counter 需要某种方式被记录下来,也就是 State,counter 目前的值需要被显示在界面中,也就是我们的 View,界面发生某些操作时(这里是用户的点击,也可以是用户的 input),需要去更新状态,也就是 Actions

  1.3 多页面状态管理

多个视图都依赖同一个状态(一个状态改了,多个界面需要进行更新)

全局单例模式(大管家)

现在要做的就是将共享的状态抽取出来,交给大管家统一进行管理,之后每个试图按照规定进行访问和修改等操作,这就是 Vuex 的基本思想

  1.4 Vuex状态管理图例

image.png

2.Vuex基本使用

  2.1 Vuex安装

npm install vuex --save

  2.2 Vuex的代码组织

1.创建一个文件夹 store,并且在其中创建一个 index.js 文件

2.在 index.js 文件中写入代码

import Vue from 'vue'
import Vuex from 'vuex'
    
vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++
        },
        decrement(state) {
            state.count--
        }
    }
})
export default store

3.挂载到 Vue 实例中

import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
 
Vue.config.productionTip = false

new Vue({
    el: '#app',
    router,
    store,
    render: h => h(App)
})
  1. 此后,在其他 Vue 组件中,可以通过 this.$store 的方式,获取到 store 对象

  2.3 count案例

<template>
  <div class="test">
    <h2>当前计数:{{count}}</h2>
    <button @click="increment">+</button>
    <button @click="decrement">-</button>
  </div>
</template>

<script>
export default {
  name: 'Test',
  computed:{
    count(){
      return this.$store.state.count
    }
  },
  methods:{
    increment(){
      this.$store.commit('increment')
    },
    decrement(){
      this.$store.commit('decrement')
    }
  }
}
</script>
<style scoped>

</style>

首先提取出一个公共的 store 对象,用于保存在多个组件中共享的状态,将 store 对象放置在 new Vue 对象中,这样可以保证在所有的组件中都可以使用到,在其他组件中使用 store 对象中保存的状态即可。

通过 this.$store.state.属性 的方式来访问状态

通过 this.$store.commit(‘Mutations中方法’) 来修改状态

注意: 我们通过提交 Mutations 的方式,而非直接改变 store.state.count ,这是因为 Vuex 可以更明确的追踪状态的变化,所以不要直接改变 store.state.count 的值。

 

3.Vuex核心概念

  3.1 state单一状态树

Vuex 提出使用单一状态树(Single Source of Truth),也可以翻译成单一数据源。

如果状态信息是保存到多个 Store 对象中的,那么之后的管理和维护等等都会变得特别困难,所以 Vuex 使用了单一状态树来管理应用层级的全部状态

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

Vuex中存在一个且只存在一个唯一的state对象

 

  3.2 getter

3.2.1 Getters基本使用

从 store 中获取一些 state 变异后的状态,则可以使用 getters

Getters: {
  powerCounter(state) {
    return state.counter * state.counter;
  },

在组件中使用 getters:

<template>
  <div id="app">
    <h3>{{this.$store.getters.getAgeLess20Count}}</h3>
  </div>
</template>

 

3.2.2 Getters本身作为参数

getters: {
  moreAgeStu(state) {
    return age => {
      return state.student.filter(s => s.age > age);
    };
  }
};
}

3.2.3 Getters传递参数

getters 默认是不能传递参数的,如果希望传递参数,那么只能让 getters 本身返回另一个函数

getters: {
  // 根据id查找学生
  getStuByID(state) {
    return id => state.students.find(s => s.id === id)
  }
}

在组件中使用:

<template>
  <div id="app">
    <h3>{{this.$store.getters.getStuByID(1002)}}</h3>
  </div>
</template>

  3.3 Mutation

3.3.1 mutation基本使用

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

Mutations 组成

字符串的事件类型(type)

一个回调函数(handler),该回调函数的第一个参数就是 state

Mutations 的定义方式

mutations: {
  increment(state) {
    state.count++
  },
  decrement(state) {
      state.count--
    }
}

通过 Mutations 更新

methods:{
  increment(){
    this.$store.commit('increment')
  },
  decrement(){
    this.$store.commit('decrement')
  }
}

3.3.2 mutation传递参数

在通过 Mutations 更新数据的时候,有时候希望携带一些额外的参数,这种参数被称为 Mutations 的载荷(Payload)

image.png

如果有很多参数需要传递,通常会以对象的形式传递,也就是 payload 是一个对象,再从对象中取出相关的信息

image.png

3.3.4 提交风格

上面的通过 commit 进行提交是一种普通的方式,Vue 还提供了另外一种风格, 它是一个包含 type 属性的对象。Mutations 中的处理方式是将整个 commit 的对象作为 payload 使用, 所以代码没有改变, 依然如下:

image.png

3.3.5 mutation响应规则

提前在store中初始化好所需的属性

当给 state 中的对象添加新属性时,使用下面的方式:

使用 Vue.set(obj, 'newProp', 123)

用新对象给旧对象重新赋值

例:

image.png

以上代码给 state 中的对象添加新属性时,由于不是响应式添加,所以界面不会更新,要想让界面更新,可以使用一下方式:

updateInfo(state, payload) {
  // state.info['height'] = payload.height
    
  // 方式一:Vue.set()
  // Vue.set(state.info, 'height', payload.height)

  // 方式二:给 info 赋值一个新的对象
  state.info = {...state.info, 'height': payload.height }

}

3.3.6 mutation常量管理

在 Mutations 中, 我们定义了很多事件类型(也就是其中的方法名称),当我们的项目不断增大时,会出现:Vuex 管理的状态越来越多,需要更新状态的情况越来越多,Mutations 中的方法越来越多,使用者需要花费大量的经历去记住这些方法,甚至是多个文件间来回切换,查看方法名称,甚至出现写错的情况,如何避免上述的问题呢?

在各种 Flux 实现中,一种很常见的方案是: 使用常量替代 Mutations 事件的类型,将这些常量放在一个单独的文件中,方便管理以及让整个 app 所有的事件类型一目了然

具体操作: 创建一个文件: mutations-types.js,并且在其中定义我们的常量,定义常量时,我们可以使用 ES5 中的风格,使用一个常量来作为函数的名称,之后在相应位置使用常量。

image.png

  3.4 Action

通常情况下,Vuex 要求 Mutations 中的方法必须是同步方法,因为当我们使用 devtools 时,devtools 可以帮助我们捕捉 Mutations 的快照,但如果是异步操作, 那么 devtools 将不能很好的追踪这个操作什么时候会被完成,此时可以引用Action

3.4.1 Action基本定义

Action 类似于 Mutations,是用来代替 Mutations 进行异步操作的,在 Action 中,可以将异步操作放在一个 Promise 中,并且在成功或者失败后,调用对应的 resolve 或reject:

image.png

3.4.2 Action返回的Promise

  3.5 Moudle

3.5.1 认识Vuex的Moudle

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

3.5.2 Moudle的局部状态

Modules 中的 mutations

虽然 mutations 是定义在模块中的,但是在组件中提交时还是使用:this.$store.commit

image.png

image.png

Modules 中的 getters

image.png

Modules 中的 actions

image.png

3.5.3 Moudle的Action写法

const ModuleA = {
  state:{},
  mutations:{},
  actions:{},
  getters:{}
}
const ModuleB = {
  state:{},
  mutations:{},
  actions:{},
  getters:{}
}
const store = new Vuex.Store({
  modules:{
    a:ModuleA,
    b:ModuleB
  }
})

store.state.a // ModuleA 的状态
store.state.b // ModuleB 的状态

 

4.项目组织结构

image.png

基本项目结构

七、网络封装

1.网络模块选择

  1.1 传统的Ajax基于XMLHttpRequest(XHR)

之所以不用它,是因为它配置和调用方式等非常混乱,编码起来看起来令人十分头疼

真实开发中很少直接使用传统的 Ajax , 而是使用 jQuery-Ajax

  1.2 jQuery-Ajax

jQuery-Ajax 相对于传统的 Ajax 非常好用

之所以不用它,是因为 jQuery 是一个重量级的框架,代码有 1w+ 行,而 Vue 的代码才 1w+ 行,所以没必要为了网络请求,特意引用一个 jQuery

  1.3 Vue-resource

官方在 Vue1.x 的时候, 推出了 Vue-resource,Vue-resource 的体积相对于 jQuery 小很多

之所以不用它,是因为在 Vue2.0 推出后,Vue 的作者就在 GitHub 的 Issues 中说明了去掉 vue-resource,并且以后也不会再更新,意味着以后 vue-reource 不再支持新的 Vue 版本,也不会再继续更新和维护,对以后的项目开发和维护都存在很大的隐患

  1.4 axios

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

axios有非常多的优点, 并且用起来也非常方便

2.axios的使用

  2.1 认识axios

2.1.1 为什么选择axios

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

2.1.2 axios请求方式

  • axios(config)
  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.head(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])

  2.2 发送请求

2.2.1 发送get请求

image.png

2.2.2 发送并发请求

使用 axios.all,可以放入多个请求的数组 axios.all([]) 返回的结果是一个数组,使用 axios.spread 可将数组 [res1,res2] 展开为 res1, res2

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

 

2.2.3 axios全局配置

在开发中,可能很多参数都是固定的,这个时候,我们可以进行一些抽取,也可以利用 axios 的全局配置

image.png

2.2.4 常见配置选项

baseURL:基本网址;
timeout:最长延迟时间;

  2.3 axios实例

2.3.1 为什么创建axios实例

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

2.3.2 如何创建axios实例

const axiosInstance = axios.create({
    baseURL: 'http://123.207.32.32:8000',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
})

// 发送网络请求
axiosInstance({
    url: '/category',
    method: 'get'
}).then(res => {
    console.log(res);
}).catch(err => {
    console.log(err);
})

2.3.3 axios的封装

image.png

2.3.3 axios的请求

image.png

 

  2.4 axios的拦截器

2.4.1 请求拦截器

image.png

请求拦截器的时候,可以实现:

在发送网络请求是,在页面添加一个loading组件作为动画;

要求用户必须登录,判断用户是否有token,如果没有九跳转到login页面;

2.4.2 响应拦截器

响应的成功拦截中,主要是对数据进行过滤,响应的失败拦截中,可以根据 status 判断报错的错误码,跳转到不同的错误提示页面:

image.png