Vue简介

Vue简介及基础用法

1、Vue基础指令

1.1 什么是Vue?

Vue.js 是一套响应式的 JavaScript 开发库。Vue.js 自问世以来所受关注度不断提高,在现在的市场上,Vue.js 是非常流行的 JavaScript 技术开发框架之一。

Vue是一款国产前端框架,它的作者尤雨溪(Evan You)是一位美籍华人,2014年2月,尤雨溪开源了一个前端开发库 Vue.js,2015年发布1.0.0版本,2016年4月发布2.0版本,目前,尤雨溪全职投入 Vue.js 的开发与维护,立志将 Vue.js 打造成与 Angular/React 平起平坐的世界顶级框架。

1.1.1 读音★★★

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式JavaScript框架。简单小巧的核心,渐进式技术栈,足以应付任何规模的应用。

1.1.2 特点★★★
  • Vue是一个遵循MVVM模式的渐进式框架
  • Vue比较易学,体积更小,灵活,高效

  • Vue的本身只关注UI视图,可以更简单的导入Vue插件和第三方库

  • Vue通过Vue对象把数据和视图完全分离开来,对视图的改变无需在操作DOM元素,只需要操作对应的数据,即可改变对应的视图结构,也就是通过双向数据绑定把View层和Model层连接了起来,通过对数据的操作就可以完成对页面视图的渲染。

1.2 数据渲染

1.2.1 Vue.js的引入★★★★★

前面我们说了Vue的一些特点,那下面我们来看一下我们如何具体使用Vue呢?
我们一般学一门语言,都会先从打印hello world学起,那么我们来看一下,Vue如何打印Hello World呢?

使用Vue我们有两种方法,一种方法类似于使用JQuery,我们可以引入Vue.js文件,还有一种方式是借助于npm来安装Vue的脚手架,做到真正的前后端分离,我们先来看一下第一种方式。

我们可以通过下面的方式引入Vue的文件

<!-- 开发环境版本,包含了有帮助的命令行警告 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>

或者

<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
1.2.2 数据绑定★★★★

上面我们说了如何导入Vue的js文件,下面我们来看一下我们如何在Vue中绑定数据,并把数据显示到页面上呢?

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统:

 <div id="app">
     <h3>{{message}}</h3>
 </div>

在script的标签中实例化一个Vue的对象,并定义绑定的数据

  new Vue({
            el: "#app",
            data:{
                message: "Hello World"
       			}
  				})

页面显示效果如下所示:

这样的话我们已经创建了一个Vue的应用,看起来像是渲染模版字符串一样,但是Vue在背后做了大量的工作,这样的话DOM和数据就建立绑定关系。

1.2.3 语法★★★★★

在上面的案例中我们把Hello World渲染到了页面上,那我们来看一下,这个语法结构是什么意思呢?

前端渲染

 <div id="app">
     <h3>{{message}}</h3>
 </div>

数据渲染到页面上,首先我们需要提供一个标签用于填充数据,这个标签我们定义了一个id属性为app,在标签中我们用小胡子插值表达式进行数据的填充。

Vue对象定义

  • 定义完页面结构,我们需要在js中通过 new Vue()创建一个Vue的实例化对象
  • 在构造函数中以对象的形式做一些配置,这些配置中比较重要的有el、data两个属性
  • el 定义可以渲染数据的DOM结构
  • data定义可以渲染到页面上的数据,跟DOM结构建立关联关系

通过上述语法结构,我们就简单完成用Vue打印Hello World的应用

1.3 指令

Vue的指令是带有特殊前缀v-的HTML特性,他可以绑定一个表达式,并且把一些特性作用到DOM结构上去。
下面我们来看一下常见的指令。

1.3.1 v-cloak★★★★★

v-cloak指令的作用防止页面加载时出现闪烁问题
那么为什么会有闪烁问题呢?代码加载的时候先加载HTML 把插值语法当做HTML内容加载到页面上 当加载完js后才把插值语法替换掉 所以我们会看到闪烁问题。
下面我们看一下案例,如何使用

<html>
	 <style type="text/css">
  /* 
    1、通过属性选择器 选择到 带有属性 v-cloak的标签  让他隐藏
 */
  [v-cloak]{
    /* 元素隐藏    */
    display: none;
  }
  </style>
<body>
  <div id="app">
    <!-- 2、 让带有插值 语法的   添加 v-cloak 属性 
         在 数据渲染完场之后,v-cloak 属性会被自动去除,
         v-cloak一旦移除也就是没有这个属性了  属性选择器就选择不到该标签
		 也就是对应的标签会变为可见
    -->
    <div  v-cloak  >{{msg}}</div>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    var vm = new Vue({
      //  el   指定元素 id 是 app 的元素  
      el: '#app',
      //  data  里面存储的是数据
      data: {
        msg: 'Hello Vue'
      }
    });
	</script>
</body>
</html>
1.3.2 v-text ★★★★
  • v-text指令用于将数据填充到标签中,作用于插值表达式类似,但是没有闪动问题
  • 如果数据中有HTML标签会将html标签一并输出
  • 注意:此处为单向绑定,数据对象上的值改变,插值会发生变化;但是当插值发生变化并不会影响数据对象的值

我们来看一下指令如何使用

	<div id="app">
    <!--  
		注意:在指令中不要写插值语法  直接写对应的变量名称 
        在 v-text 中 赋值的时候不要在写 插值语法
		一般属性中不加 {{}}  直接写 对应 的数据名 
	-->
    <p v-text="msg"></p>
    <p>
        <!-- Vue  中只有在标签的 内容中 才用插值语法 -->
        {{msg}}
    </p>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>
1.3.3 v-html ★★★★★
  • 用法和v-text 相似 但是他可以将HTML片段填充到标签中
  • 可能有安全问题, 一般只在可信任内容上使用 v-html永不用在用户提交的内容上
  • 它与v-text区别在于v-text输出的是纯文本,浏览器不会对其再进行html解析,但v-html会将其当html标签解析后输出。

参照v-text,v-html,{{}}打印的内容的区别如下:

	<div id="app">
  <p v-html="html"></p> <!-- 输出:html标签在渲染的时候被解析 -->
    
    <p>{{message}}</p> <!-- 输出:<span>通过双括号绑定</span> -->
    
  <p v-text="text"></p> <!-- 输出:<span>html标签在渲染的时候被源码输出</span> -->
</div>
<script>
  let app = new Vue({
  el: "#app",
  data: {
    message: "<span>通过双括号绑定</span>",
    html: "<span>html标签在渲染的时候被解析</span>",
    text: "<span>html标签在渲染的时候被源码输出</span>",
  }
 });
</script>
1.3.4 v-pre★★★★
  • 显示原始信息跳过编译过程
  • 跳过这个元素和它的子元素的编译过程。
  • 一些静态的内容不需要编译加这个指令可以加快渲染
<span v-pre>{{ this will not be compiled }}</span>    
	<!--  显示的是{{ this will not be compiled }}  -->
<span v-pre>{{msg}}</span>  
  <!--   即使data里面定义了msg这里仍然是显示的{{msg}} -->
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });

</script>
1.3.5 v-once★★★
  • 执行一次性的插值【当数据改变时,插值处的内容不会继续更新】
<!-- 即使data里面定义了msg 后期我们修改了 仍然显示的是第一次data里面存储的数据即 Hello Vue.js  -->
     <span v-once>{{ msg}}</span>    
<script>
    new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue.js'
        }
    });
</script>
1.3.6 v-on★★★★★
  • 用来绑定事件的
  • 形式如 v-on:事件名字 比如 v-on:click 缩写为 @click
		<div id="app">
        <!-- 绑定点击事件数量进行自增或者自减 -->
        <button v-on:click="nums++">自增</button>
        <!-- 缩写 -->
        <button @click="nums--">自减</button>
        <p>数量:{{nums}}</p>
    </div>

    <script>
        new Vue({
            el: "#app",
            data:{
                message: "Hello World",
                nums:1,
            }
        })
    </script>

1.4 双向数据绑定

所谓双向数据绑定也就是:

  • 当数据发生变化的时候,视图也就发生变化
  • 当视图发生变化的时候,数据也会跟着同步变化
    最能体现双向数据绑定的质量就是 v-model
1.4.1 v-model★★★★★
  • v-model是一个指令,限制在 <input>、<select>、<textarea>、components中使用,
  • 通过v-model绑定后,视图输入内容的变化会改变指令绑定的数据,数据发生变化也会直接作用到视图上,所以这个指令在提交表单数据时应用最多,无需在进行DOM查询获取表单的数据。
	<div id="app">
      <div>{{msg}}</div>
      <div>
          <!---当输入框中内容改变的时候,  页面上的msg  会自动更新-->
        <input type="text" v-model='msg'>
      </div>
  </div>
  <script>
        new Vue({
            el: "#app",
            data:{
                msg: "Hello Vue.js",
            }
        })
    </script>

1.5 MVVM

1.5.1 MVC★★★

MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范。

  • Model(模型)是应用程序中用于处理应用程序数据逻辑的部分。
  • View(视图)应用程序中处理数据显示的部分。
  • Controller(控制器)是应用程序中处理用户交互的部分。
1.5.2 MVP★★

全称:Model-View-Presenter ;MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方Controller/Presenter负责逻辑的处理,Model提供数据,View负责显示。

1.5.3 MVVM★★★★

而MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。MVVM 就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

  • Model(模型) 处理业务逻辑的操作

  • View(视图) 用于渲染数据的页面

  • VModel 负责视图和数据的双向数据绑定

1.6 v-on

1.6.1 事件的基本用法★★★★★
  • v-on用来绑定事件的
  • 形式如:v-on:click 缩写为 @click;
		<div id="app">
       <div>{{nums}}</div>
       <div>{{message}}</div>

       <div>
           <!-- v-on给元素绑定事件 -->
           <!-- 指令中的数据无需加this或{{}} -->
           <button v-on:click="nums++">增加</button>
           <!-- v-on可以使用@替代 -->
           <button @click="nums--">减少</button>

           <!-- 如果逻辑比较复杂的话,我们无法用表达式实现,需要自己定义一个函数 -->
           <button v-on:click="reverse()">反转字符串</button>
       </div>
    </div>
    <script>
        new Vue({
            el: "#app",
            data:{
                message: "Hello World",
                nums:1,
            },
            methods:{
                reverse(){
                    this.message = this.message.split("").reverse().join("");
                }
            }
        })
    </script>
1.6.2 事件函数传参★★★★

html中绑定事件函数后,有时候我们需要针对不同的数据做出同样的逻辑操作,这个时候我们需要传递参数,那参数如何传递呢?

<body>
    <div id="app">
        <div>{{num}}</div>
        <div>
            <!-- 如果事件直接绑定函数名称,那么默认会传递事件对象作为事件函数的第一个参数 -->
            <button v-on:click='handle1'>点击1</button>
            <!-- 2、如果事件绑定函数调用,那么事件对象必须作为最后一个参数显示传递,
                 并且事件对象的名称必须是$event 
            -->
            <button v-on:click='handle2(123, 456, $event)'>点击2</button>
        </div>
    </div>
    <script type="text/javascript" src="js/vue.js"></script>
    <script type="text/javascript">
        var vm = new Vue({
            el: '#app',
            data: {
                num: 0
            },
            methods: {
                handle1: function(event) {
                    console.log(event.target.innerHTML)
                },
                handle2: function(p, p1, event) {
                    console.log(p, p1)
                    console.log(event.target.innerHTML)
                    this.num++;
                }
            }
        });
 		</script>

1.7 修饰符

1.7.1 事件修饰符★★★★★
  • 在事件处理程序中调用 event.preventDefault()event.stopPropagation() 是非常常见的需求。
  • Vue 不推荐我们操作DOM 为了解决这个问题,Vue.js 为 v-on 提供了事件修饰符
  • 修饰符是由点开头的指令后缀来表示的 比如v-on:click.stop阻止事件冒泡
<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a>

<!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form>

<!-- 修饰符可以串联   即阻止冒泡也阻止默认事件 -->
<a v-on:click.stop.prevent="doThat"></a>

<!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

使用修饰符时,顺序很重要;相应的代码会以同样的顺序产生。因此,用 v-on:click.prevent.self 会阻止所有的点击,而 v-on:click.self.prevent 只会阻止对元素自身的点击。

1.7.2 按键修饰符★★★★

在做项目中有时会用到键盘事件,在监听键盘事件时,我们经常需要检查详细的按键。Vue 允许为 v-on 在监听键盘事件时添加按键修饰符

<!-- 只有在 `keyCode` 是 13 时调用 `vm.submit()` -->
<input v-on:keyup.13="submit">

<!-- -当点击enter 时调用 `vm.submit()` -->
<input v-on:keyup.enter="submit">

<!--当点击enter或者space时  时调用 `vm.alertMe()`   -->
<input type="text" v-on:keyup.enter.space="alertMe" >

<script>
	var vm = new Vue({
        el:"#app",
        methods: {
              submit:function(){
              	console.log("你按了回车键")
              },
              alertMe:function(){
              	console.log("回车+空格键");
              },
        }
    })

</script>

常见的事件修饰符有哪些呢?
.enter => enter键
.tab => tab键
.delete (捕获“删除”和“退格”按键) => 删除键
.esc => 取消键
.space => 空格键
.up => 上
.down => 下
.left => 左
.right => 右

1.7.3 自定义事件修饰符★★★★★

但是有时候Vue内置的修饰符可能无法满足我们的要求,那么在Vue中可以通过config.keyCodes自定义按键修饰符别名。

	<div id="app">
    <!--预先定义了keycode 116(即F5)的别名为f5,因此在文字输入框中按下F5,会触发prompt方法-->
    <input type="text" v-on:keydown.f5="prompt()">
</div>

<script>
    Vue.config.keyCodes.f5 = 116;
    
    let app = new Vue({
        el: '#app',
        methods: {
            prompt: function() {
                alert('我是 F5!');
            }
        }
    });
</script>
1.7.4 案例讲解-计算器★★★★

下面我们来简单做一个计算器的案例,简易版的

<style>
        #app{
            width: 600px;
            margin: 30px auto;
        }
</style>
<div id="app">
        <input type="text" class="inp" v-model="num1"/>
        <select v-model="flag">
            <option value="+">+</option>
            <option value="-">-</option>
            <option value="*">x</option>
            <option value="/">/</option>
        </select>
        <!--点击回车,进行计算-->
        <input type="text" class="inp" v-model="num2" @keyup.enter="sums"/>
        <!--点击按钮进行计算-->
        <button @click="sums">=</button>
        <span>{{sum1}}</span>
 </div>
 <script>
        new Vue({
            el:"#app",
            data:{
                num1:"",
                num2:"",
                sum1:"",
                flag:"+"
            },
            methods:{
                sums(){
                    this.sum1 = eval(this.num1+this.flag+this.num2);
                }
            }
        })
 </script>

1.8 v-bind

  • v-bind 指令被用来响应地更新 HTML 属性,主要是绑定属性。
  • v-bind:href 可以缩写为 :href;
<!-- 绑定一个属性 -->
<img v-bind:src="imageSrc">
<!-- 缩写 -->
<img :src="imageSrc">
1.8.1 绑定对象★★★★★
  • 我们可以给v-bind:class 一个对象,以动态地切换class。
  • 注意:v-bind:class指令可以与普通的class特性共存
    v-bind 中支持绑定一个对象 ,如果绑定的是一个对象则键为对应的类名,值为对应data中的数据
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
<!-- 
	HTML最终渲染为 <ul class="box textColor textSize"></ul>
	注意:
		textColor,textSize  对应的渲染到页面上的CSS类名	
		isColor,isSize  对应vue data中的数据  如果为true 则对应的类名 渲染到页面上 
		当 isColor 和 isSize 变化时,class列表将相应的更新,
		例如,将isSize改成false,
		class列表将变为 <ul class="box textColor"></ul>
-->
<ul class="box" v-bind:class="{textColor:isColor, textSize:isSize}">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>

<script>
var vm= new Vue({
    el:'.box',
    data:{
        isColor:true,
        isSize:true,
    }
})
</script>
1.8.2 绑定class★★★★

v-bind支持绑定一个数组,数组中的ClassA和ClassB对应为data中的数据。

<ul class="box" :class="[classA, classB]">
    <li>学习Vue</li>
    <li>学习Node</li>
    <li>学习React</li>
</ul>
<script>
var vm= new Vue({
    el:'.box',
    data:{
        classA:‘textColor‘,
        classB:‘textSize‘
    }
})
</script>
<style>
    .box{
        border:1px dashed #f0f;
    }
    .textColor{
        color:#f00;
        background-color:#eef;
    }
    .textSize{
        font-size:30px;
        font-weight:bold;
    }
</style>
1.8.3 绑定对象和绑定数组的区别★★★★★
  • 绑定对象的时候 对象的属性 即要渲染的类名 对象的属性值对应的是 data 中的数据
  • 绑定数组的时候数组里面存的是data 中的数据
1.8.4 绑定style★★★★

有些时候我们需要绑定内置样式,这个时候我们可以使用v-bind:style绑定属性。

<div v-bind:style="styleObject">绑定样式对象</div>'
<!-- CSS 属性名可以用驼峰式 (camelCase) 或短横线分隔 (kebab-case,记得用单引号括起来)    -->
 <div v-bind:style="{ color: activeColor, fontSize: fontSize,background:'red' }">内联样式</div>
 
<!--组语法可以将多个样式对象应用到同一个元素 -->
<div v-bind:style="[styleObj1, styleObj2]"></div>

<script>
	new Vue({
      el: '#app',
      data: {
        styleObject: {
          color: 'green',
          fontSize: '30px',
          background:'red'
        },
        activeColor: 'green',
   		fontSize: "30px"
      },
      styleObj1: {
             color: 'red'
       },
       styleObj2: {
            fontSize: '30px'
       }

</script>

1.9 v-model底层原理分析

1.9.1 原理分析★★★

v-model就是vue的双向绑定的指令,能将页面上控件输入的值同步更新到相关绑定的data属性,也会在更新data绑定属性时候,更新页面上输入控件的值。

v-model的实现原理大致是,通过Object.defineProperty()方法来劫持Vue数据的属性,一旦监听到数据变化,则更新数据关联的虚拟DOM树,当然在模版编译的时候,为指令v-model绑定input事件,当输入的内容发生改变时,同步更新data中绑定的数据。

1.9.2 实现★★★

我们可以通过给input框绑定一个input事件来模拟实现数据的双向绑定的效果

<div id="app">
        <div>{{msg}}</div>

        <input type="text" v-on:input="change" :value="msg"/>
    </div>

    <script>
    var vm= new Vue({
        el:'#app',
        data:{
            msg:"hello js",
        },
        methods:{
            change(e){
                this.msg = e.target.value;
            }
        }
    })
    </script>

1.10 分支结构

在实际的应用中,我们肯定会遇到通过用户操作,控制某些DOM结构的显示和隐藏的操作,这种操作我们在原来的js中时通过直接DOM来完成,在Vue中我们通过v-if或v-show指令来控制,那具体怎么使用呢?我们来具体看一下吧。

1.10.1 v-if使用场景★★★★
  • 多个元素 通过条件判断展示或者隐藏某个元素。或者多个元素
  • 进行两个视图之间的切换
<div id="app">
        <!--  判断是否加载,如果为真,就加载,否则不加载-->
        <span v-if="flag">
           如果flag为true则显示,false不显示!
        </span>
</div>

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

也可以使用v-if v-else-if v-else语句联合使用

	<div v-if="type === 'A'">
       A
    </div>
  <!-- v-else-if紧跟在v-if或v-else-if之后   表示v-if条件不成立时执行-->
    <div v-else-if="type === 'B'">
       B
    </div>
    <div v-else-if="type === 'C'">
       C
    </div>
  <!-- v-else紧跟在v-if或v-else-if之后-->
    <div v-else>
       Not A/B/C
    </div>

<script>
    new Vue({
      el: '#app',
      data: {
        type: 'C'
      }
    })
</script>
1.10.2 v-if和v-show的区别★★★★★
  • v-show本质就是标签display设置为none,控制隐藏
    • v-show只编译一次,后面其实就是控制css,而v-if不停的销毁和创建,故v-show性能更好一点。
  • v-if是动态的向DOM树内添加或者删除DOM元素
    • v-if切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件

2、Vue选项

2.1循环结构v-for

2.1.1遍历数组★★★★★

v-for = “(item,index) in 数组名”
Item 代表数组中的每一项 index 代表索引

<ul id="example-1">
  <li v-for="item in items" :key="item.message">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

效果图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yiVGesdQ-1605053467884)(./media/1594610851(1)].jpg)

2.1.2遍历对象★★★★

v-for = “(item,key,index) in 对象名”
Item 代表对象中的每一项
Key 代表对应的键名
Index 代表对应的 索引

2.1.3key的作用★★★★★

key来给每个节点做一个唯一标识
key的作用主要是为了高效的更新虚拟DOM

2.2案例讲解-选项卡

2.2.1HTML结构★★★
<div id="app">
  <div class="tab">
    <!--  tab栏  -->
    <ul>
      <li class="active">apple</li>
      <li class="">orange</li>
      <li class="">lemon</li>
    </ul>
    <!--  对应显示的图片 -->
    <div class="current"><img src="img/apple.png"></div>
    <div class=""><img src="img/orange.png"></div>
    <div class=""><img src="img/lemon.png"></div>
  </div>
</div>
2.2.2数据准备★★★★
list: [{
  id: 1,
  title: 'apple',
  path: 'img/apple.png'
}, {
  id: 2,
  title: 'orange',
  path: 'img/orange.png'
}, {
  id: 3,
  title: 'lemon',
  path: 'img/lemon.png'
}]
2.2.3v-for数据渲染★★★★

把tab栏 中的数替换到页面上

  • 把 data 中 title 利用 v-for 循环渲染到页面上
  • 把 data 中 path利用 v-for 循环渲染到页面上
<div id="app">
  <div class="tab">  
    <ul>
      <!--  
