Vue 2

一、入门

1、安装 Vue

  • 准备容器(vue所管理的范围):<div id="app"></div>
  • 引包(开发版本/生产版本)
    • 下载地址:https://v2.cn.vuejs.org/v2/guide/installation.html
    • 本地引入:<script src="../vue.js"></script>
    • 链接引入:<script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
  • 创建实例
    • 引入 VueJS 核心包,在全局环境下就有了 Vue 构造函数:
  • 添加配置 => 完成渲染

2、插值表达式

  • 使用的数据必须在 data 中定义
  • 支持的是表达式,而非语句如:if、for
  • 不能在标签属性中使用
<!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" />
    <title>插值表达式</title>
  </head>
  <body>
    <div id="app">
      <!-- 插入 data 中的内容 -->
      <h2>{{ msg }}</h2>
      <!-- 插入数值 -->
      <h2>{{ 12 }}</h2>
      <!-- 插入字符串 -->
      <h2>{{ "msg" }}</h2>
      <!-- 插入对象 -->
      <h2>{{ {id:1} }}</h2>
      <!-- 表达式 -->
      <h2>{{ 1>2 ? "true" : "false" }}</h2>
      <!-- 反转字符串 -->
      <h2>{{ txt.split('').reverse().join('') }}</h2>
      <!-- 调用函数 -->
      <h2>{{ getContent() }}</h2>
    </div>
    <!-- 引入 Vue 包 -->
    <script src="./vue.js"></script>
    <script>
      new Vue({
        // 通过 el 配置选择器
        el: "#app",
        // 数据属性
        data: {
          msg: "hello vue",
          flag: false,
          txt: "hello",
        },
        // 存放方法
        methods: {
          getContent() {
            return "content:" + this.msg;
          },
        },
      });
    </script>
  </body>
</html>

3、Vue 指令

1. v-text/v-html

<div id="app">
  <!-- 插入文本 -->
  <h2>{{ msg }}</h2>
  <h2 v-text="msg"></h2>
  <!-- 插入标签 -->
  <div v-html="htmlMsg"></div>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "插入文本",
    htmlMsg: "<h2>插入标签</h2>",
  },
  // 存放方法
  methods: {},
});

2. v-if/v-show

  • v-if
    • 作用: 控制元素显示隐藏(条件渲染)
    • 语法: v-if = “表达式” 表达式值 true 显示, false 隐藏
    • 原理: 基于条件判断,是否 创建 或 移除 元素节点
    • 场景: 要么显示,要么隐藏,不频繁切换的场景
  • v-show
    • 作用: 控制元素显示隐藏
    • 语法: v-show = “表达式” 表达式值 true 显示, false 隐藏
    • 原理: 切换 display:none 控制显示隐藏
    • 场景: 频繁切换显示隐藏的场景
<div id="app">
  <!-- 条件渲染 -->
  <div v-if="isShow">显示</div>
  <div v-else>隐藏</div>
  <h2 v-show="show">展示</h2>
  <h2 v-show="hide">隐藏</h2>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    isShow: Math.random() > 0.5,
    show: true,
    hide: false,
  },
  // 存放方法
  methods: {},
});

3. v-else&v-else-if

  • 作用: 辅助 v-if 进行判断渲染
  • 语法: v-else v-else-if = “表达式”
  • 注意: 需要紧挨着 v-if 一起使用
<div id="app">
  <p v-if="gender === 1">性别:♂ 男</p>
  <p v-else>性别:♀ 女</p>
  <hr>
  <p v-if="score >= 90">成绩评定A:奖励电脑一台</p>
  <p v-else-if="score >= 70">成绩评定B:奖励周末郊游</p>
  <p v-else-if="score >= 60">成绩评定C:奖励零食礼包</p>
  <p v-else>成绩评定D:惩罚一周不能玩手机</p>
</div>
const app = new Vue({
  el: '#app',
  data: {
    gender: 1,
    score: 5
  }
})

4. v-on

  • 作用: 注册事件 = 添加监听 + 提供处理逻辑
  • 语法:
    • v-on:事件名 = “内联语句”
    • v-on:事件名 = “methods中的函数名”
  • 简写:@事件名
  • 特殊事件
    • v-on:click.once:只触发一次
    • v-on:click.stop:阻止单击事件继续传播
    • v-on:click.prevent:提交事件不再重载页面
    • v-on:keyup.enter:绑定回撤按钮
<style>
  .box {
    width: 200px;
    height: 200px;
    background-color: red;
  }
  .active {
    width: 200px;
    height: 200px;
    background-color: green;
  }
</style>

<div id="app">
  <!-- 绑定事件 -->
  <h2>{{ num }}</h2>
  <!-- 只触发一次 -->
  <button v-on:click.once="bandleClick">点击+1(只触发一次)</button>
  <button v-on:click="num++">点击+1</button>

  <div class="box" :class="{active:isActive}"></div>
  <button @click="changClick">切换样式</button>

  <!-- 绑定回车 -->
  <input @keyup.enter="submit" />
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    num: 0,
    isActive: false,
  },
  // 存放方法
  methods: {
    bandleClick() {
      this.num += 1;
    },
    changClick() {
      this.isActive = !this.isActive;
    },
    submit() {
      alert("ok");
    },
  },
});

5. v-bind

  • 作用: 动态的设置html的标签属性
  • 语法: v-bind:属性名=“表达式”
  • 注意: 简写形式 :属性名=“表达式”
<div id="app">
  <img v-bind:src="imgUrl" v-bind:title="msg" alt="">
  <img :src="imgUrl" :title="msg" alt="">
</div>
const app = new Vue({
  el: '#app',
  data: {
    imgUrl: './images/logo.png',
    msg: 'hello vue'
  }
})

6. v-for

  • 作用: 基于数据循环, 多次渲染整个元素
  • **遍历数组语法:**v-for = “(item, index) in 数组”
    • item 每一项, index 下标
    • 省略 index: v-for = “item in 数组”
<div id="app">
  <h3>小黑水果店</h3>
  <ul>
    <li v-for="(item, index) in list">
      {{ item }} - {{ index }}
    </li>
  </ul>

  <ul>
    <li v-for="item in list">
      {{ item }}
    </li>
  </ul>
</div>
const app = new Vue({
  el: '#app',
  data: {
    list: ['西瓜', '苹果', '鸭梨', '榴莲']
  }
})

7. v-model

  • 作用: 给 表单元素 使用, 双向数据绑定 → 可以快速 获取 或 设置 表单元素内容
    • 数据变化 → 视图自动更新
    • 视图变化 → 数据自动更新
  • 语法: v-model = ‘变量’
<div id="app">
  <p>{{msg}}</p>
  <input type="text" v-model="msg" />
  <input type="text" v-model.lazy="msg" />
  <input type="text" v-model.trim="msg" />
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "尔尔",
  },
  // 存放方法
  methods: {},
});

8. 指令修饰符

  • 通过 “.” 指明一些指令 后缀,不同 后缀 封装了不同的处理操作
  • 按键修饰符
    • 键盘回车监听:@keyup.enter
  • v-model修饰符
    • 过滤首尾空白字符:v-model.trim
    • 输入值转为数值类型:v-model.number
    • change 事件之后进行同步:v-model.lazy
  • 事件修饰符
    • 阻止冒泡:@事件名.stop
    • 阻止默认行为:@事件名.prevent
<div id="app">
  <h3>@keyup.enter → 监听键盘回车事件</h3>
  <input @keyup.enter="fn" v-model="username" type="text" />

  <h3>v-model修饰符 .trim .number</h3>
  姓名:<input v-model.trim="username" type="text" /><br />
  年纪:<input v-model.number="age" type="text" /><br />

  <h3>@事件名.stop → 阻止冒泡</h3>
  <div @click="fatherFn" class="father">
    <div @click.stop="sonFn" class="son">儿子</div>
  </div>

  <h3>@事件名.prevent → 阻止默认行为</h3>
  <a @click.prevent href="http://www.baidu.com">阻止默认行为</a>
</div>
const app = new Vue({
  el: "#app",
  data: {
    username: "",
    age: "",
  },
  methods: {
    fn(e) {
      // if (e.key === 'Enter') {
      //   alert('键盘回车的时候触发', this.username)
      // }
      alert("键盘回车的时候触发:" + this.username);
    },
    fatherFn() {
      alert("老父亲被点击了");
    },
    sonFn(e) {
      // e.stopPropagation()
      alert("儿子被点击了");
    },
  },
});
<style>
  .father {
    width: 200px;
    height: 200px;
    background-color: pink;
    margin-top: 20px;
  }
  .son {
    width: 100px;
    height: 100px;
    background-color: skyblue;
  }
</style>

4、Vue特殊指令

1. :class

  • v-bind 对于样式控制的增强,语法 :class = “对象/数组”
  • 对象 → 键就是类名,值是布尔值:
    • <div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }">尔尔</div>
    • 适用场景:一个类名,来回切换
  • 数组 → 数组中所有的类,都会添加到盒子上:
    • <div class="box" :class="[类名1, 类名2]">尔尔</div>
    • 适用场景:批量添加或删除类
<div id="app">
  <div class="box" :class="{ pink: true, big: true }">尔尔</div>
  <div class="box" :class="['pink', 'big']">尔尔</div>
</div>
const app = new Vue({
  el: "#app",
  data: {},
});
<style>
  .box {
    width: 200px;
    height: 200px;
    border: 3px solid #000;
    font-size: 30px;
    margin-top: 10px;
  }
  .pink {
    background-color: pink;
  }
  .big {
    width: 300px;
    height: 300px;
  }
</style>

2. :style

  • v-bind 对于样式控制的增强:语法 :style = “样式对象”
    • <div class="box" :style="{ CSS属性名1: CSS属性值, CSS属性名2: CSS属性值 }"></div>
  • 适用场景:某个具体属性的动态设置
<div id="app">
  <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
</div>
const app = new Vue({
  el: "#app",
  data: {},
});
<style>
  .box {
    width: 200px;
    height: 200px;
    background-color: rgb(187, 150, 156);
  }
</style>

3. v-model:单个复选框

  • 单个复选框,绑定到布尔值
<div id="app">
  <!-- 复选框单选 -->
  <input type="checkbox" id="checkbox" v-model="checked" />
  <label for="checkbox">{{checked}}</label>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    checked: false,
  },
  // 存放方法
  methods: {},
});

4. v-model:多个复选框

  • 多个复选框,绑定到同一个数组
<div id="app">
  <!-- 复选框多选 -->
  <div class="box">
    <input type="checkbox" id="a" value="橘子" v-model="checkedNames" />
    <label for="a">橘子</label><br />
    <input type="checkbox" id="b" value="苹果" v-model="checkedNames" />
    <label for="b">苹果</label><br />
    <input type="checkbox" id="c" value="西瓜" v-model="checkedNames" />
    <label for="c">西瓜</label><br />
    <span>{{checkedNames}}</span>
  </div>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "尔尔",
    checked: false,
    checkedNames: [],
  },
  // 存放方法
  methods: {},
});

5. v-model:单选按钮

<div id="example">
  <input type="radio" id="one" value="One" v-model="picked">
  <label for="one">One</label>
  <br>
  <input type="radio" id="two" value="Two" v-model="picked">
  <label for="two">Two</label>
  <br>
  <span>Picked: {{ picked }}</span>
</div>
new Vue({
  el: '#example',
  data: {
    picked: ''
  }
})

6. v-model:选择框

<div id="example">
  <select v-model="selected">
    <option disabled value="">请选择</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <span>Selected: {{ selected }}</span>
</div>
new Vue({
  el: '...',
  data: {
    selected: ''
  }
})

7. v-for 列表渲染

  • key属性 = “唯一标识”
  • **作用:**给列表项添加的唯一标识。便于Vue进行列表项的正确排序复用
  • v-for 的默认行为会尝试 原地修改元素 (就地复用)
  • 推荐使用 id 作为 key(唯一),不推荐使用 index 作为 key(会变化,不对应)
