Vue2基础知识

相关代码:https://github.com/user0819/vue_test.git

一、Vue核心

1.1 Vue简介

1.1.1 官网

英文官网: Vue.js - The Progressive JavaScript Framework | Vue.js

中文官网: Vue.js - 渐进式 JavaScript 框架 | Vue.js

1.1.2 介绍与描述

动态构建用户界面的渐进式 JavaScript 框架

1.1.3 Vue 的特点

  • 遵循 MVVM 模式
  • 编码简洁, 体积小, 运行效率高, 适合移动/PC 端开发
  • 它本身只关注 UI, 也可以引入其它第三方库开发项目

1.1.4 与其它 JS 框架的关联

  • 借鉴 Angular 的模板和数据绑定技术
  • 借鉴 React 的组件化和虚拟 DOM 技术

1.1.5 Vue 周边库

  • vue-cli: vue 脚手架
  • vue-resource
  • axios
  • vue-router: 路由
  • vuex: 状态管理
  • element-ui: 基于 vue 的 UI 组件库(PC 端)

1.2 初始Vue

  1. 创建一个Vue实例,传入一个配置对象
  2. 代码依然符合html规范,只不过混入了一些特殊的Vue语法
  3. 容器里的代码被称为【Vue模板】
  4. Vue实例和容器是一一对应的
  5. 真实开发中只有一个Vue实例,并且会配合着组件一起使用
  6. {{xxx}}中的xxx要写js表达式,且xxx可以自动读取到data中的所有属性
  7. 一旦data中的数据发生改变,那么页面中用到该数据的地方也会自动更新
<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>初识Vue</title>
      <!-- 引入Vue -->
      <script type="text/javascript" src="../js/vue.js"></script>
   </head>
   <body>
      <!-- 准备好一个容器 -->
      <div id="demo">
         <h1>Hello,{{name.toUpperCase()}},{{address}}</h1>
      </div>

      <script type="text/javascript" >
         Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

         //创建Vue实例
         new Vue({
            el:'#demo', //el用于指定当前Vue实例为哪个容器服务,值通常为css选择器字符串。
            data:{ //data中用于存储数据,数据供el所指定的容器去使用,值我们暂时先写成一个对象。
               name:'xiang',
               address:'北京'
            }
         })
      </script>
   </body>
</html>

1.2.1 data与el的2种写法

el有2种写法

(1)new Vue时候配置el属性。

(2)先创建Vue实例,随后再通过vm.$mount('#root')指定el的值。

//el的两种写法
const v = new Vue({
   //el:'#root', //第一种写法
   data:{
      name:'希昂'
   }
})

v.$mount('#root') //第二种写法 

data有2种写法

(1).对象式

(2).函数式

如何选择:目前哪种写法都可以,以后学习到组件时,data必须使用函数式,否则会报错。

//data的两种写法
new Vue({
   el:'#root',
   //data的第一种写法:对象式
   /* data:{
      name:'希昂'
   } */

   //data的第二种写法:函数式
   data(){
      return {
         name:'希昂'
      }
   }
})

一个重要原则:

由Vue管理的函数,一定不要写箭头函数,一旦写了箭头函数,this就不再是Vue实例了。

1.3 模版语法

Vue模板语法有2大类:

  • 插值语法
    • 功能:用于解析标签体内容。
    • 写法:{{xxx}},xxx是js表达式,且可以直接读取到data中的所有属性。
  • 指令语法
    • 功能:用于解析标签(包括:标签属性、标签体内容、绑定事件.....)。
    • 举例:v-bind:href="xxx" 或 简写为 :href="xxx",xxx同样要写js表达式,且可以直接读取到data中的所有属性。
    • 备;注:Vue中有很多的指令,且形式都是:v-????,此处我们只是拿v-bind举个例子。

示例效果:

<body>
   <!-- 容器-->
   <div id="root">
      <h1>插值语法</h1>
      <h3>你好,{{name}}</h3>
      <hr/>
      <h1>指令语法</h1>
      <a v-bind:href="school.url.toUpperCase()" x="hello">点我去{{school.name}}学习1</a>
      <a :href="school.url" x="hello">点我去{{school.name}}学习2</a>
   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

   new Vue({
      el:'#root',
      data:{
         name:'xiang',
         school:{
            name:'B站',
            url:'http://www.bilibili.com',
         }
      }
   })
</script>

1.4 数据绑定

Vue中有2种数据绑定的方式:

  • 单向绑定(v-bind):数据只能从data流向页面。
  • 双向绑定(v-model):数据不仅能从data流向页面,还可以从页面流向data。

备注:

  • 双向绑定一般都应用在表单类元素上(如:input、select等)
  • v-model:value 可以简写为 v-model,因为v-model默认收集的就是value值。

示例效果:

<body>
   <!-- 个容器-->
   <div id="root">
      <!-- 普通写法 -->
      <!-- 单向数据绑定:<input type="text" v-bind:value="name"><br/>
      双向数据绑定:<input type="text" v-model:value="name"><br/> -->

      <!-- 简写 -->
      单向数据绑定:<input type="text" :value="name"><br/>
      双向数据绑定:<input type="text" v-model="name"><br/>

      <!-- 如下代码是错误的,因为v-model只能应用在表单类元素(输入类元素)上 -->
      <!-- <h2 v-model:x="name">你好啊</h2> -->
   </div>
</body>

<script type="text/javascript">
   new Vue({
      name:'希昂',
      el:'#root',
      data:{
         name:'希昂'
      }
   })
</script>

1.5 MVVM模型

M:模型(Model) :对应 data 中的数据

V:视图(View) :模板

VM:视图模型(ViewModel) : Vue 实例对象

1.data中所有的属性,最后都出现在了vm身上。

2.vm身上所有的属性 及 Vue原型上所有属性,在Vue模板中都可以直接使用。

1.5.1 数据代理

何为数据代理?

通过一个对象代理对另一个对象中属性的操作(读/写)

<script type="text/javascript" >
   let obj = {x:100}
   let obj2 = {y:200}

   Object.defineProperty(obj2, 'x', {
      get(){
         return obj.x
      },
      set(value){
         obj.x = value
      }
   })
</script>

1.5.2 Vue数据代理

1.Vue中的数据代理:

通过vm对象来代理data对象中属性的操作(读/写)

2.Vue中数据代理的好处

更加方便的操作data中的数据

3.基本原理:

通过Object.defineProperty()把data对象中所有属性添加到vm上。

为每一个添加到vm上的属性,都指定一个getter/setter。

在getter/setter内部去操作(读/写)data中对应的属性。

<body>
   <!-- 准备好一个容器-->
   <div id="root">
      <h2>姓名:{{name}}</h2>
      <h2>地址:{{address}}</h2>
   </div>
</body>

<script type="text/javascript">   
   const vm = new Vue({
      el:'#root',
      data:{
         name:'希昂',
         address:'南京'
      }
   })
</script>

1.6 事件处理

1.6.1 事件基本使用

  1. 使用v-on:xxx 或 @xxx 绑定事件,其中xxx是事件名;
  2. 事件的回调需要配置在methods对象中,最终会在vm上;
  3. methods中配置的函数,不要用箭头函数!否则this就不是vm了;
  4. methods中配置的函数,都是被Vue所管理的函数,this的指向是vm 或 组件实例对象;
  5. @click="demo" 和 @click="demo($event)" 效果一致,但后者可以传参;默认事件形参: event。隐含属性对象: $event。

示例:

<body>
   <!-- 容器-->
   <div id="root">
      <h2>欢迎{{name}}学习</h2>
      <!-- <button v-on:click="showInfo">点我提示信息</button> -->
      <button @click="showInfo1">点我提示信息1(不传参)</button>
      <button @click="showInfo2($event,66)">点我提示信息2(传参)</button>
   </div>
</body>

<script type="text/javascript">
   const vm = new Vue({
      el:'#root',
      data:{
         name:'希昂',
      },
      methods:{
         showInfo1(event){
            alert('同学你好!')
         },
         showInfo2(event,number){
            console.log(event,number)
            // console.log(event.target.innerText)
            // console.log(this) //此处的this是vm
            alert('同学你好!!')
         }
      }
   })
</script>

1.6.2 事件修饰符

Vue中的事件修饰符:

  • prevent:阻止默认事件(常用);
  • stop:阻止事件冒泡(常用);
  • once:事件只触发一次(常用);
  • capture:使用事件的捕获模式;
  • self:只有event.target是当前操作的元素时才触发事件;
  • passive:事件的默认行为立即执行,无需等待事件回调执行完毕;
<body>
   <!-- 容器-->
   <div id="root">
      <h2>欢迎{{name}}学习</h2>
      <!-- 阻止默认事件(常用) -->
      <a href="http://www.baidu.com" @click.prevent="showInfo">点我提示信息</a>

      <!-- 阻止事件冒泡(常用) -->
      <div class="demo1" @click="showInfo">
         <button @click.stop="showInfo">点我提示信息</button>
         <!-- 修饰符可以连续写 -->
          <a href="http://www.baidu.com" @click.prevent.stop="showInfo">点我提示信息</a>
      </div>

      <!-- 事件只触发一次(常用) -->
      <button @click.once="showInfo">点我提示信息</button>

      <!-- 使用事件的捕获模式 -->
      <div class="box1" @click.capture="showMsg(1)">
         div1
         <div class="box2" @click="showMsg(2)">
            div2
         </div>
      </div>

      <!-- 只有event.target是当前操作的元素时才触发事件; -->
      <div class="demo1" @click.self="showInfo">
         <button @click="showInfo">点我提示信息</button>
      </div>

      <!-- 事件的默认行为立即执行,无需等待事件回调执行完毕; -->
      <ul @wheel.passive="demo" class="list">
         <li>1</li>
         <li>2</li>
         <li>3</li>
         <li>4</li>
      </ul>

   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

   new Vue({
      el:'#root',
      data:{
         name:'希昂'
      },
      methods:{
         showInfo(e){
            alert('同学你好!')
         },
         showMsg(msg){
            console.log(msg)
         },
         demo(){
            for (let i = 0; i < 100000; i++) {
               console.log('#')
            }
            console.log('累坏了')
         }
      }
   })
</script>

1.6.3 按键修饰符

Vue中常用的按键别名:

  • 回车 => enter
  • 删除 => delete (捕获“删除”和“退格”键)
  • 退出 => esc
  • 空格 => space
  • 换行 => tab (特殊,必须配合keydown去使用)
  • 上 => up
  • 下 => down
  • 左 => left
  • 右 => right

Vue未提供别名的按键,可以使用按键原始的key值去绑定,但注意要转为kebab-case(短横线命名)

系统修饰键(用法特殊):ctrl、alt、shift、meta

  • 配合keyup使用:按下修饰键的同时,再按下其他键,随后释放其他键,事件才被触发。
  • 配合keydown使用:正常触发事件。

Vue.config.keyCodes.自定义键名 = 键码,可以去定制按键别名

<body>
   <!-- 容器-->
   <div id="root">
      <h2>欢迎{{name}}学习</h2>
      <input type="text" placeholder="按下回车提示输入" @keydown.huiche="showInfo">
   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
   Vue.config.keyCodes.huiche = 13 //定义了一个别名按键

   new Vue({
      el:'#root',
      data:{
         name:'希昂'
      },
      methods: {
         showInfo(e){
            console.log(e, e.key,e.keyCode)
         }
      },
   })