1、绑定key的作用 提高Vue的性能 
2、 key 需要是唯一的标识 所以需要使用id, 也可以使用index ,
index 也是唯一的 
3、 item 是 数组中对应的每一项  
4、 index 是 每一项的 索引
-->
      <li :key='item.id' v-for='(item,index) in list'>{{item.title}}</li>
    </ul>
    <div  :key='item.id' v-for='(item, index) in list'>
      <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
      <img :src="item.path">
    </div>
  </div>
</div>
<script>
  new  Vue({
    //  指定 操作元素 是 id 为app 的 
    el: '#app',
    data: {
      list: [{
        id: 1,
        title: 'apple',
        path: 'img/apple.png'
      }, {
        id: 2,
        title: 'orange',
        path: 'img/orange.png'
      }, {
        id: 3,
        title: 'lemon',
        path: 'img/lemon.png'
      }]
    }
  })
</script>
2.2.4添加点击事件★★★★
  • 4.1 、让默认的第一项tab栏高亮
  • tab栏高亮 通过添加类名active 来实现 (CSS active 的样式已经提前写好)
    • 在data 中定义一个 默认的 索引 currentIndex 为 0
    • 给第一个li 添加 active 的类名
      • 通过动态绑定class 来实现 第一个li 的索引为 0 和 currentIndex 的值刚好相等
      • currentIndex === index 如果相等 则添加类名 active 否则 添加 空类名
  • 4.2 、让默认的第一项tab栏对应的div 显示
    • 实现思路 和 第一个 tab 实现思路一样 只不过 这里控制第一个div 显示的类名是 curren
<ul>
  <!-- 动态绑定class   有 active   类名高亮  无 active   不高亮-->
  <li  :class='currentIndex==index?"active":""'
      :key='item.id' v-for='(item,index) in list'
      >{{item.title}}</li>
</ul>
<!-- 动态绑定class   有 current  类名显示  无 current  隐藏-->
<div :class='currentIndex==index?"current":""' :key='item.id' v-for='(item, index) in list'>
  <!-- :  是 v-bind 的简写   绑定属性使用 v-bind -->
  <img :src="item.path">
</div>
<script>
  new  Vue({
  el: '#app',
  data: {
    currentIndex: 0, // 选项卡当前的索引  默认为 0  
    list: [{
      id: 1,
      title: 'apple',
      path: 'img/apple.png'
    }, {
      id: 2,
      title: 'orange',
      path: 'img/orange.png'
    }, {
      id: 3,
      title: 'lemon',
      path: 'img/lemon.png'
    }]
  }
})
</script>

2.3表单

2.3.1表单基本操作★★★★

单选框如何实现单选?
1、 两个单选框需要同时通过v-model 双向绑定 一个值
2、 每一个单选框必须要有value属性 且value 值不能一样
3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据

实现代码

<div id="example-4">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>

new Vue({
  el: '#example-4',
  data: {
    picked: ''
  }
})

运行效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kwGQ3WhW-1605053467891)(./media/1594611053(1)].jpg)

复选框如何实现复选
1、 复选框需要同时通过v-model 双向绑定 一个值
2、 每一个复选框必须要有value属性 且value 值不能一样
3、 当某一个单选框选中的时候 v-model 会将当前的 value值 改变 data 中的 数据

<input type="checkbox" id="jack" value="Jack" v-model="checkedNames">
<label for="jack">Jack</label>
<input type="checkbox" id="john" value="John" v-model="checkedNames">
<label for="john">John</label>
<input type="checkbox" id="mike" value="Mike" v-model="checkedNames">
<label for="mike">Mike</label>
<br>
<span>Checked names: {{ checkedNames }}</span>

new Vue({
  el: '...',
  data: {
    checkedNames: []
  }
})

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sCPgHCqh-1605053467894)(./media/1594611136(1)].jpg)

2.3.2表单修饰符★★★★

表单域修饰符有哪些
.trim 自动过滤用户输入的首尾空白字符 - 只能去掉首尾的 不能去除中间的空格
lazy 将input事件切换成change事件 - .lazy 修饰符延迟了同步更新属性值的时机。即将原本绑定在 input 事件的同步逻辑转变为绑定在 change 事件上
.number 转换为数值
注意点: 当开始输入非数字的字符串时,因为Vue无法将字符串转换成数值

2.4自定义指令

2.4.1自定义指令有什么用★★★★★
  • 内置指令不能满足我们特殊的需求
  • Vue允许我们自定义指令
2.4.2Vue.directive注册全局自定义指令★★★★★
<!-- 
  使用自定义的指令,只需在对用的元素中,加上'v-'的前缀形成类似于内部指令'v-if','v-text'的形式。 
-->
<input type="text" v-focus>

<script>
// 注意点: 
//   1、 在自定义指令中  如果以驼峰命名的方式定义 如  Vue.directive('focusA',function(){}) 
//   2、 在HTML中使用的时候 只能通过 v-focus-a 来使用 

// 注册一个全局自定义指令 v-focus
Vue.directive('focus', {
    // 当绑定元素插入到 DOM 中。 其中 el为dom元素
    inserted: function (el) {
        // 聚焦元素
        el.focus();
  }
});
new Vue({
  el:'#app'
});
</script>
2.4.3Vue.directive注册全局自定义指令带参数★★★★
 <input type="text" v-color='msg'>
 <script type="text/javascript">
    /*
      自定义指令-带参数
      bind - 只调用一次,在指令第一次绑定到元素上时候调用

    */
    Vue.directive('color', {
      // bind声明周期, 只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置
      // el 为当前自定义指令的DOM元素  
      // binding 为自定义的函数形参   通过自定义属性传递过来的值 存在 binding.value 里面
      bind: function(el, binding){
        // 根据指令的参数设置背景色
        // console.log(binding.value.color)
        el.style.backgroundColor = binding.value.color;
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'blue'
        }
      }
    });
  </script>
2.4.4注册局部自定义指令★★★★★
  • 局部指令,需要定义在 directives 的选项 用法和全局用法一样
  • 局部指令只能在当前组件里面使用
  • 当全局指令和局部指令同名时以局部指令为准
<input type="text" v-color='msg'>
 <input type="text" v-focus>
 <script type="text/javascript">
    /*
      自定义指令-局部指令
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: {
          color: 'red'
        }
      },
      //局部指令,需要定义在  directives 的选项
      directives: {
        color: {
          bind: function(el, binding){
            el.style.backgroundColor = binding.value.color;
          }
        },
        focus: {
          inserted: function(el) {
            el.focus();
          }
        }
      }
    });
  </script>

2.5计算属性

2.5.1计算属性有什么用★★★★
  • 模板中放入太多的逻辑会让模板过重且难以维护 使用计算属性可以让模板更加的简洁
  • 计算属性是基于它们的响应式依赖进行缓存的
  • computed比较适合对多个变量或者对象进行处理后返回一个结果值,也就是数多个变量中的某一个值发生了变化则我们监控的这个值也就会发生变化
2.5.2基本用法★★★★
<div id="app">
     <!--  
        当多次调用 reverseString  的时候 
        只要里面的 num 值不改变 他会把第一次计算的结果直接返回
    直到data 中的num值改变 计算属性才会重新发生计算
     -->
    <div>{{reverseString}}</div>
    <div>{{reverseString}}</div>
     <!-- 调用methods中的方法的时候  他每次会重新调用 -->
    <div>{{reverseMessage()}}</div>
    <div>{{reverseMessage()}}</div>
  </div>
  <script type="text/javascript">
    /*
      计算属性与方法的区别:计算属性是基于依赖进行缓存的,而方法不缓存
    */
    var vm = new Vue({
      el: '#app',
      data: {
        msg: 'Nihao',
        num: 100
      },
      methods: {
        reverseMessage: function(){
          console.log('methods')
          return this.msg.split('').reverse().join('');
        }
      },
      //computed  属性 定义 和 data 已经 methods 平级 
      computed: {
        //  reverseString   这个是我们自己定义的名字 
        reverseString: function(){
          console.log('computed')
          var total = 0;
          //  当data 中的 num 的值改变的时候  reverseString  会自动发生计算  
          for(var i=0;i<=this.num;i++){
            total += i;
          }
          // 这里一定要有return 否则 调用 reverseString 的 时候无法拿到结果    
          return total;
        }
      }
    });
  </script>
2.5.3和方法有什么区别★★★★
  • 两者的执行结果是完全相同的
  • 计算属性是基于他们的依赖进行缓存的,只有在相关依赖发生改变时,他们才会重新求值,也就是说,只要他的依赖没有发生变化,那么每次访问的时候计算属性都会立即返回之前的计算结果,不再执行函数
  • 每次触发重新渲染时,调用方法将总会再次执行函数

2.6侦听器watch

2.6.1基本用法★★★★
  • 使用watch来响应数据的变化
  • 一般用于异步或者开销较大的操作
  • watch 中的属性 一定是data 中 已经存在的数据
  • 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够监听到变化,此时就需要deep属性对对象进行深度监听
2.6.2案例讲解-验证用户名★★★★
<div id="app">
  <div>
    <span>名:</span>
    <span>
      <input type="text" v-model='firstName'>
    </span>
  </div>
  <div>
    <span>姓:</span>
    <span>
      <input type="text" v-model='lastName'>
    </span>
  </div>
  <div>{{fullName}}</div>
</div>

<script type="text/javascript">
  /*侦听器*/
  var vm = new Vue({
    el: '#app',
    data: {
      firstName: 'Jim',
      lastName: 'Green',
      // fullName: 'Jim Green'
    },
    //watch  属性 定义 和 data 已经 methods 平级 
    watch: {
      //   注意:  这里firstName  对应着data 中的 firstName 
      //   当 firstName 值 改变的时候  会自动触发 watch
      firstName: function(val) {
        this.fullName = val + ' ' + this.lastName;
      },
      //   注意:  这里 lastName 对应着data 中的 lastName 
      lastName: function(val) {
        this.fullName = this.firstName + ' ' + val;
      }
    }
  });
</script>

2.7过滤器

2.7.1过滤器有什么用★★★★
  • Vue.js允许自定义过滤器,可被用于一些常见的文本格式化。
  • 过滤器可以用在两个地方:双花括号插值和v-bind表达式。
  • 过滤器应该被添加在JavaScript表达式的尾部,由“管道”符号指示
2.7.2全局过滤器★★★★

全局过滤器注册号之后所有的组件可以使用

全局注册时是filter,没有s的。而局部过滤器是filters,是有s的

<div id="app">
    <input type="text" v-model='msg'>
      <!-- upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中 -->
    <div>{{msg | upper}}</div>
    <!--  
      支持级联操作
      upper  被定义为接收单个参数的过滤器函数,表达式msg 的值将作为参数传入到函数中。
	  然后继续调用同样被定义为接收单个参数的过滤器 lower ,将upper 的结果传递到lower中
 	-->
    <div>{{msg | upper | lower}}</div>
    <div :abc='msg | upper'>测试数据</div>
  </div>

<script type="text/javascript">
  //  lower  为全局过滤器     
  Vue.filter('lower', function(val) {
    return val.charAt(0).toLowerCase() + val.slice(1);
  });
</script>
2.7.3局部过滤器★★★★

局部过滤器注册好之后只能在注册的组件内使用

var vm = new Vue({
  el: '#app',
  data: {
    msg: ''
  },
  //filters  属性 定义 和 data 已经 methods 平级 
  //  定义filters 中的过滤器为局部过滤器 
  filters: {
    //   upper  自定义的过滤器名字 
    //    upper 被定义为接收单个参数的过滤器函数,表达式  msg  的值将作为参数传入到函数中
    upper: function(val) {
      //  过滤器中一定要有返回值 这样外界使用过滤器的时候才能拿到结果
      return val.charAt(0).toUpperCase() + val.slice(1);
    }
  }
});
2.7.4过滤器串联★★★★

并且过滤器可以串联使用, 在此处是将filter1的值作为filter2的参数。

{{  data|filter1|filter2  }}
2.7.5过滤器传参★★★★
<div id="box">
  <!--
filterA 被定义为接收三个参数的过滤器函数。
其中 message 的值作为第一个参数,
普通字符串 'arg1' 作为第二个参数,表达式 arg2 的值作为第三个参数。
-->
  {{ message | filterA('arg1', 'arg2') }}
</div>
<script>
  // 在过滤器中 第一个参数 对应的是  管道符前面的数据   n  此时对应 message
  // 第2个参数  a 对应 实参  arg1 字符串
  // 第3个参数  b 对应 实参  arg2 字符串
  Vue.filter('filterA',function(n,a,b){
  if(n<10){
    return n+a;
  }else{
    return n+b;
  }
});

new Vue({
  el:"#box",
  data:{
    message: "哈哈哈"
  }
})

</script>

3、Vue基础进阶

3.1 生命周期

3.1.1 生命周期有什么用★★★★★
  • 生命周期概念:事物从出生到死亡的过程
  • Vue的生命周期:Vue实例从创建到销毁的过程 ,这些过程中会伴随着一些函数的自调用。我们称这些函数为钩子函数
  • 生命周期作用:生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑
3.1.2 常用的钩子函数★★★★★
beforeCreate在实例初始化之后,数据观测和事件配置之前被调用 此时data 和 methods 以及页面的DOM结构都没有初始化 什么都做不了
created在实例创建完成后被立即调用此时data 和 methods已经可以使用 但是页面还没有渲染出来
beforeMount在挂载开始之前被调用 此时页面上还看不到真实数据 只是一个模板页面而已
mountedel被新创建的vm.$el替换,并挂载到实例上去之后调用该钩子。 数据已经真实渲染到页面上 在这个钩子函数里面我们可以使用一些第三方的插件
beforeUpdate数据更新时调用,发生在虚拟DOM打补丁之前。 页面上数据还是旧的
updated由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。 页面上数据已经替换成最新的
beforeDestroy实例销毁之前调用
destroyed实例销毁后调用

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{msg}}</div>
    <button @click='update'>更新</button>
    <button @click='destroy'>销毁</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // Vue实例的生命周期
    var vm = new Vue({
      el: '#app',
      data: {
        msg: '生命周期'
      },
      methods: {
        update: function(){
          this.msg = 'hello';
        },
        destroy: function(){
          this.$destroy();
        }
      },
      beforeCreate: function(){
        console.log('beforeCreate');
      },
      created: function(){
        console.log('created');
      },
      beforeMount: function(){
        console.log('beforeMount');
      },
      mounted: function(){
        console.log('mounted');
      },
      beforeUpdate: function(){
        console.log('beforeUpdate');
      },
      updated: function(){
        console.log('updated');
      },
      beforeDestroy: function(){
        console.log('beforeDestroy');
      },
      destroyed: function(){
        console.log('destroyed');
      }
    });
  </script>
</body>
</html>

3.2 数组变异方法

  • 在 Vue 中,直接修改对象属性的值无法触发响应式。当你直接修改了对象属性的值,你会发现,只有数据改了,但是页面内容并没有改变
  • 变异数组方法即保持数组方法原有功能不变的前提下对其进行功能拓展
push()往数组最后面添加一个元素,成功返回当前数组的长度
pop()删除数组的最后一个元素,成功返回删除元素的值
shift()删除数组的第一个元素,成功返回删除元素的值
unshift()往数组最前面添加一个元素,成功返回当前数组的长度
splice()有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值
sort()sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组
reverse()reverse() 将数组倒序,成功返回倒序后的数组
3.2.1 push★★★

往数组最后面添加一个元素,成功返回当前数组的长度

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='add'>添加</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        add: function(){
          this.list.push(this.fname);
        }
      }
    });
  </script>
</body>
</html>
3.2.2 pop★★★

删除数组的最后一个元素,成功返回删除元素的值

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='add'>添加</button>
        <button @click='del'>后面删除</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        add: function(){
          this.list.push(this.fname);
        },
        del: function(){
          this.list.pop();
        }
      }
    });
  </script>
</body>
</html>
3.2.3 shift★★★

删除数组的第一个元素,成功返回删除元素的值

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='del'>前面删除</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        del: function(){
          this.list.shift();
        }
      }
    });
  </script>
</body>
</html>
3.2.4 splice★★★

有三个参数,第一个是想要删除的元素的下标(必选),第二个是想要删除的个数(必选),第三个是删除 后想要在原位置替换的值

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app">
        <ul>
            <li v-for="item, index in list">
                <span>{{ item }}</span>
                <span @click="del(index)">删除</span>
            </li>
        </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script>
        /*
          Vue数组操作
          1、变异方法:会影响数组的原始数据的变化。
        */
        const vm = new Vue({
            el: "#app",
            data: {
                list: ["aaa", "bbb", "ccc", "ddd"]
            },
            methods: {
                del(idx) {
                    this.list.splice(idx, 1);
                }
            }
        })
    </script>
</body>
</html>
3.2.5 sort★★★

sort() 使数组按照字符编码默认从小到大排序,成功返回排序后的数组

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='_sort'>排序</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        _sort: function(){
          this.list.sort();
        }
      }
    });
  </script>
</body>
</html>
3.2.6 reverse★★★

reverse() 将数组倒序,成功返回倒序后的数组

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='reverse'>颠倒数组</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        _reverse: function(){
          this.list.reverse();
        }
      }
    });
  </script>
</body>
</html>

3.3 替换数组

  • 不会改变原始数组,但总是返回一个新数组
filterfilter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
concatconcat() 方法用于连接两个或多个数组。该方法不会改变现有的数组
sliceslice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组
3.3.1 filter★★★

filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <button @click='_filter'>保留非数字</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        list: ['apple',2,3,'orange',8,'banana',10]
      },
      methods: {
        _filter: function(){
          this.list = this.list.filter(item => typeof item === 'string')
        }
      }
    });
  </script>
</body>
</html>
3.3.2 concat★★★

concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <button @click='_concat'>连接</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in concatData'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        list: ['apple','orange','banana'],
        list2: ['1', '4', 20],
        concatData: []  
      },
      methods: {
        _concat: function(){
          this.concatData = this.list.concat(this.list2)
        }
      }
    });
  </script>
</body>
</html>
3.3.3 slice★★★

slice() 方法可从已有的数组中返回选定的元素。该方法并不会修改数组,而是返回一个子数组

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>
      <span>
        <input type="text" v-model='fname'>
        <button @click='add'>添加</button>
        <button @click='del'>删除</button>
        <button @click='change'>替换</button>
      </span>
    </div>
    <ul>
      <li :key='index' v-for='(item,index) in list'>{{item}}</li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      Vue数组操作
      1、变异方法:会影响数组的原始数据的变化。
      2、替换数组:不会影响原始的数组数据,而是形成一个新的数组。
    */
    var vm = new Vue({
      el: '#app',
      data: {
        fname: '',
        list: ['apple','orange','banana']
      },
      methods: {
        add: function(){
          this.list.push(this.fname);
        },
        del: function(){
          this.list.pop();
        },
        change: function(){
          this.list = this.list.slice(0,2);
        }
      }
    });
  </script>
</body>
</html>

3.4 动态数组响应式数据

3.4.1 Vue.set()★★★★
  • Vue.set(a,b,c) 让 触发视图重新更新一遍,数据动态起来
  • a是要更改的数据 、 b是数据的第几项、 c是更改后的数据

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <ul>
      <li v-for='item in list'>{{item}}</li>
    </ul>
    <div>
      <div>{{info.name}}</div>
      <div>{{info.age}}</div>
      <div>{{info.gender}}</div>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 动态处理响应式数据
    var vm = new Vue({
      el: '#app',
      data: {
        list: ['apple', 'orange', 'banana'],
        info: {
          name: 'lisi',
          age: 12
        }
      },
    });
    // vm.list[1] = 'lemon';
    // Vue.set(vm.list, 2, 'lemon');
    vm.$set(vm.list, 1, 'lemon');
    // vm.info.gender = 'male';
    vm.$set(vm.info, 'gender', 'female');
  </script>
</body>
</html>

3.5 案例讲解-图书管理

案例效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Oxm9z0FH-1605053467899)(./media/1.gif)]

3.5.1 列表展示v-for★★★★

案例效果展示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-a1x2aiIU-1605053467902)(./media/bookmanage.png)]

思路:

  1. 提供静态数据

    • 数据存放在vue 的data 属性中
  2. 把提供好的数据渲染到页面上

    • 利用 v-for循环 遍历 books 将每一项数据渲染到对应的数据中

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .grid {
      margin: auto;
      width: 500px;
      text-align: center;
    }
    .grid table {
      width: 100%;
      border-collapse: collapse;
    }
    .grid th,td {
      padding: 10;
      border: 1px dashed orange;
      height: 35px;
      line-height: 35px;
    }
    .grid th {
      background-color: orange;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="grid">
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    /*
      图书管理-图书列表展示功能
      注意事项:<a href="" @click.prevent>修改</a>
      事件绑定时,可以只添加修饰符,而不绑定事件函数
    */
    var vm = new Vue({
      el: '#app',
      data: {
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      }
    });
  </script>
</body>
</html>
3.5.2 添加图书push unshift★★★★

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gEsgENUK-1605053467907)(./media/2.gif)]

思路:

  1. 通过双向绑定获取到输入框中的输入内容

  2. 给按钮添加点击事件

  3. 把输入框中的数据存储到 data 中的 books 里面

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .grid {
      margin: auto;
      width: 530px;
      text-align: center;
    }
    .grid table {
      border-top: 1px solid #C2D89A;
      width: 100%;
      border-collapse: collapse;
    }
    .grid th,td {
      padding: 10;
      border: 1px dashed #F3DCAB;
      height: 35px;
      line-height: 35px;
    }
    .grid th {
      background-color: #F3DCAB;
    }
    .grid .book {
      padding-bottom: 10px;
      padding-top: 5px;
      background-color: #F3DCAB;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
            <input type="text" id="id" v-model='id'>
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 图书管理-添加图书
    var vm = new Vue({
      el: '#app',
      data: {
        id: '',
        name: '',
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      },
      methods: {
        handle: function(){
          // 添加图书
          var book = {};
          book.id = this.id;
          book.name = this.name;
          book.date = '';
          this.books.push(book);
          // 清空表单
          this.id = '';
          this.name = '';
        }
      }
    });
  </script>
</body>
</html>
3.5.3 编辑图书v-show★★★★

案例效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qhOYsPzR-1605053467909)(./media/3.gif)]

