Vue基础 - 自我总结

vue

1. 什么是 vue

  • 官方给出的概念:vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面渐进式****框架

  • 构建用户界面:前端每天就是在做画用户界面的工作,vue 帮助我们以更简单的方式来做这些事情。

  • 渐进式:vue 不强求程序员一次性接受并使用它的全部功能和特性。

  • 框架:框架指的就是程序员必须遵守的规则或约束。

2. vue指令 - vue 中6中常见的指令

  1. 内容渲染指令(对应dom中的文本节点

    • {{ }}

    • v-html

  2. 属性绑定指令(对应dom中的属性节点

    • v-bind
    • :
  3. 事件绑定指令(对应dom中的事件

    • v-on:事件类型="事件处理函数"
    • @事件类型="事件处理函数"
    • 事件修饰符
      • .stop
      • .prevent
    • 按键修饰符
      • .enter
  4. 双向绑定指令(vue中特有的,react中想做需要自己实现)

    • v-model
    • 专属修饰符
      • .number
      • .trim
      • .lazy
  5. 条件渲染指令(在页面模板中写if

  6. 列表渲染指令(在页面模板中写for)

  • 概念:指令(Directives)是 vue 为开发者提供的一套特殊语法。
  • vue模板:指的是被vue控制区域里面的html部分

2.1 内容渲染指令

  • {{ }}

    又名:插表达式

    作用:将数据动态的渲染到 DOM 的内容区域

    <p>姓名:{{ username }}</p>
    <p>性别:{{ gender }}</p>
    
  • v-html

    作用:可以将带有HTML标签的字符串渲染成真正的 HTML 元素

    <p v-html="str"></p>
    

2.2 属性绑定指令

  • v-bind

    作用:将数据动态的绑定到元素的属性身上

    语法:v-bind:属性名=“属性值”

    <input type="text" v-bind:placeholder="inpValue">
    
  • 简写:: [推荐]

    <input type="text" :placeholder="inpValue">
    

2.3 事件绑定指令

  • 语法:v-on:事件类型="事件处理函数"

    <button v-on:click="addCount"> 点击 +1 </button>
    
  • 简写:@事件类型="事件处理函数" [推荐]

    <button @click="redCount"> 点击 -1 </button>
    
  • 注意

    1. 如果事件处理函数只有一行代码,可以写到行内

      <button @click="count = 0"> 点击恢复初始值 0 </button>
      
    2. 事件传参

      <button @click="take(2)"> 点击 * 2 </button>
      <button @click="take(10)"> 点击 * 10 </button>
      
  • 示例

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>事件绑定指令</title>
      <script src="./lib/vue.js"></script>
    </head>
    <body>
      <div id="app">
        <h3>cont 的值是:{{ count }}</h3>
        <!-- v-on -->
        <!-- 语法:v-on:事件类型="事件处理函数" -->
        <button v-on:click="addCount"> 点击 +1 </button>
        <!-- 简写:@事件类型="事件处理函数" -->
        <button @click="redCount"> 点击 -1 </button>
        <hr>
        <!-- 如果事件处理函数只有一行代码,可以写到行内 -->
        <button @click="count = 0"> 点击恢复初始值 0 </button>
        <hr>
        <!-- 事件传参 -->
        <button @click="take(2)"> 点击 * 2 </button>
        <button @click="take(10)"> 点击 * 10 </button>
      </div>
      <script>
        const vm = new Vue({
          el: '#app',
          data: {
            count: 0
          },
          methods: {
            // addCount: function() {  // 下一行为这一行的简写
            addCount() { // 上一行的简写,ES6中,对象里面函数的简写方式
              // 方法中通过 this.数据名 来拿到data中定义的数据
              this.count++
            },
            redCount() {
              this.count--
            },
            take(num) {
              this.count *= num
            }
          }
        })
      </script>
    </body>
    </html>
    
2.3.1 事件修饰符
  • .prevent 阻止事件的默认行为

    <a :href="www" @click.prevent="toBai">{{ txt }}</a>
    
  • .stop 阻止事件冒泡,谁产生的冒泡加给谁

    <div class="outer-box" @click="outerClick">
      <!-- .stop 阻止事件冒泡,谁产生的冒泡加给谁 -->
      <div class="inner-box" @click.stop="innerClick"></div>
    </div>
    
2.3.2 按键修饰符
  • enter 只有按下enter键才会触发

    <input type="text" @keyup.enter="submit">
    
  • esc只有按下esc键才会触发

    <input type="text" @keyup.esc="clearsubmit">
    

2.4 双向绑定指令

  • v-model

  • 特点:数据变化视图更新,视图变化数据更新。

  • 注意:v-model 只能运用在表单元素上。

    <input type="text" v-model="username">
    
2.4.1 专属修饰符
  • .number

    • 作用:将v-model 绑定的数据装换成数值类型(number类型)
    <input type="text" v-model.number="n1"> + <input type="text" v-model.number="n2"> = {{ n1 + n2 }}
    
  • .trim

    • 作用:自动的将 v-model 绑定的数据去掉前后的空格
    <input type="text" v-model.trim="str">
    
  • .lazy

    • 作用:将 v-model 默认的input事件(改变就触发),改变为change事件(失去焦点并且改变了触发)
    <input type="text" v-model.lazy="str2">
    

2.5 条件渲染指令

  • v-if (推荐) v-show

    • v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏
    • v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>条件渲染指令</title>
      <script src="./lib/vue.js"></script>
    </head>
    <body>
      <div id="app">
        <!-- 显示隐藏 -->
        <button @click="flag = !flag">显示与隐藏</button>
        <hr>
        <!-- v-if 和 v-show 都是用来控制 DOM 元素的显示与隐藏 -->
        <!-- v-if 和 v-show 绑定的 data 数据值为 true 显示,false 隐藏 -->
        <!-- 推荐 v-if -->
        <p v-if="flag">这是 v-if 控制的 DOM 元素</p>
        <p v-show="flag">这是 v-show 控制的 DOM 元素</p>
        <hr>
      </div>
      <script>
        const vm = new Vue({
          el: '#app',
          data: {
            a: 10,
            b: 5,
            flag: true
          }
        })
      </script>
    </body>
    </html>
    
2.5.1 v-if的配套指令
  • v-else-if v-else

  • 注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>v-if的配套指令</title>
      <script src="./lib/vue.js"></script>
    </head>
    <body>
      <div id="app">
        <!-- v-if 的配套指令:v-else-if  v-else -->
        <!-- 注意:v-else 和 v-else-if 指令必须配合 v-if 一起使用,否则报错 -->
        <p v-if="score === 'A'">优秀</p>
        <p v-else-if="score === 'B'">良好</p>
        <p v-else-if="score === 'C'">一般</p>
        <p v-else></p>
      </div>
      <script>
        const vm = new Vue({
          el: '#app',
          data: {
            score: 'A'
          }
        })
      </script>
    </body>
    </html>
    

2.6 列表渲染指令

  • v-for
  • 语法:v-for=“数组中的每一项 in 数组”
    • v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for
    • v-for指令还支持一个可选的第二个参数,即当前的索引
  • 注意:
    1. 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)
    2. key 的值只能是字符串或数字类型
    3. key 的值必须具有唯一性(建议把数据项id属性的值作为key的值)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>列表渲染指令</title>
  <script src="./lib/vue.js"></script>
</head>
<body>
  <div id="app">
    <!-- 
        注意事项:
        1. 使用 v-for 指令时 一定要指定 key 的值(既提升性能,又防止列表渲染混乱)
        2. key 的值只能是字符串或数字类型
        3. key 的值必须具有唯一性(建议把数据项id属性的值作为key的值)
    -->
    <ul>
      <!-- v-for需要循环渲染哪个元素,就在哪个元素身上添加 v-for -->
      <!-- 语法:v-for="数组中的每一项 in 数组" -->
      <li v-for="item in list" :key="item.id">姓名:{{ item.name }}</li>
      <hr>
      <!-- v-for指令还支持一个可选的第二个参数,即当前的索引 -->
      <li v-for="(item, index) in list" :key="item.id"> {{ list[index] }} </li>
    </ul>
  </div>
  <script>
    const vm = new Vue({
      el: '#app',
      data: {
        list: [
          {id: 1, name: '张三', age: 23},
          {id: 2, name: '李四', age: 24},
          {id: 3, name: '王五', age: 25},
          {id: 4, name: '赵六', age: 25}
        ]
      }
    })
  </script>
</body>
</html>

3. MVVM 的概念

3.1 vue 的特性

  • vue 框架的特性,主要体现在两方面:
    1. 数据驱动视图
      • 在使用了 vue 的页面中,data 数据的变化,会导致页面结构的重新渲染。
      • 好处:减少了程序员对 DOM 的
      • 注意:数据驱动视图是单向的数据绑定。
    2. 双向数据绑
      • data 数据的变化,会导致页面的重新渲染
      • 表单数据的变化,会被自动更新到 data 数据中
      • 好处:开发者不再需要手动操作 DOM 元素,来获取表单元素最新的值

3.2 MVVM

MVVM 是 vue 实现数据驱动视图和双向数据绑定的核心原理。

MVVM 指的是 Model、View 和 ViewModel。

它把每个 HTML 页面都拆分成了这三个部分,如图所示:

4. vue 过滤器

  1. 定义过滤器

    Vue.filter(‘过滤器名字’, fn)

  2. 使用过滤器

    {{ 要转换格式的数据 | 过滤器的名字 }}

  • 作用:

    过滤器时给在模板中使用的数据做格式转换的

  • 用途:过滤器可以使用在两个地方:

    1. 插值表达式
    2. v-bind 属性绑定
  • 使用步骤:

    1. 定义过滤器

      Vue.filter('dollar', (str) => {
      	return '$' + str
      })
      
    2. 使用过滤器

      <h2>{{ money | dollar }}<h2>
      
      data: {
      	money: 100
      }
      
  • 注意:

    1. 过滤器必须定义在 new Vue 之前。
    2. fn 函数的格式:(要转换格式的数据) => { return 转换格式后的结果 }
    3. 过滤器仅在 vue 2.x 和 vue1.x 中支持,在 vue3.x 的版本中剔除了过滤器相关功能。如果时 vue 3.x 的项目,官方建议使用计算属性或 methods 方法实现对应功能就好了。

5. 侦听器

5.1 函数形式 - 侦听器

  • 作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。

  • 使用步骤:

    <div id="app">
    	<input type="text" v-model="username">
    </div>
    
    data: {
    	username: 'zs'
    },
    watch: {
      // 这里的函数名要对应你要监听的数据名
    	username(newVal, oldVal) {
        // newVal 是变化后的新值,oldVal 是变化前的老值
        console.log(newVal, oldVal)
      }
    }
    
  • 注意:不需要带选项得时候这样写,这样写无法带选项

5.2 对象形式 - 侦听器

  • 作用:watch 侦听器可以监听数据的变化,从而针对数据的变化做特定的操作。
  • 特点可以带选项
5.2.1 innediate 选项
  • 功能:默认情况下,组件在初次加载完毕后不会调用 watch 侦听器。如果想让 watch 侦听器一进入页面就立即被 调用一次,则需要使用 immediate 选项。

    <input type="text" v-model="username">
    
    watch: {
    	username: {
      	handler(newVal, oldVal) {
        	console.log(newVal, oldVal)
        },
        immediate: true
      }
    }
    
  • 注意:

    1. 侦听器要写成对象形式,参数放在 handler 函数里面
    2. 添加 immediate 选项为 true
5.2.2 deep 选项
  • 用途:如果 watch 侦听的是一个对象,对象中的属性值发生了变化,则无法被监听到。此时需要使用 deep

  • 示例:

    <input type="text" v-model="user.age">
    
    data: {
        user: {
         age: 18
       }
    },
    watch: {
      user: {
        handler(newVal, oldVal) {
          console.log(newVal.age, oldVal.age)
        },
        deep: true
      }
    }
    
  • 注意:

    1. 侦听器要写成对象形式,参数放在 handler 函数里面
    2. 如果监听的数据是一个对象,想要对象里任何一个属性发生变化监听到,就要设置 deep 选项值为 true
5.2.3 监听对象中单个属性的变化
  • 如果只想监听对象中单个属性的变化,则可以按照如下的方式定义 watch 侦听器:

  • 示例:

    <input type="text" v-model="user.age">
    
    watch: {
    	'user.age'(newVal, oldVal) {
    		console.log(newVal, oldVal)
    	}
    }
    

6. 计算属性

6.1 计算属性 - 基本使用

  • 定义:计算属性是依赖于 data 中的数据动态计算出来的一个值

  • 特点:计算属性所依赖的任何一个 data 数据发生变化,这个计算属性也会跟着变化。

  • 示例:

    1. 定义计算属性:

      data: {
        n1: 1,
        n2: 1
      },
      // 计算属性声明在 computed 选项下面
      computed: {
        // 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。
        // 计算属性中,必须 return 一个计算的结果
        chengJi() {
          return this.n1 * this.n2
      }
      }
      
    
    2. 使用计算属性:
    
      ```html
      <!-- 计算属性 -->
      <input type="number" v-model="n1">
      *
    <input type="number" v-model="n2">
      = 
      {{ chengJi }}
    
  • 注意:

    1. 计算属性声明在 computed 选项下面
    2. 计算属性声明的时候被定义为一个方法,使用的时候是作为一个属性去使用。
    3. 计算属性中,必须 return 一个计算的结果

6.2 计算属性 - 计算属性的缓存

  • 计算属性能做的事,都可以使用方法代替。

  • 区别:计算属性会缓存计算的结果,只有计算属性依赖的数据变化时,才会重新进行计算。而函数不会缓存计算的 结果,所以计算属性的性能比函数要好。

  • 示例:

    1. 声明一个计算属性和一个方法

      // 使用 computed 选项声明计算属性
      computed: {
        // 计算属性定义的的时候是一个函数
        multi() {
          // 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存
          // 只有计算属性依赖的数据变化时,再会再次调用一次
          console.log('调用了计算属性');
          // 计算属性中,要通过 return 返回一个值
          return this.n1 * this.n2
        }
      },
      // 使用方法也可以是实现计算
      methods: {
        multiByMethod() {
          // 调用几次,打印几次
          console.log('调用了计算方法');
          return this.n1 * this.n2
        }
      }
      
    2. 在模板中分别多次调用使用计算属性和方法

      <!-- 使用 computed 计算属性 -->
      <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div>
      <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div>
      <div>通过 computed 计算属性,计算 n1 * n2 的值:{{ multi }}</div>
      
      <!-- 使用 methods 中的方法 -->
      <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div>
      <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div>
      <div>通过 methods 计算属性,计算 n1 * n2 的值:{{ multiByMethod() }}</div>
      
    • 打印结果:

      调用了计算属性

      调用了计算方法

      调用了计算方法

      调用了计算方法

  • 注意:

    1. 使用 computed 选项声明计算属性
    2. 计算属性定义的的时候是一个函数
    3. 不管调用调用几次,只要data里的值不变就会调用一次,因为计算结果会缓存
    4. 只有计算属性依赖的数据变化时,再会再次调用一次
    5. 计算属性中,要通过 return 返回一个值
    6. 使用方法,调用几次,打印几次

7. vue - cli - 安装和使用

  1. 全局安装 vue-cli npm install -g @vue/cli

    使用 vue -V 命令检查是否安装成功

  2. 基于 vue-cli 快速生成工程化的 Vue 项目

    vue create 项目的名称(如:vue create demo-1)

  3. 启动项目

    cd 项目的名称

    npm run serve

8. Vue 组件

8.1 什么是组件化开发

组件化开发指的是:根据封装的思想,把页面上可重用的 UI 结构封装为组件,提高页面 UI 结构的复用性, 从而方便项目的开发和维护。

8.2 vue 组件的三个组成部分

  • 每个 vue 组件都由 3 部分构成,分别是:
    • template -> 组件的模板结构
    • script -> 组件的 JavaScript 行为
    • style -> 组件的样式
  • 其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的

8.3 模板

示例:App.vue

<!-- vue 规定:每个组件对应的模板结构,需要定义到 <template> 节点中 -->
<template>
  <div id="app">
    <!-- 当前组件的 DOM 结构,需要定义在 template 标签内部 -->
    <h1>这是 App.vue 组件</h1>
    <!-- 
    	注意:
        1. template 是 vue 提供的容器标签,只起到包裹的作用,它不会被渲染为真正的 DOM 元素 
        2. template 中只能包含唯一的根节点
    -->
  </div>
</template>


<!-- vue 规定:开发者可以在 <script> 节点中封装组件的 JavaScrript 业务逻辑 -->
<script>
// 今后,组件相关的 data 数据、methods 方法等。
// 都需要定义到 export default 所导出的对象中
export default {
  // vue 规定:vue 组件中的 data 必须是一个函数,不能直接指向一个数据对象。
  data() {
    return {
      username: 'HI'
    }
  }
}
</script>

<!-- vue 规定:开发者可以在 <style> 节点中编写样式美化当前组件的 UI 结构,组件内的 <style> 节点是可选的 -->
<!--
    让 style 中支持 less 语法
    在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式:
    前提是需要安装 less:npm install less less-loader@7.3.0
-->
<style lang="less">
  h1 {
    color: green;
  }
</style>

8.4 使用组件

8.4.1 组件的私有注册

通过 components 注册的是私有组件,它只能在注册它的组件中使用,不能在其他组件中使用。

  • 步骤一:使用 import 语法导入需要的组件

    示例:App.vue

    import Left from '@/components/Left.vue'
    import Right from '@/components/Right.vue'
    
  • 步骤二:使用 components 节点注册组件

    示例:App.vue

    export default {
    	components: {
        Left,
        Right
      }
    }
    
  • 步骤三:以标签形式使用刚才注册的组件

    示例:App.vue

    <div id="app">
      <Left></Left>
      <Right></Right>
    </div>
    
8.4.2 组件的全局注册

在项目的入口文件 main.js 中,注册全局组件。

示例:main.js

// 全局注册
// 1. 导入需要全局注册的组件
import Count from '@/components/Count.vue'
// 2. 全局注册
// 注意:需要在 main.js 中,new Vue() 构造函数之前,进行全局组件的注册 
// 语法:Vue.component('组件的注册名字', 导入的全局组件的名称)
Vue.component('Count', Count)

9. prop

9.1 prop 是什么

prop 是组件的自定义属性。在封装通用组件的时候,合理地使用 prop 可以极大的提高组件的复用性

可以理解为 prop 是在使用组件的时候,从==外面传递给组件的一个参数==。

  • 需求:在 Count 组件中,实现 +1 功能

    示例:Count.vue

    <p>count 的值是:{{ count }}</p> 
    <button @click="count += 1">点击 +1</button>
    
    data() {
      return {
      	count: 0
      }
    }
    

9.2 prop 的使用 - prop['']

prop: [’ ']

  • 需求:在 Left 中 Count 实现 +1,在 Right 中的 Count 实现 +2

  • 使用 prop 实现

    示例:

    Count.vue

    // 定义 props 选项,用来接收外界传进来的参数
    props: ['n']
    

    Left.vue

    <!-- 通过自定义属性给组件传参 -->
    <Count :n="1"></Count>
    

    Right

    <!-- 通过自定义属性给组件传参 -->
    <Count :n="2"></Count>
    

9.3 prop 的配置选项 - type

type

在声明 prop 时,可以通过 type 来规定 prop 的数据类型

如果传递过来的数据不符合 type 设置的数据类型,vue 会在控制台中给出报错信息。

  • 注意:给 prop 设置配置选项,需要使用对象形式

  • 示例:

    // 注意:给 prop 设置配置选项,需要使用对象形式
    props: {
      n: {
        // type 可以规定传递过来的 prop 的数据类型
        // 常用的数据类型:String  Number  Boolean  Object  Array
        type: Number
      }
    }
    

9.4 prop 设置默认值 - default

default

声明 prop 时,可以通过 default 来设置 prop默认值

当外界在使用组件的时候,没有传递自定义属性,prop 就使用这个默认值。

  • 注意:给 prop 设置配置选项,需要使用对象形式

  • 示例:

    // 注意:给 prop 设置配置选项,需要使用对象形式
    props: {
      n: {
        // default 用来给 prop 设置默认值
        // 外界使用组件的时候,没有传递自定义属性,prop 就使用这个默认值
        default: 1
      }
    }
    

9.5 prop 设置是否必填 - required:true

required: true

声明 prop,可以通过 required 选项,将该 prop 设置为必填项,强制外界使用组件的时候必须传递自定义属性。 否则 vue 会给出报错。

  • 注意:给 prop 设置配置选项,需要使用对象形式

  • 注意:优先级比 设置默认值default 高,所以两者尽量不要同时出现

  • 示例:

    // 注意:给 prop 设置配置选项,需要使用对象形式
    props: {
      n: {
        // required: true 规定该 prop 是必填项
    		// 强制使用组件的时候必须传递自定义属性,否则报错
    		required: true
      }
    }
    

9.6 prop 是只读的

9.6.1 不可修改

vue 规定:组件中封装的自定义属性是只读的,我们不能直接修改 prop 的值。否则会直接报错。

  • 示例:

    props: {
    	init: {
        type: Number,
        default: 0
      }
    }
    
    <!-- prop 是只读的,不能直接修改 -->
    <p>init 的值是:{{ init }}</p> 
    <button @click="init += n">这个按钮不要点击,会报错,因为prop是只读的</button>
    
9.6.2 解决办法

要想修改 prop 传进来的初始值,可以把 prop 的值转存到 data 中然后修改 data,因为 data 中的数据都 是可读可写的!

  • 示例:
data() {
  return {
    count: 0,
    // 如果想修改 prop 传递过来的初始值,可以将 prop 的值转存到 data 中,然后修改 data
    coun: this.init
  }
}
<!-- 利用转存到 data 中在修改 -->
<p>init 的值是:{{ coun }}</p> 
<button @click="coun += n">利用转存到 data 解决不可修改,点击 +{{ n }}</button>

10. 解决组件之间的样式冲突

10.1 样式冲突的产生

默认情况下,写在 vue 组件中的样式会全局生效,因此很容易造成多个组件之间的样式冲突问题。

  • 示例:Left.vue 和 Right.vue 都有 h3 标签。我们给 Left.vue 中的 h3 设置一个样式。

    <style>
      h3 {
        color: green;
      }
    </style>
    
  • 导致组件之间样式冲突的原因是:

    1. 单页面应用程序中,所有组件的 DOM 结构,都是基于唯一的 index.html 页面进行呈现的
    2. 每个组件中的样式,都会影响整个 index.html 页面中的 DOM 元素

10.2 解决样式冲突 - scoped

scoped

  • style 节点的 scoped 属性

    vue 为 style 节点提供了 scoped 属性,从而防止组件之间的样式冲突问题:

  • 示例:Left.vue

    <style scoped>
    	/* 给组件的 style 标签设置 scoped 属性,该样式只会总用于当前组件,并不会影响其他组件的样式 */
      h3 {
        color: green;
      }
    </style>
    
  • 注意:实际开发中,建议在每个组件的 style 身上都加上 scoped 属性

10.3 样式穿透 - 深度选择器

  1. css中

    >>>

  2. less中

    /deep/

在给当前组件的 style 添加了 scoped 属性后,如果想在父组件中修改子组件的样式,就需要使用 深度选择器。

  1. 在 css 中使用 >>>

    示例:Left.vue

    <style scoped>
      /* 在 css 中使用 >>> 实现样式穿透 */
      div >>> h4 {
        color: skyblue;
      }
    </style>
    
  2. 在 less 中使用 /deep/

    示例:Right.vue

    <style scoped lang="less">
      /* 在 less 中使用 /deep/ 实现样式穿透 */
      div /deep/ h4 {
        color: skyblue;
      }
    </style>
    
  • 应用场景:需要修改第三方组件库中组件的原有样式的时候。

11. 组件的生命周期

11.1 生命周期 & 生命周期函数(钩子函数)

  • 生命周期:

    是指一个组件从创建 -> 更新 -> 销毁的整个过程

  • 生命周期函数(钩子函数):

    指的是伴随着组件的生命周期,自动按次序执行的一些 vue 框架内部提供的内置函数。这些 函数也叫做生命周期钩子函数

  • 注意:

    生命周期强调的是时间段生命周期函数强调的是时间点

11.2 组件生命周期函数的分类

beforeCreate、created、beforeMount、mounted | beforeUpdate、updated | beforeDestroy、destroyed

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WXmXNGlJ-1653472695034)(./images/生命周期图.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1NQ8Zxzq-1653472695043)(./images/生命周期图2.png)]

  • 示例:Lifecycle.vue

    <template>
      <div>
        <h2>生命周期演示</h2>
        <h3>当前n的值是:{{ n }}</h3>
        <button @click="add">点我 n + 1</button>
        <button @click="bye">点击销毁</button>
      </div>
    </template>
    
    <script>
    export default {
      data() {
        return {
          n: 0
        }
      },
      methods: {
        add() {
          this.n++
        },
        bye() {
          this.$destroy()
        }
      },
      // 1. 组件创建阶段
      // 1.1 beforeCreate 钩子函数:此时,无法访问 data 中的数据还有 methods 中的方法
      beforeCreate() {
        console.log('beforeCreate执行了')  // beforeCreate执行了
        console.log(this.n)  // undefined
      },
      // 1.2 created 钩子函数:此时,可以访问 data 中的数据还有 methods 中的方法了
      created() {
        console.log('created执行了')  // created执行了
        console.log(this.n)   // 0
      },
      // 1.3 beforeMount 钩子函数:
      // 1.3.1 页面呈现的是未经Vue编译的DOM结构
      // 1.3.2 所有对DOM的操作,最终都不奏效
      beforeMount() {
        console.log('beforeMount执行了')  // beforeMount执行了
        console.log(document.querySelector('button'))  // null 
      },
      // 1.4 mounted钩子函数:
      // 1.4.1 页面中呈现的是经过Vue编译的DOM
      // 1.4.2 对DOM的操作均有效,一般这里进行:开启定时器、发送网络请求、订阅消息、绑定自定义事件等初始化操作
      mounted() {
        console.log('mounted执行了')  // beforeMount执行了
        console.log(document.querySelector('button'))  // <button @click="add">点我 n + 1</button> 
      },
      // 2. 组件更新阶段
      // 2.1 beforeUpdate钩子函数:此时数据是新的,但是页面是旧的,即:页面尚未和数据保持同步
      beforeUpdate() {
        console.log('beforeUpdate执行了');  // beforeUpdate执行了
        console.log(this.n);  // 1
      },
      // 2.2 updated钩子函数:此时数据是新的,页面也是新的,即:页面和数据保持同步
      updated() {
        console.log('updated执行了')  //updated执行了
        console.log(this.n);  // 1
      },
      // 3. 组件销毁阶段
      // 3.1 beforeDestroy钩子函数:
      // 3.1.1 此时,data、methods、指令等等,都处于可用状态,马上要执行销毁过程,
      // 3.1.2 一般在此阶段:关闭定时器、取消订阅消息、解绑自定义事件等收尾操作
      beforeDestroy() {
        console.log('beforeDestroy执行了') // beforeDestroy执行了
        console.log(this.n);  // 5
      },
      // 3.2 destroyed钩子函数:一切已经结束,忽略这个钩子函数即可
      destroyed() {
        console.log('beforeDestroy执行了') // beforeDestroy执行了
      }
    }
    </script>
    

12. 组件间的通信

12.1 父向子传递数据

父组件向子组件传递数据需要使用自定义属性prop

  • 示例:

    父组件:App.vue

    <Left :msg="message" :user="userinfo"></Left>
    
    data() {
      return {
        message: 'Hello Vue',
        userinfo: { name: 'HI', age: 24 }
      }
    }
    

    子组件:Left.vue

    <template>
      <div>
        <h3>Left 组件</h3>
        <p>父组件传递过来的 msg :<br> {{ msg }}</p>
        <p>父组件传递过来的 user :<br>  {{ user }}</p>
      </div>
    </template>
    
    export default {
      props: ['msg', 'user']
    }
    

12.2 子向父传数据

子组件向父组件传递数据使用自定义事件$emit 方法

  • 步骤:

    1. 父组件(接收方)

      在要接收的子组件的标签上,注册一个自定义事件,并设置事件处理函数,把事件处理函数写到methods的方法中,事件处理函数的形参就是接收的值

    2. 子组件(传送方)

      使用语法:$emit('接收方定义的事件名', 要传递的数据)就会传递到接收方设置的事件处理函数的形参

  • 示例:

    父组件(接收方):App.vue

    <Left @countChange="getCount"></Left>
    
    export default {
      data() {
        return {
          countFromLeft: 0
        }
      },
      methods: {
        // 用来接收传递过来的数据
        getCount(res) {
          this.countFromLeft = res
        }
      }
    }
    

    子组件(发送方):Left.vue

    export default {
      data() {
        return {
          count: 0
        }
      },
      methods: {
    		// 每次调用这个方法,count 就会自身加一,然后把count的数据传到定义countChange这个事件的组件中。
        add() {
          this.count++
          this.$emit('countChange', this.count)
        }
      }
    }
    

12.3 兄弟之间传递数据

他们之间就没有一个直接的关联关系。所以想要进行通信,就需要借助第三方来作为 一个桥梁,才能实现通信。这个桥梁就是 EventBus。

  • 结论:
  1. 在接收数据的组件中,使用 EventBus.$on 来注册一个自定义事件,用于接收数据。
  2. 在发送数据的组件中,使用 EventBus.$emit 来触发一个自定义事件,将数据传递出去。
  • 步骤:

    1. 创建 eventBus.js 模块,并向外共享一个 Vue 的实例对象

    2. 数据接收方:调用 bus.$on(‘事件名称’, 事件处理函数) 方法注册一个自定义事件

      注意:接收方把接收注册的自定义事件要写在 created钩子函数里

    3. 数据发送方:调用 bus.$emit(‘事件名称’, 要发送的数据) 方法触发自定义事件

  • 示例:

    eventBus.js 模块

    // 导入 vue
    import Vue from 'vue'
    // 向外共享 Vue 的实例对象
    export default new Vue()
    

    兄弟组件(数据接收方):Right

    import bus from '@/eventBus.js'
    
    export default {
      data() {
        return {
          countFromLeft: 0
        }
      },
      // created钩子函数,页面加载后渲染前就开始注册事件
      created() {
        bus.$on('share', res => {
          this.countFromLeft = res
        })
      }
    }
    

    兄弟组件(数据发送方):Left

    import bus from '@/eventBus.js'
    
    export default {
      data() {
        return {
          count: 0
        }
      },
      methods: {
        add() {
          this.count++
          bus.$emit('share', this.count)
        }
      }
    }
    

13. ref 引用

用来获取页面上的DOM元素组件实例的引用

  • 把ref属性添加在标签上就是获取DOM元素

  • 把ref属性添加在组件标签上就是获取组件实例

13.1 ref - 获取 DOM 元素

  • 步骤:

    1. 使用 ref 属性,为对应的元素标签上添加引用名称
    2. 通过 this.$refs.索引名称,就可以获取DOM
  • 示例:Right.vue

    <h3 ref="myh3">Right 组件</h3>
    <button @click="getDom">点我把标题变绿</button>
    
    export default {
      methods: {
        getDom() {
          this.$refs.myh3.style.color = 'green'
        }
      }
    }
    
13.2 ref - 获取 组件实例
  • 步骤:

    1. 使用 ref 属性,为对应的组件元素标签上添加引用名称
    2. 通过 this.$refs.索引名称,就可以获取组件的示例
  • 示例:App.vue

    <Left ref="leftRef"></Left>
    <button @click="meToo">点我也能 +1 </button>
    
    methods: {
      // 用来接收传递过来的数据
      getCount(res) {
        this.countFromLeft = res
      },
      meToo() {
     		// 打印的是 Left 组件里 count 的数据   
        console.log(this.$refs.leftRef.count)
        // 调用组件上的方法
        this.$refs.leftRef.add()
      }
    }
    

14. $nextTick() 方法

  • $nextTick(cb) 方法中的 cb 为回调函数,会在组件的 DOM 更新完成之后,再执行。从而能保证在 cb 回调函数里 面可以拿到最新的 DOM 元素

  • 使用场景:当数据变化,想拿到最新的 DOM,要将获取 DOM 的代码放到 this.$nextTick(cb) 的 cb 回调函数中去

  • 示例:

    showInput() {
      this.isShow = true
      this.$nextTick(() => {
        // focus() 方法:自动获取焦点
     		this.$refs.onputRef.focus()
      })
    }
    

15. 动态组件

  • 什么是动态组件

    • 动态组件指的是,基于<component>组件,来动态的切换组件的显示与隐藏

    • <component>上 is 属性的值是谁,就是先哪个组件

15.1 动态组件的使用方法

  • 示例:App.vue

    <!-- 动态组件 <component> -->
    <!-- 通过 is 属性,指定要渲染的组件的名称 -->
    <component :is="compName"></component>
    <button @click="compName = 'Left'">展示 Left 组件</button>
    <button @click="compName = 'Right'">展示 Right 组件</button>
    
    esport default {
    	data() {
        return {
          compName: 'Left'
        }
      }
    }
    

15.2 keep-alive 缓存组件

  • 切换组件时,会将隐藏的组件销毁掉,显示时,会重新创建。因此无法保持组件的状态

  • 所以可以使用 <keep-alive><component>进行包裹,将组件缓存,从而保持动态组件的状态。

  • 示例:

    <!-- 使用 keep-alive 缓存组件,保留组件的状态 -->
    <keep-alive>
    	<component :is="compName"></component>
    </keep-alive>
    

    注: include 属性用来指定:只有 include 属性中包含的组件会被缓存。多个组件名之间使用英文的逗号分隔

    <!-- 默认情况下,keep-alive 会缓存它包裹的所有组件 -->
    <!-- 使用 include 属性来告诉 keep-alive,哪些组件需要被缓存 -->
    <keep-alive include="Left,Right">
    	<component :is="compName"></component>
    </keep-alive>
    

15.3 被缓存组件的生命周期函数

  • 被 keep-alive 缓存的组件的生命周期函数

    • 被 keep-alive 缓存的组件,会有两个生命周期函数。

    • 当组件被缓存时,会自动触发组件的 deactivated 生命周期函数。

    • 当组件被激活时,会自动触发组件的 activated 生命周期函数。

  • 示例:

    export default {
    	// 当组件被缓存时,会自动触发 deactivated 生命周期函数
      deactivated() {
        console.log('Left 组件被缓存了');
      },
      // 当组件被激活时,会自动触发 activated 生命周期函数
      activated() {
        console.log('Left 组件被激活了');
      }
    }
    

16. 插槽

  • 什么是插槽

    可以提高组件的复用性

    使用插槽可以给组件传递一段 html 内容

  • 注意:默认情况下,如果在封装组件时没有设置任何插槽,则使用组件时,在组件的开始和结束标签中间传递的内容将会被丢弃

16.1 插槽的基本使用

  • 在封装组件时,可以定义一个插槽**(坑位)**,用来接收使用组件时传递过来的内容。

  • 在使用组件时,可以在组件的开始和结束标签中间传递一段 html 内容,这段内容就会被渲染到组件中插槽所在的位置

  • 注意:如果占位插槽内有收到传递内容的话是可以设置默认内容的,插槽占位的时候直接往插槽内写内容,就是默认内容

  • 示例:

    Left.vue

    <Article>
    	<h3>咏鹅</h3>
    </Article>
    

    Article.vue

    <template>
      <div>
        <!-- <h3>文章的标题</h3> -->
        <slot></slot>
        <div>
          <p>这是文章的段落1。</p>
          <p>这是文章的段落2。</p>
          <p>这是文章的段落3。</p>
          <p>这是文章的段落4。</p>
        </div>
        <!-- 文章作者的插槽 -->
        <slot>
          <!-- 插槽默认内容 -->
          <h6>文章的作者: xxx</h6>
        </slot>
      </div>
    </template>
    
16.2 具名插槽

当我们从外界传递过来多个内容时,这时就需要设置多个插槽进行接收,为了区分不同的插槽,就需要为插 槽指定具体的 name 名称。这种带有具体名称的插槽叫做“具名插槽”。

  • 步骤:

    1. 在封装组件时,给插槽起一个名字。

    2. 在之用组件,向插槽传递内容的时候,指定要给哪个插槽传递。

      两种方法:

      ​ 旧语法(只能在 vue2.x 中使用)

      ​ 新语法(vue2.x 和 vue3.x中都能使用)

  • 注意:新语法中可以简写v-slot:title 可以简写成 #title

  • 示例:

    Article.vue

    <template>
      <div>
        <!-- <h3>文章的标题</h3> -->
        <slot name="title">
          <!-- 插槽的默认内容 -->
          <h3>标题</h3>
        </slot>
        <div>
          <p>这是文章的段落1。</p>
          <p>这是文章的段落2。</p>
          <p>这是文章的段落3。</p>
          <p>这是文章的段落4。</p>
        </div>
        <!-- 文章作者的插槽 -->
        <slot name="author">
          <!-- 插槽默认内容 -->
          <h6>文章的作者: xxx</h6>
        </slot>
      </div>
    </template>
    

    旧语法 (只能在 vue2.x 中使用):

    <Article>
      <h3 slot="title">咏鹅</h3>
      <h6 slot="author">文章的作者:骆宾王</h6>
    </Article>
    

    新语法 (vue2.x 和 vue3.x中都能使用):

    <Article>
      <template v-slot:title>
        <h3>咏鹅</h3>
      </template>
      <!-- v-slot 的简写是 # -->
      <template #author>
        <h6>文章的作者:骆宾王</h6>
      </template>
    </Article>
    
16.3 默认插槽
  • 注意:

    1. 没有指定 name 名称的插槽,叫做默认插槽
    2. 它有隐含的名称叫做 “default”。
    3. 将来使用组件的时 候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收
  • 示例:

    Article.vue

    <template>
      <div>
        <!-- <h3>文章的标题</h3> -->
        <slot name="title">
          <!-- 插槽的默认内容 -->
          <h3>标题</h3>
        </slot>
        <div>
          <p>这是文章的段落1。</p>
          <p>这是文章的段落2。</p>
          <p>这是文章的段落3。</p>
          <p>这是文章的段落4。</p>
        </div>
        <!-- 文章作者的插槽 -->
        <slot name="author">
          <!-- 插槽默认内容 -->
          <h6>文章的作者: xxx</h6>
        </slot>
    
        <!-- 默认插槽 -->
        <!-- 隐藏的名称叫做 default -->
        <!-- 注意:使用组件的时候,所有没有指定要传给具名插槽的内容,都会被默认插槽所接收 -->
        <!-- <slot name="default"></slot> -->
        <slot></slot>
      </div>
    </template>
    

    Left.vue

    <template>
      <div class="left-container">
        <h3>Left 组件</h3>
        <!-- 渲染 Article 组件 -->
    
        <!-- 新语法 -->
        <Article>
          <template v-slot:title>
            <h3>咏鹅</h3>
          </template>
          <template v-slot:author>
            <h6>文章的作者:骆宾王</h6>
          </template>
          
          <p>111</p>
          <p>222</p>
          <p>333</p>
        </Article>
      </div>
    </template>
    
16.4 作用域插槽

使用组件时,在组件的开始和结束标签的内容中,使用的数据如果是来自组件内部,那么这个插槽就是作用 域插槽。

  • 步骤:

    1. 封装组件时,在 slot 上绑定自定义属性,给使用组件时传递数据。
    2. 在使用组件时,接收组件内部传递出来的数据。
  • 示例:

    步骤一:Article.vue

    <!-- 在插槽上,使用自定义属性,给插槽的内容传递数据 -->
    <slot name="author" :age="age"></slot>
    

    步骤二:Left.vue

    • 新语法(vue2.x 和 vue3.x 中都能使用)

      <!-- scope 对象里,存放数组传递出来的数据 -->
      <template #author="scope">
      	<h6>文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }}</h6>
      </template>
      
    • 旧语法(只能在 vue2.x 中使用)

      <h6 slot="author" solt-scope="scope">
        文章的作者:骆宾王 <br> 年龄:-- {{ scope.age }}
      </h6>
      

17. 自定义指令

17.1 什么是自定义指令
  • vue 官方提供了 v-bindv-onv-ifv-forv-model 等常用的指令。除此之外 vue 还允许我们开发自定 义指令。
17.2 自定义指令的分类
  • 自定义指令分为两类:
    1. 全局自定义指令
    2. 私有自定义指令
17.3 全局自定义指令 (常用)
17.3.1 自定义指令的使用
  • 全局的自定义指令需要通过 Vue.directive() 进行声明

  • 示例:

    使用自定义属性:App.vue

    <template>
      <div class="app-container">
        <h1 v-color>App 根组件</h1>
      </div>
    </template>
    

    声明自定义属性:main.js

    // 在 main.js 中,new Vue() 之后定义全局的自定义指令
    // Vue.directive('自定义指令的命令', { 配置 })
    Vue.directive('color', {
      bind(el) {
        // el 就是绑定了该指令的 DOM 元素
        // console.log(el);
        el.style.color = 'red'
      }
    })
    
17.3.2 为自定义指令动态绑定数值
  • 步骤:

    1. 使用自定义指令时,可以指令后面添加等号并赋值,为当前指令动态绑定参数值;
    2. 声明自定义指令时,使用 bind 函数的第二个参数 binding 来获取动态绑定的参数值。
  • 示例:

    使用自定义指令:App.vue

    <template>
      <div class="app-container">
        <h1 v-color="color">App 根组件</h1>
      </div>
    </template>
    <script>
    export default {
      data() {
        return {
          color: 'red'
        }
      }
    }
    </script>
    

    声明自定义指令:main.js

    Vue.directive('color', {
      bind(el, binding) {
        // el 就是绑定了该指令的 DOM 元素
        // 可以通过 binding 参数的 value 属性,拿到动态绑定的值
        el.style.color = binding.value
      }
    })
    
17.3.3 update 函数
  • bind 函数只在组件初始化时调用 1 次。当 DOM 更新时 bind 函数不会被触发

  • update 函数会在数据变化后组件更新时被调用

  • 示例:

    Vue.directive('color', {
      // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数
      bind(el, binding) {
        el.style.color = binding.value
      },
      // 当 DOM 更新的时候,会调用 update 函数
      update(el, binding) {
        el.style.color = binding.value
      }
    })
    
17.3.4 bind 函数和 update 函数的简写形式
  • 如果 bindupdate 函数中的逻辑完全相同,则对象格式的自定义指令可以简写成函数格式

  • 示例:

    简写前:

    Vue.directive('color', {
      // 组件首次渲染时,当 vue 解析绑定到该指令的元素时,就会调用 bind 函数
      bind(el, binding) {
        el.style.color = binding.value
      },
      // 当 DOM 更新的时候,会调用 update 函数
      update(el, binding) {
        el.style.color = binding.value
      }
    })
    

    简写后:

    Vue.directive('color', function(el, binding) {
      // 在 bind 和 updata 的时候,都会执行这个函数
      el.style.color = binding.value
    })
    
17.4 私有自定义指令
  • 在每个 vue 组件中,可以在 directives 节点下声明私有自定义指令

  • 示例:App.vue

    export default {
      directives: {
        // 完整写法
        color: {
          bind(el, binding) {
            el.style.color = binding.value
          },
          update(el, binding) {
            el.style.color = binding.value
          }
        },
        // 简写形式
        color(el, binding) {
          el.style.color = binding.value
        }
      }
    }
    

vue - router

1. 路由 - 概念

1.1 URL
  1. 什么是 URL

    URL(Uniform Resource Locator,统一资源定位符)用于定位网络上的资源。

  2. URL 的组成部分

    • 格式 scheme://host:port/path?query#hash
    • 示例 http://www.liulongbin.top:8083/images/cat.png?name=ls#abc
  3. Hash(哈希):

    • # 号后面的部分,专业术语为“Hash 地址”
    • Hash 地址发生变化,不会导致浏览器页面的刷新
    • Hash 地址发生变化,会形成浏览器历史记录
    • vue 就是基于 hash 的这些特性来做路由的跳转,实现页面的切换的
1.2 应用类型与路由的概念
  • 多页应用与后端路由

    MPA(Multi Page Application):在多页应用中,想要切换页面就是跳转到一个新的 html 页面。 路由工作是由后端完成的

  • 单页应用与前端路由

    SPA(Single Page Application):在单页应用中,只有唯一的一个 HTML 页面,所以想要切换页面就只能切换组件来达到切换页面的效果

    这种切换组件达到页面切换的效果,就是前端路由。由前端程序员来完成

  • 前端路由:访问不同的 Hash 地址,显示不同的组件

1.3 前端路由的工作方式
  1. 用户点击了页面上的路由链接

  2. 导致了 URL 地址栏中的 Hash 值发生了变化

  3. 前端路由监听了到 Hash 地址的变化

  4. 前端路由把当前 Hash 地址对应的组件渲染到浏览器中

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-T3whTLup-1653472695046)(./images/路由工作方式.png)]

  • 强调:前端路由,指的是 Hash 地址组件之间对应关系

2. 模拟简易的前端路由

  • 步骤一:在 App.vue 组件中,为 <a>链接添加对应的 hash,来改变浏览器地址栏中的 hash 地址:

    <!-- 1. 为 a 连接添加跳转的 hash 地址 -->
    <a href="#/home">首页</a>
    <a href="#/movie">电影</a>
    <a href="#/about">关于</a>
    
  • 步骤二:通过**<component>**标签,动态渲染组件。示例代码如下:App.vue

    <!-- 2. 使用 <component> 标签,动态的徐娜然组件。示例代码如下:App.vue -->
    <component :is="compName"></component>
    
  • 步骤三:使用浏览器的 window.onhashchange 事件,监听浏览器地址栏中 hash 地址的变化,然后通过 location.hash 拿到页面最新的 hash。根据这个最新的 hash渲染对应的组件。以达到改变 hash 切换组件的目的

    created() {
      // 3. 监听 hash 地址的变化,拿到最新的 hash, 渲染与该 hash 对应的组件
      window.onhashchange = () => {
        const hash = location.hash
        if (hash === '#/home') {
          this.compName = 'Home'
        } else if (hash === '#/movie') {
          this.compName = 'Movie'
        } else if (hash === '#/about') {
          this.compName = 'About'
        }
      }
    }
    

3. vue-router 简介

  • 什么是 vue-router

    • vue-router 是 vue.js 官方给出的路由解决方案。它只能结合 vue 项目进行使用,能够轻松的管理 SPA 项目中组件的切换。

    • vue-router 的官方文档地址:https://router.vuejs.org/zh/

4. vue-router 的基本使用

  • 步骤:

    1. 安装 vue-router 包
    2. 创建路由模块
    3. 挂载路由模块
    4. 配置路由规则
    5. 声明路由链接占位符
  • 示例:

    步骤一:安装 vue-router 包

    # 在 vue2 的项目中,安装 vue-router@3.5.2 -S
    npm i vue-router@3.5.2 -S
    

    步骤二:创建路由模块

    在 src 源代码目录下,新建 router/index.js 路由模块,并初始化如下的代码

    // 1. 引入 Vue 和 VueRouter 的包
    import Vue from 'vue'
    import VueRouter from 'vue-router'
    
    // 2. 调用 Vue.use() 函数,把 VueRouter 安装为插件
    Vue.use(VueRouter)
    
    // 3. 创建路由的实例对象
    const router = new VueRouter()
    
    // 4. 导出路由实例对象
    export default router
    

    步骤三:挂载路由模块

    在 src/main.js 入口文件中,导入并挂载路由模块

    import Vue from 'vue'
    import App from './App.vue'
    
    // 导入路由模块
    import router from '@/router/index.js'
    
    new Vue({
      render: h => h(App),
      // 2. 挂载路由模块
      // router: router  // 简写如下行
      router
    }).$mount('#app')
    

    步骤四:配置路由规则

    在 src/router/index.js 路由模块中,通过 routes 数组声明路由的匹配规则

    // 引入要展示的组件
    import Home from '@/components/Home.vue'
    import Movie from '@/components/Movie.vue'
    import About from '@/components/About.vue'
    
    const router = new VueRouter({
      // 使用 routes 选项配置路由规则
      // 路由规则:hash 地址和对应的组件
      routes: [
        // { path: 'hash地址', component: 要展示的组件名 }
        // 注意:pash 里面的 hash 地址是不需要加 # 的
        { path: '/home', component: Home },
        { path: '/movie', component: Movie },
        { path: '/about', component: About },
      ]
    })
    

    步骤五:声明路由链接和占位符

    在 src/App.vue 组件中,使用 vue-router 提供的**<router-link><router-view>声明路由链接路由占位符**

    <template>
      <div class="app-container">
        <h1>App 根组件</h1>
    
        <!-- 声明路由跳转连接 -->
        <router-link to="/home">首页</router-link>
        <router-link to="/movie">电影</router-link>
        <router-link to="/about">关于</router-link>
        <hr />
    
        <!-- 声明路由占位符 -->
        <router-view></router-view>
        
      </div>
    </template>
    

5. vue-router 的常见用法

5.1 路由重定向
  • 路由重定向指的是:用户在访问地址 A 的时候,强制用户跳转到地址 C ,从而展示地址 C 对应的组件页面
  • 通过路由规则的 redirect 属性,指定一个新的路由地址,可以很方便地设置路由的重定向
// 创建路由的实例对象
const router = new VueRouter({
  routes: [
    // 使用重定向设置访问根路径的组件为 Home.vue
    // 语法:{ path: '用户要访问的 hash 地址', redirect: '重定向的 hash 地址' }
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/movie', component: Movie },
    { path: '/about', component: About },
  ]
})
5.2 嵌套路由
  • 在本身就是通过路由展示的组件中,再使用路由去控制其他组件的展示,就会形成嵌套路由。 这种通过路由实现组件的嵌套展示,叫做嵌套路由

  • 示例:

    步骤一: 声明子路由规则

    src/router/index.js 路由模块中,使用 children 属性声明子路由规则

    import Tab1 from '@/components/tabs/Tab1.vue'
    import Tab2 from '@/components/tabs/Tab2.vue'
    
    const router = new VueRouter({
      routes: [
        { path: '/home', component: Home },
        { path: '/movie', component: Movie },
        { 
          path: '/about', 
          component: About, 
          // 要在哪个组件中嵌套展示其他组件,children 就是在哪个路由规则下面
          // 通过 children 属性,定义子路由规则
          children: [
            { path: '/about/tab1', component: Tab1 },
            { path: '/about/tab2', component: Tab2 }
          ]
        }
      ]
    })
    

    步骤二:声明子路由链接和子路由占位符

    About.vue 组件中,声明 tab1 和 tab2 的子路由链接以及子路由占位符

    <template>
      <div class="about-container">
        <h3>About 组件</h3>
        <!-- 在关于页面中,声明子路由连接 -->
        <router-link to="/about/tab1">Tab1</router-link>
        <router-link to="/about/tab2">Tab2</router-link>
    
        <hr>
    
        <!-- 在关于页面中,声明子路由占位符 -->
        <router-view></router-view>
      </div>
    </template>
    
5.3 动态路由
  • 概念:

    把 Hash 地址中动态变化的部分定义为一个动态的参数项,从而提高路由规则的复用性。 在 vue-router 中使用**英文的冒号(😃**来定义路由的参数项。

  • 示例:

    // 在路由地址中声明动态参数项
    // 将动态的部分使用 :动态参数项的名称 代替
    { path: '/movie/:id', component: Movie }
    
    <!-- 将以下 3 个路由规则,合并成了一个,调高了路由规则的复用性 -->
    <router-link to="/movie/1">电影1</router-link>
    <router-link to="/movie/2">电影2</router-link>
    <router-link to="/movie/3">电影3</router-link>
    
  • 使用场景:一般都是在从列表页,跳转到详情页的时候,使用动态路由。

5.4 通过 $route.params 获取动态路由参数(推荐)
  • 动态路由渲染出来的组件中,可以使用 this.$route.params 对象访问到动态匹配的参数值this.$route 里面存放的是当前页面路由的相关信息

  • 示例:

    <template>
      <div class="movie-container">
        <!-- this.$route 里面存放的是当前页面路由的相关信息 -->
        <h3>Movie 组件 -- {{ this.$route.params.id }}</h3>
        <button @click="logRoute">打印 this.$route</button>
      </div>
    </template>
    
    <script>
    export default {
      name: 'Movie',
      methods: {
        logRoute() {
          console.log(this.$route);
        }
      }
    }
    </script>
    
5.5 使用 props 接收路由参数
  • 为了简化路由参数的获取形式,vue-router 允许在路由规则中开启 props 传参,那么在组件中就可以使用 props 来接 收路由上的参数了。

  • 示例:

    步骤一:在路由规则中开启 props 选项(router/index.js)

    // 开启 props 选项,允许组件使用 props 接收路由参数
    { path: '/movie/:id', component: Movie, props: true }
    

    步骤二:在组件中使用 props 接收路由参数(Movie.vue)

    <template>
      <div class="movie-container">
        <h3>Movie 组件 -- {{ id }}</h3>
      </div>
    </template>
    
    <script>
    export default {
      // 使用 props 接收路由参数
      props: ['id'],
    }
    </script>
    
5.6 编程式导航
  • 声明式导航 & 编程式导

    • 声明式导航
      • 在浏览器中,点击链接实现路由跳转的方式,叫做声明式导航
      • 例如:普通网页中点击<a>链接、vue 项目中点击 <router-link>都属于声明式导航
    • 编程式导
      • 在浏览器中,调用 JS API 方法实现路由跳转的方式,叫做编程式导航
      • 例如:普通网页中调用 location.href 跳转到新页面的方式,属于编程式导航
  • vue-router 提供了许多编程式导航的 API,其中最常用的导航 API 分别是

    1. this.$router.push(‘hash 地址’)
      • 跳转到指定 hash 地址,并增加一条历史记
    2. this.$router.go(数值 n)
      • 实现导航历史前进、后
  • 示例一:$router.push

    调用 this.$router.push() 方法,可以跳转到指定的 hash 地址,从而展示对应的组件页面。

    <template>
      <div class="home-container">
        <h3>Home 组件</h3>
        <button @click="goAbout">跳转到关于页面</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        goAbout() {
          // 编程式导航 API:
          // 语法:this.$router.push('hash地址')
          this.$router.push('/about')
        }
      }
    }
    </script>
    
  • 示例二:$router.go

    调用 this.$router.go() 方法,可以在浏览历史中前进和后退。类似于浏览器的前进后退功能。

    <template>
      <div class="about-container">
        <h3>About 组件</h3>
        <button @click="goBack">返回</button>
      </div>
    </template>
    
    <script>
    export default {
      methods: {
        goBack() {
          // 返回到记录的上一条
          // 负数回退,正数前进
          this.$router.go(-1)
        }
      }
    }
    </script>
    

6. 导航守卫

  • 什么是导航守卫

    导航守卫可以对路由跳转进行拦截,从而达到控制路由的访问权限的功能。

6.1 全局的前置守卫
  • 每次发生路由的导航跳转时,还没有真正发生跳转之前,会触发全局前置守卫。因此,我们可以在全局前置 守卫中,对每个路由进行访问权限的控制

    // 声明全局前置守卫
    // 每次发生路由跳转的时候,都会触发 beforeEach 中的 fn 回调函数
    // router.brforeEach(fn)
    router.beforeEach(() => {
        console.log('触发了 beforeEach 中的 fn 回调函数')
        // 做一些权限控制的工作
    })
    
6.2 前置守卫方法的3个形参
router.beforeEach((to, from, next) => {
    // to: 将要访问的路由信息对象
    console.log(to)
    // from: 将要离开的页面的路由信息对象
    console.log(from)
    // next 是一个函数,调用 next, 表示放行,允许这次路由的跳转
    next()
})

vue - vuex

1. vuex 概念

1.1 Vuex 是什么

  • Vuex:

    Vuex 是 vue 项目中实现全局的大范围的数据通信的解决方案。

  • 作用:

    能够方便、高效地实现组件之间的数据通信

  • 使用 Vuex 的好处:

    1. 数据的存取一步到位,不需层层传递
    2. 数据的流动非常清晰
    3. 存储在 Vuex 中的数据都是响应式

1.2 项目中安装和配置 Vuex

  1. 安装 Vuex 的依赖包

    npm i vuex@3.6.2 -S
    
  2. 创建 store 模块

    // 1. 导入 Vue 和 Vuex 依赖包
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    // 2. 把 Vuex 安装为 Vue 的插件
    Vue.use(Vuex)
    
    // 3. 创建 store 的实例对象
    const store = new Vuex.Store()
    
    // 4. 向外导出 store 的实例对象
    export default store
    
  3. 挂载 store 的实例对象到 new Vue() 中

    // 1. 导入 store 实例对象
    import store from '@/store/index.js'
    
    new Vue({
      render: h => h(App),
      // 2. 挂载 store 实例对象
      store
    }).$mount('#app')
    

2. State 的基本使用

2.1 store 的概念

  • state 本质上就是一个对象,用来存储全局的数据的。

2.2 state 的使用步骤

2.2.1 定义全局数据
  • 定义:在 Vuex 的 state 选项对象中,定义全局数据

    store/index.js

    const store = new Vuex.Store({
      // 在 state 中,定义全局数据
      state: {
        count: 0
      }
    })
    
2.2.2 组件访问 state 数据
  • 使用方式一: state 属性

    在组件中通过 this.$store.state.全局数据名称 访问全局数据

    Left.vue

    <template>
      <div class="left-container">
        <p>count 值: -- {{ this.$store.state.count }} </p>
      </div>
    </template>
    
  • 使用方式二:mapState 辅助函数

    在组件中通过 计算属性 访问全局数据

    Right.vue

    <template>
      <div class="right-container">
        <p>count 值: -- {{ count }} </p>
      </div>
    </template>
    
    // 1. 导入辅助函数 mapState
    import { mapState } from 'vuex'
    // mapState 函数的返回值是一个对象。里面存放的就是 state 中全局数据的映射
    
    export default {
      computed: {
        // 将得到的 state 中全局数据的映射,通过扩展运算符,放入 computed 中
        ...mapState(['count'])
      }
    }
    
2.2.3 解决命名冲突
  • <template>
      <div class="right-container">
        <p>count 值: -- {{ countR }} </p>
      </div>
    </template>
    
  • import { mapState } from 'vuex'
    
    export default {
      data() {
        return {
          count: 666
        }
      },
      computed: {
        // 解决命名冲突 - state中的数据名,在这个组件已经被定义
        // 语法:mapState({ '计算属性名字': ‘要访问的全局数据的名字’ })
        ...mapState({
          countR: 'count'
        })
      }
    }
    

3. Mutation 的基本使用

注意:Vuex 规定: mutation 必须是同步函数 。

3.1 开启严格模式

  • 开启 vuex 严格模式

    • 开启严格模式之后,直接修改全局数据就会报错,可以防止程序员写垃圾代码
    • 但是严格模式性能上有损耗,所以项目上线之前,要关掉严格模式
  • 示例:store/index.js

    const store = new Vuex.Store({
      // 开启严格模式
      // 会消耗性能,开发阶段开启,上线之前关闭
      strict: true,
    });
    

3.2 mutation 定义

  • mutation 是什么
    • mutation 本质上是 JavaScript 函数,专门用来变更 store 中的数据
  • 特点:想要修改 state 中的数据,只能调用 mutation 方法
  • 好处:能够确保修改来源的唯一性,方便调试和后期维护。

3.3 mutation 的使用步骤

3.3.1 定义 mutation 方法
  • 定义:在 Vuex 中定义 mutation 方法

  • 示例:store/index.js

    const store = new Vuex.Store({
      strict: true,
      state: {
        count: 0,
        n1: 0,
        n2: 0
      },
      // 【在 mutations 中,声明修改全局数据的方法】
      mutations: {
        addCount(state, n = 1) {
          // mutation 函数中,接受的第一个参数是 state
          // mutation 函数中,使用第二个参数,来接受调用时传递过来的参数
          state.count += n;
        }
      }
    });
    
  • 注意:

    1. mutation 函数中,接受的第一个参数是 state
    2. mutation 函数中,使用第二个参数,来接受调用时传递过来的参数
3.3.2 调用 mutation 函数
  • 调用方式一:commit 方法

    在组件中,通过 this.$store.commit('mutations中定义的函数名') 调用 mutation 方法

    示例:Left.vue

    add(n) {
      // 在组件中,调用全局的mutation,来修改全局数据 
      // 调用全局的 mutation 里面的方法来修改全局的数据
      // 语法:this.$store.commit('mutation函数的名字')
      this.$store.commit('addCount', n)
    }
    

    注意:

    1. 调用全局的 mutation 里面的方法来修改全局的数据
    2. 语法:this.$store.commit('mutation函数的名字')
    3. 通过传参可以提高 mutation 方法的通用性。
    4. 语法:this.$store.commit('mutation函数的名字', '参数')
  • 调用方式二:mapMutations 辅助函数

    Vuex 提供了 mapMutations 辅助函数,可以方便的把 store 中的 mutation 方法,映射到前组件的 methods 中

    示例:Right.vue

    import { mapMutations } from 'vuex'
    export default {
      methods: {
        // 使用 mapMutations 将全局的 mutation 函数,映射到当前组件的 methods 中去
        ...mapMutations(['addCount'])
        })
      }
    }
    
3.3.3 解决命名冲突
  • 示例:

    import { mapMutations } from 'vuex'
    export default {
      methods: {
        // 使用 mapMutations 中的对象格式的传参,解决命名冲突
        ...mapMutations({
          // 自定义的名称: '全局 mutation名称'
          addCount2: 'addCount'
        })
      }
    }
    

4. Action 的基本使

注意:专门用来处理 Vuex 中的异步操作

4.1 action 的定义

  • action 是什么
    • action 本质上是 JavaScript 函数,专门用来处理 Vuex 中的异步操作

4.2 action 的使用步骤

4.2.1 定义 action 方法
  • 定义:在 Vuex 中定义 action 方法

  • 示例:store/index.vue

    const store = new Vuex.Store({
      strict: true,
      // 【在 actions 中,声明处理异步操作的方法】
      actions: {
        addCountAsync(ctx, {n, time}) {
          // action 函数中,接收的第一个参数是 ctx,ctx 中有 commit 方法
          setTimeout(() => {
            // 这里不可以直接修改全局数据,要通过调用 mutations 里的方法来修改数据
            // 使用 ctx.commit 方法调用 mutation
            ctx.commit('addCount', n)
          }, time);
        }
      },
    });
    
4.2.2 调用 action 中的异步方法
  • 调用方式一:dispatch 方法

    在组件中,通过 this.$store.dispatch('actions中定义的函数名') 调用 action 方法

    示例:Left.vue

    export default {
      methods: {
        addAsync(n, time) {
          // 在组件中,调用全局的action,来实现异步处理函数,修改全局数据
          // 在组件中调用全局的 action 里面的异步函数
          // 语法:this.$store.dispatch('action函数名')
          // 第二个参数是 要传递的参数
          this.$store.dispatch('addCountAsync', {n, time})
        }
      }
    }
    
  • 调用方式二:mapActions 辅助函数

    Vuex 提供了 mapActions 辅助函数,可以方便的把 store 中的 action 方法,映射到当前组件的 methods 中

    示例:Right.vue

    import { mapActions } from 'vuex'
    export default {
      computed: {
        // 使用 mapAction 辅助函数,将全局的 action,映射到当前组件的 methods 中去
        ...mapActions (['addCountAsync'])
      }
    }
    
4.2.3 解决命名冲突
  • 示例:

    import { mapActions } from 'vuex'
    export default {
      computed: {
        // 重新命名
        ...mapActions({
          addCountAsync2: 'addCountAsync'
        })
      }
    }
    

5. Getter 的基本使用

5.1 getter 定义

  • getter 是什么

    getter 可以理解为是 Vuex 中的计算属性,它内部依赖于 state 中的数据,state 中的值变化,getter 的值 会自动更新。

5.2 getter 的使用步骤

5.2.1 定义 getter 方法
  • 定义:在 Vuex 中定义 action 方法

  • 示例:store/index.js

    const store = new Vuex.Store({
      strict: true,
      state: {
        n1: 0,
        n2: 0
      },
      // 使用 geeter 定义全局的计算属性
      // 某一个值,是依赖于全局的数据动态计算出来
      // 这时就是使用 getter
      getters: {
        sum(state) {
          // getter 函数接收的第一个参数是 state 全局数据
          return state.n1 + state.n2
        }
      }
    });
    
5.2.2 访问 getter
  • 访问方式一:getters 属性

    在组件中,通过 this.$store.getters.getters中定义的函数名,访问 gette

    示例:Left.vue

    <template>
      <div class="left-container">
        <!-- 在组件中,通过 this.$store.getters.getter函数名 -->
        <p>全局数据 n1 + n2 = {{ $store.getters.sum }}</p>
      </div>
    </template>
    
  • 访问方式二:mapGetters 辅助函数

    Vuex 提供了 mapGetters 辅助函数,可以方便的把 store 中的 getter映射到当前组件的 computed 中

    示例:Right.vue

    import { mapGetters } from 'vuex'
    // mapGetters 要结合 computed 一起使用
    export default {
      computed: {
        ...mapState(['count'])
      }
    }
    
5.2.3 解决命名冲突
  • 示例:

    import { mapGetters } from 'vuex'
    export default {
      computed: {
        // 重新命名
        ...mapState({ sum2: 'count' })
      }
    }
    

6. Module 的基本使

6.1 module 是什么

Vuex 中的 module 表示按照模块化的开发思想,把有业务关联的数据和方法封装在一起。module 就是 Vuex 中的模块化

6.2 module 的使用场景

当一个项目的页面数量很少逻辑功能简单的情况下,是完全可以不使用 module 模块的。

但是当一个项目的页面数量很多逻辑功能复杂的时候,所有的全局数据、方法都集中在了一起,会导致 Vuex 的结构混乱,不利于现阶段的开发和后期的维护,那么此时就需要使用模块来管理全局的数据和方法。

6.3 module 定义和注册模块

  • 定义模块

    • 每个模块都是彼此独立的,都可以拥有自己的 state、mutations、actions、getters 节点:

    • 示例:store/count.js

      // count 模块
      export default {
        // 当前模块的数据
        // 函数的形式为了解决
        // 模块被多次注册,并且开启命名空间时,公用同一份数据
        state: {
          num: 0
        },
        // 当前模块修改 state 数据的函数
        mutations: {
          show() {
            console.log('调用了 count 模块下的 show 方法');
          },
          addNum(state, n = 1) {
            state.num += n
          }
        },
        // 当前模块的异步操作
        actions: {
        },
        // 当前模块的计算属性
        getters: {
        }
      }
      
  • 注册模块

    • 示例:store/index.js

      // 1. 导入要注册的模块
      import count from './count.js'
      import task from './task.js'
      
      const store = new Vuex.Store({
        // 使用 modules 选项注册模块
        modules: {
          // 2. 注册模块
          // 语法:
          // 模块的注册名称:导入的模块
          // count: count
          count,
          task
        }
      })
      

6.4 namespaced (命名空间)

  • namespaced(命名空间)可以解决不同模块之间成员名称冲突的问题。在实际项目开发中,
  • 建议: 为每 个 module 模块都开启命名空间!
6.4.1 开启命名空间 - namespaced: true
  • 在定义模块时,只需在模块中声明 namespaced: true 选项,即可为当前模块开启命名空间:

  • 示例:store/count.js && store/task.js

    // count 模块
    export default {
    	// 开启命名空间 - 用于隔离模块
      namespaced:true,
      state: {},
      mutations: {},
      actions: {},
      getters: {}
    }
    
    // task 模块
    export default {
      // 开启命名空间 - 用于隔离模块
      namespaced:true,
      state: {},
      mutations: {},
      actions: {},
      getters: {}
    }
    
6.4.2 通过模块注册名访问模块下成员

当模块启用了 namespaced: true 选项之后,模块就有了自己的命名空间

  • 注意:访问时要加上模块的注册名 称才能够访问到

  • 示例:Left.vue

    add() {
      // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名')
      this.$store.commit('count/addNum')
    }
    
6.4.3 在组件中访问模块内 state 的两种方式
  • 方式一:通过 this.$store.state.模块注册名称.要访问的数据

    示例:Left.vue

    <template>
      <div class="left-container">
        <!-- 在组件中,通过 this.$store.state.模块的注册名称.模块中的数据 -->
        <p>count 模块下的 num 的值:{{ $store.state.count.num }}</p>    
      </div>
    </template>
    
  • 方式二:通过 mapState 辅助函数

    示例:Right.vue

    <template>
      <div class="right-container">
        <h3>Right 组件</h3>
        <p>count 模块下的 num 的值:{{ num }}</p>
        <p>task 模块下的 num2 的值:{{ num2 }}</p>
      </div>
    </template>
    <script>
    import { mapState, mapMutations } from 'vuex'
    export default {
      computed: {
        // ...mapState('模块的注册名称', [‘模块中数据’])
        ...mapState('count', ['num']),
        ...mapState('task', { num2: 'num' })
      }
    }
    </script>
    
6.4.4 在组件中调用模块内 mutation 的两种方式
  • 方式一:通过 this.$store.commit('组件的注册名称/要调用的mutation函数名称' , 参数)

    示例:Left.vue

    <template>
      <div class="right-container">
        <h3>Right 组件</h3>
        <p>count 模块下的 num 的值:{{ num }}</p>
        <p>task 模块下的 num2 的值:{{ num2 }}</p>
        <hr>
        <button class="btn btn-warning" @click="addNum(-1)">-1</button>
      </div>
    </template>
    
  • 方式二:通过 mapMutations 辅助函数

    示例:Right.vue

    <template>
      <div class="left-container">
        <button class="btn btn-primary" @click="add">+1</button>
        <hr>
        <button @click="show">打印 show</button>
        <hr>
      </div>
    </template>
    <script>
    export default {
      methods: {
        show() {
          this.$store.commit('count/show')
        },
        add() {
          // 在组件中,通过 this.$store.commit('模块注册的名称/mutation函数名')
          this.$store.commit('count/addNum')
        }
      }
    }
    </script>
    
6.4.5 在组件中调用模块内 action 的两种方式
  • 方式一:通过 this.$store.dispatch('组件的注册名称/要调用的action函数名称' , 参数)

    示例:Left.vue

    <template>
    	<div>
        <!-- this.$store.dispatch('组建的注册名称/要调用的action函数名称', 参数) -->
        <button class="btn btn-info" @click="$store.dispatch('count/addAsync', -1)">1秒后 +1</button>
      </div>
    </template>
    
  • 方式二:通过 mapActions 辅助函数

    示例:Right.vue

    <template>
      <div class="right-container">
        <button class="btn btn-warning" @click="addAsync(-1)">-1</button>
      </div>
    </template>
    
    <script>
    import { mapActions } from 'vuex'
    export default {
      methods: {
        ...mapMutations('count', ['addAsync'])
      }
    }
    </script>
    
6.4.6 在组件中访问模块内 getter 的两种方式
  • 方式一:通过 this.$store.getters['模块的注册名称/要访问的getter名称']

    示例:Left.vue

    <template>
      <div class="left-container">
        <h3>Left 组件</h3>
        <p>count 值:{{ this.$store.getters['count/numPlus'] }}</p>
      </div>
    </template>
    
  • 方式二:通过 mapGetters 辅助函数

    示例:Right.vue

    <template>
      <div class="right-container">
        <p>count 值: {{ numPlus }}</p>
      </div>
    </template>
    
    <script>
    import { mapGetters } from 'vuex'
    export default {
      computed: {
        ...mapGetters('count', ['numPlus'])
      }
    }
    </script>
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值