<div id="app">
  <div>
    <ul>
      <!-- :key:跟踪节点标识 -->
      <li v-for="item in menus" :key="item.id">
        <span>id:{{item.id}},name:{{item.name}}</span>
        <button @click="deleteById(item.id)">删除</button>
      </li>
    </ul>
    <ul>
      <li v-for="(item,index) in menus" :key="item.id">
        <h3>{{index}}--id:{{item.id}},name:{{item.name}}</h3>
      </li>
    </ul>
    <ol>
      <li v-for="(value,key) in obj" :key="name">
        <h3>{{key}}::{{value}}</h3>
      </li>
    </ol>
  </div>
</div>
new Vue({
  el: "#app",
  data: {
    menus: [
      { id: 1, name: "宫保鸡丁" },
      { id: 2, name: "鱼香肉丝" },
      { id: 3, name: "北京烤鸭" },
      { id: 4, name: "糖醋排骨" },
    ],
    obj: {
      title: "测试",
      name: "erer",
      age: "20",
    },
  },
  methods: {
    deleteById(id) {
      // filter: 根据条件,保留满足条件的对应项,得到一个新数组。
      this.menus = this.menus.filter(item => item.id !== id);
    }
  },
});

5、计算属性

1. 计算属性

  • 概念:基于现有的数据,计算出来的新属性。 依赖的数据变化,自动重新计算。
  • 语法:
    • 声明在 computed 配置项中,一个计算属性对应一个函数
    • 使用起来和普通属性一样使用 {{ 计算属性名 }}
  • 计算属性 → 可以将一段 求值的代码 进行封装
  • 缓存特性(提升性能):计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计算 → 并再次缓存
<div id="app">
  <p>{{reverseMsg}}</p>
  <p>{{fullName}}</p>
  <button @click="handleClick">改变</button>

  <table>
    <tr>
      <th>名字</th>
      <th>数量</th>
    </tr>
    <tr v-for="(item, index) in list" :key="item.id">
      <td>{{ item.name }}</td>
      <td>{{ item.num }}个</td>
    </tr>
  </table>

  <!-- 目标:统计求和,求得礼物总数 -->
  <p>礼物总数:{{ totalCount }} 个</p>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "hello erer",
    firstName: "erer",
    lastName: "尔尔",
    list: [
      { id: 1, name: "篮球", num: 3 },
      { id: 2, name: "足球", num: 2 },
      { id: 3, name: "铅笔", num: 5 },
    ],
  },
  // 计算属性
  computed: {
    // computed默认只有get方法
    // 最大优点:产生缓存,数据没有变化直接从缓存获取
    reverseMsg: function () {
      return this.msg.split("").reverse().join("");
    },
    fullName: function () {
      return this.firstName + this.lastName;
    },
    totalCount() {
      return this.list.reduce((sum, item) => sum + item.num, 0);
    },
  },
  // 存放方法
  methods: {
    handleClick() {
      this.msg = "computed";
    },
  },
});
<style>
  table {
    border: 1px solid #000;
    text-align: center;
    width: 240px;
  }
  th,
  td {
    border: 1px solid #000;
  }
  h3 {
    position: relative;
  }
</style>

2. computed&methods

  • computed 计算属性:
    • 作用:封装了一段对于数据的处理,求得一个结果。
    • 语法:
      • 写在 computed 配置项中
      • 作为属性,直接使用 → this.计算属性、{{ 计算属性 }}
  • methods 方法:
    • 作用:给实例提供一个方法,调用以处理业务逻辑。
    • 语法:
      • 写在 methods 配置项中
      • 作为方法,需要调用 → this.方法名( )、 {{ 方法名() }}、 @事件名=“方法名”

3. 计算属性完整写法

<div id="app">
  <p>{{msg}}</p>
  <input type="text" v-model="content" @input="handleInput" />
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "hello erer",
  },
  // 计算属性
  computed: {
    content: {
      set: function (value) {
        this.msg = value;
      },
      get: function () {
        return this.msg;
      },
    },
  },
  // 存放方法
  methods: {
    handleInput: function (event) {
      const { value } = event.target;
      // this.msg = value;
      this.content = value;
    },
  },
});

6、侦听器

<div id="app">
  <p>{{msg}}</p>
  <input type="text" v-model="msg" /> <br />
  <input type="text" v-model="obj.words" /> <br />
  <h3>{{students[0].name}}</h3>
  <br />
  <button @click='students[0].name="Tom"'>改变</button>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    msg: "尔尔",
    obj: {
      words: "",
    },
    students: [{ name: "erer", age: 18 }],
  },
  watch: {
    // key:属于 data 对象的属性名
    msg: function (newMsg, oldMsg) {
      console.log(newMsg, oldMsg);
    },
    // 防抖,延时执行
    "obj.words"(newWords) {
      clearTimeout(this.timer);
      // 延时器ID
      this.timer = setTimeout(async () => {
        console.log(newWords);
      }, 300);
    },
    // 深度监视:object、list
    students: {
      deep: true,
      // 无法获取old值
      handler: function (newStudents, oldStudents) {
        console.log(newStudents[0].name, oldStudents[0].nam);
      },
    },
  },
});

7、过滤器

1. 局部过滤器

 <div id="app">
   <h3>{{price | myFilter("$")}}</h3>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    price: 100,
  },
  // 局部过滤器
  filters: {
    myFilter: function (price, type) {
      return type + price;
    },
  },
  // 计算属性
  computed: {},
  // 存放方法
  methods: {},
});

2. 全局过滤器

<div id="app">
  <h3>{{msg | myReverse}}</h3>
</div>
// 全局过滤器
Vue.filter("myReverse", (val) => {
  return val.split("").reverse().join("");
});

8、音乐播放器

<div id="app">
  <audio :src="getCurrentSongStr" controls autoplay @ended="handleEnded"></audio>
  <ul>
    <li :class="{active:index===currentIndex}" v-for="(item,index) in musicData" :key="item.id" @click="handleClick(index)">
      <h2>{{item.id + 1}} - 歌名:{{item.name}}</h2>
      <p>{{item.author}}</p>
    </li>
  </ul>
  <button @click="handleNext">下一首</button>
</div>
const musicData = [
  {
    id: 0,
    name: "于荣光 - 少林英雄",
    author: "于荣光",
    songSrc: "./static/于荣光 - 少林英雄.mp3",
  },
  {
    id: 1,
    name: "Joel Adams - Please Dont Go",
    author: "Joel Adams",
    songSrc: "./static/Joel Adams - Please Dont Go.mp3",
  },
  {
    id: 2,
    name: "MKJ - Time",
    author: "MKJ",
    songSrc: "./static/MKJ - Time.mp3",
  },
  {
    id: 3,
    name: "Russ - Psycho (Pt. 2)",
    author: "Russ",
    songSrc: "./static/Russ - Psycho (Pt. 2).mp3",
  },
];
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    musicData,
    currentIndex: 0,
  },
  // 存放方法
  methods: {
    handleClick(id) {
      this.currentIndex = id;
    },
    handleEnded() {
      this.handleNext();
    },
    handleNext() {
      if (this.currentIndex === musicData.length - 1) {
        this.currentIndex = 0;
      } else {
        this.currentIndex += 1;
      }
    },
  },
  // 计算属性
  computed: {
    getCurrentSongStr() {
      return this.musicData[this.currentIndex].songSrc;
    },
  },
  // 局部过滤器
  filters: {},
});
<style>
  * {
    padding: 0;
    margin: 0;
  }
  ul {
    list-style: none;
  }
  ul li {
    margin: 20px 20px;
    padding: 10px 5px;
    border-radius: 3px;
  }
  ul li.active {
    background-color: #d2e2f3;
  }
</style>

二、生命周期

1、四个阶段

  • Vue生命周期:一个Vue实例从 创建 到 销毁 的整个过程。
  • 生命周期四个阶段:
    • 创建:响应式数据(data)、发送初始化渲染请求
    • 挂载:渲染模板、操作 dom
    • 更新:修改数据,更新视图
    • 销毁:销毁实例

2、生命周期函数

  • Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】
  • 让开发者可以在【特定阶段】运行自己的代码。
  • 创建阶段:
    • beforeCreate():组件创建之前
    • created():组件创建完成
  • 挂载阶段:
    • beforeMount():DOM挂载之前
    • mounted():DOM挂载完成
  • 更新阶段:
    • beforeUpdate():更新之前的DOM
    • updated():更新之后的DOM
  • 销毁阶段:
    • beforeDestroy():销毁之前
    • destroyed():销毁完成

3、案例一

<div id="app">
  <h3>{{ title }}</h3>
  <div>
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
  </div>
</div>
const app = new Vue({
  el: '#app',
  data: {
    count: 100,
    title: '计数器'
  },
  // 1. 创建阶段(准备数据)
  beforeCreate() {
    // beforeCreate 响应式数据准备好之前 undefined
    console.log('beforeCreate 响应式数据准备好之前', this.count)
  },
  created() {
    // created 响应式数据准备好之后 100
    console.log('created 响应式数据准备好之后', this.count)
  },

  // 2. 挂载阶段(渲染模板)
  beforeMount() {
    // beforeMount 模板渲染之前 {{ title }}
    console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
  },
  mounted() {
    // mounted 模板渲染之后 计数器
    console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
  },

  // 3. 更新阶段(修改数据 → 更新视图)
  beforeUpdate() {
    // beforeUpdate 数据修改了,视图还没更新 100
    console.log('beforeUpdate 数据修改了,视图还没更新', document.querySelector('span').innerHTML)
  },
  updated() {
    // updated 数据修改了,视图已经更新 101
    console.log('updated 数据修改了,视图已经更新', document.querySelector('span').innerHTML)
  },

  // 4. 卸载阶段:控制台输入 app.$destroy()
  beforeDestroy() {
    console.log('beforeDestroy, 卸载前')
    console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
  },
  destroyed() {
    console.log('destroyed,卸载后')
  }
});

4、案例二

  • activated():组件激活
  • deactivated():组件停用
<style>
  .active {
    color: red;
  }
</style>

<div id="app">
  <!-- 使用组件 -->
  <App></App>
</div>
Vue.component("Test", {
  data() {
    return {
      msg: "尔尔",
      isRed: false,
    };
  },
  template: `
          <div>
            <button @click = "handlerClick">改变</button>
            <h3 :class='{active:isRed}'>{{msg}}</h3>
          </div>
        `,
  methods: {
    handlerClick() {
      this.msg = "erer";
      this.isRed = !this.isRed;
    },
  },
  beforeCreate() {
    // 组件创建之前:undefined
    console.log("组件创建之前:", this.$data);
  },
  created() {
    // 组件创建完成: {__ob__: Observer}
    console.log("组件创建完成:", this.$data);
  },
  beforeMount() {
    // DOM挂载之前: <app></app>
    console.log("DOM挂载之前:", document.getElementById("app").innerHTML);
  },
  mounted() {
    // DOM挂载完成: <div><div><button>改变</button> <h3>尔尔</h3></div></div>
    console.log("DOM挂载完成:", document.getElementById("app").innerHTML);
  },
  beforeUpdate() {
    // 更新之前的DOM: <div><div><button>改变</button> <h3>尔尔</h3></div></div>
    console.log("更新之前的DOM:", document.getElementById("app").innerHTML);
  },
  updated() {
    // 更新之后的DOM: <div><div><button>改变</button> <h3>erer</h3></div></div>
    console.log("更新之后的DOM:", document.getElementById("app").innerHTML);
  },
  beforeDestroy() {
    console.log("销毁之前");
  },
  destroyed() {
    console.log("销毁完成");
  },
  // 配合 keep-alive 组件使用,使用缓存
  activated() {
    console.log("组件被激活了");
  },
  deactivated() {
    console.log("组件被停用了");
  },
});

const App = {
  data() {
    return {
      isShow: true,
    };
  },
  template: `
          <div>
            <keep-alive>
              <Test v-if="isShow"></Test>
            </keep-alive>
            <button @click = "clickHandler">改变生死</button>
          </div>
        `,
  methods: {
    clickHandler() {
      this.isShow = !this.isShow;
    },
  },
  components: {},
};

new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {},
  // 挂载局部组件
  components: {
    App,
  },
  // 存放方法
  methods: {},
  // 局部过滤器
  filters: {},
  // 计算属性
  computed: {},
});

5、加载数据

<div id="app">
  <ul>
    <li v-for="(item, index) in list" :key="item.id" class="news">
      <div class="left">
        <div class="title">{{ item.title }}</div>
        <div class="info">
          <span>{{ item.source }}</span>
          <span>{{ item.time }}</span>
        </div>
      </div>
      <div class="right">
        <img :src="item.img" alt="">
      </div>
    </li>
  </ul>