思路:

  1. 点击修改按钮的时候 获取到要修改的书籍名单

    • 给修改按钮添加点击事件, 需要把当前的图书的id 传递过去 这样才知道需要修改的是哪一本书籍
  2. 把需要修改的书籍名单填充到表单里面

    • 根据传递过来的id 查出books 中 对应书籍的详细信息

    • 把获取到的信息填充到表单

  3. 定义一个标识符, 主要是控制 编辑状态下当前编辑书籍的id 不能被修改 即 处于编辑状态下 当前控制书籍编号的输入框禁用

  4. 通过属性绑定给书籍编号的 绑定 disabled 的属性 flag 为 true 即为禁用

  5. flag 默认值为false 处于编辑状态 要把 flag 改为true 即当前表单为禁用

  6. 复用添加方法 用户点击提交的时候依然执行 handle 中的逻辑如果 flag为true 即 表单处于不可输入状态 此时执行的用户编辑数据数据

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .grid {
      margin: auto;
      width: 530px;
      text-align: center;
    }
    .grid table {
      border-top: 1px solid #C2D89A;
      width: 100%;
      border-collapse: collapse;
    }
    .grid th,td {
      padding: 10;
      border: 1px dashed #F3DCAB;
      height: 35px;
      line-height: 35px;
    }
    .grid th {
      background-color: #F3DCAB;
    }
    .grid .book {
      padding-bottom: 10px;
      padding-top: 5px;
      background-color: #F3DCAB;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
            <input type="text" id="id" v-model='id' :disabled="flag">
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 图书管理-编辑图书
    var vm = new Vue({
      el: '#app',
      data: {
        flag: false,
        id: '',
        name: '',
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      },
      methods: {
        handle: function(){
          if(this.flag) {
            // 编辑图书
            // 就是根据当前的ID去更新数组中对应的数据
            this.books.some((item) => {
              if(item.id == this.id) {
                item.name = this.name;
                // 完成更新操作之后,需要终止循环
                return true;
              }
            });
            this.flag = false;
          }else{
            // 添加图书
            var book = {};
            book.id = this.id;
            book.name = this.name;
            book.date = '';
            this.books.push(book);
            // 清空表单
            this.id = '';
            this.name = '';
          }
          // 清空表单
          this.id = '';
          this.name = '';
        },
        toEdit: function(id){
          // 禁止修改ID
          this.flag = true;
          console.log(id)
          // 根据ID查询出要编辑的数据
          var book = this.books.filter(function(item){
            return item.id == id;
          });
          console.log(book)
          // 把获取到的信息填充到表单
          this.id = book[0].id;
          this.name = book[0].name;
        }
      }
    });
  </script>
</body>
</html>
3.5.4 删除图书splice★★★★

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6rWqeri-1605053467912)(./media/4.gif)]

思路:

  1. 给删除按钮添加事件把当前需要删除的书籍id传递过来
  2. 根据id从数组中查找元素的索引
  3. 根据索引删除数组元素

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <style type="text/css">
    .grid {
      margin: auto;
      width: 530px;
      text-align: center;
    }
    .grid table {
      border-top: 1px solid #C2D89A;
      width: 100%;
      border-collapse: collapse;
    }
    .grid th,td {
      padding: 10;
      border: 1px dashed #F3DCAB;
      height: 35px;
      line-height: 35px;
    }
    .grid th {
      background-color: #F3DCAB;
    }
    .grid .book {
      padding-bottom: 10px;
      padding-top: 5px;
      background-color: #F3DCAB;
    }
  </style>
</head>
<body>
  <div id="app">
    <div class="grid">
      <div>
        <h1>图书管理</h1>
        <div class="book">
          <div>
            <label for="id">
              编号:
            </label>
            <input type="text" id="id" v-model='id' :disabled="flag">
            <label for="name">
              名称:
            </label>
            <input type="text" id="name" v-model='name'>
            <button @click='handle'>提交</button>
          </div>
        </div>
      </div>
      <table>
        <thead>
          <tr>
            <th>编号</th>
            <th>名称</th>
            <th>时间</th>
            <th>操作</th>
          </tr>
        </thead>
        <tbody>
          <tr :key='item.id' v-for='item in books'>
            <td>{{item.id}}</td>
            <td>{{item.name}}</td>
            <td>{{item.date}}</td>
            <td>
              <a href="" @click.prevent='toEdit(item.id)'>修改</a>
              <span>|</span>
              <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
            </td>
          </tr>
        </tbody>
      </table>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 图书管理-删除图书
    var vm = new Vue({
      el: '#app',
      data: {
        flag: false,
        id: '',
        name: '',
        books: [{
          id: 1,
          name: '三国演义',
          date: ''
        },{
          id: 2,
          name: '水浒传',
          date: ''
        },{
          id: 3,
          name: '红楼梦',
          date: ''
        },{
          id: 4,
          name: '西游记',
          date: ''
        }]
      },
      methods: {
        handle: function(){
          if(this.flag) {
            // 编辑图书
            // 就是根据当前的ID去更新数组中对应的数据
            this.books.some((item) => {
              if(item.id == this.id) {
                item.name = this.name;
                // 完成更新操作之后,需要终止循环
                return true;
              }
            });
            this.flag = false;
          }else{
            // 添加图书
            var book = {};
            book.id = this.id;
            book.name = this.name;
            book.date = '';
            this.books.push(book);
            // 清空表单
            this.id = '';
            this.name = '';
          }
          // 清空表单
          this.id = '';
          this.name = '';
        },
        toEdit: function(id){
          // 禁止修改ID
          this.flag = true;
          console.log(id)
          // 根据ID查询出要编辑的数据
          var book = this.books.filter(function(item){
            return item.id == id;
          });
          console.log(book)
          // 把获取到的信息填充到表单
          this.id = book[0].id;
          this.name = book[0].name;
        },
        deleteBook: function(id){
          // 删除图书
          // 根据id从数组中查找元素的索引
          // var index = this.books.findIndex(function(item){
          //   return item.id == id;
          // });
          // 根据索引删除数组元素
          // this.books.splice(index, 1);
          // -------------------------
          // 方法二:通过filter方法进行删除
          this.books = this.books.filter(function(item){
            return item.id != id;
          });
        }
      }
    });
  </script>
</body>
</html>
3.5.5 常用特性应用场景★★★★
  1. 过滤器

    • Vue.filter 定义一个全局过滤器
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style type="text/css">
        .grid {
          margin: auto;
          width: 530px;
          text-align: center;
        }
        .grid table {
          border-top: 1px solid #C2D89A;
          width: 100%;
          border-collapse: collapse;
        }
        .grid th,td {
          padding: 10;
          border: 1px dashed #F3DCAB;
          height: 35px;
          line-height: 35px;
        }
        .grid th {
          background-color: #F3DCAB;
        }
        .grid .book {
          padding-bottom: 10px;
          padding-top: 5px;
          background-color: #F3DCAB;
        }
        .grid .total {
          height: 30px;
          line-height: 30px;
          background-color: #F3DCAB;
          border-top: 1px solid #C2D89A;
        }
      </style>
    </head>
    <body>
      <div id="app">
        <div class="grid">
          <div>
            <h1>图书管理</h1>
            <div class="book">
              <div>
                <label for="id">
                  编号:
                </label>
                <input type="text" id="id" v-model='id' :disabled="flag">
                <label for="name">
                  名称:
                </label>
                <input type="text" id="name" v-model='name'>
                <button @click='handle'>提交</button>
              </div>
            </div>
          </div>
          <table>
            <thead>
              <tr>
                <th>编号</th>
                <th>名称</th>
                <th>时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr :key='item.id' v-for='item in books'>
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <!-- 3. 调用过滤器 -->  
                <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
                <td>
                  <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                  <span>|</span>
                  <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
      <script type="text/javascript">
        // 1. Vue.filter  定义一个全局过滤器  
        Vue.filter('format', function(value, arg) {
          function dateFormat(date, format) {
            if (typeof date === "string") {
              var mts = date.match(/(\/Date\((\d+)\)\/)/);
              if (mts && mts.length >= 3) {
                date = parseInt(mts[2]);
              }
            }
            date = new Date(date);
            if (!date || date.toUTCString() == "Invalid Date") {
              return "";
            }
            var map = {
              "M": date.getMonth() + 1, //月份 
              "d": date.getDate(), //日 
              "h": date.getHours(), //小时 
              "m": date.getMinutes(), //分 
              "s": date.getSeconds(), //秒 
              "q": Math.floor((date.getMonth() + 3) / 3), //季度 
              "S": date.getMilliseconds() //毫秒 
            };
            format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
              var v = map[t];
              if (v !== undefined) {
                if (all.length > 1) {
                  v = '0' + v;
                  v = v.substr(v.length - 2);
                }
                return v;
              } else if (t === 'y') {
                return (date.getFullYear() + '').substr(4 - all.length);
              }
              return all;
            });
            return format;
          }
          return dateFormat(value, arg);
        })
        var vm = new Vue({
          el: '#app',
          data: {
            flag: false,
            id: '',
            name: '',
            books: []
          },
          methods: {
            handle: function(){
              if(this.flag) {
                // 编辑图书
                // 就是根据当前的ID去更新数组中对应的数据
                this.books.some((item) => {
                  if(item.id == this.id) {
                    item.name = this.name;
                    // 完成更新操作之后,需要终止循环
                    return true;
                  }
                });
                this.flag = false;
              }else{
                // 添加图书
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = 2525609975000;
                this.books.push(book);
                // 清空表单
                this.id = '';
                this.name = '';
              }
              // 清空表单
              this.id = '';
              this.name = '';
            },
            toEdit: function(id){
              // 禁止修改ID
              this.flag = true;
              console.log(id)
              // 根据ID查询出要编辑的数据
              var book = this.books.filter(function(item){
                return item.id == id;
              });
              console.log(book)
              // 把获取到的信息填充到表单
              this.id = book[0].id;
              this.name = book[0].name;
            },
            deleteBook: function(id){
              // 删除图书
              // 根据id从数组中查找元素的索引
              // var index = this.books.findIndex(function(item){
              //   return item.id == id;
              // });
              // 根据索引删除数组元素
              // this.books.splice(index, 1);
              // -------------------------
              // 方法二:通过filter方法进行删除
              this.books = this.books.filter(function(item){
                return item.id != id;
              });
            }
          },
          mounted: function(){
            // 该生命周期钩子函数被触发的时候,模板已经可以使用
            // 一般此时用于获取后台数据,然后把数据填充到模板
            // 2. 提供的数据 包含一个时间戳: 为毫秒数  
            var data = [{
              id: 1,
              name: '三国演义',
              date: 2525609975000
            },{
              id: 2,
              name: '水浒传',
              date: 2525609975000
            },{
              id: 3,
              name: '红楼梦',
              date: 2525609975000
            },{
              id: 4,
              name: '西游记',
              date: 2525609975000
            }];
            this.books = data;
          }
        });
      </script>
    </body>
    </html>
    
  2. 自定义指令

    • 让表单自动获取焦点
    • 通过Vue.directive 自定义指定
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style type="text/css">
        .grid {
          margin: auto;
          width: 530px;
          text-align: center;
        }
        .grid table {
          border-top: 1px solid #C2D89A;
          width: 100%;
          border-collapse: collapse;
        }
        .grid th,td {
          padding: 10;
          border: 1px dashed #F3DCAB;
          height: 35px;
          line-height: 35px;
        }
        .grid th {
          background-color: #F3DCAB;
        }
        .grid .book {
          padding-bottom: 10px;
          padding-top: 5px;
          background-color: #F3DCAB;
        }
        .grid .total {
          height: 30px;
          line-height: 30px;
          background-color: #F3DCAB;
          border-top: 1px solid #C2D89A;
        }
      </style>
    </head>
    <body>
      <div id="app">
        <div class="grid">
          <div>
            <h1>图书管理</h1>
            <div class="book">
              <div>
                <label for="id">
                  编号:
                </label>
                <!-- 2. 通过v-自定义属性名 调用自定义指令 -->  
                <input type="text" id="id" v-model='id' :disabled="flag" v-focus>
                <label for="name">
                  名称:
                </label>
                <input type="text" id="name" v-model='name'>
                <button @click='handle' :disabled="submitFlag">提交</button>
              </div>
            </div>
          </div>
          <table>
            <thead>
              <tr>
                <th>编号</th>
                <th>名称</th>
                <th>时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr :key='item.id' v-for='item in books'>
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
                <td>
                  <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                  <span>|</span>
                  <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
      <script type="text/javascript">
        // 1. 通过Vue.directive 自定义指定
        Vue.directive('focus', {
          inserted: function (el) {
            el.focus();
          }
        });
        Vue.filter('format', function(value, arg) {
          function dateFormat(date, format) {
            if (typeof date === "string") {
              var mts = date.match(/(\/Date\((\d+)\)\/)/);
              if (mts && mts.length >= 3) {
                date = parseInt(mts[2]);
              }
            }
            date = new Date(date);
            if (!date || date.toUTCString() == "Invalid Date") {
              return "";
            }
            var map = {
              "M": date.getMonth() + 1, //月份 
              "d": date.getDate(), //日 
              "h": date.getHours(), //小时 
              "m": date.getMinutes(), //分 
              "s": date.getSeconds(), //秒 
              "q": Math.floor((date.getMonth() + 3) / 3), //季度 
              "S": date.getMilliseconds() //毫秒 
            };
            format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
              var v = map[t];
              if (v !== undefined) {
                if (all.length > 1) {
                  v = '0' + v;
                  v = v.substr(v.length - 2);
                }
                return v;
              } else if (t === 'y') {
                return (date.getFullYear() + '').substr(4 - all.length);
              }
              return all;
            });
            return format;
          }
          return dateFormat(value, arg);
        })
        var vm = new Vue({
          el: '#app',
          data: {
            flag: false,
            id: '',
            name: '',
            books: []
          },
          methods: {
            handle: function(){
              if(this.flag) {
                // 编辑图书
                // 就是根据当前的ID去更新数组中对应的数据
                this.books.some((item) => {
                  if(item.id == this.id) {
                    item.name = this.name;
                    // 完成更新操作之后,需要终止循环
                    return true;
                  }
                });
                this.flag = false;
              }else{
                // 添加图书
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = 2525609975000;
                this.books.push(book);
                // 清空表单
                this.id = '';
                this.name = '';
              }
              // 清空表单
              this.id = '';
              this.name = '';
            },
            toEdit: function(id){
              // 禁止修改ID
              this.flag = true;
              console.log(id)
              // 根据ID查询出要编辑的数据
              var book = this.books.filter(function(item){
                return item.id == id;
              });
              console.log(book)
              // 把获取到的信息填充到表单
              this.id = book[0].id;
              this.name = book[0].name;
            },
            deleteBook: function(id){
              // 删除图书
              // 根据id从数组中查找元素的索引
              // var index = this.books.findIndex(function(item){
              //   return item.id == id;
              // });
              // 根据索引删除数组元素
              // this.books.splice(index, 1);
              // -------------------------
              // 方法二:通过filter方法进行删除
              this.books = this.books.filter(function(item){
                return item.id != id;
              });
            }
          },
          computed: {
            total: function(){
              // 计算图书的总数
              return this.books.length;
            }
          },
          mounted: function(){
            // 该生命周期钩子函数被触发的时候,模板已经可以使用
            // 一般此时用于获取后台数据,然后把数据填充到模板
            var data = [{
              id: 1,
              name: '三国演义',
              date: 2525609975000
            },{
              id: 2,
              name: '水浒传',
              date: 2525609975000
            },{
              id: 3,
              name: '红楼梦',
              date: 2525609975000
            },{
              id: 4,
              name: '西游记',
              date: 2525609975000
            }];
            this.books = data;
          }
        });
      </script>
    </body>
    </html>
    
  3. 通过计算属性计算图书的总数

    • 图书的总数就是计算数组的长度

    案例效果图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-feBqxekI-1605053467916)(./media/5.gif)]

    代码演示:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style type="text/css">
        .grid {
          margin: auto;
          width: 530px;
          text-align: center;
        }
        .grid table {
          border-top: 1px solid #C2D89A;
          width: 100%;
          border-collapse: collapse;
        }
        .grid th,td {
          padding: 10;
          border: 1px dashed #F3DCAB;
          height: 35px;
          line-height: 35px;
        }
        .grid th {
          background-color: #F3DCAB;
        }
        .grid .book {
          padding-bottom: 10px;
          padding-top: 5px;
          background-color: #F3DCAB;
        }
        .grid .total {
          height: 30px;
          line-height: 30px;
          background-color: #F3DCAB;
          border-top: 1px solid #C2D89A;
        }
      </style>
    </head>
    <body>
      <div id="app">
        <div class="grid">
          <div>
            <h1>图书管理</h1>
            <div class="book">
              <div>
                <label for="id">
                  编号:
                </label>
                <input type="text" id="id" v-model='id' :disabled="flag" v-focus>
                <label for="name">
                  名称:
                </label>
                <input type="text" id="name" v-model='name'>
                <button @click='handle'>提交</button>
              </div>
            </div>
          </div>
          <div class="total">
            <span>图书总数:</span>
            <span>{{total}}</span>
          </div>
          <table>
            <thead>
              <tr>
                <th>编号</th>
                <th>名称</th>
                <th>时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr :key='item.id' v-for='item in books'>
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
                <td>
                  <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                  <span>|</span>
                  <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
      <script type="text/javascript">
        Vue.directive('focus', {
          inserted: function (el) {
            el.focus();
          }
        });
        Vue.filter('format', function(value, arg) {
          function dateFormat(date, format) {
            if (typeof date === "string") {
              var mts = date.match(/(\/Date\((\d+)\)\/)/);
              if (mts && mts.length >= 3) {
                date = parseInt(mts[2]);
              }
            }
            date = new Date(date);
            if (!date || date.toUTCString() == "Invalid Date") {
              return "";
            }
            var map = {
              "M": date.getMonth() + 1, //月份 
              "d": date.getDate(), //日 
              "h": date.getHours(), //小时 
              "m": date.getMinutes(), //分 
              "s": date.getSeconds(), //秒 
              "q": Math.floor((date.getMonth() + 3) / 3), //季度 
              "S": date.getMilliseconds() //毫秒 
            };
            format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
              var v = map[t];
              if (v !== undefined) {
                if (all.length > 1) {
                  v = '0' + v;
                  v = v.substr(v.length - 2);
                }
                return v;
              } else if (t === 'y') {
                return (date.getFullYear() + '').substr(4 - all.length);
              }
              return all;
            });
            return format;
          }
          return dateFormat(value, arg);
        })
        var vm = new Vue({
          el: '#app',
          data: {
            flag: false,
            submitFlag: false,
            id: '',
            name: '',
            books: []
          },
          methods: {
            handle: function(){
              if(this.flag) {
                // 编辑图书
                // 就是根据当前的ID去更新数组中对应的数据
                this.books.some((item) => {
                  if(item.id == this.id) {
                    item.name = this.name;
                    // 完成更新操作之后,需要终止循环
                    return true;
                  }
                });
                this.flag = false;
              }else{
                // 添加图书
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = 2525609975000;
                this.books.push(book);
                // 清空表单
                this.id = '';
                this.name = '';
              }
              // 清空表单
              this.id = '';
              this.name = '';
            },
            toEdit: function(id){
              // 禁止修改ID
              this.flag = true;
              console.log(id)
              // 根据ID查询出要编辑的数据
              var book = this.books.filter(function(item){
                return item.id == id;
              });
              console.log(book)
              // 把获取到的信息填充到表单
              this.id = book[0].id;
              this.name = book[0].name;
            },
            deleteBook: function(id){
              // 删除图书
              // 根据id从数组中查找元素的索引
              // var index = this.books.findIndex(function(item){
              //   return item.id == id;
              // });
              // 根据索引删除数组元素
              // this.books.splice(index, 1);
              // -------------------------
              // 方法二:通过filter方法进行删除
              this.books = this.books.filter(function(item){
                return item.id != id;
              });
            }
          },
          computed: {
            total: function(){
              // 计算图书的总数
              return this.books.length;
            }
          },
          mounted: function(){
            // 该生命周期钩子函数被触发的时候,模板已经可以使用
            // 一般此时用于获取后台数据,然后把数据填充到模板
            var data = [{
              id: 1,
              name: '三国演义',
              date: 2525609975000
            },{
              id: 2,
              name: '水浒传',
              date: 2525609975000
            },{
              id: 3,
              name: '红楼梦',
              date: 2525609975000
            },{
              id: 4,
              name: '西游记',
              date: 2525609975000
            }];
            this.books = data;
          }
        });
      </script>
    </body>
    </html>
    
  4. 通过监听属性(watch)验证图书名是否存在

    案例效果图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K0kpICi6-1605053467919)(./media/6.gif)]

    代码演示:

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <title>Document</title>
      <style type="text/css">
        .grid {
          margin: auto;
          width: 530px;
          text-align: center;
        }
        .grid table {
          border-top: 1px solid #C2D89A;
          width: 100%;
          border-collapse: collapse;
        }
        .grid th,td {
          padding: 10;
          border: 1px dashed #F3DCAB;
          height: 35px;
          line-height: 35px;
        }
        .grid th {
          background-color: #F3DCAB;
        }
        .grid .book {
          padding-bottom: 10px;
          padding-top: 5px;
          background-color: #F3DCAB;
        }
        .grid .total {
          height: 30px;
          line-height: 30px;
          background-color: #F3DCAB;
          border-top: 1px solid #C2D89A;
        }
      </style>
    </head>
    <body>
      <div id="app">
        <div class="grid">
          <div>
            <h1>图书管理</h1>
            <div class="book">
              <div>
                <label for="id">
                  编号:
                </label>
                <input type="text" id="id" v-model='id' :disabled="flag" v-focus>
                <label for="name">
                  名称:
                </label>
                <input type="text" id="name" v-model='name'>
                <button @click='handle' :disabled="submitFlag">提交</button>
              </div>
            </div>
          </div>
          <div class="total">
            <span>图书总数:</span>
            <span>{{total}}</span>
          </div>
          <table>
            <thead>
              <tr>
                <th>编号</th>
                <th>名称</th>
                <th>时间</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr :key='item.id' v-for='item in books'>
                <td>{{item.id}}</td>
                <td>{{item.name}}</td>
                <td>{{item.date | format('yyyy-MM-dd hh:mm:ss')}}</td>
                <td>
                  <a href="" @click.prevent='toEdit(item.id)'>修改</a>
                  <span>|</span>
                  <a href="" @click.prevent='deleteBook(item.id)'>删除</a>
                </td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
      <script type="text/javascript">
        Vue.directive('focus', {
          inserted: function (el) {
            el.focus();
          }
        });
        Vue.filter('format', function(value, arg) {
          function dateFormat(date, format) {
            if (typeof date === "string") {
              var mts = date.match(/(\/Date\((\d+)\)\/)/);
              if (mts && mts.length >= 3) {
                date = parseInt(mts[2]);
              }
            }
            date = new Date(date);
            if (!date || date.toUTCString() == "Invalid Date") {
              return "";
            }
            var map = {
              "M": date.getMonth() + 1, //月份 
              "d": date.getDate(), //日 
              "h": date.getHours(), //小时 
              "m": date.getMinutes(), //分 
              "s": date.getSeconds(), //秒 
              "q": Math.floor((date.getMonth() + 3) / 3), //季度 
              "S": date.getMilliseconds() //毫秒 
            };
            format = format.replace(/([yMdhmsqS])+/g, function(all, t) {
              var v = map[t];
              if (v !== undefined) {
                if (all.length > 1) {
                  v = '0' + v;
                  v = v.substr(v.length - 2);
                }
                return v;
              } else if (t === 'y') {
                return (date.getFullYear() + '').substr(4 - all.length);
              }
              return all;
            });
            return format;
          }
          return dateFormat(value, arg);
        })
        var vm = new Vue({
          el: '#app',
          data: {
            flag: false,
            submitFlag: false,
            id: '',
            name: '',
            books: []
          },
          methods: {
            handle: function(){
              if(this.flag) {
                // 编辑图书
                // 就是根据当前的ID去更新数组中对应的数据
                this.books.some((item) => {
                  if(item.id == this.id) {
                    item.name = this.name;
                    // 完成更新操作之后,需要终止循环
                    return true;
                  }
                });
                this.flag = false;
              }else{
                // 添加图书
                var book = {};
                book.id = this.id;
                book.name = this.name;
                book.date = 2525609975000;
                this.books.push(book);
                // 清空表单
                this.id = '';
                this.name = '';
              }
              // 清空表单
              this.id = '';
              this.name = '';
            },
            toEdit: function(id){
              // 禁止修改ID
              this.flag = true;
              console.log(id)
              // 根据ID查询出要编辑的数据
              var book = this.books.filter(function(item){
                return item.id == id;
              });
              console.log(book)
              // 把获取到的信息填充到表单
              this.id = book[0].id;
              this.name = book[0].name;
            },
            deleteBook: function(id){
              // 删除图书
              // 根据id从数组中查找元素的索引
              // var index = this.books.findIndex(function(item){
              //   return item.id == id;
              // });
              // 根据索引删除数组元素
              // this.books.splice(index, 1);
              // -------------------------
              // 方法二:通过filter方法进行删除
              this.books = this.books.filter(function(item){
                return item.id != id;
              });
            }
          },
          computed: {
            total: function(){
              // 计算图书的总数
              return this.books.length;
            }
          },
          watch: {
            name: function(val) {
              // 验证图书名称是否已经存在
              var flag = this.books.some(function(item){
                return item.name == val;
              });
              if(flag) {
                // 图书名称存在
                this.submitFlag = true;
              }else{
                // 图书名称不存在
                this.submitFlag = false;
              }
            }
          },
          mounted: function(){
            // 该生命周期钩子函数被触发的时候,模板已经可以使用
            // 一般此时用于获取后台数据,然后把数据填充到模板
            var data = [{
              id: 1,
              name: '三国演义',
              date: 2525609975000
            },{
              id: 2,
              name: '水浒传',
              date: 2525609975000
            },{
              id: 3,
              name: '红楼梦',
              date: 2525609975000
            },{
              id: 4,
              name: '西游记',
              date: 2525609975000
            }];
            this.books = data;
          }
        });
      </script>
    </body>
    </html>
    