</script>

1.7 计算属性与监视

1.7.1 计算属性-computed

  • 要显示的数据不存在,要通过计算得来
  • 在 computed 对象中定义计算属性
  • 在页面中使用{{方法名}}来显示计算的结果

示例:

<body>
   <div id="root">
      姓:<input type="text" v-model="firstName"> <br/><br/>
      名:<input type="text" v-model="lastName"> <br/><br/>
      测试:<input type="text" v-model="x"> <br/><br/>
      全名:<span>{{fullName}}</span> <br/><br/>
   </div>
</body>

<script type="text/javascript">
   const vm = new Vue({
      el:'#root',
      data:{
         firstName:'张',
         lastName:'三',
         x:'你好'
      },
      computed:{
         fullName:{
            //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
            //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
            get(){
               console.log('get被调用了')
               return this.firstName + '-' + this.lastName
            },
            //set什么时候调用? 当fullName被修改时。
            set(value){
               console.log('set',value)
               const arr = value.split('-')
               this.firstName = arr[0]
               this.lastName = arr[1]
            }
         }
      }
   })
</script>

简写:

const vm = new Vue({
   el:'#root',
   data:{
      firstName:'张',
      lastName:'三',
   },
   computed:{
      //简写:只有get
      fullName(){
         console.log('get被调用了')
         return this.firstName + '-' + this.lastName
      }
   }
})

1.7.2 监视属性-watch

通过 vm 对象的$watch()或 watch 配置来监视指定的属性

  • 当被监视的属性变化时, 回调函数自动调用, 进行相关操作
  • 监视的属性必须存在,才能进行监视!!
  • 监视的两种写法:
    • new Vue时传入watch配置
    • 通过vm.$watch监视

示例:

<body>
   <div id="root">
      <h2>今天天气很{{info}}</h2>
      <button @click="changeWeather">切换天气</button>
   </div>
</body>

<script type="text/javascript">
   const vm = new Vue({
      el:'#root',
      data:{
         isHot:true,
      },
      computed:{
         info(){
            return this.isHot ? '炎热' : '凉爽'
         }
      },
      methods: {
         changeWeather(){
            this.isHot = !this.isHot
         }
      },
      /* watch:{
         isHot:{
            immediate:true, //初始化时让handler调用一下
            //handler什么时候调用?当isHot发生改变时。
            handler(newValue,oldValue){
               console.log('isHot被修改了',newValue,oldValue)
            }
         }
      } */
   })

   vm.$watch('isHot',{
      immediate:true, //初始化时让handler调用一下
      handler(newValue,oldValue){
         console.log('isHot被修改了',newValue,oldValue)
      }
   })
</script>

深度监测:

  • Vue中的watch默认不监测对象内部值的改变(一层)
  • 配置deep:true可以监测对象内部值改变(多层)
<body>
   <div id="root">
      <h3>a的值是:{{numbers.a}}</h3>
      <button @click="numbers.a++">点我让a+1</button>
      <h3>b的值是:{{numbers.b}}</h3>
      <button @click="numbers.b++">点我让b+1</button>
      <button @click="numbers = {a:666,b:888}">彻底替换掉numbers</button>
      {{numbers.c.d.e}}
   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
   
   const vm = new Vue({
      el:'#root',
      data:{
         numbers:{
            a:1,
            b:1,
            c:{
               d:{
                  e:100
               }
            }
         }
      },
      watch:{
         //监视多级结构中某个属性的变化
         'numbers.a':{
            handler(){
               console.log('a被改变了')
            }
         },
         //监视多级结构中所有属性的变化
         numbers:{
            deep:true,
            handler(){
               console.log('numbers改变了')
            }
         }
      }
   })
</script>

1.8 class与style绑定

在应用界面中, 某个(些)元素的样式是变化的。

class/style 绑定就是专门用来实现动态样式效果的技术。

1.8.1 class绑定

  • :class="xxx"

xxx可以是字符串、对象、数组。

  • 字符串写法适用于:类名不确定,要动态获取。 'classA'
  • 对象写法适用于:要绑定多个样式,个数不确定,名字也不确定。{classA:isA, classB: isB}
  • 数组写法适用于:要绑定多个样式,个数确定,名字也确定,但不确定用不用。['classA', 'classB']

1.8.2 style绑定

  • :style="{fontSize: xxx}"其中xxx是动态值。
  • :style="[a,b]"其中a、b是样式对象。

示例:

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>绑定样式</title>
      <style>
         .basic{
            width: 400px;
            height: 100px;
            border: 1px solid black;
         }
         
         .happy{
            border: 4px solid red;;
            background-color: rgba(255, 255, 0, 0.644);
            background: linear-gradient(30deg,yellow,pink,orange,yellow);
         }
         .sad{
            border: 4px dashed rgb(2, 197, 2);
            background-color: gray;
         }
         .normal{
            background-color: skyblue;
         }

         .xiang1{
            background-color: yellowgreen;
         }
         .xiang2{
            font-size: 30px;
            text-shadow:2px 2px 10px red;
         }
         .xiang3{
            border-radius: 20px;
         }
      </style>
      <script type="text/javascript" src="../js/vue.js"></script>
   </head>
   <body>
      <div id="root">
         <!-- 绑定class样式--字符串写法,适用于:样式的类名不确定,需要动态指定 -->
         <div class="basic" :class="mood" @click="changeMood">{{name}}</div> <br/><br/>

         <!-- 绑定class样式--数组写法,适用于:要绑定的样式个数不确定、名字也不确定 -->
         <div class="basic" :class="classArr">{{name}}</div> <br/><br/>

         <!-- 绑定class样式--对象写法,适用于:要绑定的样式个数确定、名字也确定,但要动态决定用不用 -->
         <div class="basic" :class="classObj">{{name}}</div> <br/><br/>

         <!-- 绑定style样式--对象写法 -->
         <div class="basic" :style="styleObj">{{name}}</div> <br/><br/>
         <!-- 绑定style样式--数组写法 -->
         <div class="basic" :style="styleArr">{{name}}</div>
      </div>
   </body>

   <script type="text/javascript">
      Vue.config.productionTip = false
      
      const vm = new Vue({
         el:'#root',
         data:{
            name:'希昂',
            mood:'normal',
            classArr:['xiang1','xiang2','xiang3'],
            classObj:{
               xiang1:false,
               xiang2:false,
            },
            styleObj:{
               fontSize: '40px',
               color:'red',
            },
            styleObj2:{
               backgroundColor:'orange'
            },
            styleArr:[
               {
                  fontSize: '40px',
                  color:'blue',
               },
               {
                  backgroundColor:'gray'
               }
            ]
         },
         methods: {
            changeMood(){
               const arr = ['happy','sad','normal']
               const index = Math.floor(Math.random()*3)
               this.mood = arr[index]
            }
         },
      })
   </script>
   
</html>

1.9 条件渲染

1.9.1 v-if

写法:

  • v-if="表达式"
  • v-else-if="表达式"
  • v-else="表达式"

适用于:切换频率较低的场景。

特点:不展示的DOM元素直接被移除。

注意:v-if可以和:v-else-if、v-else一起使用,但要求结构不能被“打断”。

1.9.2 v-show

写法:

  • v-show="表达式"

适用于:切换频率较高的场景。

特点:不展示的DOM元素未被移除,仅仅是使用样式隐藏掉

1.9.3 示例

    <div id="root">
      <h2>当前的n值是:{{n}}</h2>
      <button @click="n++">点我n+1</button>
      <!-- 使用v-show做条件渲染 -->
      <h2 v-show="false">欢迎{{name}}</h2>
      <h2 v-show="n===1">欢迎{{name}}</h2>


      <!-- v-else和v-else-if -->
      <div v-if="n === 1">Angular</div>
      <div v-else-if="n === 2">React</div>
      <div v-else-if="n === 3">Vue</div>
      <div v-else>哈哈</div>

      <!-- v-if与template的配合使用 -->
      <template v-if="n === 1">
         <h2>你好</h2>
         <h2>希昂</h2>
         <h2>南京</h2>
      </template>

   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false

   const vm = new Vue({
      el:'#root',
      data:{
         name:'希昂',
         n:0
      }
   })
</script>

1.10 列表渲染

1.10.1 基本使用

v-for指令:

  • 用于展示列表数据
  • 语法:v-for="(item, index) in xxx" :key="yyy"
  • 可遍历:数组、对象、字符串(用的很少)、指定次数(用的很少)
<body>
   <div id="root">
      <!-- 遍历数组 -->
      <h2>人员列表(遍历数组)</h2>
      <ul>
         <li v-for="(p,index) in persons" :key="index">
            {{p.name}}-{{p.age}}
         </li>
      </ul>

      <!-- 遍历对象 -->
      <h2>汽车信息(遍历对象)</h2>
      <ul>
         <li v-for="(value,k) in car" :key="k">
            {{k}}-{{value}}
         </li>
      </ul>

      <!-- 遍历字符串 -->
      <h2>测试遍历字符串(用得少)</h2>
      <ul>
         <li v-for="(char,index) in str" :key="index">
            {{char}}-{{index}}
         </li>
      </ul>

      <!-- 遍历指定次数 -->
      <h2>测试遍历指定次数(用得少)</h2>
      <ul>
         <li v-for="(number,index) in 5" :key="index">
            {{index}}-{{number}}
         </li>
      </ul>
   </div>
</body>
   <script type="text/javascript">
       Vue.config.productionTip = false

       new Vue({
           el:'#root',
           data:{
               persons:[
                   {id:'001',name:'张三',age:18},
                   {id:'002',name:'李四',age:19},
                   {id:'003',name:'王五',age:20}
               ],
               car:{
                   name:'奥迪A8',
                   price:'70万',
                   color:'黑色'
               },
               str:'hello'
           }
       })
   </script>

1.10.2 key的原理

react、vue中的key有什么作用?(key的内部原理)

  1. 虚拟DOM中key的作用:
    1. key是虚拟DOM对象的标识,当数据发生变化时,Vue会根据【新数据】生成【新的虚拟DOM】,
    2. 随后Vue进行【新虚拟DOM】与【旧虚拟DOM】的差异比较,比较规则如下:
  2. 对比规则:
    1. 旧虚拟DOM中找到了与新虚拟DOM相同的key:
    2. 若虚拟DOM中内容没变, 直接使用之前的真实DOM
    3. 若虚拟DOM中内容变了, 则生成新的真实DOM,随后替换掉页面中之前的真实DOM。
  3. 旧虚拟DOM中未找到与新虚拟DOM相同的key
    1. 创建新的真实DOM,随后渲染到到页面。

用index作为key可能会引发的问题:

  • 若对数据进行:逆序添加、逆序删除等破坏顺序操作:会产生没有必要的真实DOM更新 ==> 界面效果没问题, 但效率低。
  • 如果结构中还包含输入类的DOM:会产生错误DOM更新 ==> 界面有问题。

开发中如何选择key?

  • 最好使用每条数据的唯一标识作为key, 比如id、手机号、身份证号、学号等唯一值。
  • 如果不存在对数据的逆序添加、逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index作为key是没有问题的。
<body>
   <div id="root">
      <!-- 遍历数组 -->
      <h2>人员列表(遍历数组)</h2>
      <button @click.once="add">添加一个老刘</button>
      <ul>
         <li v-for="(p,index) of persons" :key="index">
            {{p.name}}-{{p.age}}
            <input type="text">
         </li>
      </ul>
   </div>