</div>
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
const app = new Vue({
  el: '#app',
  data: {
    list: []
  },
  async created() {
    // 1. 发送请求获取数据
    const res = await axios.get('http://hmajax.itheima.net/api/news')
    // 2. 更新到 list 中,用于页面渲染 v-for
    this.list = res.data.data;
  }
});

6、获取焦点

<div class="container" id="app">
  <div class="search-container">
    <img src="https://www.itheima.com/images/logo.png" alt="">
    <div class="search-box">
      <input type="text" v-model="words" id="inp">
      <button>搜索一下</button>
    </div>
  </div>
</div>
const app = new Vue({
  el: '#app',
  data: {
    words: ''
  },
  // 核心思路:
  // 1. 等input框渲染出来 mounted 钩子
  // 2. 让input框获取焦点 inp.focus()
  mounted() {
    document.querySelector('#inp').focus()
  }
});

三、Vue-cli

1、安装 cli3

# 将默认下载地址改成淘宝镜像下载
npm config set registry https://registry.npm.taobao.org/
npm config get registry

# 清除缓存
npm cache clean --force
# 安装cli
npm install -g @vue/cli
# 验证
vue --version

# 创建项目
vue create project-name
# 可选 vue2/vue3
# 启动项目
npm run serve / yarn serve

2、目录介绍

  • VUE-DEMO
    • node_modules 第三包文件夹
    • public 放html文件的地方
      • favicon.ico 网站图标
      • index.html index.html 模板文件③
    • src 源代码目录 → 以后写代码的文件夹
      • assets 静态资源目录 → 存放图片、字体等
      • components 组件目录 → 存放通用组件
      • App.vue App根组件 → 项目运行看到的内容就在这里编写②
      • main.js 入口文件 → 打包或运行,第一个执行的文件①
    • .gitignore git忽视文件
    • babel.config.js babel配置文件
    • jsconfig.json js配置文件
    • package.json 项目配置文件 → 包含项目名、版本号、scripts、依赖包等
    • README.md 项目说明文档
    • vue.config.js vue-cli配置文件
    • yarn.lock yarn锁文件,由yarn自动生成的,锁定安装版本

3、相关文件

1. index.html

<!DOCTYPE html>
<html lang="">
  <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">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <!-- 兼容,给不支持js的浏览器一个提示 -->
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <!-- vue管理的容器:创建结构动态渲染 -->
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

2. main.js

  • 导入 App.vue,基于 App.vue 创建结构渲染 index.html
// 导入 vue 核心包
import Vue from 'vue'
// 导入 App.vue 根组件
import App from './App.vue'

// 提示:当前处于什么环境(生成/开发)
Vue.config.productionTip = false

// vue实例化,提供 render 方法:基于 App.vue 创建结构渲染 index.html
new Vue({
  // render: h => h(App),
  render: (createElement) => {
    return createElement(App);
  }
}).$mount('#app')
// .$mount('#app') 等同于 el: "#app",用于指定 vue 管理的容器

3. App.vue

  • template:结构 (有且只能一个根元素)
  • script: js逻辑
  • style: 样式 (可支持less,需要装包)
    • style添加 lang=“less”
    • 安装依赖:yarn add less less-loader -D / npm install less less-loader --save-dev
<template>
  <!-- <div id="app"> -->
    <div class="App">
      <div class="box">
        111
      </div>
    </div>
    <!-- <img alt="Vue logo" src="./assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App" /> -->
  <!-- </div> -->
</template>

<script>
// import HelloWorld from "./components/HelloWorld.vue";

export default {
  name: "App",
  // components: {
  //   HelloWorld,
  // },
};
</script>

<style lang="less">
// #app {
//   font-family: Avenir, Helvetica, Arial, sans-serif;
//   -webkit-font-smoothing: antialiased;
//   -moz-osx-font-smoothing: grayscale;
//   text-align: center;
//   color: #2c3e50;
//   margin-top: 60px;
// }
/**
* 让 style 支持 less
* 1、style添加 lang="less"
* 2、安装依赖:yarn add less less-loader -D
*/
.App {
  width: 400px;
  height: 400px;
  background-color: pink;
  .box {
    width: 100px;
    height: 100px;
    background-color: blue;
  }
}
</style>

4. .gitignore