3.6 组件

3.6.1 什么是组件★★★★
  • 组件 (Component) 是 Vue.js 最强大的功能之一
  • 组件可以扩展 HTML 元素,封装可重用的代码
3.6.2 组件注册★★★★

全局注册:

  • Vue.component(‘组件名称’, { }) 第1个参数是标签名称,第2个参数是一个选项对象
  • 全局组件注册后,任何vue实例都可以用

代码演示:

html:

<div id="example">
  <!-- 2、 组件使用 组件名称 是以HTML标签的形式使用  -->  
  <my-component></my-component>
</div>

js:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script>
    //   注册组件 
    // 1、 my-component 就是组件中自定义的标签名
	Vue.component('my-component', {
      template: '<div>A custom component!</div>'
    })
    // 创建根实例
    new Vue({
      el: '#example'
    })
</script>

组件注意事项:

  1. 组件参数的data值必须是函数同时这个函数要求返回一个对象

  2. 组件模板必须是单个根元素

  3. 组件模板的内容可以是模板字符串

代码演示:

html:

<div id="app">
    <!-- 4. 组件可以重复使用多次 
    因为data中返回的是一个对象所以每个组件中的数据是私有的
    即每个实例可以维护一份被返回对象的独立的拷贝   
    --> 
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
    <!-- 8、必须使用短横线的方式使用组件 -->
    <hello-world></hello-world>
</div>

js:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script type="text/javascript">
	//5  如果使用驼峰式命名组件,那么在使用组件的时候,只能在字符串模板中用驼峰的方式使用组件,
	// 7、但是在普通的标签模板中,必须使用短横线的方式使用组件
    Vue.component('HelloWorld', {
        data: function(){
            return {
                msg: 'HelloWorld'
            }
        },
        template: '<div>{{msg}}</div>'
    });
    Vue.component('button-counter', {
        // 1、组件参数的data值必须是函数 
        // 同时这个函数要求返回一个对象  
        data: function(){
            return {
                count: 0
            }
        },
        //  2、组件模板必须是单个根元素
        //  3、组件模板的内容可以是模板字符串  
        template: `
            <div>
                <button @click="handle">点击了{{count}}次</button>
                <button>测试123</button>
            // 6 在字符串模板中可以使用驼峰的方式使用组件	
            <HelloWorld></HelloWorld>
            </div>
            `,
        methods: {
            handle: function(){
                this.count += 2;
            }
        }
    })
    var vm = new Vue({
        el: '#app',
        data: {}
    });
</script>

局部注册:

只能在当前注册它的vue实例中使用

代码演示:

html:

<div id="app">
    <my-component></my-component>
</div>

js:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script>
    // 定义组件的模板
    var Child = {
      template: '<div>A custom component!</div>'
    }
    new Vue({
      //局部注册组件  
      components: {
        // <my-component> 将只在父模板可用  一定要在实例上注册了才能在html文件中使用
        'my-component': Child
      }
    })
 </script>

3.7 Vue调试工具

3.7.1 翻墙安装★★★
  1. 打开Chrome扩展页面

    点击Chrome浏览器最右方的三个点,找到更多工具/扩展程序

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TmZHSSvn-1605053467922)(./media/chrome.png)]

  2. 选中开发者模式

    点击chrome网上应用店进入:https://chrome.google.com/webstore/category/extensions

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8o60KoOe-1605053467924)(./media/kaifazhe.png)]

  3. 搜索Vue Devtools

  4. 下载该插件

  5. 将下载下来的安装包拖到chrome扩展应用程序页面

3.7.2 不翻墙安装★★★
  1. 进入vue官网,找到生态系统/工具/Devtols点进去,如下图:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XNuzBiIh-1605053467927)(./media/devtools.png)]

  2. 克隆仓库

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sIBQdclO-1605053467929)(./media/clonerep.png)]

  3. 安装依赖包

    cd vue-devtools
    yarn install
    
  4. 构建

    yarn run build
    
  5. 打开Chrome扩展页面

    点击Chrome浏览器最右方的三个点,找到更多工具/扩展程序

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8OS3UMbq-1605053467931)(./media/chrome.png)]

  6. 选中开发者模式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nUwnWzbY-1605053467934)(./media/kaifazhe.png)]

  7. 加载以解压的扩展,选择vue-devtools/packages/shell-chrome

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gQ1TDxiL-1605053467936)(./media/shellchrome.png)]

加载成功后显示如下图,同时右上角会出现一个vue图标:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ykIib1cX-1605053467938)(./media/success.png)]

3.8 组件通信

3.8.1 父组件向子组件传值★★★★★
  • 父组件发送的形式是以属性的形式绑定值到子组件身上。
  • 然后子组件用属性props接收
  • 在props中使用驼峰形式,模板中需要使用短横线的形式字符串形式的模板中没有这个限制

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{pmsg}}</div>
    <menu-item title='来自父组件的值'></menu-item>
    <menu-item :title='ptitle' content='hello'></menu-item>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 父组件向子组件传值-基本使用
    Vue.component('menu-item', {
      props: ['title', 'content'],
      data: function() {
        return {
          msg: '子组件本身的数据'
        }
      },
      template: '<div>{{msg + "----" + title + "-----" + content}}</div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        ptitle: '动态绑定属性'
      }
    });
  </script>
</body>
</html>
3.8.2 props命名规则★★★★★
  • 在props中使用驼峰形式,模板中需要使用短横线的形式
  • 字符串的模板中没有这个限制

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{pmsg}}</div>
    <menu-item :menu-title='ptitle'></menu-item>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 父组件向子组件传值-props属性名规则
    Vue.component('third-com', {
      props: ['testTile'],
      template: '<div>{{testTile}}</div>'
    });
    Vue.component('menu-item', {
      props: ['menuTitle'],
      template: '<div>{{menuTitle}}<third-com testTile="hello"></third-com></div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        ptitle: '动态绑定属性'
      }
    });
  </script>
</body>
</html>
3.8.3 props属性值类型★★★★★
  • 字符串 String
  • 数值 Number
  • 布尔值 Boolean
  • 数组 Array
  • 对象 Object

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>{{pmsg}}</div>
    <menu-item :pstr='pstr' :pnum='12' pboo='true' :parr='parr' :pobj='pobj'></menu-item>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 父组件向子组件传值-props属性值类型
    Vue.component('menu-item', {
      props: ['pstr','pnum','pboo','parr','pobj'],
      template: `
        <div>
          <div>{{pstr}}</div>
          <div>{{12 + pnum}}</div>
          <div>{{typeof pboo}}</div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
            <span>{{pobj.name}}</span>
            <span>{{pobj.age}}</span>
          </div>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        pstr: 'hello',
        parr: ['apple','orange','banana'],
        pobj: {
          name: 'lisi',
          age: 12
        }
      }
    });
  </script>
</body>
</html>
3.8.4 子组件向父组件传值★★★★★
  • 子组件用$emit()触发事件
  • $emit() 第一个参数为 自定义的事件名称 第二个参数为需要传递的数据
  • 父组件用v-on 监听子组件的事件

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
    <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 子组件向父组件传值-携带参数
    Vue.component('menu-item', {
      props: ['parr'],
      template: `
        <div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
          <button @click='$emit("enlarge-text", 5)'>扩大父组件中字体大小</button>
          <button @click='$emit("enlarge-text", 10)'>扩大父组件中字体大小</button>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父组件中内容',
        parr: ['apple','orange','banana'],
        fontSize: 10
      },
      methods: {
        handle: function(val){
          // 扩大字体大小
          this.fontSize += val;
        }
      }
    });
  </script>
</body>
</html>
3.8.5 兄弟之间的传递★★★★★
  • 兄弟之间传递数据需要借助于事件中心,通过事件中心传递数据
    • 提供事件中心 var hub = new Vue()
  • 传递数据方,通过一个事件触发hub.$emit(方法名,传递的数据)
  • 接收数据方,通过mounted(){} 钩子中 触发hub.$on()方法名
  • 销毁事件 通过hub.$off()方法名销毁之后无法进行传递数据