</body>
   <script type="text/javascript">
      Vue.config.productionTip = false
      
      new Vue({
         el:'#root',
         data:{
            persons:[
               {id:'001',name:'张三',age:18},
               {id:'002',name:'李四',age:19},
               {id:'003',name:'王五',age:20}
            ]
         },
         methods: {
            add(){
               const p = {id:'004',name:'老刘',age:40}
               this.persons.unshift(p)
            }
         },
      })
   </script>

1.10.3 数据监测

Vue监视数据的原理:

  1. vue会监视data中所有层次的数据。
  2. 如何监测对象中的数据?通过setter实现监视,且要在new Vue时就传入要监测的数据。
    1. 对象中后追加的属性,Vue默认不做响应式处理
    2. 如需给后添加的属性做响应式,请使用如下API:
      1. Vue.set(target,propertyName/index,value)
      2. vm.$set(target,propertyName/index,value)
  3. 如何监测数组中的数据? 通过包裹数组更新元素的方法实现,本质就是做了两件事:
    1. 调用原生对应的方法对数组进行更新。
    2. 重新解析模板,进而更新页面。
  4. 在Vue修改数组中的某个元素一定要用如下方法:
    1. 使用这些API:push()、pop()、shift()、unshift()、splice()、sort()、reverse()
    2. Vue.set() 或 vm.$set()

特别注意:Vue.set() 和 vm.$set() 不能给vm 或 vm的根数据对象 添加属性!!!

<body>
   <div id="root">
      <h1>学生信息</h1>
      <button @click="student.age++">年龄+1岁</button> <br/>
      <button @click="addSex">添加性别属性,默认值:男</button> <br/>
      <button @click="student.sex = '未知' ">修改性别</button> <br/>
      <button @click="addFriend">在列表首位添加一个朋友</button> <br/>
      <button @click="updateFirstFriendName">修改第一个朋友的名字为:张三</button> <br/>
      <button @click="addHobby">添加一个爱好</button> <br/>
      <button @click="updateHobby">修改第一个爱好为:开车</button> <br/>
      <button @click="removeSmoke">过滤掉爱好中的抽烟</button> <br/>
      <h3>姓名:{{student.name}}</h3>
      <h3>年龄:{{student.age}}</h3>
      <h3 v-if="student.sex">性别:{{student.sex}}</h3>
      <h3>爱好:</h3>
      <ul>
         <li v-for="(h,index) in student.hobby" :key="index">
            {{h}}
         </li>
      </ul>
      <h3>朋友们:</h3>
      <ul>
         <li v-for="(f,index) in student.friends" :key="index">
            {{f.name}}--{{f.age}}
         </li>
      </ul>
   </div>
</body>

<script type="text/javascript">
   Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

   const vm = new Vue({
      el:'#root',
      data:{
         student:{
            name:'tom',
            age:18,
            hobby:['抽烟','喝酒','烫头'],
            friends:[
               {name:'jerry',age:35},
               {name:'tony',age:36}
            ]
         }
      },
      methods: {
         addSex(){
            // Vue.set(this.student,'sex','男')
            this.$set(this.student,'sex','男')
         },
         addFriend(){
            this.student.friends.unshift({name:'jack',age:70})
         },
         updateFirstFriendName(){
            this.student.friends[0].name = '张三'
         },
         addHobby(){
            this.student.hobby.push('学习')
         },
         updateHobby(){
            // this.student.hobby.splice(0,1,'开车')
            // Vue.set(this.student.hobby,0,'开车')
            this.$set(this.student.hobby,0,'开车')
         },
         removeSmoke(){
            this.student.hobby = this.student.hobby.filter((h)=>{
               return h !== '抽烟'
            })
         }
      }
   })
</script>

1.11 收集表单数据

收集表单数据,根据类型不同,v-model收集的不一定是value值。

  • <input type="text"/>,,则v-model收集的是value值,用户输入的就是value值。
  • <input type="radio"/>,则v-model收集的是value值,且要给标签配置value值。
  • <input type="checkbox"/>
    • 没有配置input的value属性,那么收集的就是checked(勾选 or 未勾选,是布尔值)
    • 配置input的value属性:
      • v-model的初始值是非数组,那么收集的就是checked(勾选 or 未勾选,是布尔值)
      • v-model的初始值是数组,那么收集的的就是value组成的数组

备注:v-model的三个修饰符:

  • lazy:失去焦点再收集数据
  • number:输入字符串转为有效的数字
  • trim:输入首尾空格过滤

示例:

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>收集表单数据</title>
      <script type="text/javascript" src="../js/vue.js"></script>
   </head>
   <body>
      <div id="root">
         <form @submit.prevent="demo">
            账号:<input type="text" v-model.trim="userInfo.account"> <br/><br/>
            密码:<input type="password" v-model="userInfo.password"> <br/><br/>
            年龄:<input type="number" v-model.number="userInfo.age"> <br/><br/>
            性别:
            男<input type="radio" name="sex" v-model="userInfo.sex" value="male">
            女<input type="radio" name="sex" v-model="userInfo.sex" value="female"> <br/><br/>
            爱好:
            学习<input type="checkbox" v-model="userInfo.hobby" value="study">
            打游戏<input type="checkbox" v-model="userInfo.hobby" value="game">
            吃饭<input type="checkbox" v-model="userInfo.hobby" value="eat">
            <br/><br/>
            所属校区
            <select v-model="userInfo.city">
               <option value="">请选择校区</option>
               <option value="beijing">北京</option>
               <option value="shanghai">上海</option>
               <option value="shenzhen">深圳</option>
               <option value="wuhan">武汉</option>
            </select>
            <br/><br/>
            其他信息:
            <textarea v-model.lazy="userInfo.other"></textarea> <br/><br/>
            <input type="checkbox" v-model="userInfo.agree">阅读并接受<a href="http://www.baidu.com">《用户协议》</a>
            <button>提交</button>
         </form>
      </div>
   </body>

   <script type="text/javascript">
      Vue.config.productionTip = false

      new Vue({
         el:'#root',
         data:{
            userInfo:{
               account:'',
               password:'',
               age:18,
               sex:'female',
               hobby:[],
               city:'beijing',
               other:'',
               agree:''
            }
         },
         methods: {
            demo(){
               console.log(JSON.stringify(this.userInfo))
            }
         }
      })
   </script>
</html>

1.12 过滤器

1.12.1 介绍

功能: 对要显示的数据进行特定格式化后再显示

语法:

  • 注册过滤器:
    • 全局过滤器:Vue.filter(name,callback)
    • 局部过滤器:new Vue{filters:{}}
  • 使用过滤器:
    • {{ xxx | 过滤器名}}
    • v-bind:属性 = "xxx | 过滤器名"

注意: 并没有改变原本的数据, 是产生新的对应的数据

类型:局部过滤器、全局过滤器