# 忽略build目录下类型为js的文件的语法检查
build/*.js
# 忽略src/assets目录下文件的语法检查
src/assets
# 忽略src/utils目录下文件的语法检查
src/utils
# 忽略public目录下文件的语法检查
public
# 忽略当前目录下为js的文件的语法检查
#*.js
# 忽略当前目录下为vue的文件的语法检查
#*.vue

四、组件

1、普通组件的注册使用

1. 组件注册方式

  • 局部注册:只能在注册的组件内使用
    • 创建 .vue 文件 (三个组成部分)
    • 在使用的组件内导入并注册
      • 导入:import 组件对象 from '.vue文件路径'
      • 注册:components: { 组件名: 组件对象, }
  • 全局注册:所有组件内都能使用
    • 创建 .vue 文件 (三个组成部分)
    • main.js 中进行全局注册
      • 导入:import 组件对象 from '.vue文件路径'
      • 注册:Vue.component(组件名, 组件对象)
  • 使用
    • 当成 html 标签使用 <组件名></组件名>
    • 组件名规范 → 大驼峰命名法,如:ErerHeader
    • 一般都用局部注册,如果发现确实是通用组件,再定义到全局。

2. 局部注册

  • 创建 .vue 文件
<template>
  <div class="erer-main">
    我是 main 组件
  </div>
</template>

<script>
export default {

}
</script>

<style>
.erer-main{
  height: 500px;
  line-height: 500px;
  text-align: center;
  font-size: 30px;
  background-color: #f79646;
  color: white;
}
</style>
  • 在使用的组件内导入并注册
<template>
  <div class="App">
    <!-- 头部组件 -->
    <erer-header></erer-header>  
    <!-- 主体组件 -->
    <erer-main></erer-main> 
    <!-- 底部组件 -->
    <erer-footer></erer-footer> 
  </div>
</template>

<script>

// 导入需要注册的组件
// import 组件对象 from '.vue文件路径'
import ErerHeader from "./components/ErerHeader.vue"
import ErerMain from "./components/ErerMain.vue"
import ErerFooter from "./components/ErerFooter.vue"

export default {
  // 局部注册
  components: {
    // 组件名: 组件对象,
    ErerHeader: ErerHeader,
    ErerMain,
    ErerFooter,
  }
}
</script>

<style>
.App{
  width: 600px;
  height: 700px;
  background-color: #87ceeb;
  margin: 0 outo;
  padding: 20px;
}
</style>

3. 全局注册

  • 创建 .vue 文件
<template>
  <button class="erer-button">通用按钮</button>
</template>

<script>
export default {};
</script>

<style>
.erer-button {
  height: 50px;
  line-height: 50px;
  padding: 0 20px;
  background-color: #3bae56;
  border-radius: 5px;
}
</style>
  • main.js 中进行全局注册
import Vue from "vue";
import App from "./App.vue";

// 导入需要注册的组件
import ErerButton from "./components/ErerButton.vue";
Vue.config.productionTip = false;

// 全局注册
// Vue.component(组件名, 组件对象)
Vue.component("ErerButton", ErerButton);

new Vue({
	render: (h) => h(App),
}).$mount("#app");
  • 使用组件
<template>
  <div class="erer-footer">
    我是 footer 组件
    <!-- 使用组件 -->
    <erer-button></erer-button>
  </div>
</template>

<script>
export default {

}
</script>

<style>
.erer-footer{
  height: 100px;
  line-height: 100px;
  text-align: center;
  font-size: 30px;
  background-color: #4f81bd;
  color: white;
}
</style>

2、组件组成部分

  • 结构
    • 只能有一个根元素
  • 样式
    • 全局样式(默认):影响所有组件
    • 局部样式:scoped 下样式,只作用于当前组件
  • 逻辑
    • el 根实例独有, data 是一个函数,其他配置项一致

3、样式冲突 scoped

  • 默认情况:写在组件中的样式会全局生效 → 因此很容易造成多个组件之间的样式冲突问题。
    • 全局样式:默认组件中的样式会作用到全局
    • 局部样式:可以给组件加上 scoped 属性, 可以让样式只作用于当前组件
<template>
  <div>BaseOne</div>
</template>

<script>
export default {

}
</script>

<style scoped>
div {
  border: 3px solid blue;
  margin: 30px;
}
</style>
  • scoped原理

    • 当前组件内标签都被添加 data-v-hash值 的属性
    • css选择器都被添加 [data-v-hash值] 的属性选择器
  • 最终效果:

    • 必须是当前组件的元素, 才会有这个自定义属性, 才会被这个样式作用到

4、data 是一个函数

  • 一个组件的 data 选项必须是一个函数;
  • 保证每个组件实例,维护独立的一份数据对象;
  • 每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象。

五、组件通讯

1、父子通讯

1. 父传子:props

  • 父组件声明变量:data() { return { myTitle: "尔尔", }; },
  • 父组件给子组件标签添加属性的方式传值:<my-son :title="myTitle"></my-son>
  • 子组件内部通过 props 接收父组件传值:props: ["title",]
  • 模版中直接使用:<div class="my-son">我是 Son 组件:{{ title }}</div>

2. 子传父:$emit

  • 在子组件中触发父组件绑定的事件:<button @click="changeFn">修改 title</button>
  • 子组件通过 KaTeX parse error: Expected '}', got 'EOF' at end of input: …ngeFn() { this.emit(“changTitle”, this.title === “erer” ? “尔尔” : “erer”); }, },`
  • 在父组件中监听子组件事件:<my-son :title="myTitle" @changTitle="handleChange"></my-son>
  • 父组件处理子组件传来参数:methods: { handleChange(newVal) { this.myTitle = newVal; } },

3. props 案例

  • props 定义:组件上 注册的一些 自定义属性

  • props 作用:向子组件传递数据

  • 特点:

    • 可以 传递 任意数量 的prop
    • 可以 传递 任意类型 的prop
  • 子组件

<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{ name }}</div>
    <div>年龄:{{ age }}</div>
    <div>是否单身:{{ isSingle ? "是" : "否" }}</div>
    <div>座驾:{{ car.brand }}</div>
    <div>兴趣爱好:{{ hobby.join("、") }} </div>
  </div>
</template>

<script>
export default {
  props: ["name", "age", "isSingle", "car", "hobby"],
};
</script>

<style>
.userinfo {
  width: 300px;
  border: 3px solid #000;
  padding: 20px;
}
.userinfo > div {
  margin: 20px 10px;
}
</style>
  • 父组件
<template>
  <div class="App">
    <user-info :name="name" :age="age" :isSingle="isSingle" :car="car" :hobby="hobby"></user-info>
  </div>
</template>

<script>
import UserInfo from "./04components/UserInfo.vue";

export default {
  components: {
    UserInfo,
  },
  data() {
    return {
      name: "erer",
      age: 12,
      isSingle: true,
      car: {
        brand: "奔驰",
      },
      hobby: ["python", "游戏"],
    };
  },
};
</script>

<style>
</style>

4. props 校验

  • 作用:
    • 为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示
    • 帮助开发者,快速发现错误
  • 语法:
    • 类型校验
    • 非空校验
    • 默认值
    • 自定义校验
  • 校验方法一:
props: {
  // Number String Boolean ...
	校验的属性名: 类型 
},
  • 校验方法二:
props: {
  校验的属性名: {
    type: 类型, // Number String Boolean ...
    required: true, // 是否必填
    default: 默认值, // 默认值
    validator (value) {
      // 自定义校验逻辑
      return 是否通过校验
    }
  }
},
  • 案例
<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{ name }}</div>
    <div>年龄:{{ age }}</div>
    <div>是否单身:{{ isSingle ? "是" : "否" }}</div>
    <div>座驾:{{ car.brand }}</div>
    <div>兴趣爱好:{{ hobby.join("、") }} </div>
  </div>
</template>

<script>
export default {
  props: {
    name: String,         // 字符串校验
    age: {
      type: Number,       // 数字校验
      require: true,      // 是否必填
      default: 0,         // 默认值
      validator(value) {  // 自定义
        if(0 < value && value < 100) {
          return true;
        }
        alert("年龄必须介于0-100之间!");
        return false;
      },
    },
    isSingle: Boolean,    // 布尔值校验
    car: Object,          // 对象校验
    hobby: Array,         // 数组校验
  },
};
</script>

<style>
.userinfo {
  width: 300px;
  border: 3px solid #000;
  padding: 20px;
}
.userinfo > div {
  margin: 20px 10px;
}
</style>

2、props & data

  • 共同点:都可以给组件提供数据。

  • 区别:

    • data 的数据是自己的 → 随便改
    • prop 的数据是外部的 → 不能直接改,要遵循 单向数据流
  • 单向数据流:父级 prop 的数据更新,会向下流动,影响子组件。这个数据流动是单向的。

  • 子组件

<template>
  <div class="base-count">
    <button @click="sub">-</button>
    <span> {{ count }} - {{ number }}</span>
    <button @click="add">+</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 99,
    };
  },
  props: {
    number: {
      type: Number,
      default: 888,
    },
  },
  methods: {
    add() {
      this.count++;
      this.$emit("changNumber", this.number + 1);
    },
    sub() {
      this.count--;
      this.$emit("changNumber", this.number - 1);
    },
  },
};
</script>

<style>
.base-count {
  margin: 20px;
}
</style>
  • 父组件
<template>
  <div class="App">
    <base-count :number="number" @changNumber="handleChangeNumber"></base-count>
  </div>
</template>

<script>
import BaseCount from "./03components/BaseCount.vue";

export default {
  components: {
    BaseCount,
  },
  data() {
    return {
      number: 1000,
    };
  },
  methods: {
    handleChangeNumber(value) {
      this.number = value;
    },
  },
};
</script>

<style>
</style>

3、event bus 事件总线

  • 作用:非父子组件之间,进行简易消息传递。(复杂场景 → Vuex)

  • Utils>EventBus.js

import Vue from 'vue'
// 创建一个 所有组件都可以访问的事件总线(空的 vue 实例)
const Bus  =  new Vue()
export default Bus
  • 发送消息:Bus.$emit('sendMsg','这是一个消息')
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from "@/utils/EventBus";
export default {
  methods: {
    sendMsgFn() {
      // 发送消息:通过触发事件传递消息
      Bus.$emit("sendMsg", "今天天气不错,适合旅游");
    },
  },
};
</script>

<style scoped>
.base-b {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
  • 订阅消息:created(){ Bus.$on('sendMsg', (msg) => {this.msg = msg}) }
<template>
  <div class="base-a">
    我是A组件(接受方)
    <p>{{ msg }}</p>
  </div>
</template>

<script>
import Bus from "@/utils/EventBus";
export default {
  data() {
    return {
      msg: "",
    };
  },
  created() {
    // 订阅消息:组件接收方,监听 bus 的事件
    Bus.$on("sendMsg", (msg) => {
      this.msg = msg;
    });
  },
};
</script>

<style scoped>
.base-a {
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

4、provide & inject

  • provide & inject 作用:跨层级共享数据
  • 父组件 provide 提供数据
<template>
  <div class="App">
    <!-- 7、provide & inject -->
    <h2>7、provide & inject</h2>
    <button @click="change">修改数据</button>
    <son-a></son-a>
    <son-b></son-b>
  </div>
</template>

<script>
  
import SonA from "./07components/SonA.vue";
import SonB from "./07components/SonB.vue";

export default {
  components: {
    SonA,
    SonB,
  },
  provide() {
    return {
      color: this.color,
      userInfo: this.userInfo,
    };
  },
  data() {
    return {
      color: "pink",
      userInfo: {
        name: "erer",
        age: 50,
      },
    };
  },
  methods: {
    change() {
      // 简单类型非响应式数据
      this.color = this.color === "red" ? "pink" : "red";
      // 复杂类型响应式数据
      this.userInfo.name = this.userInfo.name === "erer" ? "尔尔" : "erer";
    },
  },
};
</script>
<style>
</style>
  • 子/孙组件 inject 取值使用
<template>
  <div class="grandSon">
    我是GrandSon
    {{ color }} - {{ userInfo.name }} - {{ userInfo.age }}
  </div>
</template>

<script>
export default {
  inject: ['color', 'userInfo'],
}
</script>

<style>
.grandSon {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
  height: 100px;
}
</style>

5、v-model 组件封装

1. v-model 原理

  • 原理:v-model本质上是一个语法糖。例如应用在输入框上,就是 value属性 和 input事件 的合写。
  • 作用:提供数据的双向绑定
    • 数据变,视图跟着变 :value
    • 视图变,数据跟着变 @input
  • 注意:$event 用于在模板中,获取事件的形参
<span>{{ msg }}</span><br><br>
<input v-model="msg" type="text"><br><br>
<!-- 模版中 可以通过 $event 获取事件的形参 -->
<input :value="msg" @input="msg = $event.target.value" type="text"><br><br>

2. 表单类组件封装

  • 表单类组件 封装 → 实现 子组件 和 父组件数据 的双向绑定
    • 父传子:数据 应该是父组件 props 传递 过来的,将 v-model 拆解绑定数据
    • 子传父:监听输入,子传父传值给父组件修改
  • 父组件:<BaseSelect :cityId="selectId" @事件名="selecteId = $event" />
  • 子组件:
    • <select :value="cityId" @change="handleChange">...</select>
    • props: { cityId: String },
    • methods: { handleChange (e) { this.$emit('事件名', e.target.value)}}
<template>
  <div class="App">
    <!-- 9、表单类组件封装 -->
    <h2>9、表单类组件封装</h2>
    <span>{{ selectId }}</span><br><br>
    <base-select :cityId="selectId" @changeId="selectId = $event"></base-select>
  </div>
</template>

<script>
import BaseSelect from "./09components/BaseSelect.vue";
export default {
  components: {
    BaseSelect,
  },
  data() {
    return {
      selectId: "102",
    };
  },
};
</script>
<style>
</style>
<template>
  <div>
    <select :value="cityId" @change="handleChange">
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>

<script>
export default {
  props: {
    cityId: String,
  },
  methods: {
    handleChange(e) {
      this.$emit("changeId", e.target.value);
    },
  },
};
</script>

<style>
</style>

3. v-model 简化代码

  • 父组件 v-model 简化代码,实现 子组件 和 父组件数据 双向绑定
    • 子组件中:props 通过 value 接收,事件触发 input
    • 父组件中:v-model 给组件直接绑数据
  • v-mode= "参数名" => :value="参数名" + @input="参数名 = $event"
<template>
  <div class="App">
    <!-- 9、表单类组件封装 -->
    <h2>9、表单类组件封装</h2>
    <span>{{ selectId }}</span><br><br>
    <!-- v-model 直接绑数据 -->
    <base-select v-model="selectId"></base-select>
  </div>
</template>

<script>
import BaseSelect from "./09components/BaseSelect.vue";
export default {
  components: {
    BaseSelect,
  },
  data() {
    return {
      selectId: "102",
    };
  },
};
</script>
<template>
  <div>
    <select :value="value" @change="handleChange">
      <option value="101">北京</option>
      <option value="102">上海</option>
      <option value="103">武汉</option>
      <option value="104">广州</option>
      <option value="105">深圳</option>
    </select>
  </div>
</template>
<script>
export default {
  // props 通过 value 接收
  props: {
    value: String,
  },
  methods: {
    handleChange(e) {
      // 事件触发使用 input
      this.$emit("input", e.target.value);
    },
  },
};
</script>
<style>
</style>

6、.sync 修饰符

  • 作用:可以实现 子组件 与 父组件数据 的 双向绑定,简化代码
  • 特点:prop属性名,可以自定义,非固定为 value
  • 场景:封装弹框类的基础组件, visible属性 true显示 false隐藏
  • 本质:就是 :属性名 和 @update:属性名 合写
  • :visible.sync="属性名" => :visible="属性名" + @update.visible="属性名 = $event"
<template>
  <div class="App">
    <!-- 10、.sync 修饰符 -->
    <h2>10、.sync 修饰符</h2>
    <button @click="isShow = true">退出按钮</button>
    <base-dialog :visible.sync="isShow"></base-dialog>
  </div>
</template>

<script>
import BaseDialog from "./10components/BaseDialog.vue";

export default {
  components: {
    BaseDialog,
  },
  data() {
    return {
      isShow: false,
    };
  },
};
</script>
<template>
  <div v-show="visible" class="base-dialog-wrap">
    <div class="base-dialog">
      <div class="title">
        <h3>温馨提示:</h3>
        <button @click="close" class="close">x</button>
      </div>
      <div class="content">
        <p >你确认要退出本系统么?</p>
      </div>
      <div class="footer">
        <button @click="close" >确认</button>
        <button @click="close" >取消</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    visible: Boolean,
  },
  methods: {
    close() {
      this.$emit("update:visible", false);
    },
  },
};
</script>

<style scoped>
</style>

7、ref 和 $refs

  • 作用:利用 ref 和 $refs 可以用于 获取 dom 元素, 或 组件实例

  • 特点:查找范围 → 当前组件内 (更精确稳定)

    • 目标标签 – 添加 ref 属性:<div ref="属性名">我是渲染图表的容器</div>
    • 通过 KaTeX parse error: Expected '}', got 'EOF' at end of input: …nsole.log(this.refs.属性名) },`
    • 目标组件 - 添加 ref 属性:<BaseForm ref="属性名"></BaseForm>
    • 调用 r e f s 获取组件实例: ‘ t h i s . refs 获取组件实例:`this. refs获取组件实例:this.refs.属性名.组件方法()`
<template>
  <div class="App">
    <!-- 11、ref 和 $refs -->
    <h2>11、ref 和 $refs</h2>
    <!-- 调用 $refs 获取组件实例 -->
    <base-chart></base-chart>
    <!-- 通过 $refs 获取dom对象 -->
    <base-form ref="baseForm"></base-form>
    <button @click="handelGet">获取数据</button>
    <button @click="handelReset">重置数据</button>
  </div>
</template>
<script>
import BaseChart from "./11components/BaseChart.vue";
import BaseForm from "./11components/BaseForm.vue";
export default {
  components: {
    BaseChart,
    BaseForm,
  },
  methods: {
    handelGet() {
      console.log(this.$refs.baseForm.getFormData());
    },
    handelReset() {
      this.$refs.baseForm.resetFormData();
    },
  },
};
</script>
<style>
</style>
<template>
  <div ref="myChart" class="base-chart-box">子组件</div>
</template>

<script>
import * as echarts from "echarts";

export default {
  mounted() {
    // 基于准备好的dom,初始化echarts实例
    const myChart = echarts.init(this.$refs.myChart);
    // 绘制图表
    myChart.setOption({
      title: {
        text: "ECharts 入门示例",
      },
      tooltip: {},
      xAxis: {
        data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"],
      },
      yAxis: {},
      series: [
        {
          name: "销量",
          type: "bar",
          data: [5, 20, 36, 10, 10, 20],
        },
      ],
    });
  },
};
</script>

<style scoped>
.base-chart-box {
  width: 400px;
  height: 300px;
  border: 3px solid #000;
  border-radius: 6px;
}
</style>
<template>
  <div class="app">
    <div>
      账号: <input v-model="username" type="text">
    </div>
    <div>
      密码: <input v-model="password" type="text">
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      username: "admin",
      password: "123456",
    };
  },
  methods: {
    getFormData() {
      //console.log('获取表单数据', this.username, this.password);
      return {
        username: this.username,
        password: this.password,
      };
    },
    resetFormData() {
      this.username = "";
      this.password = "";
      console.log("重置表单数据成功");
    },
  },
};
</script>

<style scoped>
</style>

8、$nextTick

  • $nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体
<template>
  <div class="App">
    <!-- 12、Vue异步更新、$nextTick -->
    <h2>12、Vue异步更新、$nextTick</h2>
    <next-tick-case></next-tick-case>
  </div>
</template>

<script>
import NextTickCase from "./12components/NextTickCase.vue";
export default {
  components: {
    NextTickCase,
  },
};
</script>

<style>
</style>
<template>
  <div class="next-tick-case">
    <div v-if="isShowEdit">
      <input type="text" v-model="editValue" ref="inp" />
      <button>确认</button>
    </div>
    <div v-else>
      <span>{{ title }}</span>
      <button @click="handleEdit">编辑</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      title: "大标题",
      isShowEdit: false,
      editValue: "",
    };
  },
  methods: {
    handleEdit() {
      // 显示输入框(Vue 是 异步更新 DOM (提升性能))
      this.isShowEdit = true;
      // 获取焦点(等 DOM 更新后, 才会触发执行此方法里的函数体)
      this.$nextTick(() => {
        this.$refs.inp.focus();
      });
    },
  },
};
</script>
<style>
</style>

9、对象变更检测

  • this.$set(this.user, "age", 20);
  • this.user = Object.assign({}, this.user, {})
<div id="app">
  <h3>{{user.name}},{{user.age}},{{user.weight}},{{user.phone}}</h3>
  <button @click="handleAdd">添加属性</button>
</div>
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    user: {},
  },
  created() {
    // 模拟异步请求
    setTimeout(() => {
      this.user = {
        name: "尔尔",
      };
    }, 1000);
  },
  // 挂载局部组件
  components: {},
  // 存放方法
  methods: {
    handleAdd() {
      // this.user.age = 20;  // 错误方法
      // 添加单个响应式属性
      this.$set(this.user, "age", 20);
      // 添加多个响应式属性
      this.user = Object.assign({}, this.user, {
        weight: 20,
        phone: 18888888888,
      });
    },
  },
  // 局部过滤器
  filters: {},
  // 计算属性
  computed: {},
});

10、mixin混入偷懒技术

<div id="app">
  <h3>{{msg}}</h3>
</div>
const myMixin = {
  data() {
    return {
      msg: "123",
    };
  },
  created() {
    this.sayHello();
  },
  methods: {
    sayHello() {
      console.log("hello");
    },
  },
};
new Vue({
  // 绑定模块
  el: "#app",
  // 数据属性
  data: {
    title: "尔尔",
  },
  mixins: [myMixin],
  // 挂载局部组件
  components: {},
  // 存放方法
  methods: {},
  // 局部过滤器
  filters: {},
  // 计算属性
  computed: {},
});

六、vue进阶

1、自定义指令

  • 自定义指令:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能
  • 全局注册
// 全局注册自定义指令
Vue.directive("focus", {
	// inserted会在指令所在的元素被插入到页面的时候触发
	inserted(el) {
		el.focus();
	},
});
  • 局部注册
export default {
  directives: {
    // 指令名
    focus: {
      inserted(el) {
        el.focus();
      },
    },
  },
};
  • 使用 <input v-指令名 type="text">

2、指令的值

  • 语法:在绑定指令时,可以通过 “=” 的形式为指令 绑定 具体的参数值
  • 通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数。
<template>
  <div>
    <h1>自定义指令:指令的值</h1>
    <span v-color="color1">指令的值测试1</span><br>
    <span v-color="color2">指令的值测试2</span>
  </div>
</template>
<script>
export default {
  data() {
    return {
      color1: "red",
      color2: "blue",
    };
  },
  directives: {
    color: {
      inserted(el, binding) {
        el.style.color = binding.value;
      },
      update(el, binding) {
        el.style.color = binding.value;
      },
    },
  },
};
</script>

<style>
</style>

3、v-loading

  • 分析
    • 本质 loading 效果就是一个蒙层,盖在了盒子上
    • 数据请求中,开启loading状态,添加蒙层
    • 数据请求完毕,关闭loading状态,移除蒙层
  • 实现
    • 准备一个 loading 类,通过伪元素定位,设置宽高,实现蒙层
    • 开启关闭 loading 状态(添加移除蒙层),本质只需要添加移除类即可
    • 结合自定义指令的语法进行封装复用
<template>
  <div>
    <get-news></get-news>
  </div>
</template>
<script>
import GetNews from "./components/GetNews.vue";
export default {
  components: {
    GetNews,
  },
};
</script>
<style>
</style>
<template>
  <div class="get-news" v-loading="isLoading">
    <ul>
      <li v-for="item in list" :key="item.id" class="news">
        <div class="left">
          <div class="title">{{ item.title }}</div>
          <div class="info">
            <span>{{ item.source }}</span>
            <span>{{ item.time }}</span>
          </div>
        </div>
        <div class="right">
          <img :src="item.img" alt="">
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
// 安装axios =>  yarn add axios
import axios from "axios";
// 接口地址:http://hmajax.itheima.net/api/news
// 请求方式:get
export default {
  data() {
    return {
      list: [],
      isLoading: true,
    };
  },
  async created() {
    // 1. 发送请求获取数据
    const res = await axios.get("http://hmajax.itheima.net/api/news");

    setTimeout(() => {
      // 2. 更新到 list 中,用于页面渲染 v-for
      this.list = res.data.data;
      this.isLoading = false;
    }, 2000);
  },
  directives: {
    loading: {
      inserted(el, binding) {
        binding.value
          ? el.classList.add("loading")
          : el.classList.remove("loading");
      },
      update(el, binding) {
        binding.value
          ? el.classList.add("loading")
          : el.classList.remove("loading");
      },
    },
  },
};
</script>

<style scoped>
.loading:before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #fff url("@/assets/loading.gif") no-repeat center;
}

.box {
  width: 800px;
  min-height: 500px;
  border: 3px solid orange;
  border-radius: 5px;
  position: relative;
}
.news {
  display: flex;
  height: 120px;
  width: 600px;
  margin: 0 auto;
  padding: 20px 0;
  cursor: pointer;
}
.news .left {
  flex: 1;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  padding-right: 10px;
}
.news .left .title {
  font-size: 20px;
}
.news .left .info {
  color: #999999;
}
.news .left .info span {
  margin-right: 20px;
}
.news .right {
  width: 160px;
  height: 120px;
}
.news .right img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
</style>

4、插槽

1. 默认插槽

  • 作用:让组件内部的一些 结构 支持 自定义
  • 使用步骤:
    • 先在组件内用 <slot></slot> 占位
    • 使用组件时, 传入具体标签内容插入
<template>
  <div class="dialog-content"> 
    <slot></slot>
  </div> 
</template>

2. 后备内容

  • 插槽后备内容:封装组件时,可以为预留的 <slot></slot>插槽提供后备内容(默认内容)。
  • 语法: 在 <slot></slot>标签内,放置内容, 作为默认显示内容
  • 效果:
    • 外部使用组件时,不传东西,则 <slot></slot>会显示后备内容
    • 外部使用组件时,传东西了,则 <slot></slot>整体会被换掉
<template>
  <div class="dialog-content"> 
    <slot>我是后备内容</slot>
  </div> 
</template>

3. 具名插槽

  • 多个 <slot></slot>使用name属性区分名字
<div class="dialog-header">
	<slot name="head"></slot>
</div>
<div class="dialog-content">
	<slot name="content"></slot>
</div>
<div class="dialog-footer">
	<slot name="footer"></slot>
</div>
  • template 配合 v-slot: 名字来分发对应标签
<MyDialog>
	<template v-slot:head>大标题</template>
	<template v-slot:content>内容文本</template>
	<template v-slot:footer><button>按钮</button></template>
</MyDialog>
  • 具名插槽简化语法:
<MyDialog>
	<template #head>大标题</template>
	<template #content>内容文本</template>
	<template #footer><button>按钮</button></template>
</MyDialog>

4. 插槽案例

  • App.vue
<template>
  <div>
    
    <my-dialog></my-dialog>
    
    <!-- 没有分发对应标签不生效 -->
    <my-dialog>你确认要退出吗?</my-dialog>

    <my-dialog>
      <template v-slot:content>你确认要删除吗?</template>
    </my-dialog>

    <my-dialog>
      <template #head>大标题</template>
      <template #content>内容文本</template>
      <template #footer><button>按钮</button></template>
    </my-dialog>

  </div>
</template>
<script>
import MyDialog from "./components/MyDialog.vue";
export default {
  components: {
    MyDialog,
  },
};
</script>

<style>
</style>
  • MyDialog
<template>
  <div class="dialog">
    <div class="dialog-header">
      <slot name="head">
        <h3>友情提示</h3>
        <span class="close">✖️</span>
      </slot>
    </div>
    <div class="dialog-content">
      <slot name="content">我是默认内容</slot>
    </div>
    <div class="dialog-footer">
      <slot name="footer">
        <button>取消</button>
        <button>确认</button>
      </slot>
    </div>
  </div>
</template>

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

<style scoped>
* {
  margin: 0;
  padding: 0;
}
.dialog {
  width: 470px;
  height: 230px;
  padding: 0 25px;
  background-color: #ffffff;
  margin: 40px;
  border-radius: 5px;
}
.dialog-header {
  height: 70px;
  line-height: 70px;
  font-size: 20px;
  border-bottom: 1px solid #ccc;
  position: relative;
}
.dialog-header .close {
  position: absolute;
  right: 0px;
  top: 0px;
  cursor: pointer;
}
.dialog-content {
  height: 80px;
  font-size: 18px;
  padding: 15px 0;
}
.dialog-footer {
  display: flex;
  justify-content: flex-end;
}
.dialog-footer button {
  width: 65px;
  height: 35px;
  background-color: #ffffff;
  border: 1px solid #e1e3e9;
  cursor: pointer;
  outline: none;
  margin-left: 10px;
  border-radius: 3px;
}
.dialog-footer button:last-child {
  background-color: #007acc;
  color: #fff;
}
</style>

5. 作用域插槽

  • 作用域插槽: 定义 slot 插槽的同时, 是可以传值的。
  • 给 插槽 上可以 绑定数据,将来 使用组件时可以用。
  • 场景:封装表格组件
    • 父传子,动态渲染表格内容
    • 利用默认插槽,定制操作列
    • 删除或查看都需要用到 当前项的 id,属于 组件内部的数据 通过 作用域插槽 传值绑定,进而使用
  • 基本使用步骤:
    • 给 slot 标签, 以 添加属性的方式传值:<slot :id="item.id" msg="测试文本"></slot>
    • 所有添加的属性, 都会被收集到一个对象中:{ id: 3, msg: '测试文本' }
    • 在template中, 通过 #插槽名= "obj" 接收,默认插槽名为 default
<MyTable :list="list">
	<template #default="obj">
		<button @click="del(obj.id)">删除</button>
  </template>
</MyTable>
  • App.vue
<template>
  <div>
    <MyTable :data="list1">
      <template #operation="obj">
        <button @click="deleteById(obj.row.id)">删除</button>
      </template>
    </MyTable>
    <MyTable :data="list2">
      <!-- 支持结构 -->
      <template #operation="{ row }">
        <button @click="show(row)">查看</button>
      </template>
    </MyTable>
  </div>
</template>
<script>
import MyTable from "./components/MyTable.vue";
export default {
  components: {
    MyTable,
  },
  data() {
    return {
      color1: "red",
      color2: "blue",
      list1: [
        { id: 1, name: "张小花", age: 18 },
        { id: 2, name: "孙大明", age: 19 },
        { id: 3, name: "刘德忠", age: 17 },
      ],
      list2: [
        { id: 1, name: "赵小云", age: 18 },
        { id: 2, name: "刘蓓蓓", age: 19 },
        { id: 3, name: "姜肖泰", age: 17 },
      ],
    };
  },
  methods: {
    deleteById(id) {
      this.list1 = this.list1.filter((item) => item.id !== id);
    },
    show(item) {
      alert(item.name + ":" + item.age);
    },
  },
};
</script>

<style>
</style>
  • MyTable.vue
<template>
  <table class="my-table">
    <thead>
      <tr>
        <th>序号</th>
        <th>姓名</th>
        <th>年纪</th>
        <th>操作</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="(item, index) in data" :key="item.id">
        <td>{{ index + 1 }}</td>
        <td>{{ item.name }}</td>
        <td>{{ item.age }}</td>
        <td>
          <slot :row="item"  msg="测试文本" name="operation"></slot>
        </td>
      </tr>
    </tbody>
  </table>
</template>

<script>
export default {
  props: {
    data: Array,
  },
};
</script>

<style scoped>
.my-table {
  width: 450px;
  text-align: center;
  border: 1px solid #ccc;
  font-size: 24px;
  margin: 30px auto;
}
.my-table thead {
  background-color: #1f74ff;
  color: #fff;
}
.my-table thead th {
  font-weight: normal;
}
.my-table thead tr {
  line-height: 40px;
}
.my-table th,
.my-table td {
  border-bottom: 1px solid #ccc;
  border-right: 1px solid #ccc;
}
.my-table td:last-child {
  border-right: none;
}
.my-table tr:last-child td {
  border-bottom: none;
}
.my-table button {
  width: 65px;
  height: 35px;
  font-size: 18px;
  border: 1px solid #ccc;
  outline: none;
  border-radius: 3px;
  cursor: pointer;
  background-color: #ffffff;
  margin-left: 5px;
}
</style>

七、路由

1、路由介绍

1. 单页应用程序

  • 单页面应用(SPA): 所有功能在 一个html页面 上实现
开发分类实现方式页面性能开发效率用户体验学习成本首屏加载SEO
单页一个html页面按需更新 性能高非常好
多页多个html页面整页更新 性能低中等一般中等
  • 单页面应用
    • 系统类网站 / 内部网站 / 文档类网站 /移动端站点
  • 多页面应用
    • 公司官网 / 电商类网站

2. 路由介绍

  • Vue中路由:路径 和 组件 的 映射 关系
  • 根据路由就能知道不同路径的,应该匹配渲染哪个组件

2、VueRouter

1. VueRouter 的 介绍

  • 目标:认识插件 VueRouter,掌握 VueRouter 的基本使用步骤
  • 作用:修改地址栏路径时,切换显示匹配的组件
  • 说明:Vue 官方的一个路由插件,是一个第三方包
  • 官网:https://v3.router.vuejs.org/zh/

2. VueRouter 的 使用

  • 5个基础步骤 (固定)
    • 下载 VueRouter 模块到当前工程,版本3.6.5:npm install vue-router@3.6.5
    • 引入:import VueRouter from 'vue-router'
    • 安装注册:Vue.use(VueRouter)
    • 创建路由对象:const router = new VueRouter()
    • 注入,将路由对象注入到new Vue实例中,建立关联:new Vue({ render: h => h(App), router }).$mount('#app')
import Vue from "vue";
import App from "./App.vue";

// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter();

Vue.config.productionTip = false;
new Vue({
	render: (h) => h(App),
  // 5. 注入到new Vue中,建立关联
	router,
}).$mount("#app");
  • 2 个核心步骤
  • 创建需要的组件 (views目录)
<template>
  <div>
    <p>发现音乐</p>
    <p>发现音乐</p>
    <p>发现音乐</p>
    <p>发现音乐</p>
  </div>
</template>
<script>
export default {
  name: 'FindMusic'
}
</script>
<style>
</style>
  • 配置路由规则(path: 地址栏路径, component:组 件)
import Vue from "vue";
import App from "./App.vue";
// A.创建需要的组件
import Find from "./views/Find.vue";
import My from "./views/My.vue";
import Friend from "./views/Friend.vue";

// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter({
	// B.配置路由规则
	routes: [
		{ path: "/find", component: Find },
		{ path: "/my", component: My },
		{ path: "/friend", component: Friend },
	],
});

Vue.config.productionTip = false;

new Vue({
	render: (h) => h(App),
  // 5. 注入到new Vue中,建立关联
	router,
}).$mount("#app");
  • 配置导航,配置路由出口(路径匹配的组件显示的位置)
<template>
  <div>
    <div class="footer_wrap">
      <a href="#/find">发现音乐</a>
      <a href="#/my">我的音乐</a>
      <a href="#/friend">朋友</a>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

3. 组件存放目录问题

  • 组件分类:页面组件 & 复用组件
  • 注意:都是 .vue文件 (本质无区别)
  • 分类开来 更易维护
    • src/views文件夹:页面组件 - 页面展示 - 配合路由用
    • src/components文件夹:复用组件 - 展示数据 - 常用于复用

4. 路由的封装抽离

  • @/router/index.js
import Vue from "vue";
// A.创建需要的组件
import Find from "@/views/Find.vue";
import My from "@/views/My.vue";
import Friend from "@/views/Friend.vue";

// 1. 下载 v3.6.5
// 2. 引入
import VueRouter from "vue-router";
// 3. 安装注册 Vue.use(Vue插件)
Vue.use(VueRouter);
// 4. 创建路由对象
const router = new VueRouter({
	// B.配置路由规则
	routes: [
		{ path: "/find", component: Find },
		{ path: "/my", component: My },
		{ path: "/friend", component: Friend },
	],
});
// 导出 router
export default router;
  • main.js
import Vue from "vue";
import App from "./App.vue";
// 导入 router
import router from "./router";

Vue.config.productionTip = false;

new Vue({
	render: (h) => h(App),
	// 5. 注入到new Vue中,建立关联
	router,
}).$mount("#app");

3、声明式导航

1. 导航高亮

  • vue-router 提供了一个全局组件 router-link (取代 a 标签)
  • 能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
  • 能高亮,默认就会提供高亮类名,可以直接设置高亮样式
<template>
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/friend">朋友</router-link>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

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

<style lang="less">
.footer_wrap a.router-link-active {
  background-color: purple;
}
</style>

2. 精确匹配/模糊匹配

  • router-link-active 模糊匹配 (用的多):to=“/my” 可以匹配 /my、/my/a、/my/b …
  • router-link-exact-active 精确匹配:to=“/my” 仅可以匹配 /my

3. 自定义高亮类名

  • @/router/index.js
import Vue from "vue";
import Find from "@/views/Find.vue";
import My from "@/views/My.vue";
import Friend from "@/views/Friend.vue";

import VueRouter from "vue-router";
Vue.use(VueRouter);
const router = new VueRouter({
	routes: [
		{ path: "/find", component: Find },
		{ path: "/my", component: My },
		{ path: "/friend", component: Friend },
	],
	// 配置模糊匹配的类名
	linkActiveClass: "avtive",
	// 配置精确匹配的类名
	linkExactActiveClass: "exact-active",
});

export default router;
  • App.vue
<template>
  <div>
    <div class="footer_wrap">
      <router-link to="/find">发现音乐</router-link>
      <router-link to="/my">我的音乐</router-link>
      <router-link to="/friend">朋友</router-link>
    </div>
    <div class="top">
      <!-- 路由出口 → 匹配的组件所展示的位置 -->
      <router-view></router-view>
    </div>
  </div>
</template>

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

<style lang="less">
.footer_wrap a.avtive {
  background-color: purple;
}
</style>

4. 跳转传参

  • 查询参数传参
    • 比较适合传多个参数
    • 跳转:to="/path?参数名=值&参数名2=值"
    • 获取:$route.query.参数名
  • 动态路由传参
    • 优雅简洁,传单个参数比较方便
    • 配置动态路由:path: "/path/参数名"
    • 跳转:to="/path/参数值"
    • 获取:$route.params.参数名

5. 查询参数传参

  • 匹配路由规则
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
	routes: [
		{ path: "/home", component: Home },
		{ path: "/search", component: Search },
	],
});
  • 语法格式如下:to="/path?参数名=值"
<router-link to="/search?key=erer">erer</router-link>
  • 对应页面组件接收传递过来的值:$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>
<script>
export default {
  created () {
    console.log(this.$route.query.key);
  }
}
</script>

6. 动态路由传参

  • 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
	routes: [
		{ path: "/home", component: Home },
		{ path: "/search/:words", component: Search },
	],
});
  • 配置导航链接:to="/path/参数值"
<router-link to="/search/erer">erer</router-link>
  • 对应页面组件接收传递过来的值:$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>
<script>
export default {
  created () {
    console.log(this.$route.params.words);
  }
}
</script>

7. 动态路由参数可选符

  • 问题:配了路由 path: "/search/:words" 当访问路径是 http://localhost:8080/#/search/ 会显示空白
  • 原因: /search/:words 表示,必须要传参数。
  • 解决:如果不传参数,也希望匹配,可以加个可选符 “?” /search/:words?
import Search from "@/views/Search";
const router = new VueRouter({
	routes: [
		{ path: "/search/:words?", component: Search },
	],
});

4、路由进阶

1. 路由重定向

  • 问题:网页打开, url 默认是 / 路径,未匹配到组件时,会出现空白
  • 说明:重定向 → 匹配path后, 强制跳转path路径
  • 语法:{ path: 匹配路径, redirect: 重定向到的路径 }
const router = new VueRouter({
	routes: [
		{ path: '/', redirect: '/home'},
	],
});

2. 404

  • 作用:当路径找不到匹配时,给个提示页面 “@/views/NotFound”
<template>
  <div>
    <h1>404 Not Found</h1>
  </div>
</template>

<script>
export default {
}
</script>

<style>
</style>
  • 位置:配在路由最后
  • 语法:path: "*" (任意路径) – 前面不匹配就命中最后这个
import NotFound from "@/views/NotFound"
const router = new VueRouter({
	routes: [
		{ path: '/', redirect: '/home'},
		{ path: "/home", component: Home },
		{ path: "/search/:words?", component: Search },

		{ path: '*', component: NotFound }
	],
});

3. 模式设置

  • hash路由(默认)例如: http://localhost:8080/#/home
  • history路由(常用)例如: http://localhost:8080/home (以后上线需要服务器端支持)
const router = new VueRouter({
	mode: "history",
	routes: [
		{ path: "/", redirect: "/home" },
		{ path: "/home", component: Home },
		{ path: "/search/:words?", component: Search },

		{ path: "*", component: NotFound },
	],
});

5、编程式导航

1. 通过路径跳转

  • path 路径跳转 (简易方便):this.$router.push({path: '路由路径' })
this.$router.push('/search')

this.$router.push({
	path: '/search' 
})

2. 通过路由名字跳转

  • name 命名路由跳转 (适合 path 路径长的场景):this.$router.push({ name: '路由名'})
const router = new VueRouter({
	mode: "history",
	routes: [
		{ name: "/search", path: "/search/:words?", component: Search },
	],
});
this.$router.push({ 
  name: '/search'
})

3. 路径跳转query传参

  • 传参:this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input v-model="inputValue" type="text">
      <button @click="goSearch">搜索一下</button>
    </div>
  </div>
</template>

<script>
export default {
  name: "FindMusic",
  data() {
    return {
      inputValue: "",
    }
  },
  methods: {
    goSearch() {
      // this.$router.push(`/search?key=${this.inputValue}`)
      this.$router.push({
        path: "/search",
        query: {
          key: this.inputValue,
        },
      });
    },
  },
};
</script>

<style>
</style>
  • 获取参数:$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>

4. 路径跳转动态路由传参

  • 传参:this.$router.push('/路径/参数值')
methods: {
  goSearch() {
    this.$router.push(`/search/${this.inputValue}`)
  },
},
  • 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
	routes: [
		{ path: "/search/:words?", component: Search },
	],
});
  • 获取参数:$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>

5. 路由名字跳转query传参

  • 传参:this.$router.push({ name: '路由名字', query: { 参数名1: '参数值1',参数名2: '参数值2' }})
methods: {
  goSearch() {
    this.$router.push({
      name: "/search",
      query: {
        key: this.inputValue,
      },
    });
  },
},
  • 获取参数:$route.query.参数名
<p>搜索关键字: {{ $route.query.key }} </p>

6. 路由名字跳转态路由传参

  • 传参:this.$router.push({ name: '路由名字', params: {参数名: '参数值', }})
methods: {
  goSearch() {
    this.$router.push({
      name: "/search",
      params: {
        words: this.inputValue,
      },
    });
  },
  • 配置动态路由
import Home from "@/views/Home";
import Search from "@/views/Search";
const router = new VueRouter({
	routes: [
		{ path: "/search/:words?", component: Search },
	],
});
  • 获取参数:$route.params.参数名
<p>搜索关键字: {{ $route.params.words }} </p>

6、组件缓存

  • 问题:从面经 点到 详情页,又点返回,数据重新加载了 → 希望回到原来的位置
  • 原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了
  • 解决:利用 keep-alive 将组件缓存下来

1. keep-alive

  • keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。
  • keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。

2. 优点

  • 在组件切换过程中 把切换出去的组件保留在内存中,防止重复渲染DOM,
  • 减少加载时间及性能消耗,提高用户体验性。

3. 属性

  • include : 组件名数组,只有匹配的组件会被缓存
  • exclude : 组件名数组,任何匹配的组件都不会被缓存
  • max : 最多可以缓存多少组件实例
<template>
  <div class="h5-wrapper">
    <!-- include="组件名" 组件名:组件name,没有配置name匹配文件名 -->
    <keep-alive include="keepArr">
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

<script>
export default {
  name: "h5-wrapper",
  data() {
    return {
      keepArr: ["LayoutPage"],
    };
  },
};
</script>

4. 生命周期函数

  • activated 当组件被激活(使用)的时候触发 → 进入这个页面的时候触发
  • deactivated 当组件不被使用的时候触发 → 离开这个页面的时候触发
  • 组件缓存后就不会执行组件的 created, mounted, destroyed 等钩子了
  • 所以其提供了actived 和 deactived钩子,帮我们实现业务需求。
activated () {
	console.log('actived 激活 → 进入页面'); 
},
deactivated() {
	console.log('deactived 失活 → 离开页面'); 
}

八、vuex

1、vuex概述

1. Vue介绍

  • vuex 是一个 vue 的 状态管理工具,状态就是数据。
  • 大白话:vuex 是一个插件,可以帮我们管理 vue 通用的数据 (多组件共享的数据)
    • 购物车数据
    • 个人信息数据

2. 场景

  • 某个状态 在 很多个组件 来使用 (个人信息)
  • 多个组件 共同维护 一份数据 (购物车)

3. 优势

  • 共同维护一份数据,数据集中化管理
  • 响应式变化
  • 操作简洁 (vuex提供了一些辅助函数)

2、构建 vuex 环境

  • 安装 vuex:npm install vuex@3 --legacy-peer-deps
  • 新建vuex 模块文件:新建 store/index.js 专门存放 vuex
  • 创建仓库:Vue.use(Vuex) 创建仓库 const store = new Vuex.Store()
  • main.js 导入挂载:在 main.js 中导入挂载到 Vue 实例上

3、state 状态

1. 提供数据

  • State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储。
  • 在 state 对象中可以添加我们要共享的数据。
// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'

// 插件安装
Vue.use(Vuex)

// 创建仓库
const store = new Vuex.Store({
  // state 状态, 即所有组件共享的数据, 类似于 vue 组件中的 data
  state: {
    title: '大标题',
    count: 100
  }
})

// 导出
export default store

2. 使用数据

  • 通过 store 直接访问:
    • 模板中: {{ $store.state.xxx }}
    • 组件逻辑中: this.$store.state.xxx
    • JS模块中: store.state.xxx
<template>
  <div id="app">
    <h1>
      根组件 <br>
      - {{ $store.state.title }} <br>
      - {{ $store.state.count }}
    </h1>
  </div>
</template>

<script>
export default {
  name: 'app',
  created () {
    console.log(this.$store.state.count)
  }
}
</script>

<style>
</style>

3. mapState

  • mapState是辅助函数,帮助我们把 store中的数据 自动 映射到 组件的计算属性中
    • 导入 数组方式 mapState:import { mapState } from 'vuex'
    • 数组方式 mapState 引入state:mapState(['count'])
    • 展开运算 符映射:computed: { }
<template>
  <div id="app">
    <h1>
      根组件 <br>
      - {{ title }} <br>
      - {{ count }}
    </h1>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'app',
  components: {
    Son1,
    Son2
  },
  computed: {
    ...mapState(['count', 'title'])
  },
}
</script>

<style>
</style>

4、mutations

1. 单向数据流

  • 目标: 明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据
<template>
  <div class="box">
    <h2>Son1 子组件</h2>
    从vuex中获取的值: <label>{{ count }}</label>
    <br>
    <button @click="handleAdd">值 + 1</button>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Son1Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    handleAdd () {
      // 错误代码
      // this.$store.state.count++
    }
  }
}
</script>

<style lang="css" scoped>
.box{
  border: 3px solid #ccc;
  width: 400px;
  padding: 10px;
  margin: 20px;
}
h2 {
  margin-top: 10px;
}
</style>
  • 通过 strict: true 可以开启严格模式
// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'

// 插件安装
Vue.use(Vuex)

// 创建仓库
const store = new Vuex.Store({
  strict: true,
  // state 状态, 即所有组件共享的数据, 类似于 vue 组件中的 data
  state: {
    title: '大标题',
    count: 100
  }
})

// 导出
export default store

2. 修改数据

  • 目标:掌握 mutations 的操作流程,来修改 state 数据。 (state数据的修改只能通过 mutations )
  • 定义 mutations 对象,对象中存放修改 state 的方法
const store = new Vuex.Store({
  strict: true,
  // 1. 通过 state 提供所有组件共享的数据
  state: {
    title: '大标题',
    count: 100
  },
  // 2. 通过 mutations 提供修改数据的方法
  mutations: {
    // 所有 mutations 函数的第一个参数都是 state
    addCount (state) {
      state.count += 1
    },
    addFive (state) {
      state.count += 5
    }
  }
})
  • 组件中提交调用 mutations:this.$store.commit('addCount')
<template>
  <div class="box">
    <h2>Son1 子组件</h2>
    从vuex中获取的值: <label>{{ count }}</label>
    <br>
    <button @click="handleAdd">值 + 1</button>
    <button @click="handleAddFive">值 + 5</button>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Son1Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    handleAdd () {
      // 错误代码
      // this.$store.state.count++
      this.$store.commit('addCount')
    },
    handleAddFive () {
      this.$store.commit('addFive')
    }
  }
}
</script>

<style lang="css" scoped>
</style>

3. mutations 传参

  • 目标:掌握 mutations 传参语法
  • 提供 mutation 函数 (带参数 - 提交载荷 payload ):只能传递一个参数,多个参数需要包装成一个对象
const store = new Vuex.Store({
  strict: true,
  // 1. 通过 state 提供所有组件共享的数据
  state: {
    title: '大标题',
    count: 100
  },
  // 2. 通过 mutations 提供修改数据的方法
  mutations: {
    // 所有 mutations 函数的第一个参数都是 state
    addCount (state, num) {
      state.count += num
    }
  }
})
  • 页面中提交调用 mutation可以传递参数的 this.$store.commit( 'xxx', 参数 )
<template>
  <div class="box">
    <h2>Son1 子组件</h2>
    从vuex中获取的值: <label>{{ count }}</label>
    <br>
    <button @click="handleAdd(1)">值 + 1</button>
    <button @click="handleAdd(5)">值 + 5</button>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Son1Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    handleAdd (n) {
      this.$store.commit('addCount', n)
    }
  }
}
</script>

<style lang="css" scoped>
.box{
  border: 3px solid #ccc;
  width: 400px;
  padding: 10px;
  margin: 20px;
}
h2 {
  margin-top: 10px;
}
</style>

4. mutations 双向绑定

  • 更新方法
const store = new Vuex.Store({
  state: {
    count: 100
  },
  mutations: {
    changeCount (state, newValue) {
      state.count = newValue
    }
  }
})
  • 双向绑定
<template>
  <div id="app">
    <h1>
      根组件 <br>
      - {{ title }} <br>
      - {{ count }}
    </h1>
    <input :value="count" @input="handleInput" type="text">
  </div>
</template>

<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
import { mapState } from 'vuex'

export default {
  name: 'app',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    handleInput (e) {
      const num = +e.target.value
      this.$store.commit('changeCount', num)
    }
  }
}
</script>

<style>
</style>

5. mapMutations

  • mapMutations 和 mapState很像,它是把位于mutations中的方法提取了出来,映射到组件methods中
<template>
  <div class="box">
    <h2>Son2 子组件</h2>
    从vuex中获取的值:<label>{{ count }}</label>
    <br />
    <button @click="subCount(1)">值 - 1</button>
    <button @click="subCount(5)">值 - 5</button>
  </div>
</template>

<script>
import { mapState, mapMutations } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    // handleSub (num) {
    //   this.$store.commit('subCount', num)
    // }
    ...mapMutations(['subCount'])
  }
}
</script>

<style lang="css" scoped>
</style>

5、actions

  • 目标:明确 actions 的基本语法,处理异步操作。
  • 需求: 一秒钟之后, 修改 state 的 count 成 666。
  • 说明:mutations 必须是同步的 (便于监测数据变化,记录调试)

1. 提供 action 方法

const store = new Vuex.Store({
  strict: true,
  // 1. 通过 state 提供所有组件共享的数据
  state: {
    count: 100
  },
  // 2. 通过 mutations 提供修改数据的方法
  mutations: {
    changeCount (state, newValue) {
      state.count = newValue
    }
  },
  // 3. action 处理异步
  actions: {
    // context 上下文,为分模块可以当场 store 仓库
    changeCountAction  (context, num) {
      setTimeout(() => {
        context.commit('changeCount', num)
      }, 1000)
    }
  }
})

2. dispatch 调用

<template>
  <div class="box">
    <h2>Son1 子组件</h2>
    从vuex中获取的值: <label>{{ count }}</label>
    <br>
    <button @click="handleChange">1秒后修改为666</button>
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Son1Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    handleChange () {
      this.$store.dispatch('changeCountAction', 666)
    }
  }
}
</script>

<style lang="css" scoped>
</style>

3. mapActions

  • mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中
<template>
  <div class="box">
    <h2>Son2 子组件</h2>
    从vuex中获取的值:<label>{{ count }}</label>
    <br />
    <button @click="subCount(1)">值 - 1</button>
    <button @click="subCount(5)">值 - 5</button>
    <button @click="changeCountAction(888)">1秒后修改为888</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapState(['count'])
  },
  methods: {
    // handleSub (num) {
    //   this.$store.commit('subCount', num)
    // }
    ...mapMutations(['subCount']),
    // handleChange () {
    //   this.$store.dispatch('changeCountAction', 666)
    // },
    ...mapActions(['changeCountAction'])
  }
}
</script>

<style lang="css" scoped>
</style>

6、getters

  • 目标:掌握核心概念 getters 的基本语法 (类似于计算属性)
  • 说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时会用到getters
  • 例如:state中定义了list,为 1-10 的数组,组件中,需要显示所有大于5的数据

1. 提供数据

const store = new Vuex.Store({
  state: {
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  },
  // 4. getters 类似于计算属性
  getters: {
    // 第一个参数都是 state,必须有返回值
    filterList (state) {
      return state.list.filter(item => item > 5)
    }
  }
})

2. 使用数据

<template>
  <div class="box">
    <div>{{ $store.getters.filterList }}</div>
  </div>
</template>

<script>

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

<style lang="css" scoped>
</style>

3. mapGetters

<template>
  <div class="box">
    <h2>Son2 子组件</h2>
    从vuex中获取的值:<label>{{ count }}</label>
    <br />
    <button @click="subCount(1)">值 - 1</button>
    <button @click="subCount(5)">值 - 5</button>
    <button @click="changeCountAction(888)">1秒后修改为888</button>
    <br>
    <div>{{ filterList }}</div>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    // mapState、mapGetters 映射属性
    ...mapState(['count']),
    ...mapGetters(['filterList'])
  },
  methods: {
    // mapMutations、mapActions 映射方法
    ...mapMutations(['subCount']),
    ...mapActions(['changeCountAction'])
  }
}
</script>

<style lang="css" scoped>
</style>

7、分模块

  • 目标:掌握核心概念 module 模块的创建
  • 由于 vuex 使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时, store 对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越难以维护)

1. 新建模块

  • user 模块:store/modules/user.js
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  },
  score: 88
}
const mutations = {}
const action = {}
const getters = {}

export default {
  state,
  mutations,
  action,
  getters
}
  • setting 模块:store/modules/setting.js
// setting 模块
const state = {
  theme: 'light',
  desc: 'test'
}
const mutations = {}
const action = {}
const getters = {}

export default {
  state,
  mutations,
  action,
  getters
}

2. 挂载模块

import Vue from 'vue'
import Vuex from 'vuex'
// 引入模块
import user from './moduels/user'
import setting from './moduels/setting'

// 插件安装
Vue.use(Vuex)

// 创建仓库
const store = new Vuex.Store({
  // 5. modules 模块
  modules: {
    user,
    setting
  }
})

// 导出
export default store

3. 访问 state

  • 目标:掌握模块中 state 的访问语法
  • 尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的 state 中,属性名就是模块名
  • 直接通过模块名访问 $store.state.模块名.xxx
<div class="box">
  <!-- 测试访问模块中的数据 -->
  <div>{{ $store.state.user.userInfo.name }}</div>	<!-- erer -->
  <div>{{ $store.state.setting }}</div>	<!-- "theme": "light", "desc": "test" -->
</div>
  • 通过 mapState 映射:默认根级别的映射 mapState([ 'xxx' ])
<template>
  <div class="box">
    <div>{{ user.userInfo.name }}</div>	<!-- erer -->
    <div>{{ setting }}</div>	<!-- { "theme": "light", "desc": "test" } -->
  </div>
</template>

<script>
import { mapState } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapState(['user', 'setting']),
  },
}
</script>

<style lang="css" scoped>
</style>
  • 通过 mapState 映射:子模块的映射 mapState('模块名', ['xxx']) - 需要开启命名空间
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  },
  score: 88
}
const mutations = {}
const action = {}
const getters = {}

export default {
  namespaced: true,
  state,
  mutations,
  action,
  getters
}
<template>
  <div class="box">
    <div>{{ userInfo }}</div>  <!-- { "name": "erer", "age": 18 } -->
    <div>{{ theme }}</div>  <!-- light -->
    <div>{{ desc }}</div>  <!-- test -->
  </div>
</template>

<script>
import { mapState} from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    // mapState、mapGetters 映射属性
    ...mapState('user', ['userInfo']),
    ...mapState('setting', ['theme', 'desc']),
  },
}
</script>

<style lang="css" scoped>
</style>

4. 访问 getters

  • 目标:掌握模块中 getters 的访问语法
  • 直接通过模块名访问 $store.getters['模块名/xxx ']
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  },
  score: 88
}
const mutations = {}
const action = {}
const getters = {
  upperCaseName (state) {
    return state.userInfo.name.toUpperCase()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  action,
  getters
}
<div class="box">
  <div>{{ $store.getters['user/upperCaseName'] }}</div> <!-- ERER -->
</div>
  • 通过 mapGetters 映射:默认根级别的映射 mapGetters([ 'xxx' ])
  • 通过 mapGetters 映射:子模块的映射 mapGetters('模块名', ['xxx']) - 需要开启命名空间
<template>
  <div class="box">
     <!-- 访问模块中的 getters -->
     <div>{{ upperCaseName }}</div>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapGetters('user', ['upperCaseName'])
  },
}
</script>

<style lang="css" scoped>
</style>

5. 访问 mutations

  • 目标:掌握模块中 mutation 的调用语法
  • 注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
  • 直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  },
  score: 88
}
const mutations = {
  updateUserInfo (state, newInfo) {
    state.userInfo = newInfo
  }
}
const action = {}
const getters = {}

export default {
  namespaced: true,
  state,
  mutations,
  action,
  getters
}
<template>
  <div class="box">
     <div>{{ $store.state.user.userInfo.name }}</div>
     <div>{{ $store.state.user.userInfo.age }}</div>     
     <!-- 访问模块中的 mutations -->
     <button @click="changeUserInfo()">更新个人信息</button>
  </div>
</template>

<script>

export default {
  name: 'Son1Com',
  methods: {
    // 访问模块中的 mutations
    changeUserInfo () {
      this.$store.commit('user/updateUserInfo', {
        name: '尔尔',
        age: 30
      })
    }
  }
}
</script>

<style lang="css" scoped>
</style>
  • 通过 mapMutations 映射:默认根级别的映射 mapMutations([ 'xxx' ])
  • 通过 mapMutations 映射:子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间
<template>
  <div class="box">
    <div>{{ userInfo.name }}</div>
    <div>{{ userInfo.age }}</div>
    <!-- 访问模块中的 mutations -->
    <button @click="updateUserInfo({ name: '尔尔',age: 28 })">更新个人信息</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapState('user', ['userInfo'])
  },
  methods: {
    ...mapMutations('user', ['updateUserInfo'])
  }
}
</script>

<style lang="css" scoped>
</style>

6. 访问 actions

  • 目标:掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可)
  • 注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模块。
  • 直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  },
  score: 88
}
const mutations = {
  updateUserInfo (state, newInfo) {
    state.userInfo = newInfo
  }
}
const actions = {
  updateUserInfoSecend (context, newInfo) {
    setTimeout(() => {
      context.commit('updateUserInfo', newInfo)
    }, 1000)
  }
}
const getters = {}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}
<template>
  <div class="box">
    <!-- 访问模块中的 actions -->
    <div>{{ $store.state.user.userInfo.name }}</div>
    <div>{{ $store.state.user.userInfo.age }}</div>
    <button @click="changeUserInfoSecond()">1s后更新个人信息</button>
  </div>
</template>

<script>
export default {
  name: 'Son1Com',
  methods: {
    // 访问模块中的 actions
    changeUserInfoSecond () {
      this.$store.dispatch('user/updateUserInfoSecend', {
        name: '尔尔',
        age: 30
      })
    }
  }
}
</script>

<style lang="css" scoped>
</style>
  • 通过 mapActions 映射:默认根级别的映射 mapActions([ 'xxx' ])
  • 通过 mapActions 映射:mapActions('模块名', ['xxx']) - 需要开启命名空间
<template>
  <div class="box">
    <!-- 访问模块中的 actions -->
    <div>{{ userInfo.name }}</div>
    <div>{{ userInfo.age }}</div>
    <button @click="updateUserInfoSecend( { name: '尔尔',age: 28 } )">1s后更新个人信息</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    ...mapState('user', ['userInfo']),
  },
  methods: {
    ...mapActions('user', ['updateUserInfoSecend'])
  }
}
</script>

<style lang="css" scoped>
</style>

8、案例

1. 子模块

  • user 模块:store/modules/user.js
// user 模块
const state = {
  userInfo: {
    name: 'erer',
    age: 18
  }
}
const mutations = {
  updateUserInfo (state, newInfo) {
    state.userInfo = newInfo
  }
}
const actions = {
  updateUserInfoSecend (context, newInfo) {
    setTimeout(() => {
      context.commit('updateUserInfo', newInfo)
    }, 1000)
  }
}
const getters = {
  upperCaseName (state) {
    return state.userInfo.name.toUpperCase()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}

2. 创建仓库

// 存放 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
import user from './moduels/user'

// 插件安装
Vue.use(Vuex)

// 创建仓库
const store = new Vuex.Store({
  strict: true,
  // 1. 通过 state 提供所有组件共享的数据
  state: {
    title: '大标题',
    count: 100,
    list: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
  },
  // 2. 通过 mutations 提供修改数据的方法
  mutations: {
    // 所有 mutations 函数的第一个参数都是 state
    addCount (state, num) {
      state.count += num
    },
    subCount (state, num) {
      state.count -= num
    },
    changeCount (state, newValue) {
      state.count = newValue
    }
  },
  // 3. actions 处理异步
  actions: {
    // context 上下文,为分模块可以当场 store 仓库
    changeCountAction  (context, num) {
      setTimeout(() => {
        context.commit('changeCount', num)
      }, 1000)
    }
  },
  // 4. getters 类似于计算属性
  getters: {
    // 第一个参数都是 state,必须有返回值
    filterList (state) {
      return state.list.filter(item => item > 5)
    }
  },
  // 5. modules 模块
  modules: {
    user
  }
})

// 导出
export default store

3. 导入挂载

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from '@/store/index'

Vue.config.productionTip = false

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

4. 直接访问

<template>
  <div class="box">
    <h2>访问根仓库中的信息</h2>
    <!-- 访问 state -->
    <div>{{ $store.state.count }}</div> <!-- 100 -->
    <div>{{ $store.state.list }}</div>  <!-- [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] -->
    <hr>

    <!-- 访问 getters -->
    <div>{{ $store.getters.filterList }}</div>  <!-- [ 6, 7, 8, 9, 10 ] -->
    <hr>

    <!-- 访问 mutations -->
    <button @click="handleAdd(1)">值 + 1</button>
    <button @click="handleAdd(5)">值 + 5</button>
    <hr>

    <!-- 访问 actions -->
    <button @click="handleChange">1秒后修改为666</button>
    <hr>

    <h2>访问模块中的信息</h2>
    <!-- 访问模块中的 state -->
    <div>{{ $store.state.user.userInfo.name }}</div>  <!-- erer -->
    <div>{{ $store.state.user.userInfo }}</div>       <!-- { "name": "erer", "age": 18 } -->
    <hr>

    <!-- 访问模块中的 getters -->
    <div>{{ $store.getters['user/upperCaseName'] }}</div> <!-- ERER -->
    <hr>

    <div>{{ $store.state.user.userInfo.name }}</div>  <!-- erer -->
    <div>{{ $store.state.user.userInfo.age }}</div>   <!-- 18 -->
    <!-- 访问模块中的 mutations -->
    <button @click="changeUserInfo()">更新个人信息</button>
    <hr>

    <div>{{ $store.state.user.userInfo.name }}</div>  <!-- erer -->
    <div>{{ $store.state.user.userInfo.age }}</div>   <!-- 18 -->
    <!-- 访问模块中的 actions -->
    <button @click="changeUserInfoSecond()">1s后更新个人信息</button>
  </div>
</template>

<script>
export default {
  name: 'Son1Com',
  methods: {
    handleAdd (n) {
      // 访问 mutations
      this.$store.commit('addCount', n)
    },
    handleChange () {
      // 访问 actions
      this.$store.dispatch('changeCountAction', 666)
    },
    changeUserInfo () {
      // 访问模块中的 mutations
      this.$store.commit('user/updateUserInfo', {
        name: '尔尔',
        age: 30
      })
    },
    changeUserInfoSecond () {
      // 访问模块中的 actions
      this.$store.dispatch('user/updateUserInfoSecend', {
        name: '尔尔',
        age: 30
      })
    }
  }
}
</script>

<style lang="css" scoped>
</style>

5. 子模块映射

<template>
  <div class="box">

    <h2>访问根仓库中的信息</h2>
    <!-- 访问 state -->
    <div>{{ count }}</div>  <!-- 100 -->
    <div>{{ list }}</div>   <!-- [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ] -->
    <hr>

    <!-- 访问 getters -->
    <div>{{ filterList }}</div> <!-- [ 6, 7, 8, 9, 10 ] -->
    <hr>

    <!-- 访问 mutations -->
    <button @click="subCount(1)">值 - 1</button>
    <button @click="subCount(5)">值 - 5</button>
    <hr>

    <!-- 访问 actions -->
    <button @click="changeCountAction(888)">1秒后修改为888</button>
    <hr>

    <h2>访问模块中的信息</h2>
    <!-- 访问模块中的 state -->
    <div>{{ user.userInfo.name }}</div> <!-- erer -->
    <div>{{ userInfo }}</div>           <!-- { "name": "erer", "age": 18 } -->
    <hr>

    <!-- 访问模块中的 getters -->
    <div>{{ upperCaseName }}</div>    <!-- ERER -->
    <hr>

    <div>{{ userInfo.name }}</div>    <!-- erer -->
    <div>{{ userInfo.age }}</div>     <!-- 18 -->
    <!-- 访问模块中的 mutations -->
    <button @click="updateUserInfo({ name: '尔尔',age: 28 })">更新个人信息</button>
    <hr>

    <div>{{ userInfo.name }}</div>    <!-- erer -->
    <div>{{ userInfo.age }}</div>     <!-- 18 -->
    <!-- 访问模块中的 actions -->
    <button @click="updateUserInfoSecend({ name: '尔尔',age: 28 })">1s后更新个人信息</button>
  </div>
</template>

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'

export default {
  name: 'Son2Com',
  computed: {
    // mapState、mapGetters 映射属性
    ...mapState(['count', 'list']),
    ...mapGetters(['filterList']),
    // 模块中的映射属性:mapState、mapGetters
    ...mapState(['user']),
    ...mapState('user', ['userInfo']),
    ...mapState('setting', ['theme', 'desc']),
    ...mapGetters('user', ['upperCaseName'])
  },
  methods: {
    // mapMutations、mapActions 映射方法
    ...mapMutations(['subCount']),
    ...mapActions(['changeCountAction']),
    // 模块中的映射方法:mapMutations、mapActions
    ...mapMutations('user', ['updateUserInfo']),
    ...mapActions('user', ['updateUserInfoSecend'])
  }
}
</script>

<style lang="css" scoped>
</style>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值