代码演示:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Document</title>
</head>
<body>
  <div id="app">
    <div>父组件</div>
    <div>
      <button @click='handle'>销毁事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
  <script type="text/javascript">
    // 兄弟组件之间数据传递
    // 提供事件中心
    var hub = new Vue();
    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
        // 监听事件
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>点击</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          // 触发兄弟组件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 监听事件
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      },
      methods: {
        handle: function(){
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>
</body>
</html>

4、Vue基础扩展

4.1 插槽

  • 组件的最大特性就是复用性,而用好插槽能大大提高组件的可复用能力
  • 在Vue中插槽是很重要的存在,通过插槽,我们可以把父组件中指定的DOM作用到子组件的任意位置,后面我们坐项目用到的组件库比如element-ui,vant-ui都频繁用到的插槽,Vue的插槽主要有匿名插槽,具名插槽,作用域插槽三种,下面我们分别来认识一下他们。
4.1.1 匿名插槽★★★★

故名思义就是没有名字的插槽,只需要在子组件中使用<slot></slot>引入即可

我们来看一下案例哈,如何引入插槽的:

<div id="app">
        <!-- 这里的所有组件标签中嵌套的内容会替换掉slot  如果不传值 则使用 slot 中的默认值  -->
        <alert-box>有bug发生</alert-box>
        <alert-box>有一个警告</alert-box>
        <alert-box></alert-box>
</div>
<script type="text/javascript">
        /*
          组件插槽:父组件向子组件传递内容
        */
        Vue.component('alert-box', {
            template: `
            <div>
              <strong>ERROR:</strong>
                        # 当组件渲染的时候,这个 <slot> 元素将会被替换为“组件标签中嵌套的内容”。
                        # 插槽内可以包含任何模板代码,包括 HTML
              <slot>默认内容</slot>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });
</script>
4.1.2 具名插槽★★★★
  • 具有名字的插槽就是具名插槽
  • 使用 <slot> 中的 “name” 属性绑定元素,name就是插槽的名字
<div id="app">
        <base-layout>
            <!-- 2、 通过slot属性来指定, 这个slot的值必须和下面slot组件得name值对应上
                    如果没有匹配到 则放到匿名的插槽中   -->
            <p slot='header'>标题信息</p>
            <p>主要内容1</p>
            <p>主要内容2</p>
            <p slot='footer'>底部信息信息</p>
        </base-layout>

        <base-layout>
            <!-- 注意点:template临时的包裹标签最终不会渲染到页面上     -->
            <template slot='header'>
                <p>标题信息1</p>
                <p>标题信息2</p>
            </template>
            <p>主要内容1</p>
            <p>主要内容2</p>
            <template slot='footer'>
                <p>底部信息信息1</p>
                <p>底部信息信息2</p>
            </template>
        </base-layout>
</div>
<script type="text/javascript">
        /*
          具名插槽
        */
        Vue.component('base-layout', {
            template: `
            <div>
              <header>
                ###	1、 使用 <slot> 中的 "name" 属性绑定元素 指定当前插槽的名字
                <slot name='header'></slot>
              </header>
              <main>
                <slot></slot>
              </main>
              <footer>
                ###  注意点: 
                ###  具名插槽的渲染顺序,完全取决于模板,而不是取决于父组件中元素的顺序
                <slot name='footer'></slot>
              </footer>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {

            }
        });
</script>
4.1.3 作用域插槽★★★★
  • 可以让父组件对子组件的内容进行加工处理
  • 既可以复用子组件的slot,又可以使slot内容不一致
    • 父组件中使用slot-scope绑定一个属性
    • 子组件中给<slot>标签绑定一个自定义属性,可以传递子组件的内容
    • 父组件通过slot-scope接受子组件传递过来的值即可
<div id="app">
        <!-- 
            1、当我们希望li 的样式由外部使用组件的地方定义,因为可能有多种地方要使用						该组件,但样式希望不一样 这个时候我们需要使用作用域插槽   
        -->
        <fruit-list :list='list'>
            <!-- 2、 父组件中使用了<template>元素,而且包含scope="slotProps",
                slotProps在这里只是临时变量   
            --->
            <template slot-scope='slotProps'>
                <strong v-if='slotProps.info.id==3' class="current">
                    {{slotProps.info.name}}
                </strong>
                <span v-else>{{slotProps.info.name}}</span>
            </template>
        </fruit-list>
</div>
<script type="text/javascript">
        /*
          作用域插槽
        */
        Vue.component('fruit-list', {
            props: ['list'],
            template: `
            <div>
              <li :key='item.id' v-for='item in list'>
              //3、 在子组件模板中,<slot>元素上有一个类似props传递数据给组										件的写法 msg="xxx",
              //插槽可以提供一个默认内容,如果如果父组件没有为这个插槽提供了内										容,会显示默认的内容。
                如果父组件为这个插槽提供了内容,则默认的内容会被替换掉
                <slot :info='item'>{{item.name}}</slot>
              </li>
            </div>
          `
        });
        var vm = new Vue({
            el: '#app',
            data: {
                list: [{
                    id: 1,
                    name: 'apple'
                }, {
                    id: 2,
                    name: 'orange'
                }, {
                    id: 3,
                    name: 'banana'
                }]
            }
        });
</script>

4.2 案例讲解-购物车

前面我们学了组件,我们也知道Vue的核心是组件系统和数据驱动,下面我们来做一个购物车的案例,巩固深化一下我们所学的理论知识

4.2.1 组件化布局组件拆分★★★★
  • 把静态页面转换为组件化模式
  • 把组件渲染到页面上显示效果如下所示

代码效果如下:

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.3.7/dist/css/bootstrap.min.css">
<style>
        #app {
            width: 600px;
            margin-top: 30px;
        }
        .header {
            width: 100%;
            height: 40px;
            line-height: 40px;
            text-align: center;
            background-color: aquamarine;
        }

        .footer {
            width: 100%;
            height: 50px;
            line-height: 50px;
            background-color: azure;
            text-align: right;
        }

        .footer>span {
            padding: 0px 15px;
            font-weight: bold;
            font-size: 15px;
            color: brown;
        }
</style>

上面写了点简单的样式,效果如上图所示

<div id="app" class="container">
    <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            template: `
                <div class="header">我的商品</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            template: `
            <table class="table table-bordered">
                <tr>
                    <td><img src="media/day06-a.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
                <tr>
                    <td><img src="media/day06-b.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
                <tr>
                    <td><img src="media/day06-c.jpg" width="50" /></td>
                    <td>
                        <button>-</button>
                        <input type="text" />
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger">删除</button></td>
                </tr>
            </table>
            `
        }
        //结算组件
        var CartTotal = {
            template: `
            <div class="footer">
                <span>总价: 100</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            // 引入子组件
            template: `
            <div>
                <cart-title></cart-title>
                <cart-list></cart-list>
                <cart-total></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            }
        });
        new Vue({
            el: "#app",
            data: {
            },
            methods: {

            }
        })
</script>
4.2.2完成标题和结算组件功能 组件传值★★★★
  • 标题组件事件动态渲染
    • 从父组件把标题数据传递过来 即 父向子组件传值
    • 把传递过来的数据渲染到页面上
  • 结算功能组件
    • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
    • 把传递过来的数据计算最终价格渲染到页面上
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total:function(){
                    let amounts = 0;
                    this.goods_list.forEach(item=>{
                        amounts +=item.price*item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list ></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            }
        });
 </script>
4.2.3 完成列表组件删除商品功能splice★★★★
  • 从父组件把商品列表list 数据传递过来 即 父向子组件传值
  • 把传递过来的数据渲染到页面上
  • 点击删除按钮的时候删除对应的数据
    • 给按钮添加点击事件把需要删除的id传递过来
      • 子组件中不推荐操作父组件的数据,有可能多个子组件使用父组件的数据,我们需要把数据 传递给父组件让父组件操作数据
      • 父组件删除对应的数据
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            props: ['goods_list'],
            template: `
            <table class="table table-bordered">
                <tr v-for="(item,index) in goods_list" :key="item.id">
                    <td>
                        <img :src="item.img" width="50" />
                        {{item.name}}
                    </td>
                    <td>
                        <button>-</button>
                        <input type="text" v-model="item.num"/>
                        <button>+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger" @click='del(item.id)'>删除</button></td>
                </tr>
            </table>
            `,
            methods: {
                del(id) {
                    console.log(id);
                    this.$emit("cart-del", id);
                }
            }
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total: function () {
                    let amounts = 0;
                    this.goods_list.forEach(item => {
                        amounts += item.price * item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list :goods_list="goods_list" @cart-del="removeGoods"></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            },
            methods: {
                removeGoods(id) {
                    console.log(id)
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    this.goods_list.splice(index, 1);
                }
            }
        });
        new Vue({
            el: "#app",
            data: {
            },
            methods: {

            }
        })
</script>
4.2.4 完成列表组件更新商品数量computed★★★★
  • 将输入框中的默认数据动态渲染出来
  • 输入框失去焦点的时候 更改商品的数量
  • 点击按钮+和按钮-更新商品的数量,同步更新总价格
  • 子组件中不推荐操作数据 把这些数据传递给父组件 让父组件处理这些数据
  • 父组件中接收子组件传递过来的数据并处理
<div id="app" class="container">
        <my-cart></my-cart>
</div>
<script>
        //1、 把静态页面转换成组件化模式
        //1.1  标题组件 
        var CartTitle = {
            props: ['title'],
            template: `
                <div class="header">{{title}}</div>
            `
        }
        //1.2  商品列表组件
        var CartList = {
            props: ['goods_list'],
            template: `
            <table class="table table-bordered">
                <tr v-for="(item,index) in goods_list" :key="item.id">
                    <td>
                        <img :src="item.img" width="50" />
                        {{item.name}}
                    </td>
                    <td>
                        <button @click="decr(item.id)">-</button>
                        <input type="text" :value="item.num" @blur="changeNums(item.id,$event)"/>
                        <button @click="incr(item.id)">+</button>
                    </td>
                    <td><button class="btn btn-sm btn-danger" @click='del(item.id)'>删除</button></td>
                </tr>
            </table>
            `,
            methods: {
                del(id) {
                    console.log(id);
                    this.$emit("cart-del", id);
                },
                changeNums(id, event) {
                    this.$emit("change-num",
                        {
                            id: id,
                            type: 'change',
                            num: event.target.value
                        }
                    )
                },
                incr(id) {
                    this.$emit('change-num',
                        {
                            id: id,
                            type: 'incr',
                        }
                    );
                },
                decr(id) {
                    this.$emit('change-num',
                        {
                            id: id,
                            type: 'decr',
                        }
                    );
                }
            }
        }
        //结算组件,子组件接受值即可
        var CartTotal = {
            props: ['goods_list'],
            template: `
            <div class="footer">
                <span>总价: {{total}}</span> &nbsp;&nbsp;
                <button class="btn btn-danger">结算</button>
            </div>
            `,
            computed: {
                total: function () {
                    let amounts = 0;
                    this.goods_list.forEach(item => {
                        amounts += item.price * item.num;
                    })
                    return amounts.toFixed(2);
                }
            },
        }
        //定义一个全局组件 my-cart
        Vue.component("my-cart", {
            data: function () {
                return {
                    title: "我的商品",
                    goods_list: [
                        {
                            id: 1,
                            name: 'TCL彩电',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-a.jpg'
                        }, {
                            id: 2,
                            name: '机顶盒',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-b.jpg'
                        }, {
                            id: 3,
                            name: '海尔冰箱',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-c.jpg'
                        }, {
                            id: 4,
                            name: '小米手机',
                            price: 1000,
                            num: 1,
                            img: 'media/day06-d.jpg'
                        }, {
                            id: 5,
                            name: 'PPTV电视',
                            price: 1000,
                            num: 2,
                            img: 'media/day06-e.jpg'
                        }
                    ]
                }
            },
            // 引入子组件,父组件向子组件进行传值
            template: `
            <div>
                <cart-title :title="title"></cart-title>
                <cart-list :goods_list="goods_list" 
                    @cart-del="removeGoods"
                    @change-num="changeNums"
                ></cart-list>
                <cart-total :goods_list="goods_list"></cart-total>
            </div>
            `,
            //注册子路由组件
            components: {
                'cart-title': CartTitle,
                'cart-list': CartList,
                'cart-total': CartTotal
            },
            methods: {
                removeGoods(id) {
                    console.log(id)
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    this.goods_list.splice(index, 1);
                },
                changeNums(data) {
                    let id = data.id;
                    let index = this.goods_list.findIndex(item => {
                        return item.id == id;
                    });
                    if (data.type == "change") {
                        this.goods_list[index].num = data.num;
                    }

                    if (data.type == "incr") {
                        this.goods_list[index].num++;
                    }
                    if (data.type == "decr") {
                        this.goods_list[index].num--;
                    }
                }
            }
        });
</script>

4.3 接口调用方式

前端要做动态数据渲染,只能通过调用接口的方式来获取服务端的数据,目前常见的方式有ajax,fetch,axios等常见的方式可以获取接口的数据,我们来分别认识一下这些操作方式…

4.3.1 原生ajax★★★★

ajax主要是负责是客户端和服务端异步数据通信的工具,原生的ajax对浏览器支持比较好,所以群众基础还是比较广的,我们先来看一下原生的ajax如何实现对接口数据的请求:

原生的ajax请求数据大致分为如下几个步骤:

<script>
        let xhr = null;
        //实例化xmlhttprequest对象
        xhr = new XMLHttpRequest();
        //配置要请求的接口地址
        xhr.open("post","url地址",true);
        //设置请求的头部信息,如果是post请求的话
        xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
        //设置请求后状态发生变化的回调函数
        xhr.onreadystatechange = ()=>{

        };
        // 发送请求
        xhr.send();
</script>
4.3.2 jQuery的ajax★★★★

但是我们大部分时候可能用上面原生的ajax的时候是很少的,我们一般都是用jQuery封装好的Api,这个前提是我们需要先引入jQuery文件,否则提示报错,jQuery的ajax使用步骤大致如下所示

<script>
        $.ajax({
            url:"",
            data:{

            },
            type: "post",
            dataType: "json",
            async: true,
            success(res){

            },
            error(){

            }
        })
</script>
4.3.3 fetch★★★★
  • Fetch API是新的ajax解决方案 Fetch会返回Promise
  • fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
  • 基本结构大致如下 fetch(url, options).then()
<script type="text/javascript">
        /*
          Fetch API 基本用法
              fetch(url).then()
             第一个参数请求的路径   
             Fetch会返回Promise   
             所以我们可以使用then 拿到请求成功的结果 
        */
        fetch('http://localhost:3000/fdata').then(function (data) {
            // text()方法属于fetchAPI的一部分,
            //它返回一个Promise实例对象,用于获取后台返回的数据
            return data.text();
        }).then(function (data) {
            //   在这个then里面我们能拿到最终的数据  
            console.log(data);
        })
</script>
  • fetch支持很多请求的方式如POST,GET,DELETE,UPDATE,PATCH和PUT
    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
<script type="text/javascript">
        /*
              Fetch API 调用接口传递参数
        */
        //1.1 GET参数传递 - 传统URL  通过url  ? 的形式传参
        fetch('http://localhost:3000/books?id=123', {
            //get 请求可以省略不写 默认的是GET 
            method: 'get'
        }).then(function (data) {
            //它返回一个Promise实例对象,用于获取后台返回的数据
            return data.text();
        }).then(function (data) {
            //在这个then里面我们能拿到最终的数据
            console.log(data)
        });

        //1.2  GET参数传递  restful形式的URL  通过 / 的形式传递参数  即  id = 456 和id后台的配置有关
        fetch('http://localhost:3000/books/456', {
            //get 请求可以省略不写 默认的是GET 
            method: 'get'
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //2.1  DELETE请求方式参数传递      删除id  是  id = 789
        fetch('http://localhost:3000/books/789', {
            method: 'delete'
        })
            .then(function (data) {
                return data.text();
            }).then(function (data) {
                console.log(data)
            });

        //3 POST请求传参
        fetch('http://localhost:3000/books', {
            method: 'post',
            //3.1  传递数据 
            body: 'uname=lisi&pwd=123',
            //3.2  设置请求头 
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //POST请求传参
        fetch('http://localhost:3000/books', {
            method: 'post',
            body: JSON.stringify({
                uname: '张三',
                pwd: '456'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });

        //PUT请求传参     修改id 是 123 的
        fetch('http://localhost:3000/books/123', {
            method: 'put',
            body: JSON.stringify({
                uname: '张三',
                pwd: '789'
            }),
            headers: {
                'Content-Type': 'application/json'
            }
        }).then(function (data) {
            return data.text();
        }).then(function (data) {
            console.log(data)
        });
</script>
4.3.4 axios★★★★
  • 基于promise用于浏览器和node.js的http客户端
  • 支持浏览器和node.js
  • 支持promise
  • 能拦截请求和响应
  • 自动转换JSON数据
  • 能转换请求和响应数据

axios基础用法

  • get和 delete请求传递参数
    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数
    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
<script type="text/javascript">
        // 1. 发送get 请求
        axios.get('http://localhost:3000/adata').then(function (ret) {
            //  拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
            // 注意data属性是固定的用法,用于获取后台的实际数据
            // console.log(ret.data)
            console.log(ret)
        })
        // 2.  get 请求传递参数
        // 2.1  通过传统的url  以 ? 的形式传递参数
        axios.get('http://localhost:3000/axios?id=123').then(function (ret) {
            console.log(ret.data)
        })
        // 2.2  restful 形式传递参数
        axios.get('http://localhost:3000/axios/123').then(function (ret) {
            console.log(ret.data)
        })
        // 2.3  通过params  形式传递参数
        axios.get('http://localhost:3000/axios', {
            params: {
                id: 789
            }
        }).then(function (ret) {
            console.log(ret.data)
        })
        //3 axios delete 请求传参     传参的形式和 get 请求一样
        axios.delete('http://localhost:3000/axios', {
            params: {
                id: 111
            }
        }).then(function (ret) {
            console.log(ret.data)
        })

        // 4  axios 的 post 请求
        // 4.1  通过选项传递参数
        axios.post('http://localhost:3000/axios', {
            uname: 'lisi',
            pwd: 123
        }).then(function (ret) {
            console.log(ret.data)
        })
        // 4.2  通过 URLSearchParams  传递参数
        var params = new URLSearchParams();
        params.append('uname', 'zhangsan');
        params.append('pwd', '111');
        axios.post('http://localhost:3000/axios', params).then(function (ret) {
            console.log(ret.data)
        })

        //5  axios put 请求传参   和 post 请求一样
        axios.put('http://localhost:3000/axios/123', {
            uname: 'lisi',
            pwd: 123
        }).then(function (ret) {
            console.log(ret.data)
        })
</script>

axios全局配置

//配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
//配置 超时时间
axios.defaults.timeout = 2500;
//配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
//配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';

axios拦截器
axios的拦截器分为请求拦截器和响应拦截器:

  • 请求拦截器
    • 请求拦截器的作用是在请求发送前进行一些操作
      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器
    • 响应拦截器的作用是在接收到响应后进行一些操作
      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页
<script>
        // 1. 请求拦截器 
        axios.interceptors.request.use(function (config) {
            console.log(config.url)
            // 1.1  任何请求都会经过这一步   在发送请求之前做些什么   
            config.headers.mytoken = 'nihao';
            // 1.2  这里一定要return   否则配置不成功  
            return config;
        }, function (err) {
            //1.3 对请求错误做点什么    
            console.log(err)
        })
        //2. 响应拦截器 
        axios.interceptors.response.use(function (res) {
            //2.1  在接收响应做些什么  
            var data = res.data;
            return data;
        }, function (err) {
            //2.2 对响应错误做点什么  
            console.log(err)
        })
</script>

4.4 异步编程

4.4.1 异步★★★

异步和同步是相对的,同步我们一般指的是同时进行,比如张三等李四去吃饭,这个时候李四如果没有去吃饭的话,张三就会一直等着,
就比如请求服务器数据,如果是同步的话,比如等到服务器返回数据后在执行后续的代码逻辑,但是如果是异步的话,我只需要把请求发送出去,然后继续执行我的代码,这个时候就加入了一个异步线程,当数据返回后在执行对应的逻辑,就形成了异步操作。
所以同步是阻塞的,异步是非阻塞的。

代码如下

function doSomething(){
  return new Promise(function(resolve){
    setTimeout(function(){
      console.log('执行结束');
      let result = 6;
      resolve(result);
    },100);
    console.log("异步操作")
  });
} 
doSomething().then(result=>{
  console.log('接收到结果为:'+result);
});

执行效果如下:

4.4.2 单线程
  • JavaScript的执行环境是「单线程」
  • 所谓单线程,是指JS引擎中负责解释和执行JavaScript代码的线程只有一个,也就是一次只能完成一项任务,这个任务执行完后才能执行下一个,它会「阻塞」其他任务。这个任务可称为主线程
  • 异步模式可以一起执行多个任务
4.4.3 常见异步调用★★★

JS中常见的异步调用有

  • 定时任务
  • ajax
  • 事件函数

定时任务
常用到的就是超时函数setTimeout,设置后就会把要执行的代码加入异步队列,然后继续执行后面的代码,等到时间后从异步队列中执行相对应的代码,不会阻塞后面代码的执行。

ajax
ajax本身就是为了解决客户端和服务端异步通信而产生的,jQuery中的ajax我们可以通过设置async的属性控制同步或者异步,默认为异步,async为true为异步,否则为同步

事件函数
采用事件驱动模式。
任务的执行不取决代码的顺序,而取决于某一个事件是否发生。
监听函数有:on,bind,listen,addEventListener,observe

也就是说只有当事件触发后才会执行对应的代码,函数才会被调用,这样的话就不会阻塞其它代码的执行,但是用的多了,可以会导致页面流程不太流畅…

5、异步请求数据

5.1promise

5.1.1promise解决了什么问题★★★★★
  • 主要解决异步深层嵌套的问题
  • promise 提供了简洁的API 使得异步操作更加容易
5.1.2基于Promise发送Ajax请求解决回调地狱★★★★★
<script type="text/javascript">
  /*
     1. Promise基本使用
           我们使用new来构建一个Promise  Promise的构造函数接收一个参数,是函数,并且传入两个参数,resolve,reject, 分别表示异步操作执行成功后的回调函数和异步操作执行失败后的回调函数
    */
  var p = new Promise(function(resolve, reject){
    //2. 这里用于实现异步任务  setTimeout
    setTimeout(function(){
      var flag = false;
      if(flag) {
        //3. 正常情况
        resolve('hello');
      }else{
        //4. 异常情况
        reject('出错了');
      }
    }, 100);
  });
//  5 Promise实例生成以后,可以用then方法指定resolved状态和reject状态的回调函数 
//  在then方法中,你也可以直接return数据而不是Promise对象,在后面的then中就可以接收到数据了  
p.then(function(data){
  console.log(data)
},function(info){
  console.log(info)
});
</script>

5.2promise基本API

5.2.1.then()★★★★★

得到异步任务正确的结果

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.2.catch()★★★★★

获取异常信息

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.3.finally()★★★

成功与否都会执行(不是正式标准)

foo()
  .then(function(data){
  # 得到异步任务正确的结果
  console.log(data)
},function(data){
  # 获取异常信息
  console.log(data)
})
# 成功与否都会执行(不是正式标准) 
  .finally(function(){
    console.log('finished')
  });
5.2.4静态方法all()★★★

Promise.all方法接受一个数组作参数,数组中的对象(p1、p2、p3)均为promise实例(如果不是一个promise,该项会被用Promise.resolve`转换为一个promise)。它的状态由这三个promise实例决定

5.2.5静态方法race()★★★