1.12.2 示例

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>过滤器</title>
      <script type="text/javascript" src="../js/vue.js"></script>
      <script type="text/javascript" src="../js/dayjs.min.js"></script>
   </head>
   <body>
      <div id="root">
         <h2>显示格式化后的时间</h2>
         <!-- 原时间戳 -->
         <h3>时间戳:{{time}}</h3>
         <!-- 计算属性实现 -->
         <h3>现在是:{{fmtTime}}</h3>
         <!-- methods实现 -->
         <h3>现在是:{{getFmtTime()}}</h3>
         <!-- 过滤器实现 -->
         <h3>现在是:{{time | timeFormatter}}</h3>
         <!-- 过滤器实现(传参) -->
         <h3>现在是:{{time | timeFormatter('YYYY_MM_DD') | mySlice}}</h3>
         <h3 :x="msg | mySlice">希昂</h3>
      </div>

      <div id="root2">
         <h2>{{msg | mySlice}}</h2>
      </div>
   </body>

   <script type="text/javascript">
      Vue.config.productionTip = false
      //全局过滤器
      Vue.filter('mySlice',function(value){
         return value.slice(0,5)
      })
      
      new Vue({
         el:'#root',
         data:{
            time:1621561377603, //时间戳
            msg:'你好,希昂'
         },
         computed: {
            fmtTime(){
               return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
         },
         methods: {
            getFmtTime(){
               return dayjs(this.time).format('YYYY年MM月DD日 HH:mm:ss')
            }
         },
         //局部过滤器
         filters:{
            timeFormatter(value,str='YYYY年MM月DD日 HH:mm:ss'){
               // console.log('@',value)
               return dayjs(value).format(str)
            }
         }
      })

      new Vue({
         el:'#root2',
         data:{
            msg:'hello,xiang!'
         }
      })
   </script>
</html>

1.13 内置指令与自定义指令

1.13.1 常用内置指令

  • v-text : 更新元素的 textContent
  • v-html : 更新元素的 innerHTML
  • v-if : 如果为 true, 当前标签才会输出到页面
  • v-else: 如果为 false, 当前标签才会输出到页面
  • v-show : 通过控制 display 样式来控制显示/隐藏
  • v-for : 遍历数组/对象
  • v-on : 绑定事件监听, 一般简写为@
  • v-bind:xxx : 绑定解析表达式, 可以省略 v-bind
  • v-model : 双向数据绑定
  • v-cloak : 保证组件显示正常。vue创建完毕后删除此属性, 与 css 配合: [v-cloak] { display: none }
  • v-once:所在节点在初次动态渲染后,就视为静态内容了
  • v-pre指令:跳过其所在节点的编译过程
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>内置指令</title>
    <style>
        [v-cloak] {
            display: none;
        }
    </style>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
    <h2 v-text="text"></h2>
    <h2 v-html="html"></h2>
    <hr>

    <h2 v-if="isShow">{{chineseName}}</h2>
    <h2 v-else>{{name}}</h2>
    <h2 v-show="isShow">{{chineseName}}</h2>
    <hr>

    <h2>{{name}}</h2>
    <hr>

    <h2 v-for="(myName,index) in names" :key="index">{{index}}-{{ myName}}</h2>
    <hr>

    <h2 v-on:click="myAlert(name)">点击试试:{{name}}</h2>
    <hr>

    <a v-bind:href="baiduPre + baiduSuf">百度一下</a>
    <hr>

    <input type="text" v-model="inputValue">
    <hr>

    <h2 v-cloak>vue加载完成才显示</h2>
    <h2 v-once>仅编译一次:{{name}}</h2>
    <h2 v-pre>跳过编译,不信你看:{{name}}</h2>
    <hr>
</div>
<script type="text/javascript" src="http://localhost:8080/resource/5s/vue.js"></script>
</body>

<script type="text/javascript">
    console.log(1)
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

    new Vue({
        el: '#root',
        data: {
            text: '你好,希昂',
            html: '<strong style="color:red">你好,希昂</strong>',
            isShow: true,
            name: 'xiang',
            chineseName: '希昂',
            names: ['xiang', '希昂'],
            baiduPre:"https://www.bai",
            baiduSuf:"du.com",
            inputValue:"你好"
        },
        methods:{
            myAlert(a){
                alert(a);
            }
        }
    })
</script>
</html>

1.13.2 自定义指令

注册全局指令:

//方式一
Vue.directive(指令名,配置对象)
//方式二 
Vue.directive(指令名,回调函数)

注册局部指令:

//方式一
new Vue({
    directives:{指令名:配置对象}
})

//方式二
new Vue({
    directives{指令名:回调函数}
})

//比如
directives: {
    'my-directive' : {
        bind (el, binding) {
            el.innerHTML = binding.value.toupperCase()
        }
     }
}

配置对象中常用的3个回调:

  • bind:指令与元素成功绑定时调用。
  • inserted:指令所在元素被插入页面时调用。
  • update:指令所在模板结构被重新解析时调用。

备注:

  • 指令定义时不加v-,但使用时要加v-;
  • 指令名如果是多个单词,要使用kebab-case命名方式,不要用camelCase命名。
<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>自定义指令</title>
      <script type="text/javascript" src="../js/vue.js"></script>
   </head>
   <body>
      <div id="root">
         <h2>当前的n值是:<span v-text="n"></span> </h2>
         <!-- <h2>放大10倍后的n值是:<span v-big-number="n"></span> </h2> -->
         <h2>放大10倍后的n值是:<span v-big="n"></span> </h2>
         <button @click="n++">点我n+1</button>
         <hr/>
         <input type="text" v-my-focus:value="n">
      </div>
   </body>
   
   <script type="text/javascript">
      Vue.config.productionTip = false
      //定义全局指令
      /* Vue.directive('my-focus',{
         //指令与元素成功绑定时(一上来)
         bind(element,binding){
            element.value = binding.value
         },
         //指令所在元素被插入页面时
         inserted(element,binding){
            element.focus()
         },
         //指令所在的模板被重新解析时
         update(element,binding){
            element.value = binding.value
         }
      }) */
      new Vue({
         el:'#root',
         data:{
            n:10
         },
         directives:{
            big(element,binding){
               console.log('big',this) //注意此处的this是window
               // console.log('big')
               element.innerText = binding.value * 10
            },
            'my-focus':{
               //指令与元素成功绑定时(一上来)
               bind(element,binding){
                  element.value = binding.value
               },
               //指令所在元素被插入页面时
               inserted(element,binding){
                  element.focus()
               },
               //指令所在的模板被重新解析时
               update(element,binding){
                  element.value = binding.value
               }
            }
         }
      })
   </script>
</html>

1.14 Vue实例生命周期

1.14.1 生命周期分析

1、初始化显示

  • beforeCreate()
  • created()
  • beforeMount()
  • mounted()

2、更新状态: this.xxx = value

  • beforeUpdate()
  • updated()

3、销毁 vue 实例: vm.$destory()

  • beforeDestory()
  • destoryed()

常用的生命周期钩子:

  • mounted: 发送ajax请求、启动定时器、绑定自定义事件、订阅消息等【初始化操作】。
  • beforeDestroy: 清除定时器、解绑自定义事件、取消订阅消息等【收尾工作】。
<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>分析生命周期</title>
      <!-- 引入Vue -->
      <script type="text/javascript" src="../js/vue.js"></script>
   </head>
   <body>
      <!-- 准备好一个容器-->
      <div id="root" :x="n">
         <h2 v-text="n"></h2>
         <h2>当前的n值是:{{n}}</h2>
         <button @click="add">点我n+1</button>
         <button @click="bye">点我销毁vm</button>
      </div>
   </body>

   <script type="text/javascript">
      Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。

      new Vue({
         el:'#root',
         // template:`
         //     <div>
         //        <h2>当前的n值是:{{n}}</h2>
         //        <button @click="add">点我n+1</button>
         //     </div>
         // `,
         data:{
            n:1
         },
         methods: {
            add(){
               console.log('add')
               this.n++
            },
            bye(){
               console.log('bye')
               this.$destroy()
            }
         },
         watch:{
            n(){
               console.log('n变了')
            }
         },
         beforeCreate() {
            console.log('beforeCreate')
         },
         created() {
            console.log('created')
         },
         beforeMount() {
            console.log('beforeMount')
         },
         mounted() {
            console.log('mounted')
         },
         beforeUpdate() {
            console.log('beforeUpdate')
         },
         updated() {
            console.log('updated')
         },
         beforeDestroy() {
            console.log('beforeDestroy')
         },
         destroyed() {
            console.log('destroyed')
         },
      })
   </script>
</html>

二、Vue组件化编程

2.1 模块与组件

2.1.1 概念

模块:向外提供特定功能的 js 程序, 一般就是一个 js 文件。

组件:用来实现局部(特定)功能效果的代码集合(html/css/js/image…..)

模块化:当应用中的 js 以模块来编写的, 那这个应用就是一个模块化的应用。

组件化:当应用中的功能都是多组件的方式来编写的, 那这个应用就是一个组件化的应用,

2.1.2 组件使用步骤

步骤:

  1. 定义组件(创建组件)
  2. 注册组件
  3. 使用组件(写组件标签)

定义组件

  • 使用Vue.extend(options)创建,其中options和new Vue(options)时传入的那个options几乎一样,但也有点区别;
  • 区别如下:
    • el不要写,为什么? ——— 最终所有的组件都要经过一个vm的管理,由vm中的el决定服务哪个容器。
    • data必须写成函数,为什么? ———— 避免组件被复用时,数据存在引用关系。
  • 备注:使用template可以配置组件结构。

注册组件

  • 局部注册:靠new Vue的时候传入components选项
  • 全局注册:靠Vue.component('组件名',组件)

使用组件

  •  <school></school>

2.1.3 组件说明

组件名:

  • 一个单词组成:
    • 第一种写法(首字母小写):school
    • 第二种写法(首字母大写):School
  • 多个单词组成:
    • 第一种写法(kebab-case命名):my-school
    • 第二种写法(CamelCase命名):MySchool (需要Vue脚手架支持)
  • 备注:
    • 组件名尽可能回避HTML中已有的元素名称,例如:h2、H2都不行。
    • 可以使用name配置项指定组件在开发者工具中呈现的名字。

组件标签:

  • 第一种写法:<school></school>
  • 第二种写法:<school/>
  • 备注:不使用脚手架时,会导致后续组件不能渲染。

简写方式:

const school = Vue.extend(options) 
可简写为:
const school = options

VueComponent:

  • school组件本质是一个名为VueComponent的构造函数,且不是程序员定义的,是Vue.extend生成的。
  • 我们只需要写或,Vue解析时会帮我们创建school组件的实例对象,即Vue帮我们执行的:new VueComponent(options)。
  • 特别注意:每次调用Vue.extend,返回的都是一个全新的VueComponent

关于this指向:

  • 组件配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【VueComponent实例对象】。
  • new Vue(options)配置中:data函数、methods中的函数、watch中的函数、computed中的函数 它们的this均是【Vue实例对象】。

重要内置关系:

  • VueComponent.prototype.__proto__ === Vue.prototype
  • 让组件实例对象(vc)可以访问到 Vue原型上的属性、方法。

VueComponent的实例对象,以后简称vc(也可称之为:组件实例对象)。Vue的实例对象,以后简称vm。

2.2 非单文件组件

组件不是定义在单独的vue文件中。

但也一样遵循:定义、注册、使用。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
    <h1>{{name}}</h1>
    <school></school>
    <student></student>
</div>
</body>
<script>

    const school = Vue.extend({
        name: 'school',
        template:
                `
                  <div>
                  <h2>学校名称:{{ name }}</h2>
                  <h2>学校地址:{{ address }}</h2>
                  </div>>
                `,
        data() {
            return {
                name: '哈哈哈',
                address: '南京'
            }
        }
    })

    const student = Vue.extend({
        name: 'student',
        template:
                `
                  <div>
                  <h2>学生名称:{{ name }}</h2>
                  </div>>
                `,
        data() {
            return {
                name: '希昂',
            }
        }
    })

    new Vue({
        el: "#root",
        data: {
            name: 'xiang'
        },
        components: {
            student: student,
            school: school
        }
    })

</script>
</html>

2.3 单文件组件

在单独的vue文件中定义。

遵循:定义、注册、使用。

标准文件格式:

<template>
...
</template>

<script>
...
</script>

<style>
...
</style>

简单示例(需要配合脚手架才能运行)

  • index.html
<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>练习一下单文件组件的语法</title>
   </head>
   <body>
      <!-- 准备一个容器 -->
      <div id="root"></div>
</body>
</html>
  • main.js
import App from './App.vue'

new Vue({
   el:'#root',
   template:`<App></App>`,
   components:{App},
})
  • App.vue
<template>
 <div>
  <Student></Student>
 </div>
</template>

<script>
 //引入组件
 import Student from './Student.vue'

 export default {
  name:'App',
  components:{
   Student
  }
 }
</script>
  • Student.vue
<template>
 <div>
  <h2>学生姓名:{{name}}</h2>
  <h2>学生年龄:{{age}}</h2>
 </div>
</template>

<script>
  export default {
  name:'Student',
  data(){
   return {
    name:'张三',
    age:18
   }
  }
 }
</script>

三、Vue脚手架

3.1 初始化脚手架

3.1.1 说明

Vue 脚手架是 Vue 官方提供的标准化开发工具(开发平台)。

最新的版本是 4.x。

文档: Vue CLI

3.1.2 具体步骤

1、仅第一次执行时:全局安装@vue/cli:

npm install -g @vue/cli

2、切换到要创建项目的目录,使用命令创建项目

vue create xxxx

3、启动项目

npm run serve

备注:

1、如出现下载缓慢请配置 npm 淘宝镜像:

npm config set registry http://registry.npm.taobao.org

2、Vue 脚手架隐藏了所有 webpack 相关的配置,若想查看具体的webpakc 配置,请执行

vue inspect > output.js

3.1.3 模版项目的结构

.
├── node_modules
├── public
│         ├── favicon.ico
│         └── index.html
├── src
│         ├── App.vue
│         ├── assets
│         │         └── logo.png
│         ├── components
│         │         └── HelloWorld.vue
│         └── main.js
├── README.md
├── babel.config.js
├── jsconfig.json
├── package-lock.json
├── package.json
└── vue.config.js

main.js中render的说明

  • vue.js与vue.runtime.xxx.js的区别:
    • vue.js是完整版的Vue,包含:核心功能+模板解析器。
    • vue.runtime.xxx.js是运行版的Vue,只包含:核心功能;没有模板解析器。
  • 因为vue.runtime.xxx.js没有模板解析器,所以不能使用template配置项,需要使用render函数接收到的createElement函数去指定具体内容。
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

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

3.2 ref与props

3.2.1 ref

作用:用于给节点打标识,便于vue直接通过标识获取该对象。

对象:可以直接加载DOM上,也可以加载Vue Component上

读取方式:this.$refs.xxxxxx

<template>
 <div>
  <h1 v-text="msg" ref="title"></h1>
  <button ref="btn" @click="showDOM">点我输出上方的DOM元素</button>
  <School ref="sch"/>
 </div>
</template>

<script>
 //引入School组件
 import School from './components/School'

 export default {
  name:'App',
  components:{School},
  data() {
   return {
    msg:'欢迎学习Vue!'
   }
  },
  methods: {
   showDOM(){
    console.log(this.$refs.title) //真实DOM元素
    console.log(this.$refs.btn) //真实DOM元素
    console.log(this.$refs.sch) //School组件的实例对象(vc)
    
    console.log('原来内容:' + this.$refs.title.textContent)
    this.$refs.title.textContent = "变了"
   }
  },
 }
</script>

3.2.2 props

作用:用于父组件给子组件传递数据

传值方式:在使用子组件时传入对应属性值即可。

字符值直接传递、表达式需要使用v-bind:
<Student name="希昂" sex="男" :age=myAge />

读取方式:

读取方式一: 只指定名称
props: ['name', 'age', 'setName']

读取方式二: 指定名称和类型
props: {
    name: String, 
    age: Number, 
    setNmae: Function
}

读取方式三: 指定名称/类型/必要性/默认值
props: {
    name: {type: String, required: true, default:xxx}, 
}

App.vue

<template>
 <div>
  <Student name="希昂" sex="男" v-bind:age=itemAge />
 </div>
</template>

<script>
 import Student from './components/Student'

 export default {
  name:'App',
    data(){
    return {
      itemAge: 25
      }
    },
  components:{Student}
 }
</script>

Student.vue

<template>
 <div>
  <h1>{{msg}}</h1>
  <h2>学生姓名:{{name}}</h2>
  <h2>学生性别:{{sex}}</h2>
  <h2>学生年龄:{{myAge+1}}</h2>
  <button @click="updateAge">尝试修改收到的年龄</button>
 </div>
</template>

<script>
 export default {
  name:'Student',
  data() {
   console.log(this)
   return {
    msg:'我是一个学生',
    myAge:this.age
   }
  },
  methods: {
   updateAge(){
    this.myAge++
   }
  },
  //简单声明接收
  props:['name','age','sex']

  //接收的同时对数据进行类型限制
  /* props:{
   name:String,
   age:Number,
   sex:String
  } */

  //接收的同时对数据:进行类型限制+默认值的指定+必要性的限制
  // props:{
  //   name:{
  //    type:String, //name的类型是字符串
  //    required:true, //name是必要的
  //   },
  //   age:{
  //    type:Number,
  //    default:99 //默认值
  //   },
  //   sex:{
  //    type:String,
  //    required:true
  //   }
  // }
 }
</script>

3.3 混入

作用:定义通用的方法,通过install方法引入,简化重复代码。

可以理解为一个公共对象。根据引入的地方,决定它的作用域:组件内或全局。

Vue 插件是一个包含 install 方法的对象

通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等

引入方法:

  • 方式一:通过import引入
  • 方式二:在vue中使用mixins

示例:

在mixin.js中定义:数据、方法、事件。在main.js中引入,作为全局方法和事件。

mixin.js

export const hunhe = {
   methods: {
      showName(){
         alert(this.name)
      }
   },
   mounted() {
      console.log('你好啊!')
   },
}
export const hunhe2 = {
   data() {
      return {
         x:100,
         y:200
      }
   },
}

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
// 混入:注册全局对象
import {hunhe,hunhe2} from './mixin'
//关闭Vue的生产提示
Vue.config.productionTip = false

Vue.mixin(hunhe)
Vue.mixin(hunhe2)


//创建vm
new Vue({
   el:'#app',
   render: h => h(App)
})

App.vue

<template>
 <div>
  <School/>
  <hr>
  <Student/>
 </div>
</template>

<script>
 import School from './components/School'
 import Student from './components/Student'

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

School.vue

<template>
 <div>
  <h2 @click="showName">学生姓名:{{name}}</h2>
  <h2>学生性别:{{sex}}</h2>
 </div>
</template>

<script>
 // import {hunhe,hunhe2} from '../mixin'

 export default {
  name:'Student',
  data() {
   return {
    name:'张三',
    sex:'男'
   }
  },
  // mixins:[hunhe,hunhe2]
 }
</script>

3.4 插件

作用类似混入,实现方式稍有不同。

Vue 插件是一个包含 install 方法的对象

通过 install 方法给 Vue 或 Vue 实例添加方法, 定义全局指令等。

示例:在plugins.js中定义插件,并在main.js中引入使用,作为全局插件。

plugins.js

export default {
   install(Vue,x,y,z){
      console.log(x,y,z)
      //全局过滤器
      Vue.filter('mySlice',function(value){
         return value.slice(0,4)
      })

      //定义全局指令
      Vue.directive('fbind',{
         //指令与元素成功绑定时(一上来)
         bind(element,binding){
            element.value = binding.value
         },
         //指令所在元素被插入页面时
         inserted(element,binding){
            element.focus()
         },
         //指令所在的模板被重新解析时
         update(element,binding){
            element.value = binding.value
         }
      })

      //定义混入
      Vue.mixin({
         data() {
            return {
               x:100,
               y:200
            }
         },
      })

      //给Vue原型上添加一个方法(vm和vc就都能用了)
      Vue.prototype.hello = ()=>{alert('你好啊')}
   }
}

main.js

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入插件
import plugins from './plugins'
//关闭Vue的生产提示
Vue.config.productionTip = false

//应用(使用)插件
Vue.use(plugins,1,2,3)
//创建vm
new Vue({
   el:'#app',
   render: h => h(App)
})

App.vue

<template>
 <div>
  <School/>
  <hr>
  <Student/>
 </div>
</template>

<script>
 import School from './components/School'
 import Student from './components/Student'

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

School.vue

<template>
 <div>
    <!--  使用插件中的方法 -->
  <h2>学校名称:{{name | mySlice}}</h2>
  <h2>学校地址:{{address}}</h2>
  <button @click="test">点我测试一个hello方法</button>
 </div>
</template>

<script>
 export default {
  name:'School',
  data() {
   return {
    name:'AAAA',
    address:'北京',
   }
  },
  methods: {
   test(){
     // 使用插件中的方法
    this.hello()
   }
  },
 }
</script>

3.5 Todo-list案例

具体实现,详见代码:https://github.com/user0819/vue_test.git

浏览器本地缓存:

  • localStorage:会一直在浏览器中,页面关闭(会话结束)后,缓存内容也不会消失,
  • sessionStorage:仅存在浏览器当前会话中,页面关闭(会话结束),缓存内容消失

localStorage.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>localStorage</title>
   </head>
   <body>
      <h2>localStorage</h2>
      <button onclick="saveData()">点我保存一个数据</button>
      <button onclick="readData()">点我读取一个数据</button>
      <button onclick="deleteData()">点我删除一个数据</button>
      <button onclick="deleteAllData()">点我清空一个数据</button>

      <script type="text/javascript" >
         let p = {name:'张三',age:18}

         function saveData(){
            localStorage.setItem('msg','hello!!!')
            localStorage.setItem('msg2',666)
            localStorage.setItem('person',JSON.stringify(p))
         }
         function readData(){
            console.log(localStorage.getItem('msg'))
            console.log(localStorage.getItem('msg2'))

            const result = localStorage.getItem('person')
            console.log(JSON.parse(result))

            // console.log(localStorage.getItem('msg3'))
         }
         function deleteData(){
            localStorage.removeItem('msg2')
         }
         function deleteAllData(){
            localStorage.clear()
         }
      </script>
   </body>
</html>

sessionStorage.html

<!DOCTYPE html>
<html>
   <head>
      <meta charset="UTF-8" />
      <title>sessionStorage</title>
   </head>
   <body>
      <h2>sessionStorage</h2>
      <button onclick="saveData()">点我保存一个数据</button>
      <button onclick="readData()">点我读取一个数据</button>
      <button onclick="deleteData()">点我删除一个数据</button>
      <button onclick="deleteAllData()">点我清空一个数据</button>

      <script type="text/javascript" >
         let p = {name:'张三',age:18}

         function saveData(){
            sessionStorage.setItem('msg','hello!!!')
            sessionStorage.setItem('msg2',666)
            sessionStorage.setItem('person',JSON.stringify(p))
         }
         function readData(){
            console.log(sessionStorage.getItem('msg'))
            console.log(sessionStorage.getItem('msg2'))

            const result = sessionStorage.getItem('person')
            console.log(JSON.parse(result))

            // console.log(sessionStorage.getItem('msg3'))
         }
         function deleteData(){
            sessionStorage.removeItem('msg2')
         }
         function deleteAllData(){
            sessionStorage.clear()
         }
      </script>
   </body>
</html>

3.6 Vue中的自定义事件

组件之间需要互相之间的一些调用,可以用props传递属性或方法,但层级有限制,如果间隔多层需要一层层传递,很麻烦。可以考虑通过自定义事件进行方法调用或传递数据。

作用:在组件中自定义事件,通常用于组件间的方法调用。

使用方式:在组件A中定义事件,在组件B中触发事件,从而实现B调用A中方法。

注册事件:

<Header @addTodo="addTodo"/>

或者
<Header ref="header"/>
this.$refs.header.$on('addTodo', this.addTodo)

触发事件

this.$emit('addTodo', todo)

销毁事件

this.$off('xiang') //解绑一个自定义事件
// this.$off(['xiang','demo']) //解绑多个自定义事件
// this.$off() //解绑所有的自定义事件

示例:

App.vue

<template>
 <div class="app">
  <h1>{{msg}},学校名称:{{schoolName}},学生姓名是:{{studentName}}</h1>

  <!-- 通过父组件给子组件传递函数类型的props实现:子给父传递数据 -->
  <School :getSchoolName="getSchoolName"/>

  <!-- 通过父组件给子组件绑定一个自定义事件实现:子给父传递数据(第二种写法,使用ref) -->
  <Student ref="student" @click.native="show"/>
 </div>
</template>

<script>
 import Student from './components/Student'
 import School from './components/School'

 export default {
  name:'App',
  components:{School,Student},
  data() {
   return {
    msg:'你好啊',
    studentName:'',
        schoolName: ''
   }
  },
  methods: {
   getSchoolName(name){
    console.log('App收到了学校名:',name)
        this.schoolName = name;
   },
   getStudentName(name,...params){
    console.log('App收到了学生名:',name,params)
    this.studentName = name
   },
   m1(){
    console.log('demo事件被触发了!')
   },
   show(){
    alert(123)
   }
  },
  mounted() {
   this.$refs.student.$on('xiang',this.getStudentName) //绑定自定义事件
   // this.$refs.student.$once('xiang',this.getStudentName) //绑定自定义事件(一次性)
  },
 }
</script>

<style scoped>
 .app{
  background-color: gray;
  padding: 5px;
 }
</style>

Student.vue

<template>
 <div class="student">
  <h2>学生姓名:{{name}}</h2>
  <h2>学生性别:{{sex}}</h2>
  <h2>当前求和为:{{number}}</h2>
  <button @click="add">点我number++</button>
  <button @click="sendStudentName">把学生名给App</button>
  <button @click="unbind">解绑xiang事件</button>
  <button @click="death">销毁当前Student组件的实例(vc)</button>
 </div>
</template>

<script>
 export default {
  name:'Student',
  data() {
   return {
    name:'张三',
    sex:'男',
    number:0
   }
  },
  methods: {
   add(){
    console.log('add回调被调用了')
    this.number++
   },
   sendStudentName(){
    //触发Student组件实例身上的xiang事件
    this.$emit('xiang',this.name,666,888,900)
    // this.$emit('demo')
    // this.$emit('click')
   },
   unbind(){
    this.$off('xiang') //解绑一个自定义事件
    // this.$off(['xiang','demo']) //解绑多个自定义事件
    // this.$off() //解绑所有的自定义事件
   },
   death(){
    this.$destroy() //销毁了当前Student组件的实例,销毁后所有Student实例的自定义事件全都不奏效。
   }
  },
 }
</script>

<style lang="css" scoped>
 .student{
  background-color: pink;
  padding: 5px;
  margin-top: 30px;
 }
</style>

School.vue

<template>
 <div class="school">
  <h2>学校名称:{{name}}</h2>
  <h2>学校地址:{{address}}</h2>
  <button @click="sendSchoolName">把学校名给App</button>
 </div>
</template>

<script>
 export default {
  name:'School',
  props:['getSchoolName'],
  data() {
   return {
    name:'希昂',
    address:'北京',
   }
  },
  methods: {
   sendSchoolName(){
    this.getSchoolName(this.name)
   }
  },
 }
</script>

<style scoped>
 .school{
  background-color: skyblue;
  padding: 5px;
 }
</style>

3.7 全局事件总线

上述自定义事件中,组件B若想触发事件时,仅能触发它自身上的事件,可以触发的事件优先,或者说可以传递信息的场景有限。

需要一种事件,是全局的事件,任何组件都可以直接注册事件,任何组件都可以调用事件,从而实现信息的全局传递。

全局事件总线:将应用的vm对象,设置为全局可见并定义为$bus。任何注册在vm组件上的事件,都是全局可见的,称为全局事件总线。

Vue 原型对象上包含事件处理的方法:

$on(eventName, listener) 绑定自定义事件监听
$emit(eventName, data) 分发自定义事件
$off(eventName) 解绑自定义事件监听
$once(eventName, listener)  绑定事件监听, 但只能处理一次

关键点:在创建vm的时候安装全局事件总线

new Vue({
    beforeCreate () { 
    // 尽量早的执行挂载全局事件总线对象的操作
    Vue.prototype.$globalEventBus = this
    },
}).$mount('#root')

注册、触发、销毁

//注册
this.$globalEventBus.$on('deleteTodo', this.deleteTodo)

//触发
this.$globalEventBus.$emit('deleteTodo', this.index)

//销毁
this.$globalEventBus.$off('deleteTodo')

3.8 消息订阅与发布

除了全局事件总线外,也可以使用消息订阅与发布,进行全局的信息传递。

类似后台应用中消息队列的生产与消费。

包含以下操作:

(1) 订阅消息 --对应绑定事件监听

(2) 发布消息 --分发事件

(3) 取消消息订阅 --解绑事件监听

可以通过现有的一些插件库实现,比如:pubsub-js

安装插件:

npm i pubsub-js

引入插件:

import pubsub from 'pubsub-js'

发布消息:

pubsub.publish('hello',666)

订阅消息

this.pubId = pubsub.subscribe('hello',(msgName,data)=>{
 console.log(this)
 // console.log('有人发布了hello消息,hello消息的回调执行了',msgName,data)
})

取消订阅

pubsub.unsubscribe(this.pubId)

3.9 过度与动画

3.9.1 vue 动画的理解

  1. 操作 css 的 trasition 或 animation
  2. vue 会给目标元素添加/移除特定的 class
  3. 过渡的相关类名:
    1. xxx-enter-active: 指定显示的 transition
    2. xxx-leave-active: 指定隐藏的 transition
    3. xxx-enter/xxx-leave-to: 指定隐藏时的样式

3.9.2 基本过渡动画的编码

  1. 在目标元素外包裹
  2. 定义 class 样式
    1. 指定过渡样式: transition
    2. 指定隐藏时的样式: opacity/其它

四、Vue中的ajax

4.1 开发环境Ajax跨域问题

前端项目中如果请求其他服务的接口,如果后端没有放开跨域限制,则浏览器会限制请求,报跨域错误。

比如:前端项目运行在http://localhost:8080/,去访问http://localhost:5002/students时就会出现跨域问题。

export default {
  name:'App',
  methods: {
    getStudents(){
      axios.get('http://localhost:5002/students').then(
          response => {
            console.log('请求成功了',response.data)
          },
          error => {
            console.log('请求失败了',error.message)
          }
      )
    },
    getCars(){
      axios.get('http://localhost:5001/cars').then(
          response => {
            console.log('请求成功了',response.data)
          },
          error => {
            console.log('请求失败了',error.message)
          }
      )
    }
  },
}

要想解决此问题,则需要配置代理服务器。vue-cli脚手架帮构建一个代理服务器,它的请求路径同前端项目运行环境一致。由它去访问后台接口,从而避免跨域问题。

具体方式:

  1. vue.config.js中开启代理服务器
  2. 调用处向代理服务器发送请求
  3. vue-cli会自动帮我们进行代理请求发送

vue.config.js

devServer: {
    proxy: {
        '/xiang': {
            target: 'http://localhost:5000',
            pathRewrite: {'^/xiang': ''},
            // ws: true, //用于支持websocket
            // changeOrigin: true //用于控制请求头中的host值
        },
        '/demo': {
            target: 'http://localhost:5001',
            pathRewrite: {'^/demo': ''},
            // ws: true, //用于支持websocket
            // changeOrigin: true //用于控制请求头中的host值
        }
    }
}

App.vue

export default {
  name:'App',
  methods: {
    getStudents(){
      //此处将请求发送给代理服务器
      axios.get('http://localhost:8080/xiang/students').then(
          response => {
            console.log('请求成功了',response.data)
          },
          error => {
            console.log('请求失败了',error.message)
          }
      )
    },
    getCars(){
      axios.get('http://localhost:8080/demo/cars').then(
          response => {
            console.log('请求成功了',response.data)
          },
          error => {
            console.log('请求失败了',error.message)
          }
      )
    }
  },
}

4.2 vue项目中常用的两个ajax库

4.2.1 axios

通用的 Ajax 请求库, 官方推荐,使用广泛。需要安装引入。

<script>
 import axios from 'axios'
 export default {
  name:'Search',
  data() {
   return {
    keyWord:''
   }
  },
  methods: {
   searchUsers(){
    //请求前更新List的数据
    this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
    axios.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
     response => {
      console.log('请求成功了')
      //请求成功后更新List的数据
      this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
     },
     error => {
      //请求后更新List的数据
      this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
     }
    )
   }
  },
 }
</script>

4.2.2 vue-resource

vue自带插件,需要引入。但已不再维护,了解即可。

//引入插件
import vueResource from 'vue-resource'
//使用插件
Vue.use(vueResource)

<script>
 export default {
  name:'Search',
  data() {
   return {
    keyWord:''
   }
  },
  methods: {
   searchUsers(){
    //请求前更新List的数据
    this.$bus.$emit('updateListData',{isLoading:true,errMsg:'',users:[],isFirst:false})
    this.$http.get(`https://api.github.com/search/users?q=${this.keyWord}`).then(
     response => {
      console.log('请求成功了')
      //请求成功后更新List的数据
      this.$bus.$emit('updateListData',{isLoading:false,errMsg:'',users:response.data.items})
     },
     error => {
      //请求后更新List的数据
      this.$bus.$emit('updateListData',{isLoading:false,errMsg:error.message,users:[]})
     }
    )
   }
  },
 }
</script>

4.4 slot插槽

父组件向子组件传递带数据的标签,当一个组件有不确定的结构时, 就需要使用slot 技术,注意:插槽内容是在父组件中编译后, 再传递给子组件的。

分类:

  • 默认插槽
  • 命名插槽
  • 作用域插槽

示例:父组件下有三个子组件。

效果一:默认插槽