Promise.race方法同样接受一个数组作参数。当p1, p2, p3中有一个实例的状态发生改变(变为fulfilledrejected`),p的状态就跟着改变。并把第一个改变状态的promise的返回值,传给p的回调函数

/*
      Promise常用API-对象方法
    */
// console.dir(Promise)
function queryData(url) {
  return new Promise(function(resolve, reject){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
      if(xhr.readyState != 4) return;
      if(xhr.readyState == 4 && xhr.status == 200) {
        // 处理正常的情况
        resolve(xhr.responseText);
      }else{
        // 处理异常情况
        reject('服务器错误');
      }
    };
    xhr.open('get', url);
    xhr.send(null);
  });
}

var p1 = queryData('http://localhost:3000/a1');
var p2 = queryData('http://localhost:3000/a2');
var p3 = queryData('http://localhost:3000/a3');
Promise.all([p1,p2,p3]).then(function(result){
  //   all 中的参数  [p1,p2,p3]   和 返回的结果一 一对应["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
  console.log(result) //["HELLO TOM", "HELLO JERRY", "HELLO SPIKE"]
})
Promise.race([p1,p2,p3]).then(function(result){
  // 由于p1执行较快,Promise的then()将获得结果'P1'。p2,p3仍在继续执行,但执行结果将被丢弃。
  console.log(result) // "HELLO TOM"
})

5.3fetch

5.3.1fetch API 中的HTTP请求★★★★★
  • fetch(url, options).then()
  • HTTP协议,它给我们提供了很多的方法,如POST,GET,DELETE,UPDATE,PATCH和PUT
    • 默认的是 GET 请求
    • 需要在 options 对象中 指定对应的 method method:请求使用的方法
    • post 和 普通 请求的时候 需要在options 中 设置 请求头 headers 和 body
5.3.2参数传递★★★★★

​ Fetch API 调用接口传递参数

// GET参数传递 - 传统URL  通过url  ? 的形式传参 
fetch('http://localhost:3000/books?id=123', {
// GET参数传递  restful形式的URL  通过/ 的形式传递参数  即  id = 456 和id后台的配置有关   
fetch('http://localhost:3000/books/456', {
  // get 请求可以省略不写 默认的是GET 
  method: 'get'
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//  DELETE请求方式参数传递      删除id  是  id=789
fetch('http://localhost:3000/books/789', {
  method: 'delete'
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

// POST请求传参
fetch('http://localhost:3000/books', {
  method: 'post',
  //  传递数据 
  body: 'uname=lisi&pwd=123',
  //  设置请求头 
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//POST请求传参
fetch('http://localhost:3000/books', {
  method: 'post',
  body: JSON.stringify({
    uname: '张三',
    pwd: '456'
  }),
  headers: {
    'Content-Type': 'application/json'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});

//PUT请求传参     修改id 是 123 的 
fetch('http://localhost:3000/books/123', {
  method: 'put',
  body: JSON.stringify({
    uname: '张三',
    pwd: '789'
  }),
  headers: {
    'Content-Type': 'application/json'
  }
})
  .then(function(data) {
  return data.text();
}).then(function(data) {
  console.log(data)
});
5.3.3fetchAPI 中响应数据格式★★★★★

用fetch来获取数据,如果响应正常返回,我们首先看到的是一个response对象,其中包括返回的一堆原始字节,这些字节需要在收到后,需要我们通过调用方法将其转换为相应格式的数据,比如JSONBLOB或者TEXT等等

/*
      Fetch响应结果的数据格式
    */
fetch('http://localhost:3000/json').then(function(data){
  // return data.json();   //  将获取到的数据使用 json 转换对象
  return data.text(); //  //  将获取到的数据 转换成字符串 
}).then(function(data){
  // console.log(data.uname)
  // console.log(typeof data)
  var obj = JSON.parse(data);
  console.log(obj.uname,obj.age,obj.gender)
})

5.4Axios

5.4.1axios基础用法★★★★★
  • 基于promise用于浏览器和node.js的http客户端

  • 支持浏览器和node.js

  • 支持promise

  • 能拦截请求和响应

  • 自动转换JSON数据

  • 能转换请求和响应数据

  • get和 delete请求传递参数

    • 通过传统的url 以 ? 的形式传递参数
    • restful 形式传递参数
    • 通过params 形式传递参数
  • post 和 put 请求传递参数

    • 通过选项传递参数
    • 通过 URLSearchParams 传递参数
5.4.2传参★★★★★
  1. 发送get 请求

    axios.get('http://localhost:3000/adata').then(function(ret){ 
      // 拿到 ret 是一个对象      所有的对象都存在 ret 的data 属性里面
      // 注意data属性是固定的用法,用于获取后台的实际数据
      // console.log(ret.data)
      console.log(ret)
    })
    
  2. get 请求传递参数

    // 通过传统的url  以 ? 的形式传递参数
    axios.get('http://localhost:3000/axios?id=123').then(function(ret){
      console.log(ret.data)
    })
    // restful 形式传递参数 
    axios.get('http://localhost:3000/axios/123').then(function(ret){
      console.log(ret.data)
    })
    // 通过params  形式传递参数 
    axios.get('http://localhost:3000/axios', {
      params: {
        id: 789
      }
    }).then(function(ret){
      console.log(ret.data)
    })
    
  3. axios delete 请求传参 传参的形式和 get 请求一样

    axios.delete('http://localhost:3000/axios', {
      params: {
        id: 111
      }
    }).then(function(ret){
      console.log(ret.data)
    })
    
  4. axios 的 post 请求

    // 通过选项传递参数
    axios.post('http://localhost:3000/axios', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
    // 通过 URLSearchParams  传递参数 
    var params = new URLSearchParams();
    params.append('uname', 'zhangsan');
    params.append('pwd', '111');
    axios.post('http://localhost:3000/axios', params).then(function(ret){
      console.log(ret.data)
    })
    
  5. axios put 请求传参 和 post 请求一样

    axios.put('http://localhost:3000/axios/123', {
      uname: 'lisi',
      pwd: 123
    }).then(function(ret){
      console.log(ret.data)
    })
    
5.4.3axios 全局配置★★★★★
配置公共的请求头 
axios.defaults.baseURL = 'https://api.example.com';
配置 超时时间
axios.defaults.timeout = 2500;
配置公共的请求头
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
配置公共的 post 的 Content-Type
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
5.4.4axios 拦截器★★★★
  • 请求拦截器

    • 请求拦截器的作用是在请求发送前进行一些操作
      • 例如在每个请求体里加上token,统一做了处理如果以后要改也非常容易
  • 响应拦截器

    • 响应拦截器的作用是在接收到响应后进行一些操作

      • 例如在服务器返回登录状态失效,需要重新登录的时候,跳转到登录页

      • 请求拦截

        axios.interceptors.request.use(function(config) {
          console.log(config.url)
          // 任何请求都会经过这一步   在发送请求之前做些什么   
          config.headers.mytoken = 'nihao';
          // 这里一定要return   否则配置不成功  
          return config;
        }, function(err){
          // 对请求错误做点什么    
          console.log(err)
        })
        
      • 响应拦截

        axios.interceptors.response.use(function(res) {
          // 在接收响应做些什么  
          var data = res.data;
          return data;
        }, function(err){
          // 对响应错误做点什么  
          console.log(err)
        })
        

5.5 async await

5.5.1基本用法★★★★
  • async作为一个关键字放到函数前面
    • 任何一个async函数都会隐式返回一个promise
  • await关键字只能在使用async定义的函数中使用
    • ​ await后面可以直接跟一个 Promise实例对象
    • ​ await函数不能单独使用
5.5.2处理多个异步请求★★★★
async function queryData() {
  //  添加await之后 当前的await 返回结果之后才会执行后面的代码   
  var info = await axios.get('async1');
  // 让异步代码看起来、表现起来更像同步代码
  var ret = await axios.get('async2?info=' + info.data);
  return ret.data;
}
queryData().then(function(data){
  console.log(data)
})

5.6案例讲解-图书列表

5.6.1获取图书列表 axios.get()★★★★
  • 导入axios 用来发送ajax
  • 把获取到的数据渲染到页面上
<div id="app">
  <div class="grid">
    <table>
      <thead>
        <tr>
          <th>编号</th>
          <th>名称</th>
          <th>时间</th>
          <th>操作</th>
        </tr>
      </thead>
      <tbody>
        <!-- 5.  把books  中的数据渲染到页面上   -->
        <tr :key='item.id' v-for='item in books'>
          <td>{{item.id}}</td>
          <td>{{item.name}}</td>
          <td>{{item.date }}</td>
          <td>
            <a href="">修改</a>
            <span>|</span>
            <a href="">删除</a>
          </td>
        </tr>
      </tbody>
    </table>
  </div>
</div>
<script type="text/javascript" src="js/vue.js"></script>
// 导入axios   
<script type="text/javascript" src="js/axios.js"></script>
<script type="text/javascript">
  /*
             图书管理-添加图书
         */
  //   配置公共的url地址  简化后面的调用方式
  axios.defaults.baseURL = 'http://localhost:3000/';
  axios.interceptors.response.use(function(res) {
    return res.data;
  }, function(error) {
    console.log(error)
  });

  var vm = new Vue({
    el: '#app',
    data: {
      flag: false,
      submitFlag: false,
      id: '',
      name: '',
      books: []
    },
    methods: {
      // 定义一个方法 用来发送 ajax 
      //  使用 async  来 让异步的代码  以同步的形式书写 
      queryData: async function() {
        // 调用后台接口获取图书列表数据
        // var ret = await axios.get('books');
        // this.books = ret.data;
        //  发送ajax请求  把拿到的数据放在books 里面   
        this.books = await axios.get('books');
      }
    },

    mounted: function() {
      // mounted  里面 DOM已经加载完毕  在这里调用函数  
      this.queryData();
    }
  });
</script>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-omrrXNhy-1605053467944)(./media/bb60aaf4cb08ef3f7e2599d072b4ea7.png)]

5.6.2添加图书 axios.post()★★★★
  • 获取用户输入的数据 发送到后台
  • 渲染最新的数据到页面上
methods: {
  handle: async function(){
    if(this.flag) {
      // 编辑图书
      // 就是根据当前的ID去更新数组中对应的数据
      this.books.some((item) => {
        if(item.id == this.id) {
          item.name = this.name;
          // 完成更新操作之后,需要终止循环
          return true;
        }
      });
      this.flag = false;
    }else{
      //  在前面封装好的 handle 方法中  发送ajax请求  
      //  使用async  和 await 简化操作 需要在 function 前面添加 async   
      var ret = await axios.post('books', {
        name: this.name
      })
      //  根据后台返回的状态码判断是否加载数据 
      if(ret.status == 200) {
        //  调用 queryData 这个方法  渲染最新的数据 
        this.queryData();
      }
    }
    // 清空表单
    this.id = '';
    this.name = '';
  },        
}         

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4X3U48qP-1605053467946)(./media/d2c77c89cc4def32764b446e9fc6582.png)]

5.6.3验证图书名字是否存在 axios.post()★★★★
  • 添加图书之前发送请求验证图示是否已经存在
  • 如果不存在 往后台里面添加图书名称
    • 图书存在与否只需要修改submitFlag的值即可

watch: {
  name: async function(val) {
    // 验证图书名称是否已经存在
    // var flag = this.books.some(function(item){
    //   return item.name == val;
    // });
    var ret = await axios.get('/books/book/' + this.name);
    if(ret.status == 1) {
      // 图书名称存在
      this.submitFlag = true;
    }else{
      // 图书名称不存在
      this.submitFlag = false;
    }
  }
},
5.6.4编辑图书 axios.post()★★★★
  • 根据当前书的id 查询需要编辑的书籍
  • 需要根据状态位判断是添加还是编辑
methods: {
  handle: async function(){
    if(this.flag) {
      // 编辑图书   把用户输入的信息提交到后台
      var ret = await axios.put('books/' + this.id, {
        name: this.name
      });
      if(ret.status == 200){
        //  完成添加后 重新加载列表数据
        this.queryData();
      }
      this.flag = false;
    }else{
      // 添加图书
      var ret = await axios.post('books', {
        name: this.name
      })
      if(ret.status == 200) {
        // 重新加载列表数据
        this.queryData();
      }
    }
    // 清空表单
    this.id = '';
    this.name = '';
  },
    toEdit: async function(id){
      //  flag状态位用于区分编辑和添加操作
      this.flag = true;
      //  根据id查询出对应的图书信息  页面中可以加载出来最新的信息
      // 调用接口发送ajax 请求  
      var ret = await axios.get('books/' + id);
      this.id = ret.id;
      this.name = ret.name;
    },
5.6.5删除图书 axios.get()★★★★

把需要删除的id书籍 通过参数的形式传递到后台

deleteBook: async function(id){
  // 删除图书
  var ret = await axios.delete('books/' + id);
  if(ret.status == 200) {
    // 重新加载列表数据
    this.queryData();
  }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7d3yYqtp-1605053467948)(./media/1e7e7e2177b810dac6e5b57b07baf15.png)]

6、路由

6.1 路由概念

6.1.1 什么是路由★★★★

路由的本质就是一种对应关系,比如说我们在url地址中输入我们要访问的url地址之后,浏览器要去请求这个url地址对应的资源。

那么url地址和真实的资源之间就有一种对应的关系,就是路由。

6.1.2 前端路由和后端路由★★★★
  1. 前端路由和后端路由的实现方式

    • 后端路由是由服务器端进行实现,并完成资源的分发
    • 前端路由是依靠hash值(锚链接)的变化进行实现
  2. 后端路由性能相对前端路由来说较低,所以,我们接下来主要学习的是前端路由

  3. 前端路由基本概念

    • 根据不同的事件来显示不同的页面内容,即事件与事件处理函数之间的对应关系
    • 前端路由主要做的事情就是监听事件并分发执行事件处理函数

6.2 路由初体验

6.2.1 前端路由实现★★★★

前端路由实现原理:

前端路由是基于hash值的变化进行实现的(比如点击页面中的菜单或者按钮改变URL的hash值,根据hash值的变化来控制组件的切换)
核心实现依靠一个事件,即监听hash值变化的事件

核心代码:

window.onhashchange = function(){
    //location.hash可以获取到最新的hash值
    location.hash
}

前端路由实现tab栏切换:

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3MUJmqlY-1605053467950)(./media/01前端路由.png)]

点击每个超链接之后,会进行相应的内容切换,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xbGP1pkp-1605053467954)(./media/01前端路由效果图.png)]

核心思路:

在页面中有一个vue实例对象,vue实例对象中有四个组件,分别是tab栏切换需要显示的组件内容
在页面中有四个超链接,如下:

<a href="#/zhuye">主页</a> 
<a href="#/keji">科技</a> 
<a href="#/caijing">财经</a>
<a href="#/yule">娱乐</a>

当我们点击这些超链接的时候,就会改变url地址中的hash值,当hash值被改变时,就会触发onhashchange事件
在触发onhashchange事件的时候,我们根据hash值来让不同的组件进行显示:

window.onhashchange = function() {
    // 通过 location.hash 获取到最新的 hash 值
    console.log(location.hash);
    switch(location.hash.slice(1)){
        case '/zhuye':
        //通过更改数据comName来指定显示的组件
        //因为 <component :is="comName"></component> ,组件已经绑定了comName
        vm.comName = 'zhuye'
        break
        case '/keji':
        vm.comName = 'keji'
        break
        case '/caijing':
        vm.comName = 'caijing'
        break
        case '/yule':
        vm.comName = 'yule'
        break
    }
}

代码演示:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <!-- 导入 vue 文件 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    </head>
    <body>
        <!-- 被 vue 实例控制的 div 区域 -->
        <div id="app">
            <!-- 切换组件的超链接 -->
            <a href="#/zhuye">主页</a> 
            <a href="#/keji">科技</a> 
            <a href="#/caijing">财经</a>
            <a href="#/yule">娱乐</a>
            <!-- 根据 :is 属性指定的组件名称,把对应的组件渲染到 component 标签所在的位置 -->
            <!-- 可以把 component 标签当做是【组件的占位符】 -->
            <component :is="comName"></component>
        </div>
        <script>
            // #region 定义需要被切换的 4 个组件
            // 主页组件
            const zhuye = {
                template: '<h1>主页信息</h1>'
            }
            // 科技组件
            const keji = {
                template: '<h1>科技信息</h1>'
            }
            // 财经组件
            const caijing = {
                template: '<h1>财经信息</h1>'
            }
            // 娱乐组件
            const yule = {
                template: '<h1>娱乐信息</h1>'
            }
            // #endregion
            // #region vue 实例对象
            const vm = new Vue({
                el: '#app',
                data: {
                    comName: 'zhuye'
                },
                // 注册私有组件
                components: {
                    zhuye,
                    keji,
                    caijing,
                    yule
                }
            })
            // #endregion
            // 监听 window 的 onhashchange 事件,根据获取到的最新的 hash 值,切换要显示的组件的名称
            window.onhashchange = function() {
                // 通过 location.hash 获取到最新的 hash 值
                console.log(location.hash);
                switch(location.hash.slice(1)){
                    case '/zhuye':
                        vm.comName = 'zhuye'
                        break
                    case '/keji':
                        vm.comName = 'keji'
                        break
                    case '/caijing':
                        vm.comName = 'caijing'
                        break
                    case '/yule':
                        vm.comName = 'yule'
                        break
                }
            }
        </script>
    </body>
</html>

6.3 Vue-Router介绍

它是一个Vue.js官方提供的路由管理器。是一个功能更加强大的前端路由器,推荐使用。
Vue Router和Vue.js非常契合,可以一起方便的实现SPA(single page web application,单页应用程序)应用程序的开发。
Vue Router依赖于Vue,所以需要先引入Vue,再引入Vue Router

6.3.1 有哪些特性★★★★★
  • 支持H5历史模式或者hash模式
  • 支持嵌套路由
  • 支持路由参数
  • 支持编程式路由
  • 支持命名路由
  • 支持路由导航守卫
  • 支持路由过渡动画特效
  • 支持路由懒加载
  • 支持路由滚动行为
6.3.2 使用步骤★★★★★
  1. 导入js文件

    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    
  2. 添加路由链接:<router-link>是路由中提供的标签,默认会被渲染为a标签,to属性默认被渲染为href属性,
    to属性的值会被渲染为#开头的hash地址

    <router-link to="/user">User</router-link>
    <router-link to="/login">Login</router-link>
    
  3. 添加路由填充位(路由占位符)

    <router-view></router-view>
    
  4. 定义路由组件

    var User = { template:"<div>This is User</div>" }
    var Login = { template:"<div>This is Login</div>" }
    
  5. 配置路由规则并创建路由实例

    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes:[
            //每一个路由规则都是一个对象,对象中至少包含path和component两个属性
            //path表示  路由匹配的hash地址,component表示路由规则对应要展示的组件对象
            {path:"/user",component:User},
            {path:"/login",component:Login}
        ]
    })
    
  6. 将路由挂载到Vue实例中

    new Vue({
        el:"#app",
        //通过router属性挂载路由对象
        router:myRouter
    })
    

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7d3FSnIT-1605053467957)(./media/1.gif)]

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user">User</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        template: '<h1>User 组件</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/user', component: User },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>
6.3.3 重定向★★★★

路由重定向:可以通过路由重定向为页面设置默认展示的组件

在路由规则中添加一条路由规则即可,如下:

var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //path设置为/表示页面最初始的地址 / ,redirect表示要被重定向的新地址,设置为一个路由即可
        { path:"/",redirect:"/user"},
        { path: "/user", component: User },
        { path: "/login", component: Login }
    ]
})

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="./lib/vue_2.5.22.js"></script>
    <script src="./lib/vue-router_3.0.2.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user">User</router-link>
      <router-link to="/register">Register</router-link>

      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        template: '<h1>User 组件</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user'},
          { path: '/user', component: User },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

效果和6.2.2案例效果图一样,区别在与6.2.2需要点击一下User才可以显示User组件;而重定向是打开浏览器后会直接显示User组件

6.4 嵌套路由

6.4.1 什么是嵌套路由★★★★
  1. 嵌套路由概念

    当我们进行路由的时候显示的组件中还有新的子级路由链接以及内容。

    嵌套路由最关键的代码在于理解子级路由的概念:

    比如我们有一个/login的路由
    那么/login下面还可以添加子级路由,如:
    /login/account
    /login/phone

  2. 嵌套路由案例

    案例效果图:

    页面效果大致如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0hVzwbo9-1605053467959)(./media/2.gif)]

    核心代码:

    // 创建路由实例对象
    const router = new VueRouter({
        // 所有的路由规则
        routes: [
            { path: '/', redirect: '/user'},
            { path: '/user', component: User },
            // children 数组表示子路由规则
            { path: '/register', component: Register, children: [
                { path: '/register/tab1', component: Tab1 },
                { path: '/register/tab2', component: Tab2 }
            ] }
        ]
    })
    

    完整代码演示:

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <meta http-equiv="X-UA-Compatible" content="ie=edge" />
        <title>Document</title>
        <!-- 导入 vue 文件 -->
        <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
      </head>
      <body>
        <!-- 被 vm 实例所控制的区域 -->
        <div id="app">
          <router-link to="/user">User</router-link>
          <router-link to="/register">Register</router-link>
          <!-- 路由占位符 -->
          <router-view></router-view>
        </div>
        <script>
          const User = {
            template: '<h1>User 组件</h1>'
          }
          const Register = {
            template: `<div>
              <h1>Register 组件</h1>
              <hr/>
              <!-- 子路由链接 -->
              <router-link to="/register/tab1">tab1</router-link>
              <router-link to="/register/tab2">tab2</router-link>
              <!-- 子路由的占位符 -->
              <router-view />
            <div>`
          }
          const Tab1 = {
            template: '<h3>tab1 子组件</h3>'
          }
          const Tab2 = {
            template: '<h3>tab2 子组件</h3>'
          }
          // 创建路由实例对象
          const router = new VueRouter({
            // 所有的路由规则
            routes: [
              { path: '/', redirect: '/user'},
              { path: '/user', component: User },
              // children 数组表示子路由规则
              { path: '/register', component: Register, children: [
                { path: '/register/tab1', component: Tab1 },
                { path: '/register/tab2', component: Tab2 }
              ] }
            ]
          })
          // 创建 vm 实例对象
          const vm = new Vue({
            // 指定控制的区域
            el: '#app',
            data: {},
            // 挂载路由实例对象
            // router: router
            router
          })
        </script>
      </body>
    </html>
    

6.5 动态路由

6.5.1 什么是动态路由★★★★

你可以在一个路由中设置多段“路径参数”,对应的值都会设置到$route.params中。例如:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L6azlMmk-1605053467961)(./media/04动态路由.png)]

核心代码演示:

var User = { template:"<div>用户:{{$route.params.id}}</div>"}
var myRouter = new VueRouter({
    //routes是路由规则数组
    routes: [
        //通过/:参数名  的形式传递参数 
        { path: "/user/:id", component: User },
    ]
})
6.5.2 多种实现方式★★★★
  1. 通过$route.params来获取路径参数

    var User = { template:"<div>用户:{{$route.params.id}}</div>"}
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            { path: "/user/:id", component: User },
        ]
    })
    
  2. 通过props来接收参数

    var User = { 
        props:["id"],
        template:"<div>用户:{{id}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为true,route.params将会被设置为组件属性
            { path: "/user/:id", component: User,props:true },
        ]
    })
    
  3. 我们可以将props设置为对象,那么就直接将对象的数据传递给组件进行使用

    var User = { 
        props:["username","pwd"],
        template:"<div>用户:{{username}}---{{pwd}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为对象,则传递的是对象中的数据给组件
            { path: "/user/:id", component: User,props:{username:"jack",pwd:123} },
        ]
    })
    
  4. 如果想要获取传递的参数值还想要获取传递的对象数据,那么props应该设置为函数形式

    var User = { 
        props:["username","pwd","id"],
        template:"<div>用户:{{id}} -> {{username}}---{{pwd}}</div>"
    }
    var myRouter = new VueRouter({
        //routes是路由规则数组
        routes: [
            //通过/:参数名  的形式传递参数 
            //如果props设置为函数,则通过函数的第一个参数获取路由对象
            //并可以通过路由对象的params属性获取传递的参数
            //
            { path: "/user/:id", component: User,props:(route)=>{
                return {username:"jack",pwd:123,id:route.params.id}
            } 
            },
        ]
    })
    

动态路由案例:

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f4K5kvbm-1605053467964)(./media/3.gif)]

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link to="/user/3">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.6 命名路由

6.6.1 什么是命名路由★★★

给路由取别名

命名路由案例:

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xS4FEfdx-1605053467967)(./media/3.gif)]

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
	  <!-- 添加了别名之后,可以使用别名进行跳转 -->
      <router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: '<h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>'
      }
      const Register = {
        template: '<h1>Register 组件</h1>'
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            // 命名路由: 通过name属性为路由添加一个别名
            name: 'user',
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.7 编程式导航

6.7.1 导航的方式有几种★★★★
  1. 声明式导航:通过点击链接的方式实现的导航

    <router-link to="/user">User1</router-link>
    
  2. 编程式导航:调用js的api方法实现导航

    this.$router.push( { name:'user' } )
    
6.7.2 编程式导航的实现★★★★
this.$router.push("hash地址");
this.$router.push("/login");
this.$router.push({ name:'user' , params: {id:123} });
this.$router.push({ path:"/login" });
this.$router.push({ path:"/login",query:{username:"jack"} });
this.$router.go( n ); //n为数字,参考history.go
this.$router.go( -1 );

编程式导航案例:

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p5ZznZlq-1605053467968)(./media/4.gif)]

代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 被 vm 实例所控制的区域 -->
    <div id="app">
      <router-link to="/user/1">User1</router-link>
      <router-link to="/user/2">User2</router-link>
      <router-link :to="{ name: 'user', params: {id: 3} }">User3</router-link>
      <router-link to="/register">Register</router-link>
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      const User = {
        props: ['id', 'uname', 'age'],
        template: `<div>
          <h1>User 组件 -- 用户id为: {{id}} -- 姓名为:{{uname}} -- 年龄为:{{age}}</h1>
          <button @click="goRegister">跳转到注册页面</button>
        </div>`,
        methods: {
          goRegister() {
            // 编程式导航  
            this.$router.push('/register')
          }
        },
      }
      const Register = {
        template: `<div>
          <h1>Register 组件</h1>
          <button @click="goBack">后退</button>
        </div>`,
        methods: {
          goBack() {
            // 编程式导航  
            this.$router.go(-1)
          }
        }
      }
      // 创建路由实例对象
      const router = new VueRouter({
        // 所有的路由规则
        routes: [
          { path: '/', redirect: '/user' },
          {
            // 命名路由
            name: 'user',
            path: '/user/:id',
            component: User,
            props: route => ({ uname: 'zs', age: 20, id: route.params.id })
          },
          { path: '/register', component: Register }
        ]
      })
      // 创建 vm 实例对象
      const vm = new Vue({
        // 指定控制的区域
        el: '#app',
        data: {},
        // 挂载路由实例对象
        // router: router
        router
      })
    </script>
  </body>
</html>

6.8 案例讲解-后台管理系统开发准备

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w7HGoJsu-1605053467970)(./media/5 (2)].gif)

6.8.1 案例需求★★★
  • 点击左侧的"用户管理",“权限管理”,“商品管理”,“订单管理”,"系统设置"都会出现对应的组件并展示内容

  • 其中"用户管理"组件展示的效果如上图所示,在用户管理区域中的详情链接也是可以点击的,点击之后将会显示用户详情信息。

6.8.2 布局方案★★★

div+css布局实现页面的自适应

6.8.3 技术选型★★★
  • 路由的基础用法
  • 嵌套路由
  • 路由重定向
  • 路由传参
  • 编程式导航
6.8.4 开发流程★★★
  1. 静态布局编写
  2. 引入vue,vue-router文件
  3. 抽离组件
  4. 功能实现
6.8.5 开发思路★★★★
  1. 根据效果图编写布局

  2. 在页面中引入vue,vue-router

    <!-- 导入 vue 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
    
  3. 创建Vue实例对象,准备开始编写代码实现功能

    const vm = new Vue({
        el: '#app',
        router
    })
    
  4. 抽离并渲染app根组件

    const App = {
    template: `<div>
        <!-- 头部区域 -->
        <header class="header">后台管理系统</header>
        <!-- 中间主体区域 -->
        <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
                <ul>
                    <li><a href="#">用户管理</a></li>
                    <li><a href="#">权限管理</a></li>
                    <li><a href="#">商品管理</a></li>
                    <li><a href="#">订单管理</a></li>
                    <li><a href="#">系统设置</a></li>
                </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right"><div class="main-content"> </div>
        </div>
        <!-- 尾部区域 -->
        <footer class="footer">版权信息</footer>
    </div>`
    }
    
  5. 将左侧菜单改为路由链接

    <!-- 左侧菜单栏 -->
    <div class="content left">
        <ul>
            <li><router-link to="/users">用户管理</router-link></li>
            <li><router-link to="/rights">权限管理</router-link></li>
            <li><router-link to="/goods">商品管理</router-link></li>
            <li><router-link to="/orders">订单管理</router-link></li>
            <li><router-link to="/settings">系统设置</router-link></li>
        </ul>
    </div>
    
  6. 创建左侧菜单对应的路由组件

    const Users = {
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>1</td>
                	<td>张三</td>
                	<td>20</td>
                    <td>
                    	<a href="javascript:;">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    const UserInfo = {
        props: ['id'],
        template: `<div>
    	<h5>用户详情页 --- 用户Id为:{{id}}</h5>
    	<button>后退</button>
    	</div>`
    }
    const Rights = {
        template: `<div>
        <h3>权限管理区域</h3>
        </div>`
    }
    const Goods = {
        template: `<div>
        <h3>商品管理区域</h3>
        </div>`
    }
    const Orders = {
        template: `<div>
    	<h3>订单管理区域</h3>
    	</div>`
    }
    const Settings = {
        template: `<div>
    	<h3>系统设置区域</h3>
    	</div>`
    }
    
  7. 在右侧主题区域添加路由占位符

    <!-- 右侧内容区域 -->
    <div class="content right"><div class="main-content">
        <router-view />
    </div>
    
  8. 添加子路由规则

    // 创建路由对象
    const router = new VueRouter({
        routes: [
            {
                path: '/',
                component: App,
                children: [
                    { path: '/users', component: Users },
                    { path: '/userinfo/:id', component: UserInfo, props: true },
                    { path: '/rights', component: Rights },
                    { path: '/goods', component: Goods },
                    { path: '/orders', component: Orders },
                    { path: '/settings', component: Settings }
                ]
            }
        ]
    })
    
  9. 通过路由重定向默认渲染用户组件

    // 创建路由对象
    const router = new VueRouter({
        routes: [
            {
                path: '/',
                component: App,
                redirect: '/users',
                children: [
                    { path: '/users', component: Users },
                    { path: '/userinfo/:id', component: UserInfo, props: true },
                    { path: '/rights', component: Rights },
                    { path: '/goods', component: Goods },
                    { path: '/orders', component: Orders },
                    { path: '/settings', component: Settings }
                ]
            }
        ]
    })
    
  10. 渲染用户列表数据

    const Users = {
        data() {
            return {
                userlist: [
                    { id: 1, name: '张三', age: 10 },
                    { id: 2, name: '李四', age: 20 },
                    { id: 3, name: '王五', age: 30 },
                    { id: 4, name: '赵六', age: 40 }
                ]
            }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>{{item.id}}</td>
                	<td>{{item.name}}</td>
                	<td>{{item.age}}</td>
                    <td>
                    	<a href="javascript:;" @click="goDetail(item.id)">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    
  11. 编程式导航实现路由详情

    const Users = {
        data() {
            return {
                userlist: [
                    { id: 1, name: '张三', age: 10 },
                    { id: 2, name: '李四', age: 20 },
                    { id: 3, name: '王五', age: 30 },
                    { id: 4, name: '赵六', age: 40 }
                ]
            }
        },
        methods: {
            goDetail(id) {
                console.log(id)
                this.$router.push('/userinfo/' + id)
            }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
            <thead>
            	<tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
            </thead>
            <tbody>
                <tr v-for="item in userlist" :key="item.id">
                	<td>{{item.id}}</td>
                	<td>{{item.name}}</td>
                	<td>{{item.age}}</td>
                    <td>
                    	<a href="javascript:;">详情</a>
                    </td>
                </tr>
            </tbody>
        </table>
        </div>`
    }
    
  12. 实现后退功能

    const UserInfo = {
        props: ['id'],
        template: `<div>
    	<h5>用户详情页 --- 用户Id为:{{id}}</h5>
    	<button @click="goback()">后退</button>
    	</div>`,
        methods: {
            goback() {
                // 实现后退功能
                this.$router.go(-1)
            }
        }
    }
    