<template>
 <div class="container">
  <Category title="美食" >
   <img src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
      <ul>
        <li v-for="(f,index) in foods" :key="index">{{f}}</li>
      </ul>
  </Category>

  <Category title="游戏" >
   <ul>
    <li v-for="(g,index) in games" :key="index">{{g}}</li>
   </ul>
  </Category>

  <Category title="电影">
   <video controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
      <ul>
        <li v-for="(film,index) in films" :key="index">{{film}}</li>
      </ul>
  </Category>
 </div>
</template>

<script>
 import Category from './components/Category'
 export default {
  name:'App',
  components:{Category},
  data() {
   return {
    foods:['火锅','烧烤','小龙虾','牛排'],
    games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
    films:['《教父》','《拆弹专家》','《你好,李焕英》','《希昂》']
   }
  },
 }
</script>

<style scoped>
 .container{
  display: flex;
  justify-content: space-around;
 }
</style>

<template>
 <div class="category">
  <h3>{{title}}分类</h3>
  <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
  <slot>我是一些默认值,当使用者没有传递具体结构时,我会出现</slot>
 </div>
</template>

<script>
 export default {
  name:'Category',
  props:['title']
 }
</script>

效果二:具名插槽

<template>
 <div class="container">
  <Category title="美食" >
   <img slot="center" src="https://s3.ax1x.com/2021/01/16/srJlq0.jpg" alt="">
   <a slot="footer" href="http://www.baidu.com">更多美食</a>
  </Category>

  <Category title="游戏" >
   <ul slot="center">
    <li v-for="(g,index) in games" :key="index">{{g}}</li>
   </ul>
   <div class="foot" slot="footer">
    <a href="http://www.baidu.com">单机游戏</a>
    <a href="http://www.baidu.com">网络游戏</a>
   </div>
  </Category>

  <Category title="电影">
   <video slot="center" controls src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
   <template v-slot:footer>
    <div class="foot">
     <a href="http://www.baidu.com">经典</a>
     <a href="http://www.baidu.com">热门</a>
     <a href="http://www.baidu.com">推荐</a>
    </div>
    <h4>欢迎前来观影</h4>
   </template>
  </Category>
 </div>
</template>

<script>
 import Category from './components/Category'
 export default {
  name:'App',
  components:{Category},
  data() {
   return {
    foods:['火锅','烧烤','小龙虾','牛排'],
    games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
    films:['《教父》','《拆弹专家》','《你好,李焕英》','《希昂》']
   }
  },
 }
</script>
<template>
 <div class="category">
  <h3>{{title}}分类</h3>
  <!-- 定义一个插槽(挖个坑,等着组件的使用者进行填充) -->
  <slot name="center">我是一些默认值,当使用者没有传递具体结构时,我会出现1</slot>
  <slot name="footer">我是一些默认值,当使用者没有传递具体结构时,我会出现2</slot>
 </div>
</template>

<script>
 export default {
  name:'Category',
  props:['title']
 }
</script>

效果三:作用域插槽

<template>
 <div class="container">

  <Category title="游戏">
   <template scope="xiang">
    <ul>
     <li v-for="(g,index) in xiang.games" :key="index">{{g}}</li>
    </ul>
   </template>
  </Category>

  <Category title="游戏">
   <template scope="{games}">
    <ol>
     <li style="color:red" v-for="(g,index) in games" :key="index">{{g}}</li>
    </ol>
   </template>
  </Category>

  <Category title="游戏">
   <template slot-scope="{games}">
    <h4 v-for="(g,index) in games" :key="index">{{g}}</h4>
   </template>
  </Category>

 </div>
</template>

<script>
 import Category from './components/Category'
 export default {
  name:'App',
  components:{Category},
 }
</script>
<template>
 <div class="category">
  <h3>{{title}}分类</h3>
  <slot :games="games" msg="hello">我是默认的一些内容</slot>
 </div>
</template>

<script>
 export default {
  name:'Category',
  props:['title'],
  data() {
   return {
    games:['红色警戒','穿越火线','劲舞团','超级玛丽'],
   }
  },
 }
</script>

五、vuex

5.1 理解vuex

概念:专门在 Vue 中实现集中式状态(数据)管理的一个Vue 插件,对vue应用中多个组件的共享状态进行集中式的管理(读/写),也是一种组件间通信的方式,且适用于任意组件间通信。

Github : GitHub - vuejs/vuex: 🗃️ Centralized State Management for Vue.js.

使用场景

  • 多个组件依赖于同一状态
  • 来自不同组件的行为需要变更同一状态

工作原理图:

5.2 vuex核心概念

5.2.1 state

存储vuex 管理的状态对象。它应该是唯一的。

示例代码:

const state = { xxx:initValue }
5.2.2 actions

它的值为一个对象,包含多个响应用户动作的回调函数。通过 commit( )来触发 mutation 中函数的调用, 间接更新state

如何触发 actions 中的回调?在组件中使用: $store.dispatch('对应的 action 回调名') 触发

可以包含异步代码(定时器, ajax 等等)。

示例代码:

const actions = {
   zzz(context,value){
     context.commit('yyy',value)
   }
}

5.2.3 mutations

它的值是一个对象,包含多个直接更新 state 的方法。

谁能调用 mutations 中的方法?如何调用?在 action 中使用:commit('对应的 mutations 方法名') 触发。

mutations 中方法的特点:不能写异步代码、只能单纯的操作state。(约定)

示例代码:

const mutations = {
   yyy(state,value){
      state.xxx = value
   }
}

5.2.4 getters

值为一个对象,包含多个用于返回数据的函数。可以理解为是state的一些计算属性。

如何使用:$store.getters.xxx

示例代码:

const getters = {
   bigSum(state){
      return state.sum * 10
   }
}

5.2.5 简单总结

stat:存储数据

actions:定义支持的一些方法,可以包含一些处理逻辑。 $store.dispatch('zzz') 触发

mutations:定义最原子的方法,里面不要包含复杂逻辑。$store.commit('yyy') 触发

5.3示例

5.3.1 定义store内容

在src目录下创建store目录,并创建index.js.

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
//应用Vuex插件
Vue.use(Vuex)

//准备actions——用于响应组件中的动作
const actions = {
   jiaWait(context,value){
      console.log('actions中的jiaWait被调用了')
      setTimeout(()=>{
         context.commit('JIA',value)
      },500)
   }
}
//准备mutations——用于操作数据(state)
const mutations = {
   JIA(state,value){
      console.log('mutations中的JIA被调用了')
      state.sum += value
   },
   JIAN(state,value){
      console.log('mutations中的JIAN被调用了')
      state.sum -= value
   }
}
//准备state——用于存储数据
const state = {
   sum:0 //当前的和
}

//准备getters——用于将state中的数据进行加工
const getters = {
   bigSum(state){
      return state.sum*10
   }
}

//创建并暴露store
export default new Vuex.Store({
   actions,
   mutations,
   state,
})

5.3.2 引入store

main.js引入store

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store'

//关闭Vue的生产提示
Vue.config.productionTip = false

//创建vm
new Vue({
   el:'#app',
   render: h => h(App),
   store
})

5.3.3 使用store

Count.vue中可以通过$store对象,获取状态对象,或调用方法。

可以通过this.$store.dispatch调用actions,也可以直接通过this.$store.commit调用mutations。

<template>
 <div>
  <h1>当前求和为:{{$store.state.sum}}</h1>
  <h3>当前求和放大10倍为:{{$store.getters.bigSum}}</h3>
  <select v-model.number="n">
   <option value="1">1</option>
   <option value="2">2</option>
   <option value="3">3</option>
  </select>
  <button @click="increment">+</button>
  <button @click="decrement">-</button>
  <button @click="incrementOdd">当前求和为奇数再加</button>
  <button @click="incrementWait">等一等再加</button>
 </div>
</template>

<script>
 export default {
  name:'Count',
  data() {
   return {
    n:1, //用户选择的数字
   }
  },
  methods: {
   increment(){
    this.$store.commit('JIA',this.n)
   },
   decrement(){
    this.$store.commit('JIAN',this.n)
   },
   incrementOdd(){
    this.$store.dispatch('jiaOdd',this.n)
   },
   incrementWait(){
    this.$store.dispatch('jiaWait',this.n)
   },
  },
  mounted() {
   console.log('Count',this)
  },
 }
</script>

<style lang="css">
 button{
  margin-left: 5px;
 }
</style>

5.4 map简写

上述示例中,调用$store方法时,总是要加一些this.$store.xxx前缀才能获取值或者调用方法,不够简洁。

可以通过如下几个对象进行简化,直接将$store中的属性值或方法映射成vue对象中的属性或方法。

mapState,mapGetters,mapMutations,mapActions

示例:

<template>
  <div>
    <h1>当前求和为:{{sum}}</h1>
    <h3>当前求和放大10倍为:{{bigSum}}</h3>
    <h3>我是{{name}},学习{{subject}}</h3>
    <select v-model.number="n">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
    </select>
    <button @click="increment(n)">+</button>
    <button @click="decrement(n)">-</button>
    <button @click="incrementOdd(n)">当前求和为奇数再加</button>
    <button @click="incrementWait(n)">等一等再加</button>
  </div>
</template>

<script>
import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
export default {
  name:'Count',
  data() {
    return {
      n:1, //用户选择的数字
    }
  },
  computed:{
    //借助mapState生成计算属性,从state中读取数据。(对象写法)
    // ...mapState({he:'sum',xuexiao:'school',xueke:'subject'}),

    //借助mapState生成计算属性,从state中读取数据。(数组写法)
    ...mapState(['sum','name','subject']),


    //借助mapGetters生成计算属性,从getters中读取数据。(对象写法)
    // ...mapGetters({bigSum:'bigSum'})

    //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
    ...mapGetters(['bigSum'])

  },
  methods: {

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
    ...mapMutations({increment:'JIA',decrement:'JIAN'}),

    //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(数组写法)
    // ...mapMutations(['JIA','JIAN']),

    //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
    ...mapActions({incrementOdd:'jiaOdd',incrementWait:'jiaWait'})

    //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(数组写法)
    // ...mapActions(['jiaOdd','jiaWait'])
  },
  mounted() {
    const x = mapState({he:'sum',xuexiao:'school',xueke:'subject'})
    console.log(x)
  },
}
</script>

<style lang="css">
button{
  margin-left: 5px;
}
</style>

5.5 vuex模块化

vuex支持按业务模块定义state。

5.5.1 定义store

定义模块一:count.js

//求和相关的配置
export default {
   namespaced:true,
   actions:{
      jiaOdd(context,value){
         console.log('actions中的jiaOdd被调用了')
         if(context.state.sum % 2){
            context.commit('JIA',value)
         }
      },
      jiaWait(context,value){
         console.log('actions中的jiaWait被调用了')
         setTimeout(()=>{
            context.commit('JIA',value)
         },500)
      }
   },
   mutations:{
      JIA(state,value){
         console.log('mutations中的JIA被调用了')
         state.sum += value
      },
      JIAN(state,value){
         console.log('mutations中的JIAN被调用了')
         state.sum -= value
      },
   },
   state:{
      sum:0, //当前的和
      school:'希昂',
      subject:'前端',
   },
   getters:{
      bigSum(state){
         return state.sum*10
      }
   },
}

定义模块二:person.js

//人员管理相关的配置
import axios from 'axios'
import { nanoid } from 'nanoid'
export default {
   namespaced:true,
   actions:{
      addPersonWang(context,value){
         if(value.name.indexOf('王') === 0){
            context.commit('ADD_PERSON',value)
         }else{
            alert('添加的人必须姓王!')
         }
      },
      addPersonServer(context){
         axios.get('https://api.uixsj.cn/hitokoto/get?type=social').then(
            response => {
               context.commit('ADD_PERSON',{id:nanoid(),name:response.data})
            },
            error => {
               alert(error.message)
            }
         )
      }
   },
   mutations:{
      ADD_PERSON(state,value){
         console.log('mutations中的ADD_PERSON被调用了')
         state.personList.unshift(value)
      }
   },
   state:{
      personList:[
         {id:'001',name:'张三'}
      ]
   },
   getters:{
      firstPersonName(state){
         return state.personList[0].name
      }
   },
}

定义核心store.js

//该文件用于创建Vuex中最为核心的store
import Vue from 'vue'
//引入Vuex
import Vuex from 'vuex'
import countOptions from './count'
import personOptions from './person'
//应用Vuex插件
Vue.use(Vuex)

//创建并暴露store
export default new Vuex.Store({
   modules:{
      countAbout:countOptions,
      personAbout:personOptions
   }
})

5.5.2 引入store

与之前相同

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入store
import store from './store'


//创建vm
new Vue({
   el:'#app',
   render: h => h(App),
   store
})

5.5.3 使用store

使用时注意指定模块。

方式一:使用$store方式

<script>
 import {nanoid} from 'nanoid'
 export default {
  name:'Person',
  data() {
   return {
    name:''
   }
  },
  computed:{
   personList(){
    return this.$store.state.personAbout.personList
   },
   sum(){
    return this.$store.state.countAbout.sum
   },
   firstPersonName(){
    return this.$store.getters['personAbout/firstPersonName']
   }
  },
  methods: {
   add(){
    const personObj = {id:nanoid(),name:this.name}
    this.$store.commit('personAbout/ADD_PERSON',personObj)
    this.name = ''
   },
   addWang(){
    const personObj = {id:nanoid(),name:this.name}
    this.$store.dispatch('personAbout/addPersonWang',personObj)
    this.name = ''
   },
   addPersonServer(){
    this.$store.dispatch('personAbout/addPersonServer')
   }
  },
 }
</script>

方式二:使用map方式

<script>
 import {mapState,mapGetters,mapMutations,mapActions} from 'vuex'
 export default {
  name:'Count',
  data() {
   return {
    n:1, //用户选择的数字
   }
  },
  computed:{
   //借助mapState生成计算属性,从state中读取数据。(数组写法)
   ...mapState('countAbout',['sum','school','subject']),
   ...mapState('personAbout',['personList']),
   //借助mapGetters生成计算属性,从getters中读取数据。(数组写法)
   ...mapGetters('countAbout',['bigSum'])
  },
  methods: {
   //借助mapMutations生成对应的方法,方法中会调用commit去联系mutations(对象写法)
   ...mapMutations('countAbout',{increment:'JIA',decrement:'JIAN'}),
   //借助mapActions生成对应的方法,方法中会调用dispatch去联系actions(对象写法)
   ...mapActions('countAbout',{incrementOdd:'jiaOdd',incrementWait:'jiaWait'})
  },
  mounted() {
   console.log(this.$store)
  },
 }
</script>

六、vue-router

6.1 相关理解

vue-router:是vue 的一个插件库,专门用来实现 SPA 应用。

SPA 应用:

是单页 Web 应用(single page web application,SPA)。整个应用只有一个完整的页面,点击页面中的导航链接不会刷新页面,只会做页面的局部更新。数据需要通过 ajax 请求获取。

路由:

一个路由就是一组映射关系(key - value)。key 为路径, value 可能是 function 或 component。

路由分类

  • 后端路由
    • 理解:value 是 function, 用于处理客户端提交的请求。
    • 工作过程:服务器接收到一个请求时, 根据请求路径找到匹配的函数来处理请求, 返回响应数据。
  • 前端路由
    • 理解:value 是 component,用于展示页面内容。
    • 工作过程:当浏览器的路径改变时, 对应的组件就会显示

6.2 基本路由

6.2.1 编写路由步骤

  1. 定义路由组件
  2. 注册路由
  3. 使用路由

6.2.2 定义路由组件