6.9 案例讲解-后台管理系统功能实现

6.9.1 根组件创建 template★★★★★
const app = {
template:`<div>
    <!-- 头部区域 -->
    <header class="header">传智后台管理系统</header>
    <!-- 中间主体区域 -->
    <div class="main">
        <!-- 左侧菜单栏 -->
        <div class="content left">
            <ul>
                <li>用户管理</li>
                <li>权限管理</li>
                <li>商品管理</li>
                <li>订单管理</li>
                <li>系统设置</li>
            </ul>
        </div>
        <!-- 右侧内容区域 -->
        <div class="content right">
            <div class="main-content">添加用户表单</div>
        </div>
    </div>
    <!-- 尾部区域 -->
    <footer class="footer">版权信息</footer>
</div>`
}
6.9.2 默认显示根组件 routes★★★★★
const myRouter = new VueRouter({
    routes:[
        {path:"/",component:app}
    ]
})
const vm = new Vue({
    el:"#app",
    data:{},
    methods:{},
    router:myRouter
})

补充:到此为止,基本的js代码都处理完毕了,我们还需要设置一个路由占位符

<body>
  <div id="app">
    <router-view></router-view>
  </div>
</body>
6.9.3 跳转配置 router-link★★★★★
const app = {
    template:`<div>
        ........
        <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
                <ul>
                    <!-- 注意:我们把所有li都修改为了路由链接 -->
                    <li><router-link to="/users">用户管理</router-link></li>
                    <li><router-link to="/accesses">权限管理</router-link></li>
                    <li><router-link to="/goods">商品管理</router-link></li>
                    <li><router-link to="/orders">订单管理</router-link></li>
                    <li><router-link to="/systems">系统设置</router-link></li>
                </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right">
                <div class="main-content">
                    <!-- 在 -->
                    <router-view></router-view> 
                </div>
            </div>
        </div>
        .......
    </div>`
}
6.9.4 用户信息列表 data数据模拟★★★★★

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jsfZvi3v-1605053467971)(./media/list.png)]

代码演示:

userList:[
    {id:1,name:"zs",age:18},
    {id:2,name:"ls",age:19},
    {id:3,name:"wang",age:20},
    {id:4,name:"jack",age:21},
]
6.9.5 用户详情 传参★★★★★

案例效果图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A5VhPHMH-1605053467973)(./media/detail.png)]

代码演示:

const Users = {
    data(){
        return {
            userList:[
                {id:1,name:"zs",age:18},
                {id:2,name:"ls",age:19},
                {id:3,name:"wang",age:20},
                {id:4,name:"jack",age:21},
            ]
        }
    },
    template:`<div>
        <h3>用户管理</h3>
        <table>
            <thead>
                <tr>
                    <th>编号</th>
                    <th>姓名</th>
                    <th>年龄</th>
                    <th>操作</th>
                </tr>
            </thead>
            <tbody>
                <tr :key="item.id" v-for="item in userList">
                    <td>{{item.id}}</td>
                    <td>{{item.name}}</td>
                    <td>{{item.age}}</td>
                    <td><a href="javascript:;" @click="goDetail(item.id)">详情</a></td>
                </tr>
            </tbody>
        </table>
    </div>`,
    methods:{
        goDetail(id){
            this.$router.push("/userinfo/"+id);
        }
    }
}

完整代码演示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>基于vue-router的案例</title>
    <style type="text/css">
      html,
      body,
      #app {
        margin: 0;
        padding: 0px;
        height: 100%;
      }
      .header {
        height: 50px;
        background-color: #545c64;
        line-height: 50px;
        text-align: center;
        font-size: 24px;
        color: #fff;
      }
      .footer {
        height: 40px;
        line-height: 40px;
        background-color: #888;
        position: absolute;
        bottom: 0;
        width: 100%;
        text-align: center;
        color: #fff;
      }
      .main {
        display: flex;
        position: absolute;
        top: 50px;
        bottom: 40px;
        width: 100%;
      }
      .content {
        flex: 1;
        text-align: center;
        height: 100%;
      }
      .left {
        flex: 0 0 20%;
        background-color: #545c64;
      }
      .left a {
        color: white;
        text-decoration: none;
      }
      .right {
        margin: 5px;
      }
      .btns {
        width: 100%;
        height: 35px;
        line-height: 35px;
        background-color: #f5f5f5;
        text-align: left;
        padding-left: 10px;
        box-sizing: border-box;
      }
      button {
        height: 30px;
        background-color: #ecf5ff;
        border: 1px solid lightskyblue;
        font-size: 12px;
        padding: 0 20px;
      }
      .main-content {
        margin-top: 10px;
      }
      ul {
        margin: 0;
        padding: 0;
        list-style: none;
      }
      ul li {
        height: 45px;
        line-height: 45px;
        background-color: #a0a0a0;
        color: #fff;
        cursor: pointer;
        border-bottom: 1px solid #fff;
      }
      table {
        width: 100%;
        border-collapse: collapse;
      }
      td,
      th {
        border: 1px solid #eee;
        line-height: 35px;
        font-size: 12px;
      }
      th {
        background-color: #ddd;
      }
    </style>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
	<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>
  </head>
  <body>
    <!-- 要被 vue 实例所控制的区域 -->
    <div id="app">
      <!-- 路由占位符 -->
      <router-view></router-view>
    </div>
    <script>
      // 定义 APP 根组件
      const App = {
        template: `<div>
          <!-- 头部区域 -->
          <header class="header">后台管理系统</header>
          <!-- 中间主体区域 -->
          <div class="main">
            <!-- 左侧菜单栏 -->
            <div class="content left">
              <ul>
                <li><router-link to="/users">用户管理</router-link></li>
                <li><router-link to="/rights">权限管理</router-link></li>
                <li><router-link to="/goods">商品管理</router-link></li>
                <li><router-link to="/orders">订单管理</router-link></li>
                <li><router-link to="/settings">系统设置</router-link></li>
              </ul>
            </div>
            <!-- 右侧内容区域 -->
            <div class="content right"><div class="main-content">
              <router-view />
            </div></div>
          </div>
          <!-- 尾部区域 -->
          <footer class="footer">版权信息</footer>
        </div>`
      }
      const Users = {
        data() {
          return {
            userlist: [
              { id: 1, name: '张三', age: 10 },
              { id: 2, name: '李四', age: 20 },
              { id: 3, name: '王五', age: 30 },
              { id: 4, name: '赵六', age: 40 }
            ]
          }
        },
        methods: {
          goDetail(id) {
            console.log(id)
            this.$router.push('/userinfo/' + id)
          }
        },
        template: `<div>
        <h3>用户管理区域</h3>
        <table>
          <thead>
            <tr><th>编号</th><th>姓名</th><th>年龄</th><th>操作</th></tr>
          </thead>
          <tbody>
            <tr v-for="item in userlist" :key="item.id">
              <td>{{item.id}}</td>
              <td>{{item.name}}</td>
              <td>{{item.age}}</td>
              <td>
                <a href="javascript:;" @click="goDetail(item.id)">详情</a>
              </td>
            </tr>
          </tbody>
        </table>
      </div>`
      }
      const UserInfo = {
        props: ['id'],
        template: `<div>
          <h5>用户详情页 --- 用户Id为:{{id}}</h5>
          <button @click="goback()">后退</button>
        </div>`,
        methods: {
          goback() {
            // 实现后退功能
            this.$router.go(-1)
          }
        }
      }
      const Rights = {
        template: `<div>
        <h3>权限管理区域</h3>
      </div>`
      }
      const Goods = {
        template: `<div>
        <h3>商品管理区域</h3>
      </div>`
      }
      const Orders = {
        template: `<div>
        <h3>订单管理区域</h3>
      </div>`
      }
      const Settings = {
        template: `<div>
        <h3>系统设置区域</h3>
      </div>`
      }
      // 创建路由对象
      const router = new VueRouter({
        routes: [
          {
            path: '/',
            component: App,
            redirect: '/users',
            children: [
              { path: '/users', component: Users },
              { path: '/userinfo/:id', component: UserInfo, props: true },
              { path: '/rights', component: Rights },
              { path: '/goods', component: Goods },
              { path: '/orders', component: Orders },
              { path: '/settings', component: Settings }
            ]
          }
        ]
      })
      const vm = new Vue({
        el: '#app',
        router
      })
    </script>
  </body>
</html>

7、前端工程化

7.1 模块化分类

传统开发模式主要有命名冲突和文件依赖的问题。

模块化是一个语言膨胀的必经之路,它能够帮助开发者拆分和组织代码。

模块化就是把单独的一个功能封装到一个模块(文件)中,模块之间相互隔离,但是可以通过特定的接口公开内部成员,也可以依赖别的模块。

模块化开发的好处:方便代码的重用,从而提升开发效率,并且方便后期的维护

7.1.1 浏览器端的模块化★★★

浏览器端的模块话我们主要探讨AMD和CMD两种规范

AMD

Asynchronous Module Definition,异步模块定义,

// 定义AMD规范的模块
define([function() {
  return 模块
})

AMD规范的被依赖模块是异步加载的,而定义的模块是被当作回调函数来执行的,依赖于require.js模块管理工具库,当然,AMD规范不是采用匿名函数自调用的方式来封装,我们依然可以利用闭包的原理来实现模块的私有成员和公有成员:

define(['module1', 'module2'], function(m1, m2) {
  let x = 1;
  function add() {
    x += 1;
    return x;
  }
  return { add };
})

CMD

CMD 是 SeaJS 在推广过程中对模块定义的规范化产出。AMD 推崇依赖前置,CMD 推崇依赖就近。

define(function(require, exports, module) {
  //  同步加载模块
  var a = require('./a');
  a.doSomething();
  // 异步加载一个模块,在加载完成时,执行回调
  require.async(['./b'], function(b) {
    b.doSomething();
  });
  // 对外暴露成员
  exports.doSomething = function() {};
});
// 使用模块
seajs.use('path');

CMD加载完某个依赖模块后并不执行,只是下载而已,在所有依赖模块加载完成后进入主逻辑,遇到require语句的时候才执行对应的模块,这样模块的执行顺序和书写顺序是完全一致的。

因此,在CMD中require函数同步加载模块时没有HTTP请求过程。

7.1.2 服务器端的模块化★★★
  • 服务器端的模块化规范是使用CommonJS规范:
    • 使用require引入其他模块或者包
    • 使用exports或者module.exports导出模块成员
    • 一个文件就是一个模块,都拥有独立的作用域
// 文件名:x.js
let x = 1;
function add() {
  x += 1;
  return x;
}
module.exports.x = x;
module.exports.add = add;

CommonJS通过require()引入模块依赖,require函数可以引入Node的内置模块、自定义模块和npm等第三方模块。

// 文件名:main.js
let xm = require('./x.js');
console.log(xm.x);  // 1
console.log(xm.add());  // 2
console.log(xm.x);   // 1

require函数同步加载了x.js,并且返回了module.exports输出字面量的拷贝值

7.1.3 ES6模块化★★★
  • ES6模块化规范中定义:
    • 每一个js文件都是独立的模块
    • 导入模块成员使用import关键字
    • 暴露模块成员使用export关键字
//index.js
export default {
  username: "liguanh",
  age;18
}

//导入对象
import object from "./index"
console.log(object);

ES6的模块化已经不是规范了,而是JS语言的特性。随着ES6的推出,AMD和CMD也随之成为了历史。ES6模块与模块化规范相比,有两大特点:

  • 模块化规范输出的是一个值的拷贝,ES6 模块输出的是值的引用。
  • 模块化规范是运行时加载,ES6 模块是编译时输出接口。

小结: 推荐使用ES6模块化,因为AMD,CMD局限使用与浏览器端,而CommonJS在服务器端使用。
ES6模块化是浏览器端和服务器端通用的规范.

7.2 nodeJS中安装babel体验ES6模块化

7.2.1 安装babel★★★★
  • 打开终端或者gitbash窗口,在终端中输入如下命令:

    npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/node
    
  • 安装完毕之后,再次输入命令安装:

    npm install --save @babel/polyfill
    
7.2.2 创建babel.config.js★★★★
  • 在项目目录中创建babel.config.js文件。编辑js的代码如下:

    const presets = [
                ["@babel/env",{
                    targets:{
                        edge:"17",
                        firefox:"60",
                        chrome:"67",
                        safari:"11.1"
                    }
                }]
            ]
    //暴露模块化的数据
    module.exports = { presets }
    
7.2.3 创建index.js文件

在项目目录中创建index.js文件作为入口文件
在index.js中输入需要执行的js代码,例如:

for(var i=0;i<10;i++){
  console.log("hello world");
}
7.2.4 使用npx执行★★★★
  • 打开终端或者gitbash执行如下的命令:

    npx babel-node ./index.js
    

7.3 设置默认的导入导出

在实际代码中,我们通过export关键字是能够对外暴露本模块中的变量对象,函数,类的,尽管使用如下方式export向外暴露多个变量对象,后面跟一大括号,变量名与变量名之间用逗号隔开:

exprot {identifier1,identifier2,...}

但是问题来了,如果我不想写这些变量对象呢?那么可以使用default关键字指定单个变量,函数或者类,但是要格外注意一点就是每个模块只能设置一个默认的导出值。那下面我们来看一下默认导入和导出具体怎么使用。

7.3.1 默认导出★★★★
export default {
        成员A,
        成员B,
        .......
    },
//导出信息如下:
let num = 100;
export default{
        num
  }
7.3.2 默认导入★★★★

导入的话我们需要使用import关键字:

import 接收名称对象 from "模块标识符",如下:
import test from "./test.js"

**注意:**在一个模块中,只允许使用export default向外默认暴露一次成员,千万不要写多个export default。
如果在一个模块中没有向外暴露成员,其他模块引入该模块时将会得到一个空对象

这里还要注意一点就是:若是使用export default的方式默认导出,此处的sub就不要加{}双大括号,否则就会报错。

7.4 设置按需导入导出

7.4.1 按需导出★★★★★
var name = "随笔川迹";
var age = 18;
var weChatPublic = "itclanCoder";

function sub(num1,num2){
  return num1-num2;
}

export default {
   name,
   age,
   weChatPublic,
   sub
}
7.4.2 按需导入★★★★★
import { name,age,weChatPublic,sub} from "./test.js"
//同时导入默认导出的成员以及按需导入的成员
import { name,age as uname ,uage } from "./test.js"

注意:一个模块中既可以按需导入也可以默认导入,一个模块中既可以按需导出也可以默认导出

7.5 直接导入并执行代码

7.5.1 import★★★★★

有时候,我们只想单纯执行某个模块中的代码,并不需要得到模块中向外暴露的成员,此时,可以直接导入并执行模块代码。

// 直接导入并执行模块代码
import './m2.js'
// 当前文件模块为 m2.js
// 在当前模块中执行一个 for 循环操作
for(let i = 0; i < 3; i++) {
console.log(i)
}

7.6 webpack

7.6.1 概念★★★★★

webpack是一个流行的前端项目构建工具,可以解决目前web开发的困境。
webpack提供了模块化支持,代码压缩混淆,解决js兼容问题,性能优化等特性,提高了开发效率和项目的可维护性

7.6.2 webpack基本使用★★★★★

创建项目目录并初始化

创建项目,并打开项目所在目录的终端,输入命令:

npm init -y

创建首页及js文件

在项目目录中创建index.html页面,并初始化页面结构:在页面中摆放一个ul,ul里面放置几个li
在项目目录中创建js文件夹,并在文件夹中创建index.js文件

安装jQuery

打开项目目录终端,输入命令:

npm install jQuery -S

导入jQuery

打开index.js文件,编写代码导入jQuery并实现功能:

import $ from "jquery";
$(function() {
  $("li:odd").css("background", "cyan");
  $("li:odd").css("background", "pink");
});

**注意:**此时项目运行会有错误,因为import $ from “jquery”;这句代码属于ES6的新语法代码,在浏览器中可能会存在兼容性问题
所以我们需要webpack来帮助我们解决这个问题。

安装webpack

  • 打开项目目录终端,输入命令:

    npm install webpack webpack-cli -D
    
  • 然后在项目根目录中,创建一个 webpack.config.js 的配置文件用来配置webpack
    在 webpack.config.js 文件中编写代码进行webpack配置,如下:

    module.exports = {
            mode:"development"//可以设置为development(开发模式),production(发布模式)
        }
    

    补充:mode设置的是项目的编译模式。
    如果设置为development则表示项目处于开发阶段,不会进行压缩和混淆,打包速度会快一些
    如果设置为production则表示项目处于上线发布阶段,会进行压缩和混淆,打包速度会慢一些

  • 修改项目中的package.json文件添加运行脚本dev,如下:

    "scripts":{
            "dev":"webpack"
    }
    

    注意:scripts节点下的脚本,可以通过 npm run 运行,如:
    运行终端命令:npm run dev
    将会启动webpack进行项目打包

  • 运行dev命令进行项目打包,并在页面中引入项目打包生成的js文件

    打开项目目录终端,输入命令:
    npm run dev
    等待webpack打包完毕之后,找到默认的dist路径中生成的main.js文件,将其引入到html页面中。
    浏览页面查看效果。

  • 设置webpack的打包入口/出口

    在webpack 4.x中,默认会将src/index.js 作为默认的打包入口js文件
    默认会将dist/main.js 作为默认的打包输出js文件
    如果不想使用默认的入口/出口js文件,我们可以通过改变 webpack.config.js 来设置入口/出口的js文件,如下:

    module.exports = {
            mode:"development",
            //设置入口文件路径
            entry: path.join(__dirname,"./src/xx.js"),
            //设置出口文件
            output:{
                //设置路径
                path:path.join(__dirname,"./dist"),
                //设置文件名
                filename:"res.js"
            }
    }
    
  • 设置webpack的自动打包

    默认情况下,我们更改入口js文件的代码,需要重新运行命令打包webpack,才能生成出口的js文件
    那么每次都要重新执行命令打包,这是一个非常繁琐的事情,那么,自动打包可以解决这样繁琐的操作。
    实现自动打包功能的步骤如下:

    • 安装自动打包功能的包:webpack-dev-server
      npm install webpack-dev-server -D

    • 修改package.json中的dev指令如下:

      "scripts":{
          "dev":"webpack-dev-server"
       }
      
    • 将引入的js文件路径更改为:

      <script src="/bundle.js"></script>
      
    • 运行npm run dev,进行打包

    • 打开网址查看效果:http://localhost:8080

      **注意:**webpack-dev-server自动打包的输出文件,默认放到了服务器的根目录中.

      补充:
      在自动打包完毕之后,默认打开服务器网页,实现方式就是打开package.json文件,修改dev命令:
      "dev": "webpack-dev-server --open --host 127.0.0.1 --port 9999"

  • 配置html-webpack-plugin

    使用html-webpack-plugin 可以生成一个预览页面。
    因为当我们访问默认的 http://localhost:8080/的时候,看到的是一些文件和文件夹,想要查看我们的页面
    还需要点击文件夹点击文件才能查看,那么我们希望默认就能看到一个页面,而不是看到文件夹或者目录。
    实现默认预览页面功能的步骤如下:

    • 安装默认预览功能的包:html-webpack-plugin

      npm install html-webpack-plugin -D

    • 修改webpack.config.js文件,如下:

       //导入包
       const HtmlWebpackPlugin = require("html-webpack-plugin");
       //创建对象
       const htmlPlugin = new HtmlWebpackPlugin({
       			//设置生成预览页面的模板文件
           template:"./src/index.html",
           //设置生成的预览页面名称
           filename:"index.html"
       })
      
    • 继续修改webpack.config.js文件,添加plugins信息:

      module.exports = {
                      ......
                      plugins:[ htmlPlugin ]
                  }
      
  • webpack中的加载器

    通过loader打包非js模块:默认情况下,webpack只能打包js文件,如果想要打包非js文件,需要调用loader加载器才能打包

    • loader加载器包含:
      • less-loader
      • sass-loader
      • url-loader:打包处理css中与url路径有关的文件
      • babel-loader:处理高级js语法的加载器
      • postcss-loader
      • css-loader,style-loader

    **注意:**指定多个loader时的顺序是固定的,而调用loader的顺序是从后向前进行调用

    • 安装style-loader,css-loader来处理样式文件

      • 安装包
        npm install style-loader css-loader -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        }
                    ]
                }
            }
        
    • 安装less,less-loader处理less文件

      • 安装包
        npm install less-loader less -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        },
                        {
                            test:/\.less$/,
                            use:['style-loader','css-loader','less-loader']
                        }
                    ]
                }
            }
        
    • 安装sass-loader,node-sass处理less文件

      • 安装包
        npm install sass-loader node-sass -D

      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
                ......
                plugins:[ htmlPlugin ],
                module : {
                    rules:[
                        {
                            //test设置需要匹配的文件类型,支持正则
                            test:/\.css$/,
                            //use表示该文件类型需要调用的loader
                            use:['style-loader','css-loader']
                        },
                        {
                            test:/\.less$/,
                            use:['style-loader','css-loader','less-loader']
                        },
                        {
                            test:/\.scss$/,
                            use:['style-loader','css-loader','sass-loader']
                        }
                    ]
                }
            }
        

        补充:安装sass-loader失败时,大部分情况是因为网络原因,详情参考:
        https://segmentfault.com/a/1190000010984731?utm_source=tag-newest

    • 安装post-css自动添加css的兼容性前缀(-ie-,-webkit-)

      • 安装包
        npm install postcss-loader autoprefixer -D

      • 在项目根目录创建并配置postcss.config.js文件

        const autoprefixer = require("autoprefixer");
        module.exports = {
            plugins:[ autoprefixer ]
        }
        
      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
            ......
            plugins:[ htmlPlugin ],
            module : {
                rules:[
                    {
                        //test设置需要匹配的文件类型,支持正则
                        test:/\.css$/,
                        //use表示该文件类型需要调用的loader
                        use:['style-loader','css-loader','postcss-loader']
                    },
                    {
                        test:/\.less$/,
                        use:['style-loader','css-loader','less-loader']
                    },
                    {
                        test:/\.scss$/,
                        use:['style-loader','css-loader','sass-loader']
                    }
                ]
            }
        }
        
    • 打包样式表中的图片以及字体文件
      在样式表css中有时候会设置背景图片和设置字体文件,一样需要loader进行处理
      使用url-loader和file-loader来处理打包图片文件以及字体文件

      • 安装包
        npm install url-loader file-loader -D
      • 配置规则:更改webpack.config.js的module中的rules数组
      module.exports = {
          ......
          plugins:[ htmlPlugin ],
          module : {
              rules:[
                  {
                      //test设置需要匹配的文件类型,支持正则
                      test:/\.css$/,
                      //use表示该文件类型需要调用的loader
                      use:['style-loader','css-loader']
                  },
                  {
                      test:/\.less$/,
                      use:['style-loader','css-loader','less-loader']
                  },
                  {
                      test:/\.scss$/,
                      use:['style-loader','css-loader','sass-loader']
                  },{
                      test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
                      //limit用来设置字节数,只有小于limit值的图片,才会转换
                      //为base64图片
                      use:"url-loader?limit=16940"
                  }
              ]
          }
      }
      
    • 打包js文件中的高级语法:在编写js的时候,有时候我们会使用高版本的js语法
      有可能这些高版本的语法不被兼容,我们需要将之打包为兼容性的js代码
      我们需要安装babel系列的包

      • 安装babel转换器
        npm install babel-loader @babel/core @babel/runtime -D

      • 安装babel语法插件包

        ```shell
        

        npm install @babel/preset-env @babel/plugin-transform-runtime @babel/plugin-proposal-class-properties -D
        ```

      • 在项目根目录创建并配置babel.config.js文件

        module.exports = {
                presets:["@babel/preset-env"],
                plugins:[ "@babel/plugin-transform-runtime", "@babel/plugin-proposal-class-properties" ]
            }
        
      • 配置规则:更改webpack.config.js的module中的rules数组

        module.exports = {
            ......
            plugins:[ htmlPlugin ],
            module : {
                rules:[
                    {
                        //test设置需要匹配的文件类型,支持正则
                        test:/\.css$/,
                        //use表示该文件类型需要调用的loader
                        use:['style-loader','css-loader']
                    },
                    {
                        test:/\.less$/,
                        use:['style-loader','css-loader','less-loader']
                    },
                    {
                        test:/\.scss$/,
                        use:['style-loader','css-loader','sass-loader']
                    },{
                        test:/\.jpg|png|gif|bmp|ttf|eot|svg|woff|woff2$/,
                        //limit用来设置字节数,只有小于limit值的图片,才会转换
                        //为base64图片
                        use:"url-loader?limit=16940"
                    },{
                        test:/\.js$/,
                        use:"babel-loader",
                        //exclude为排除项,意思是不要处理node_modules中的js文件
                        exclude:/node_modules/
                    }
                ]
            }
        }
        
7.6.3 webpack中使用Vue★★★★

上一节我们安装处理了vue单文件组件的加载器,想要让vue单文件组件能够使用,我们必须要安装vue
并使用vue来引用vue单文件组件。

  • 安装Vue
    npm install vue -S

  • 在index.js中引入vue:import Vue from "vue"

  • 创建Vue实例对象并指定el,最后使用render函数渲染单文件组件

    const vm = new Vue({
            el:"#first",
            render:h=>h(app)
        })
    
7.6.4 webpack打包发布★★★★

在项目上线之前,我们需要将整个项目打包并发布。

  • 配置package.json

    "scripts":{
            "dev":"webpack-dev-server",
            "build":"webpack -p"
    }
    
  • 在项目打包之前,可以将dist目录删除,生成全新的dist目录

7.7 Vue脚手架

7.7.1 什么是脚手架★★★★★

Vue脚手架可以快速生成Vue项目基础的架构。

7.7.2 脚手架的基本使用★★★★★
  • 安装3.x版本的Vue脚手架:
    npm install -g @vue/cli
  • 基于3.x版本的脚手架创建Vue项目:
    • 使用命令创建Vue项目
      • 命令:vue create my-project
      • 选择Manually select features(选择特性以创建项目)
      • 勾选特性可以用空格进行勾选。
      • 是否选用历史模式的路由:n
      • ESLint选择:ESLint + Standard config
      • 何时进行ESLint语法校验:Lint on save
      • babel,postcss等配置文件如何放置:In dedicated config files(单独使用文件进行配置)
      • 是否保存为模板:n
      • 使用哪个工具安装包:npm
7.7.3 图形化方式创建Vue项目★★★★
  • 命令:vue ui
    在自动打开的创建项目网页中配置项目信息。
7.7.4 2X旧创建创建旧版Vue项目★★★★
npm install -g @vue/cli-init
vue init webpack my-project
7.7.5 脚手架生成的项目结构分析★★★★
  • node_modules:依赖包目录
  • public:静态资源目录
  • src:源码目录
  • src/assets:资源目录
  • src/components:组件目录
  • src/views:视图组件目录
  • src/App.vue:根组件
  • src/main.js:入口js
  • src/router.js:路由js
  • babel.config.js:babel配置文件
7.7.6 脚手架的自定义配置★★★★
  • 通过 package.json 进行配置 [不推荐使用]

    "vue":{
                "devServer":{
                    "port":"9990",
                    "open":true
                }
       }
    
  • 通过单独的配置文件进行配置,创建vue.config.js

    module.exports = {
                devServer:{
                    port:8888,
                    open:true
                }
     }
    

7.8 ElementUI的基本使用

Element-UI:一套基于2.0的桌面端组件库

7.8.1 安装★★★★
npm install element-ui -S
7.8.2 全局引入★★★★
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI)
7.8.3 局部引入★★★★

借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。

首先,安装 babel-plugin-component:

npm install babel-plugin-component -D

然后,将 .babelrc 修改为:

{
  "presets": [["es2015", { "modules": false }]],
  "plugins": [
    [
      "component",
      {
        "libraryName": "element-ui",
        "styleLibraryName": "theme-chalk"
      }
    ]
  ]
}
7.8.4 组件使用方法演示★★★★★

接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:

import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';

Vue.component(Button.name, Button);
Vue.component(Select.name, Select);
/* 或写为
 * Vue.use(Button)
 * Vue.use(Select)
 */

new Vue({
  el: '#app',
  render: h => h(App)
});
<el-row>
  <el-col :span="24"><div class="grid-content bg-purple-dark"></div></el-col>
</el-row>
<el-row>
  <el-col :span="12"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="12"><div class="grid-content bg-purple-light"></div></el-col>
</el-row>
<el-row>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple-light"></div></el-col>
  <el-col :span="8"><div class="grid-content bg-purple"></div></el-col>
</el-row>

8、Vuex

8.1Vuex概述

8.1.1Vuex概念★★★★

Vuex是实现组件全局状态(数据)管理的一种机制,可以方便的实现组件之间的数据共享

使用Vuex管理数据的好处:

  • 能够在vuex中集中管理共享的数据,便于开发和后期进行维护
  • 能够高效的实现组件之间的数据共享,提高开发效率
  • 存储在vuex中的数据是响应式的,当数据发生改变时,页面中的数据也会同步更新
8.1.2Vuex和组件通信对比★★★★

使用Vuex来管理数据共享,各组件无需关注组件间的数据通信传输,一切数据的读取和更新都是各组件与Vuex数据仓库间的操作,避免了复杂项目中数据管理混乱的情况发生

8.1.3Vuex使用场景★★★★★

涉及到非父子关系的组件,例如兄弟关系、祖孙关系,甚至更远的关系组件之间的联系

中大型单页应用,考虑如何更好地在组件外部管理状态

8.2Vuex的基本使用

8.2.1安装★★★★★
npm i vuex -s
8.2.2语法★★★★★
import Vue from 'vue'
import Vuex from 'vuex'

//挂载Vuex
Vue.use(Vuex)

//创建VueX对象
const store = new Vuex.Store({
    state:{
        //存放的键值对就是所要管理的状态
        name:'helloVueX'
    }
})

export default store
8.2.3Vuex完成计数器案例★★★★★

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5VleoyB7-1605053467979)(./media/1594632811(1)].png)

打开刚刚创建的vuex项目,找到src目录中的App.vue组件,将代码重新编写如下:

<template>
  <div>
    <my-addition></my-addition>

    <p>----------------------------------------</p>

    <my-subtraction></my-subtraction>
  </div>
</template>

<script>
import Addition from './components/Addition.vue'
import Subtraction from './components/Subtraction.vue'

export default {
  data() {
    return {}
  },
  components: {
    'my-subtraction': Subtraction,
    'my-addition': Addition
  }
}
</script>

<style>
</style>

在components文件夹中创建Addition.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>+1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

在components文件夹中创建Subtraction.vue组件,代码如下:

<template>
    <div>
        <h3>当前最新的count值为:</h3>
        <button>-1</button>
    </div>
</template>

<script>
export default {
  data() {
    return {}
  }
}
</script>

<style>
</style>

最后在项目根目录(与src平级)中创建 .prettierrc 文件,编写代码如下:

{
    "semi":false,
    "singleQuote":true
}

8.3Vuex中的核心特性

8.3.1State★★★★★

vuex中的数据源,我们需要保存的数据就保存在这里,可以在页面通过 this.$store.state来获取我们定义的数据;

8.3.2Mutation★★★★★

mutations是操作state数据的方法的集合,比如对该数据的修改、增加、删除等等。

8.3.3Action★★★★★

由于直接在mutation方法中进行异步操作,将会引起数据失效。所以提供了Actions来专门进行异步操作,最终提交mutation方法。

Actions中的方法有两个默认参数

  • context 上下文(相当于箭头函数中的this)对象
  • payload 挂载参数。
8.3.4Getter★★★★★

可以对state中的成员加工后传递给外界

Getters中的方法有两个默认参数

  • state 当前VueX对象中的状态对象
  • getters 当前getters对象,用于将getters下的其他getter拿来用
8.3.5Module★★★★

当项目庞大,状态非常多时,可以采用模块化管理模式。Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

8.4Vuex案例

8.4.1初始化案例State★★★★★

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Kn2imoL-1605053467981)(./media/1594632918(1)].jpg)

首先使用vue ui初始化一个使用vuex的案例
然后打开public文件夹,创建一个list.json文件,文件代码如下:

[
    {
        "id": 0,
        "info": "Racing car sprays burning fuel into crowd.",
        "done": false
    },
    {
        "id": 1,
        "info": "Japanese princess to wed commoner.",
        "done": false
    },
    {
        "id": 2,
        "info": "Australian walks 100km after outback crash.",
        "done": false
    },
    {
        "id": 3,
        "info": "Man charged over missing wedding girl.",
        "done": false
    },
    {
        "id": 4,
        "info": "Los Angeles battles huge wildfires.",
        "done": false
    }
]

再接着,打开main.js,添加store.js的引入,如下:

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

// 1. 导入 ant-design-vue 组件库
import Antd from 'ant-design-vue'
// 2. 导入组件库的样式表
import 'ant-design-vue/dist/antd.css'

Vue.config.productionTip = false
// 3. 安装组件库
Vue.use(Antd)

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

再接着打开store.js,添加axios请求json文件获取数据的代码,如下:

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    //所有任务列表
    list: [],
    //文本输入框中的值
    inputValue: 'AAA'
  },
  mutations: {
    initList(state, list) {
      state.list = list
    },
    setInputValue(state,value){
      state.inputValue = value
    }
  },
  actions: {
    getList(context) {
      axios.get('/list.json').then(({ data }) => {
        console.log(data);
        context.commit('initList', data)
      })
    }
  }
})

最后,代开App.vue文件,将store中的数据获取并展示:

<template>
  <div id="app">
    <a-input placeholder="请输入任务" class="my_ipt" :value="inputValue" @change="handleInputChange" />
    <a-button type="primary">添加事项</a-button>

    <a-list bordered :dataSource="list" class="dt_list">
      <a-list-item slot="renderItem" slot-scope="item">
        <!-- 复选框 -->
        <a-checkbox :checked="item.done">{{item.info}}</a-checkbox>
        <!-- 删除链接 -->
        <a slot="actions">删除</a>
      </a-list-item>

      <!-- footer区域 -->
      <div slot="footer" class="footer">
        <!-- 未完成的任务个数 -->
        <span>0条剩余</span>
        <!-- 操作按钮 -->
        <a-button-group>
          <a-button type="primary">全部</a-button>
          <a-button>未完成</a-button>
          <a-button>已完成</a-button>
        </a-button-group>
        <!-- 把已经完成的任务清空 -->
        <a>清除已完成</a>
      </div>
    </a-list>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'app',
  data() {
    return {
      // list:[]
    }
  },
  created(){
    // console.log(this.$store);
    this.$store.dispatch('getList')
  },
  methods:{
    handleInputChange(e){
      // console.log(e.target.value)
      this.$store.commit('setInputValue',e.target.value)
    }
  },
  computed:{
    ...mapState(['list','inputValue'])
  }
}
</script>

<style scoped>
#app {
  padding: 10px;
}

.my_ipt {
  width: 500px;
  margin-right: 10px;
}

.dt_list {
  width: 500px;
  margin-top: 10px;
}

.footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
}
</style>
8.4.2完成添加事项Mutation★★★★★

首先,打开App.vue文件,给“添加事项”按钮绑定点击事件,编写处理函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pOdOljHh-1605053467983)(./media/1594632918(1)].jpg)

//绑定事件
<a-button type="primary" @click="addItemToList">添加事项</a-button>

//编写事件处理函数
methods:{
    ......
    addItemToList(){
      //向列表中新增事项
      if(this.inputValue.trim().length <= 0){
        return this.$message.warning('文本框内容不能为空')
      }

      this.$store.commit('addItem')
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  state: {
    //所有任务列表
    list: [],
    //文本输入框中的值
    inputValue: 'AAA',
    //下一个id
    nextId:5
  },
  mutations: {
    ........
    //添加列表项
    addItem(state){
      const obj = {
        id :state.nextId,
        info: state.inputValue.trim(),
        done:false
      }
      //将创建好的事项添加到数组list中
      state.list.push(obj)
      //将nextId值自增
      state.nextId++
      state.inputValue = ''
    }
  }
  ......
})
8.4.3完成删除事项Mutation★★★★★

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W6dZNwfZ-1605053467984)(./media/1594632918(1)].jpg)

首先,打开App.vue文件,给“删除”按钮绑定点击事件,编写处理函数

//绑定事件
<a slot="actions" @click="removeItemById(item.id)">删除</a>

//编写事件处理函数
methods:{
    ......
    removeItemById(id){
      //根据id删除事项
      this.$store.commit('removeItem',id)
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    removeItem(state,id){
      //根据id删除事项数据
      const index = state.list.findIndex( x => x.id === id )
      // console.log(index);
      if(index != -1) state.list.splice(index,1);
    }
  }
  ......
})
8.4.4完成选中状态的改变Action★★★★★

首先,打开App.vue文件,给“复选”按钮绑定点击事件,编写处理函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fGzxVr0L-1605053467986)(./media/1594632918(1)].jpg)

//绑定事件
<a-checkbox :checked="item.done" @change="cbStateChanged(item.id,$event)">{{item.info}}</a-checkbox>

//编写事件处理函数
methods:{
    ......
    cbStateChanged(id,e){
      //复选框状态改变时触发
      const param = {
        id:id,
        status:e.target.checked
      }

      //根据id更改事项状态
      this.$store.commit('changeStatus',param)
    }
  }

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    changeStatus(state,param){
      //根据id改变对应事项的状态
      const index = state.list.findIndex( x => x.id === param.id )
      if(index != -1) state.list[index].done = param.status
    }
  }
  ......
})
8.4.5剩余项统计Getter★★★★★

打开store.js,添加getters完成剩余项统计

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iTAsCZ1m-1605053467987)(./media/1594632918(1)].jpg)

getters:{
  unDoneLength(state){
    const temp = state.list.filter( x => x.done === false )
    console.log(temp)
    return temp.length
  }
}

打开App.vue,使用getters展示剩余项

//使用映射好的计算属性展示剩余项

{{unDoneLength}}条剩余</span>

//导入getters
import { mapState,mapGetters } from 'vuex'
//映射
computed:{
  ...mapState(['list','inputValue']),
  ...mapGetters(['unDoneLength'])
}
8.4.6清除完成事项Mutation★★★★★

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9BH7C0o-1605053467988)(./media/1594632918(1)].jpg)

首先,打开App.vue文件,给“清除已完成”按钮绑定点击事件,编写处理函数

<!-- 把已经完成的任务清空 -->
<a @click="clean">清除已完成</a>

//编写事件处理函数
methods:{
  ......
  clean(){
    //清除已经完成的事项
    this.$store.commit('cleanDone')
  }
}

然后打开store.js编写addItem

export default new Vuex.Store({
  ......
  mutations: {
    ........
    cleanDone(state){
      state.list = state.list.filter( x => x.done === false )
    }
  }
  ......
})
8.4.7点击选项卡切换事项Getter★★★★★

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jIuI88PV-1605053467991)(./media/1594632918(1)].jpg)

打开App.vue,给“全部”,“未完成”,“已完成”三个选项卡绑定点击事件,编写处理函数
并将列表数据来源更改为一个getters。

<a-list bordered :dataSource="infoList" class="dt_list">
  ......
  <!-- 操作按钮 -->
  <a-button-group>
    <a-button :type="viewKey ==='all'?'primary':'default'" @click="changeList('all')">全部</a-button>
    <a-button :type="viewKey ==='undone'?'primary':'default'" @click="changeList('undone')">未完成</a-button>
    <a-button :type="viewKey ==='done'?'primary':'default'" @click="changeList('done')">已完成</a-button>
  </a-button-group>
  ......
</a-list>

//编写事件处理函数以及映射计算属性
methods:{
  ......
  changeList( key ){
    //点击“全部”,“已完成”,“未完成”时触发
    this.$store.commit('changeKey',key)
  }
},
computed:{
  ...mapState(['list','inputValue','viewKey']),
  ...mapGetters(['unDoneLength','infoList'])
}

打开store.js,添加getters,mutations,state

export default new Vuex.Store({
  state: {
    ......
    //保存默认的选项卡值
    viewKey:'all'
  },
  mutations: {
    ......
    changeKey(state,key){
      //当用户点击“全部”,“已完成”,“未完成”选项卡时触发
      state.viewKey = key
    }
  },
  ......
  getters:{
    .......
    infoList(state){
      if(state.viewKey === 'all'){
        return state.list
      }
      if(state.viewKey === 'undone'){
        return state.list.filter( x => x.done === false )
      }
      if(state.viewKey === 'done'){
        return state.list.filter( x => x.done === true )
      }
    }
  }
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值