引入VueRouter,创建路由器: router/index.js

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../components/About'
import Home from '../components/Home'

//创建并暴露一个路由器
export default new VueRouter({
   routes:[
      {
         path:'/about',
         component:About
      },
      {
         path:'/home',
         component:Home
      }
   ]
})

6.2.3 注册路由

在main.js中引入路由器,注册路由

//引入Vue
import Vue from 'vue'
//引入App
import App from './App.vue'
//引入VueRouter
import VueRouter from 'vue-router'
//引入路由器
import router from './router'

//关闭Vue的生产提示
Vue.config.productionTip = false
//应用插件
Vue.use(VueRouter)

//创建vm
new Vue({
   el:'#app',
   render: h => h(App),
   router:router
})

6.2.4 使用路由

  • 借助router-link标签实现路由的切换
  • 使用router-view指定组件的呈现位置

App.vue

<template>
  <div>
    <div class="row">
      <div class="col-xs-offset-2 col-xs-8">
        <div class="page-header"><h2>Vue Router Demo</h2></div>
      </div>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
         <!-- Vue中借助router-link标签实现路由的切换 -->
         <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
          <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
        </div>
      </div>
      <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
              <!-- 指定组件的呈现位置 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

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

About.vue

<template>
 <h2>我是About的内容</h2>
</template>

<script>
 export default {
  name:'About'
 }
</script>

6.2.5 示例效果

默认进入主页,注意路径中包含/#/

点击About路由跳转,注意路径中包含/#/about。About组件被激活。

6.3 嵌套路由

路由支持嵌套(多级)。注意定义和使用方式。

6.3.1 定义多级路由

router/index.js。注意path仅在第一层加了/

// 该文件专门用于创建整个应用的路由器
import VueRouter from 'vue-router'
//引入组件
import About from '../pages/About'
import Home from '../pages/Home'
import News from '../pages/News'
import Message from '../pages/Message'

//创建并暴露一个路由器
export default new VueRouter({
   routes:[
      {
         path:'/about',
         component:About
      },
      {
         path:'/home',
         component:Home,
         children:[
            {
               path:'news',
               component:News,
            },
            {
               path:'message',
               component:Message,
            }
         ]
      }
   ]
})

6.3.2 使用多级路由

App.vue 同上

<template>
  <div>
    <div class="row">
      <Banner/>
    </div>
    <div class="row">
      <div class="col-xs-2 col-xs-offset-2">
        <div class="list-group">
         <!-- Vue中借助router-link标签实现路由的切换 -->
         <router-link class="list-group-item" active-class="active" to="/about">About</router-link>
          <router-link class="list-group-item" active-class="active" to="/home">Home</router-link>
        </div>
      </div>
      <div class="col-xs-6">
        <div class="panel">
          <div class="panel-body">
              <!-- 指定组件的呈现位置 -->
            <router-view></router-view>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
 import Banner from './components/Banner'
 export default {
  name:'App',
  components:{Banner}
 }
</script>

Home.vue 它内部有一层路由,注意跳转路径是父+子:/home/news

<template>
 <div>
  <h2>Home组件内容</h2>
  <div>
   <ul class="nav nav-tabs">
    <li>
     <router-link class="list-group-item" active-class="active" to="/home/news">News</router-link>
    </li>
    <li>
     <router-link class="list-group-item" active-class="active" to="/home/message">Message</router-link>
    </li>
   </ul>
   <router-view></router-view>
  </div>
 </div>
</template>

<script>
 export default {
  name:'Home',
 }
</script>

message.vue

<template>
 <div>
  <ul>
   <li>
    <a href="/message1">message001</a>&nbsp;&nbsp;
   </li>
   <li>
    <a href="/message2">message002</a>&nbsp;&nbsp;
   </li>
   <li>
    <a href="/message/3">message003</a>&nbsp;&nbsp;
   </li>
  </ul>
 </div>
</template>

<script>
 export default {
  name:'Message'
 }
</script>

6.3.3 效果

url中路由路径是多级。

6.4 路由传参

路由跳转时,可以传递参数。通过标签的to属性。

6.4.1 使用方式

传参方式:

//简单用法,直接跳转,没有参数
 <router-link to="/home/message/detail">{{m.title}}</router-link>
 
//简单写法:跳转+参数传递
<router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>

//对象写法:跳转+参数传递
<router-link :to="{
 path:'/home/message/detail',
 query:{
  id:m.id,
  title:m.title
 }
}">
 {{m.title}}
</router-link>

参数接收:

接收处使用$router对象接收

<li>消息编号:{{$route.query.id}}</li>
<li>消息标题:{{$route.query.title}}</li>

6.4.2 示例

Message路由到Detail中,并传递参数

Message.vue 传递参数

<template>
 <div>
  <ul>
   <li v-for="m in messageList" :key="m.id">
    <!-- 跳转路由并携带query参数,to的字符串写法 -->
    <!-- <router-link :to="`/home/message/detail?id=${m.id}&title=${m.title}`">{{m.title}}</router-link>&nbsp;&nbsp; -->

    <!-- 跳转路由并携带query参数,to的对象写法 -->
    <router-link :to="{
     path:'/home/message/detail',
     query:{
      id:m.id,
      title:m.title
     }
    }">
     {{m.title}}
    </router-link>
   
   </li>
  </ul>
  <hr>
  <router-view></router-view>
 </div>
</template>

<script>
 export default {
  name:'Message',
  data() {
   return {
    messageList:[
     {id:'001',title:'消息001'},
     {id:'002',title:'消息002'},
     {id:'003',title:'消息003'}
    ]
   }
  },
 }
</script>

Detail.vue 接收参数

<template>
 <ul>
  <li>消息编号:{{$route.query.id}}</li>
  <li>消息标题:{{$route.query.title}}</li>
 </ul>
</template>

<script>
 export default {
  name:'Detail',
  mounted() {
   console.log(this.$route)
  },
 }
</script>

效果:

6.5 路由重要属性

6.5.1 路由名称

定义路由时,除了path外,还有一个名称属性name。也可以用于跳转。

使用方式:

// 定义name,注意name不得重复
{
   path:'message',
   component:Message,
   children:[
      {
         name:'xiangqing',
         path:'detail',
         component:Detail,
      }
   ]
}

//使用to的对象写法
<router-link :to="{
 name:'xiangqing',
 query:{
  id:m.id,
  title:m.title
 }
}">
 {{m.title}}
</router-link>

6.5.2 路由的params参数

路由跳转时,除了上述的参数传递方式外,还可以通过params参数的方式传递。概念同java的pathVariable。

使用方式:

//定义路由
{
   path:'message',
   component:Message,
   children:[
      {
         name:'xiangqing',
         path:'detail/:id/:title',
         component:Detail,
      }
   ]
}

//接收使用param参数
<template>
 <ul>
  <li>消息编号:{{$route.params.id}}</li>
  <li>消息标题:{{$route.params.title}}</li>
 </ul>
</template>

6.5.3 路由的props参数

路由跳转时,如果有参数传递,可以将传递的参数转换成to对象的props参数,从而简化使用。

//定义
{
   path:'message',
   component:Message,
   children:[
      {
         name:'xiangqing',
         path:'detail',
         component:Detail,

         //props的第一种写法,值为对象,该对象中的所有key-value都会以props的形式传给Detail组件。
         // props:{a:1,b:'hello'}

         //props的第二种写法,值为布尔值,若布尔值为真,就会把该路由组件收到的所有params参数,以props的形式传给Detail组件。
         // props:true

         //props的第三种写法,值为函数
         props($route){
            return {
               id:$route.query.id,
               title:$route.query.title,
               a:1,
               b:'hello'
            }
         }

      }
   ]
}

//使用
<template>
 <ul>
  <li>消息编号:{{id}}</li>
  <li>消息标题:{{title}}</li>
 </ul>
</template>

<script>
 export default {
  name:'Detail',
  props:['id','title']
 }
</script>

6.5.4 编程式路由

相关 API:

  1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
  2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
  3. this.$router.back(): 请求(返回)上一个记录路由
  4. this.$router.go(-1): 请求(返回)上一个记录路由
  5. this.$router.go(1): 请求下一个记录路由

6.6 两个新的生命周期钩子

router路由切换时,默认是创建和销毁。对应的生命周期是:mounted()和Destory().

但可以通过keep-alive的方式保存组件,切换时不再进行销毁和新建。仅限于在父组件里切换时,如果父组件被切换销毁了,这里还是会被销毁。

<keep-alive include="News">
 <router-view></router-view>
</keep-alive>

此时对应的生命周期,就是两个新的生命周期:

  • activated : 激活
  • deactivated:失活
<script>
 export default {
  name:'News',
  data() {
   return {
    opacity:1
   }
  },
   beforeDestroy() {
   console.log('News组件即将被销毁了')
  },
   mounted(){
       console.log('News组件创建了')
  },
  activated() {
   console.log('News组件被激活了')
   this.timer = setInterval(() => {
    console.log('@')
    this.opacity -= 0.01
    if(this.opacity <= 0) this.opacity = 1
   },16)
  },
  deactivated() {
   console.log('News组件失活了')
   clearInterval(this.timer)
  },
 }
</script>

6.7 路由守卫

所谓的路由守卫,就是在进入路由前进行校验拦截或放行,以及进入路由后进行一些补充事件。

6.7.1 全局路由守卫

在router/index.js中设置全局路由守卫。

//全局前置路由守卫————初始化的时候被调用、每次路由切换之前被调用
router.beforeEach((to,from,next)=>{
   console.log('前置路由守卫',to,from)
   if(to.meta.isAuth){ //判断是否需要鉴权
      if(localStorage.getItem('name')==='xiang'){
         next()
      }else{
         alert('名称不对,无权限查看!')
      }
   }else{
      next()
   }
})

//全局后置路由守卫————初始化的时候被调用、每次路由切换之后被调用
router.afterEach((to,from)=>{
   console.log('后置路由守卫',to,from)
   document.title = to.meta.title || '哈哈哈'
})

6.7.2 独享路由守卫

在router/index.js的每个组件上,可以设置独享路由守卫。只有前置,没有后置。

{
   name:'xinwen',
   path:'news',
   component:News,
   meta:{isAuth:true,title:'新闻'},
   beforeEnter: (to, from, next) => {
      console.log('独享路由守卫',to,from)
      if(to.meta.isAuth){ //判断是否需要鉴权
         if(localStorage.getItem('name')==='xiang'){
            next()
         }else{
            alert('名称不对,无权限查看!')
         }
      }else{
         next()
      }
   }
},

6.7.3 组件内路由守卫

可以在组件里,设置beforeRouteEnter和beforeRouteLeave。

<template>
 <h2>我是About的内容</h2>
</template>

<script>
 export default {
  name:'About',
  //通过路由规则,进入该组件时被调用
  beforeRouteEnter (to, from, next) {
   console.log('About--beforeRouteEnter',to,from)
   if(to.meta.isAuth){ //判断是否需要鉴权
    if(localStorage.getItem('school')==='xiang'){
     next()
    }else{
     alert('学校名不对,无权限查看!')
    }
   }else{
    next()
   }
  },

  //通过路由规则,离开该组件时被调用
  beforeRouteLeave (to, from, next) {
   console.log('About--beforeRouteLeave',to,from)
   next()
  }
 }
</script>

七、Vue UI组件库

7.1 移动端常用UI组件库

7.2 PC端常用UI组件库

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值