Bootstrap

VUE复习

VUE2

vue快速上手

vue概念

  • Vue是一套**构建用户界面 ** 的 渐进式 框架,主要用于构建用户界面和前端交互

举个例子

想象一下,你正在搭建一个房子,Vue 就像是一套工具箱,里面包含了各种工具,比如锤子、锯子、螺丝刀等。这些工具可以让你更快、更轻松地完成房子的建造。

创建vue实例

1)准备容器

2)引包 (官网) - 开发版本 / 生产版本

3)创建 Vue 实例 new Vue()

4)指定配置项 el data => 渲染数据

el 指定挂载点,选择器指定控制的是哪个盒子
data 提供数据

<body>
<!-- 
  创建Vue实例,初始化渲染
  1. 准备容器 (Vue所管理的范围)
  2. 引包 (开发版本包 / 生产版本包) 官网
  3. 创建实例
  4. 添加配置项 => 完成渲染
-->
<!-- 不是Vue管理的范围 -->
<div class="box2">
  box2 -- {{ count }}
</div>
<div class="box">
  box -- {{ msg }}
</div>
-----------------------------------------------------
<!-- Vue所管理的范围 -->
<div id="app">
  <!-- 这里将来会编写一些用于渲染的代码逻辑 -->
  <h1>{{ msg }}</h1>
  <a href="#">{{ count }}</a>
</div>

<!-- 引入的是开发版本包 - 包含完整的注释和警告 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>
  // 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
  const app = new Vue({
    // 通过 el 配置选择器,指定 Vue 管理的是哪个盒子
    el: '#app',
    // 通过 data 提供数据
    data: {
      msg: 'Hello 传智播客',
      count: 666
    }
  })
</script>
</body>

插值表达式

  • 我们可以用插值表达式渲染出Vue提供的数据
  • 利用表达式进行插值,渲染到页面中
  • 语法格式:{{ 表达式 }}
<body>
<!-- 
  插值表达式:Vue的一种模板语法
  作用:利用 表达式 进行插值渲染
  语法:{{ 表达式 }}

  注意点:
    1. 使用的数据要存在
    2. 支持的是表达式,不是语句  if  for
    3. 不能在标签属性中使用 {{ }}
 -->
<div id="app">
  <p>{{ nickname }}</p>
  <p>{{ nickname.toUpperCase() }}</p>
  <p>{{ nickname + '你好' }}</p>
  <p>{{ age >= 18 ? '成年' : '未成年' }}</p>
  <p>{{ friend.name }}</p>
  <p>{{ friend.desc }}</p>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script>

  const app = new Vue({
    el: '#app',
    data: {
      nickname: 'tony',
      age: 18,
      friend: {
        name: 'jepson',
        desc: '热爱学习 Vue'
      }
    }
  })
</script>
</body>

在这里插入图片描述

  • 错误用法:
 1.在插值表达式中使用的数据 必须在data中进行了提供
  <p>{{ hobby }}</p>//如果在data中不存在 则会报错
  2.支持的是表达式,而非语句,比如:if   for ...
  <p>{{ if }}</p>
  3.不能在标签属性中使用 {{  }} 插值 (插值表达式只能标签中间使用)
  <p title="{{ nickname }}">我是p标签</p> 

响应式特性

数据改变,视图会自动更新→使用Vue开发专注于业务核心逻辑

在这里插入图片描述

<body>
<div id="app">
  {{ msg }}
  {{ count }}
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      // 响应式数据 → 数据变化了,视图自动更新
      msg: '你好,黑马',
      count: 100
    }
  })
  // data中的数据,是会被添加到实例上
  // 1. 访问数据  实例.属性名
  // 2. 修改数据  实例.属性名 = 新值
</script>
</body>

开发者工具

安装配置

在这里插入图片描述

vue指令

常用指令

指令(Directives)是 Vue 提供的带有 v- 前缀 的 特殊 标签属性

内容渲染指令

用来辅助开发者渲染 DOM 元素的文本内容

1)v-text(类似innerText)

语法:<p v-text="uname">hello</p>,将 uame 值渲染到 p 标签中

2)v-html(类似 innerHTML)

语法:<p v-html="intro">hello</p>,将 intro 值渲染到 p 标签中

 <div id="app">
    <h2>个人信息</h2>
	// 既然指令是vue提供的特殊的html属性,所以咱们写的时候就当成属性来用即可
    <p v-text="uname">姓名:</p> 
    <p v-html="intro">简介:</p>
  </div> 

<script>
        const app = new Vue({
            el:'#app',
            data:{
                uname:'张三',
                intro:'<h2>这是一个<strong>非常优秀</strong>的boy<h2>'
            }
        })
</script>
条件渲染指令

用来辅助开发者按需控制 DOM 的显示与隐藏

1)v-show

语法: v-show = “表达式” 表达式值为 true 显示, false 隐藏

原理:切换 display:none 控制显示隐藏

场景:频繁切换显示隐藏的场景

2)v-if

语法: v-if= “表达式” 表达式值 true显示, false 隐藏

原理:基于条件判断,是否创建 或 移除元素节点

场景:不频繁切换的场景

<body>
  <!-- 
    v-show底层原理:切换 css 的 display: none 来控制显示隐藏
    v-if  底层原理:根据 判断条件 控制元素的 创建 和 移除(条件渲染)
  -->
  <div id="app">
    <div v-show="flag" class="box">我是v-show控制的盒子</div>
    <div v-if="flag" class="box">我是v-if控制的盒子</div>
  </div>
  
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        flag: false
      }
    })
  </script>
</body>

3)v-else、v-else-if

<body>
  <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>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        gender: 2,
        score: 95
      }
    })
  </script>
</body>
事件绑定指令

为DOM注册事件时使用

注册事件=添加监听+提供处理逻辑

语法:v-on:事件名 = “处理函数”

简写:@事件名

<body>
  <div id="app">
    <button @click="fn">切换显示隐藏</button>
    <h1 v-show="isShow">黑马程序员</h1>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app4 = new Vue({
      el: '#app',
      data: {
        isShow: true
      },
      methods: {
        fn () {
          // 让提供的所有methods中的函数,this都指向当前实例
          // console.log('执行了fn', app.isShow)
          // console.log(app3 === this)
          this.isShow = !this.isShow
        }
      }
    })
  </script>
</body>
属性绑定指令

动态设置html的标签属性 比如:src、url、title

语法:**v-bind:**属性名=“表达式”

简写: :属性名=“表达式”

<img v-bind:src="url" /> = <img :src="url" />v-bind可以省略

<body>
  <div id="app">
    <!-- v-bind:src   =>   :src -->
    <img v-bind:src="imgUrl" v-bind:title="msg" alt="">
    <img :src="imgUrl" :title="msg" alt="">
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        imgUrl: './imgs/10-02.png',
        msg: 'hello'
      }
    })
  </script>
</body>
列表渲染指令

使用v-for列表渲染指令,辅助开发者基于一个数组来循环渲染一个列表结构

语法:

v-for = “(item, index) in arr” :key=“唯一值”

item→数组中的每一项, index →每一项的索引

key=“唯一值”,给列表项添加的唯一标识

<div id="app">
    <h3>小黑的书架</h3>
    <ul>
      <li v-for="(item,index) in booksList":key="item.id">
         <span>{{item.name}}</span>
        <span>{{item.author}}</span>
        <button @click="del(item.id)">删除</button>
      </li>
    </ul>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        booksList: [
          { id: 1, name: '《红楼梦》', author: '曹雪芹' },
          { id: 2, name: '《西游记》', author: '吴承恩' },
          { id: 3, name: '《水浒传》', author: '施耐庵' },
          { id: 4, name: '《三国演义》', author: '罗贯中' }
        ]
      }methods: {
        del(id) {
          // console.log('删除', id);
          // filter:根据条件,保留满足条件的对应项,得到一个新数组
          // 不会改变原数组,指挥将收集的放在一个新的数组
          // this.booksList.filter(item => item.id !== id)
          this.booksList = this.booksList.filter(item => item.id !== id)
        }
      }
    })
  </script>
双向绑定指令V-model

即数据改变视图自动更新;视图变化数据自动更新

作用:给表单元素(input、radio、select)使用,双向绑定数据

语法:v-model=“变量”

<body>
  <div id="app">
    <!-- 
      v-model 可以让数据和视图,形成双向数据绑定
      (1) 数据变化,视图自动更新
      (2) 视图变化,数据自动更新
      可以快速[获取]或[设置]表单元素的内容
     -->
    账户:<input type="text" v-model="username"> <br><br>
    密码:<input type="password" v-model="password"> <br><br>
    <button @click="login">登录</button>
    <button @click="reset">重置</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        password: ''
      },
      methods: {
        login () {
          console.log(this.username, this.password)
        },
        reset () {
          this.username = ''
          this.password = ''
        }
      }
    })
  </script>
</body>

补充指令

指令修饰符

通过“.”指明一些指令后缀 →不同的后缀封装了不同的处理操作 —> 简化代码

1)按键修饰符:@keyup.enter —>当点击enter键的时候才触发

<div id="app">
    <h3>@keyup.enter  →  监听键盘回车事件</h3>
    <input @keyup.enter="fn" v-model="username" type="text">
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: ''
      },
      methods: {
        fn (e) {
          // if (e.key === 'Enter') {
          //   console.log('键盘回车的时候触发', this.username)
          // }

          console.log('键盘回车的时候触发', this.username)
        }
      }
    })
  </script>

2)v-model修饰符:

v-model.trim —>去除首位空格

v-model.number —>转数字

3)事件修饰符:

@事件名.stop —> 阻止冒泡

@事件名.prevent —>阻止默认行为

@事件名.stop.prevent —>可以连用 即阻止事件冒泡也阻止默认行为

<div id="app">
    <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>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        age: '',
      },
      methods: {
        fatherFn () {
          alert('老父亲被点击了')
        },
        sonFn (e) {
          // e.stopPropagation()
          alert('儿子被点击了')
        }
      }
    })
  </script>
v-bind对样式增强的操作

1)操作class:针对 class 类名style 行内样式 进行控制

语法::class = “对象/数组”

对象:适用一个类名,来回切换

<div class="box" :class="{ 类名1: 布尔值, 类名2: 布尔值 }"></div>
<div class="box" :class='{red:true,big:true}'>dimple</div>

数组:适用批量添加或删除类

<div class="box" :class="[ 类名1, 类名2, 类名3 ]"></div>
 <div class="box" :class="['red','big']">dimple</div>
<div id="app">
    <div class="box" :class="{ red: true, big: true }">dimple</div>
    <div class="box" :class="['red', 'big']">dimple</div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>

2)操作style:

语法::style = “样式对象”

适用场景:某个具体属性的动态设置

<div id="app">
    <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'green' }"></div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
v-model应用于其他表单元素
输入框  input:text   ——> value
文本域  textarea	 ——> value
复选框  input:checkbox  ——> checked
单选框  input:radio   ——> checked
下拉菜单 select    ——> value
...
<div id="app">
    <h3>小黑学习网</h3>

    姓名:
      <input type="text" v-model="username"> 
      <br><br>

    是否单身:
      <input type="checkbox" v-model="isSingle"> 
      <br><br>

    <!-- 
      前置理解:
        1. name:  给单选框加上 name 属性 可以分组 → 同一组互相会互斥
        2. value: 给单选框加上 value 属性,用于提交给后台的数据
      结合 Vue 使用 → v-model
    -->
    性别: 
      <input v-model="gender" type="radio" name="gender" value="1">男
      <input v-model="gender" type="radio" name="gender" value="2">女
      <br><br>

    <!-- 
      前置理解:
        1. option 需要设置 value 值,提交给后台
        2. select 的 value 值,关联了选中的 option 的 value 值
      结合 Vue 使用 → v-model
    -->
    所在城市:
      <select v-model="cityId">
        <option value="101">北京</option>
        <option value="102">上海</option>
        <option value="103">成都</option>
        <option value="104">南京</option>
      </select>
      <br><br>

    自我描述:
      <textarea v-model="desc"></textarea> 

    <button>立即注册</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        isSingle: false,
        gender: "2",
        cityId: '102',
        desc: ""
      }
    })
  </script>

计算属性与侦听器

computed计算属性与methods计算属性

1)computed计算属性:封装了一段对于数据的处理,求得一个结果

2)methods计算属性:给Vue实例提供一个方法,调用以处理业务逻辑

<div id="app">
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <h3>小黑的礼物清单🛒<span>{{ totalCountFn() }}</span></h3>
    <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>礼物总数:{{ totalCountFn() }} 个</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // 现有的数据
        list: [
          { id: 1, name: '篮球', num: 3 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      methods: {
        totalCountFn () {
          console.log('methods方法执行了')
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      },
      computed: {
        // 计算属性:有缓存的,一旦计算出来结果,就会立刻缓存
        // 下一次读取 → 直接读缓存就行 → 性能特别高
        // totalCount () {
        //   console.log('计算属性执行了')
        //   let total = this.list.reduce((sum, item) => sum + item.num, 0)
        //   return total
        // }
      }
    })
  </script>

两者比较

计算属性具有缓存特性,再次使用时直接读取缓存

methods没有缓存特性,太麻烦了

3)计算属性完整写法

计算属性默认的简写→只能读取访问,无法修改

如果要修改→需使用完整写法

在这里插入图片描述

<div id="app">
    姓:<input type="text" v-model="firstName"> +
    名:<input type="text" v-model="lastName"> =
    <span>{{ fullName}}</span><br><br>
    <button @click="changeName">改名卡</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
 		firstName: '刘',
        lastName: '备'
      },
      methods: {
        changeName() {
          this.fullName = '吴青峰'
        }
      },
      computed: {
        // 简写→获取,没有配置设置的逻辑
        // fullName() {
        //   return this.firstName + this.lastName
        // }

        // 完整写法
        fullName: {
          // 1.当fullname计算数学被获取求值时,执行get(有缓存,先读缓存)
          // 会将返回值作为求值的结果
          get() {
            return this.firstName + this.lastName
          },
          // 2.当fullname计算数学,被修改赋值时,执行set
          // 修改的值,传给set方法的形参
          set(value) {
            // console.log(value);
            // console.log(value.slice(0, 1));
            // console.log(value.slice(1));

            /*  
            slice(start:开始位置的索引,end:结束位置的索引,但不包含该索引位置的元素)→截取
            2.splice(index,0,插入的项)→删除
            */
            this.firstName = value.slice(0, 1)
            this.lastName = value.slice(1)
          }
        }
      }
    })
  </script>

watch侦听器

监视数据变化,执行一些业务逻辑或异步操作

1)简单写法:简单类型数据直接监视

<div id="app">
      <!-- 条件选择框 -->
      <div class="query">
        <span>翻译成的语言:</span>
        <select>
          <option value="italy">意大利</option>
          <option value="english">英语</option>
          <option value="german">德语</option>
        </select>
      </div>

      <!-- 翻译框 -->
      <div class="box">
        <div class="input-wrap">
          <textarea v-model="obj.words"></textarea>
          <span><i>⌨️</i>文档翻译</span>
        </div>
        <div class="output-wrap">
          <div class="transbox">mela</div>
        </div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <script>
      // 接口地址:https://applet-base-api-t.itheima.net/api/translate
      // 请求方式:get
      // 请求参数:
      // (1)words:需要被翻译的文本(必传)
      // (2)lang: 需要被翻译成的语言(可选)默认值-意大利
      // -----------------------------------------------
      
      const app = new Vue({
        el: '#app',
        data: {
          // words: ''
          obj: {
            words: ''
          }
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
          // 该方法会在数据变化时调用执行
          // newValue新值, oldValue老值(一般不用)
          // words (newValue) {
          //   console.log('变化了', newValue)
          // }

          'obj.words' (newValue) {
            console.log('变化了', newValue)
          }
        }
      })
    </script>

2)完整写法:添加额外配置项

  1. deep:true 对复杂类型进行深度监听
  2. immdiate:true 初始化 立刻执行一次
watch: {// watch 完整写法
  对象: {
    deep: true, // 深度监视
    immdiate:true,//立即执行handler函数
    handler (newValue) {
      console.log(newValue)
    }
  }
}

生命周期

简介

一个Vue实例从 创建 到 销毁 的整个过程

四个阶段:① 创建 ② 挂载 ③ 更新 ④ 销毁

在这里插入图片描述

生命周期钩子

Vue生命周期过程中,会自动运行一些函数,被称为【生命周期钩子】→ 让开发者可以在【特定阶段】运行自己的代码

在这里插入图片描述

<div id="app">
    <h3>{{ title }}</h3>
    <div>
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        count: 100,
        title: '计数器'
      },
      // 1. 创建阶段(准备数据)
      beforeCreate () {
        console.log('beforeCreate 响应式数据准备好之前', this.count)
      },
      created () {
        console.log('created 响应式数据准备好之后', this.count)
        // this.数据名 = 请求回来的数据
        // 可以开始发送初始化渲染的请求了
      },

      // 2. 挂载阶段(渲染模板)
      beforeMount () {
        console.log('beforeMount 模板渲染之前', document.querySelector('h3').innerHTML)
      },
      mounted () {
        console.log('mounted 模板渲染之后', document.querySelector('h3').innerHTML)
        // 可以开始操作dom了
      },

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

      // 4. 卸载阶段
      beforeDestroy () {
        console.log('beforeDestroy, 卸载前')
        console.log('清除掉一些Vue以外的资源占用,定时器,延时器...')
      },
      destroyed () {
        console.log('destroyed,卸载后')
      }
    })
  </script>

工程化开发

开发Vue的两种方式

  • 核心包传统开发模式:基于html / css / js 文件,直接引入核心包,开发 Vue。

  • 工程化开发模式:基于构建工具(例如:webpack)的环境中开发Vue

  • 工程化配置优点:提高编码效率,比如使用JS新语法、Less/Sass、Typescript等通过webpack都可以编译成浏览器识别的ES3/ES5/CSS等

  • 问题:

1)webpack配置不简单

2)雷同的基础配置

3)缺乏统一的标准

  • 为了解决以上问题,所以我们需要一个工具,生成标准化的配置

脚手架Vue CLI

是Vue官方提供的一个全局命令工具,可以帮助我们快速创建一个开发Vue项目的标准化基础架子

使用步骤:

全局安装(只需安装一次即可) yarn global add @vue/cli 或者 npm i @vue/cli -g

查看vue/cli版本: vue --version

创建项目架子:vue create project-name(项目名不能使用中文)

启动项目:yarn serve 或者 npm run serve(命令不固定,找package.json)

项目目录介绍

在这里插入图片描述

重点关注:main.js 入口文件、App.vue App根组件 、index.html 模板文件

运行流程:

在这里插入图片描述

组件

组件化开发

一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为

优点:便于维护,利于复用 → 提升开发效率

组件分类:普通组件、根组件

如下图,可按照模块对组件进行划分

在这里插入图片描述

根组件APP.VUE

  • 整个应用最上层的组件,包裹所有普通小组件
    在这里插入图片描述

语法高亮插件
在这里插入图片描述

组件构成

1)template:结构 (有且只能一个根元素)

2)script: js逻辑

3)style: 样式 (可支持less,需要装包)

在这里插入图片描述

让组件支持less

(1) style标签,lang=“less” 开启less功能

(2) 装包: yarn add less less-loader -D 或者npm i less less-loader -D

<template>
    <!-- vue2中有且只能用一个根元素 -->
  <div class="app">
    <div class="box" @click="fn"></div>
  </div>
</template>

<script>
// 导出的是当前组件的配置项
// 里面可以提供data(特殊) methods computed watch生命周期
export default {
  created(){
    console.log('我是created');
  },
  methods:{
    fn(){
      alert('你好')
    }
  }
}
</script>
<style lang="less">
/* 让style支持less
1.给style加上lang="less"
2.按照依赖包,less less-loader
npm i less less-loader -D
*/
.app{
  width: 400px;
  height: 400px;
  background-color:pink;
  .box{
  width: 100px;
  height: 100px;
  background-color: blue;
}
}
</style>

普通组件注册-局部注册

只能在注册的组件内使用

步骤:

1)创建 .vue 文件 (三个组成部分)

2)在使用的组件内导入并注册

命名:大驼峰命名法, 如 HmHeader

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

export default {  // 局部注册
  components: {
   '组件名': 组件对象,
    HmHeader:HmHeaer,
    HmHeader
  }
}

如下图,APP.VUE由三个组件构成

在这里插入图片描述

HmHeader.vue

<template>
  <div class="hm-header">
    我是hm-header
  </div>
</template>

<script>
export default {

}
</script>

<style>
.hm-header {
  height: 100px;
  line-height: 100px;
  text-align: center;
  font-size: 30px;
  background-color: #8064a2;
  color: white;
}
</style>

HmMain.vue

<template>
  <div class="hm-main">
    我是hm-main
  </div>
</template>

<script>
export default {

}
</script>

<style>
.hm-main {
  height: 400px;
  line-height: 400px;
  text-align: center;
  font-size: 30px;
  background-color: #f79646;
  color: white;
  margin: 20px 0;
}
</style>

HmFooter.vue

<template>
  <div class="hm-footer">
    我是hm-footer
  </div>
</template>

<script>
export default {

}
</script>

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

App.vue

<template>
  <div class="app">
    <HmHeader></HmHeader>
    <HmMain></HmMain>
    <HmFooter></HmFooter>
  </div>
</template>

<script>
  import HmHeader from './components/HmHeader'
  import HmMain from './components/HmMain'
  import HmFooter from './components/HmFooter'
  
  export default {  // 局部注册
  components: {
   //'组件名': 组件对象,
   // HmHeader:HmHeaer,
    HmHeader,
    HmMain,
    HmFooter
  }
}
</script>

普通组件注册-全局注册

全局注册的组件,在项目的任何组件中都能使用

步骤:

1)创建.vue组件(三个组成部分)

2)main.js中进行全局注册

语法:Vue.component(‘组件名’, 组件对象)

// 文件核心作用:导入app.vue,基于app.vue创建结构渲染index.html
// 1.导入vue核心包
import vue from 'vue'
// 2.导入app.vue的跟组件
import App from './App.vue'

// 编写导入的代码,往代码的顶部编写(规范)
import HmButton from './components/HmButton'

Vue.config.productionTip = false

// 进行全局注册→在所有的组件范围内都能直接使用
// createApp(App).component(组件名,组件对象)
vue.component('HmButton', HmButton)

// 3.基于APP创建结构渲染index.html
new Vue({
  render: h => h(App),
}).$mount('#app')

举例,如需要给 HmHeader,HmMain, HmFooter三部分中展示一个通用按钮

HmButton.Vue

<template>
  <button class="hm-button">通用按钮</button>
</template>

<script>
export default {

}
</script>

<style>
.hm-button {
  height: 50px;
  line-height: 50px;
  padding: 0 20px;
  background-color: #3bae56;
  border-radius: 5px;
  color: white;
  border: none;
  vertical-align: middle;
  cursor: pointer;
}
</style>

组件的样式冲突解决

1.全局样式:默认组件中的样式会作用到全局,任何一个组件中都会受到此样式的影响

2.局部样式:给组件加上scoped 属性→让样式只作用于当前组件

<style scoped>
/* 
  1.style中的样式 默认是作用到全局的
  2.加上scoped可以让样式变成局部样式

  组件都应该有独立的样式,推荐加scoped(原理)
  -----------------------------------------------------
  scoped原理:
  1.给当前组件模板的所有元素,都会添加上一个自定义属性
  data-v-hash值
  data-v-5f6a9d56  用于区分开不通的组件
  2.css选择器后面,被自动处理,添加上了属性选择器
  div[data-v-5f6a9d56]
*/
div{
  border: 3px solid blue;
  margin: 30px;
}
</style>

scoped原理:

当前组件内标签都被添加data-v-hash值 的属性

css选择器都被添加 [data-v-hash值] 的属性选择器

在这里插入图片描述

data必须是一个函数

是为了保证每个组件实例,维护独立的一份数据对象

每次创建新的组件实例,都会新执行一次 data 函数,得到一个新对象

export default {
  // data必须是一个函数,
  data() {
    return {
      count: 999,
    }
  },
}

组件通信

实现组件与组件之间的数据传递

组件的数据是独立的,无法直接访问其他组件的数据→需采用组件通信

解决方法:父子关系、非父子关系

在这里插入图片描述

父子通信流程

在这里插入图片描述

1)父组件通过 props 将数据传递给子组件

  1. 给子组件以添加属性的方式传值
  2. 子组件内部通过props接收
  3. 模板中直接使用 props接收的值

父组件App.vue

<template>
  <div class="app" style="border: 3px solid #000; margin: 10px">
    我是APP组件
    <!-- 1.给组件标签,添加属性方式 赋值 -->
    <Son :title="myTitle"></Son>
  </div>
</template>
<script>
import Son from './components/Son.vue'
export default {
  name: 'App',
  data() {
    return {
      myTitle: '学前端',
    }
  },
  components: {
    Son,
  },
}
</script>
<style>
</style>

子组件Son.vue

<template>
  <div class="son" style="border:3px solid #000;margin:10px">
    <!-- 3.直接使用props的值 -->
    我是Son组件 {{title}}
  </div>
</template>
<script>
export default {
  name: 'Son-Child',
  // 2.通过props来接受
  props:['title']
}
</script>
<style>
</style>

2)子组件利用 $emit 通知父组件修改更新

$emit触发事件,给父组件发送消息通知

父组件监听$emit触发的事件

提供处理函数,在函数的性参中获取传过来的参数

子组件Son.vue

<template>
  <div class="son" style="border: 3px solid #000; margin: 10px">
    我是Son组件 {{ title }}
    <button @click="changeFn">修改title</button>
  </div>
</template>
<script>
export default {
  name: 'Son-Child',
  props: ['title'],
  methods: {
    changeFn() {
      //1. 通过this.$emit() 向父组件发送通知
      this.$emit('changTitle','今天也开心')
    },
  },
}
</script>
<style>
</style>

父组件App.vue

<template>
  <div class="app" style="border: 3px solid #000; margin: 10px">
    我是APP组件
    <!-- 2.父组件对子组件的消息进行监听 -->
    <Son :title="myTitle" @changTitle="handleChange"></Son>
  </div>
</template>
<script>
import Son from './components/Son.vue'
export default {
  name: 'App',
  data() {
    return {
      myTitle: '学前端,就来黑马程序员',
    }
  },
  components: {
    Son,
  },
  methods: {
    // 3.提供处理函数,提供逻辑
    handleChange(newTitle) {
      this.myTitle = newTitle
    },
  },
}
</script>
<style>
</style>

Props

Props是组件上注册的一些 自定义属性

作用:向子组件传递数据

特点:可以传递任意数量、任意类型的prop

props校验:为组件的 prop 指定验证要求,不符合要求,控制台就会有错误提示 → 帮助开发者,快速发现错误

props: {
  校验的属性名: {
    type: 类型,  // Number String Boolean ...
    required: true, // 是否必填
    default: 默认值, // 默认值
    validator (value) {
      // 自定义校验逻辑
      return 是否通过校验
    }
  }
},

props&data、单向数据流

区别:

data 的数据是自己的 → 随便改

prop 的数据是外部的 → 不能直接改,要遵循 单向数据流

单向数据流

父级props 的数据更新,会向下流动,影响子组件→这个数据流动是单向的

<template>
  <div class="base-count">
    <button @click="handleSub">-</button>
    <span>{{ count }}</span>
    <button @click="handleAdd">+</button>
  </div>
</template>
<script>
export default {
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
  //   return {
  //     count: 100,
  //   }
  // },
  // 2.外部传过来的数据 不能随便修改
  props: {
    count: {
      type: Number,
    },
  },
  methods: {
    handleSub() {
      this.$emit('changeCount', this.count - 1)
    },
    handleAdd() {
      this.$emit('changeCount', this.count + 1)
    },
  },
}
</script>
<style>
.base-count {
  margin: 20px;
}
</style>
非父子通信(拓展)
event bus事件总线

步骤

创建一个都能访问的事件总线 (空Vue实例)

import Vue from 'vue'
const Bus = new Vue()
export default Bus

A组件(接受方),监听Bus的 $on事件

created () {
  Bus.$on('sendMsg', (msg) => {
    this.msg = msg
  })
}

B组件(发送方),触发Bus的$emit事件

Bus.$emit('sendMsg', '这是一个消息')

在这里插入图片描述

代码示例:

EventBus.js

import Vue from 'vue'
const Bus  =  new Vue()
export default Bus

BaseA.vue(接收方)

<template>
  <div class="base-a">
    我是A组件(接受方)
    <p>{{msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

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

BaseB.vue(发送方)

<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>

BaseB.vue(接收方)

<template>
  <div class="base-c">
    我是C组件(接受方)
    <p>{{msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
  data() {
    return {
      msg: '',
    }
  },
  created() {
    Bus.$on('sendMsg', (msg) => {
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

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

在这里插入图片描述

1.父组件 provide提供数据

<template>
  <div class="app">
    我是APP组件
    <button @click="change">修改数据</button>
    <SonA></SonA>
    <SonB></SonB>
  </div>
</template>

<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
  provide() {
    return {
      // 简单类型 是非响应式的
      color: this.color,
      // 复杂类型 是响应式的
      userInfo: this.userInfo,
    }
  },
  data() {
    return {
      color: 'pink',
      userInfo: {
        name: 'zs',
        age: 18,
      },
    }
  },
  methods: {
    change() {
      this.color = 'red'
      this.userInfo.name = 'ls'
    },
  },
  components: {
    SonA,
    SonB,
  },
}
</script>

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

2.子/孙组件 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>

进阶语法

v-model

原理

其本质为语法糖,即为value属性 input事件 的合写

  • 数据变,视图跟着变 :value
  • 视图变,数据跟着变 @input
  • $event 用于在模板中,获取事件的形参
<template>
  <div id="app" >
    <input v-model="msg1" type="text">
    //不能直接写e,要通过$event→模板中获取事件
    <input :value="msg2" @input="msg2 = $event.target.value" type="text">
  </div>
</template>
v-model简化代码

v-model其实就是 :value和@input事件的简写

子组件:props通过value接收数据,事件触发 input

<select :value="value" @change="handleChange">...</select>
props: {
  value: String
},
methods: {
  handleChange (e) {
    this.$emit('input', e.target.value)
  }
}

父组件:v-model直接绑定数据

<BaseSelect v-model="selectId"></BaseSelect>

.sync修饰符

可以实现 子组件父组件数据双向绑定→子组件可以修改父组件传过来的props值

封装弹框类的基础组件时 → visible属性 true显示 false隐藏

本质:.sync修饰符 就是 :属性名@update:属性名 合写

父组件

//.sync写法
<BaseDialog :visible.sync="isShow" />
--------------------------------------
//完整写法
<BaseDialog 
  :visible="isShow" 
  @update:visible="isShow = $event" 
/>

子组件

props: {
  visible: Boolean
},

this.$emit('update:visible', false)

ref和$refs

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

在这里插入图片描述

语法:

1.目标组件 – 添加 ref 属性

<div ref="chartRef">我是渲染图表的容器</div>

2.通过 this.$refs.xxx, 获取目标组件,就可以调用组件对象里面的方法

(注意:之前只用document.querySelect(‘.box’) 获取的是整个页面中的盒子)

mounted () {
  console.log(this.$refs.chartRef)
}

Vue异步更新 & $nextTick

要求:编辑标题, 编辑框自动聚焦
在这里插入图片描述

语法:

1.点击编辑,显示编辑框

让编辑框,立刻获取焦点

this.isShowEdit = true  //显示输入框
this.$refs.inp.focus()  //获取焦点

$nextTick:等 DOM 更新后, 才会触发执行此方法里的函数体

语法: this.$nextTick(函数体)

注意:$nextTick 内的函数体 一定是箭头函数,这样才能让函数内部的this指向Vue实例

this.$nextTick(() => {
  this.$refs.inp.focus()
})

自定义指令

  • 内置指令:v-html、v-if、v-bind、v-on… ,可以直接使用
  • 自定义指令:v-focus、v-loading、v-lazy…

基本语法

1)全局注册

//在main.js中
// 1.全局注册指令
Vue.directive('focus', {
  // inserted会在指令所在的元素,被插入到页面中时触发
  inserted(el) {
    // el 就是指令所绑定的元素
    // console.log(el);
    el.focus()
  }
})

2)局部注册

//在Vue组件的配置项中
 directives:{
    // 指令名:指令配置项
    focus:{
      inserted(el){
        el.focus()
      }
    }
  }

3)使用:当页面加载时,让元素将获得焦点

<input v-focus ref="inp" type="text">

指令的值

语法

1.在绑定指令时,可以通过“等号”的形式为指令 绑定 具体的参数值

<div v-color="color">我是内容</div>

2.通过 binding.value 可以拿到指令值,指令值修改会 触发 update 函数

directives: {
  color: {
    inserted (el, binding) {
      el.style.color = binding.value
    },
    update (el, binding) {
      el.style.color = binding.value
    }
  }
}

插槽

默认插槽

让组件内部的一些 结构 支持 自定义

组件内容部分,不希望写死,希望使用时候自定义

在这里插入图片描述

基本语法:

  1. 组件内需要定制的结构部分,改用slot占位
  2. 使用组件时, MyDialog标签内部, 传入结构替换slot
  3. 给插槽传入内容时,可以传入纯文本、html标签、组件
    在这里插入图片描述

MyDialog.vue

<template>
  <div class="dialog">
    <div class="dialog-header">
      <h3>友情提示</h3>
      <span class="close">✖️</span>
    </div>

    <div class="dialog-content">
      <!-- 1.在需要定制的位置使用slot占位 -->
    <slot></slot>
    </div>
    <div class="dialog-footer">
      <button>取消</button>
      <button>确认</button>
    </div>
  </div>
</template>

App.vue

<template>
  <div>
     <!-- 2.在使用组件时,传入内容 -->
    <MyDialog>你确认要删除吗</MyDialog>
    <MyDialog>你确认要退出吗</MyDialog>
  </div>
</template>
<script>
import MyDialog from './components/MyDialog.vue'
export default {
  data () {
    return {

    }
  },
  components: {
    MyDialog
  }
}
</script>
<style>
body {
  background-color: #b3b3b3;
}
</style>

具名插槽

一个组件有多处结构需要外界传入标签,但默认插槽只能定制一个位置,这时可采用具名插槽
在这里插入图片描述

语法:

多个slot使用name属性区分名字

<template>
  <div class="dialog">
    <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>
  </div>
</template>

template配合v-slot:名字来分发对应标签

<template>
  <div>
    <MyDialog>
      <!-- 需要通过template标签包裹需要分发的结构,包成一个整体 -->
      <template v-slot:head>
        <div>我是大标题</div>
      </template>
      
      <template v-slot:content>
        <div>我是内容</div>
      </template>

      <template #footer>
        <button>取消</button>
        <button>确认</button>
      </template>
    </MyDialog>
  </div>
</template>

作用域插槽

给插槽可以绑定数据,进行传值→可用来封装表格组件

使用步骤

  1. 给 slot 标签, 以 添加属性的方式传值

    <slot :id="item.id" msg="测试文本"></slot>
    
  2. 所有添加的属性, 都会被收集到一个对象中

    { id: 3, msg: '测试文本' }
    
  3. 在template中, 通过 #插槽名= "obj" 接收,默认插槽名为 default

    <MyTable :list="list">
      <template #default="obj">
        <button @click="del(obj.id)">删除</button>
      </template>
    </MyTable>
    

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>
          <!-- 1. 给slot标签,添加属性的方式传值 -->
          <slot :row="item" msg="测试文本"></slot>

          <!-- 2. 将所有的属性,添加到一个对象中 -->
          <!-- 
             {
               row: { id: 2, name: '孙大明', age: 19 },
               msg: '测试文本'
             }
           -->
        </td>
      </tr>
    </tbody>
  </table>
</template>

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

<style scoped>

</style>

App.vue

<template>
  <div>
    <MyTable :data="list">
      <!-- 3. 通过template #插槽名="变量名" 接收 -->
      <template #default="obj">
        <button @click="del(obj.row.id)">
          删除
        </button>
      </template>
    </MyTable>
    
    <MyTable :data="list2">
      <template #default="{ row }">
        <button @click="show(row)">查看</button>
      </template>
    </MyTable>
  </div>
</template>
<script>
import MyTable from './components/MyTable.vue'
export default {
  data () {
    return {
      list: [
        { 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: {
    del (id) {
      this.list = this.list.filter(item => item.id !== id)
    },
    show (row) {
      // console.log(row);
      alert(`姓名:${row.name}; 年纪:${row.age}`)
    }
  },
  components: {
    MyTable
  }
}
</script>

路由

路由入门

基本使用

Vue中路由:路径组件映射关系

根据路由就能知道不同路径,应该匹配渲染哪个组件
在这里插入图片描述

VueRouter的使用(5+2)

5个基础步骤

  1. 下载 VueRouter 模块到当前工程,版本3.6.5

    yarn add [email protected]
    
  2. main.js中引入VueRouter

    import VueRouter from 'vue-router'
    
  3. 安装注册

    Vue.use(VueRouter)
    

    创建路由对象

    const router = new VueRouter()
    

    注入,将路由对象注入到new Vue实例中,建立关联

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

上述5步完成之后,便可看到浏览器路由地址变为/#/的形式→项目路由已被Vue-Router管理

在这里插入图片描述

2个核心步骤

1.创建需要的组件(view目录),配置路由规则
在这里插入图片描述

2.配置导航,配置路由出口(路径匹配的组件显示的位置)

main.js

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

// 路由的使用步骤 5 + 2
// 5个基础步骤
// 1. 下载 v3.6.5
// 2. 引入
// 3. 安装注册 Vue.use(Vue插件)
// 4. 创建路由对象
// 5. 注入到new Vue中,建立关联

// 2个核心步骤
// 1. 建组件(views目录),配规则
// 2. 准备导航链接,配置路由出口(匹配的组件展示的位置) 
import Find from './views/Find'
import My from './views/My'
import Friend from './views/Friend'
import VueRouter from 'vue-router'
Vue.use(VueRouter) // VueRouter插件初始化

const router = new VueRouter({
  // routes 路由规则们
  // route  一条路由规则 { path: 路径, component: 组件 }
  routes: [
    { path: '/find', component: Find },
    { path: '/my', component: My },
    { path: '/friend', component: Friend },
  ]
})

Vue.config.productionTip = false

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

App.vue

<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>
组件存放目录

组件分类(分类存放,更易维护):

页面组件(src/views文件夹)-页面展示-配合路由使用

复用组件(src/components文件夹)-展示数据-封装复用

在这里插入图片描述

路由的封装抽离

所有的路由配置都在main.js中不太合→将路由模块抽离处理,拆分模块,利于维护
在这里插入图片描述

路由进阶

声明式导航

1)导航链接: router-link (取代 a 标签)

要实现导航高亮效果,使用a标签太麻烦,vue-router 提供了一个全局组件 router-link 来取代a

在这里插入图片描述

语法:

能跳转,配置 to 属性指定路径(必须) 。本质还是 a 标签 ,to 无需 #
能高亮,默认就会提供高亮类名,可以直接设置高亮样式

<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>

2)两个类名

使用router-link跳转后→当点击链接时,默认添加两个class值→ router-link-exact-activerouter-link-active

在这里插入图片描述

router-link-active:模糊匹配(用的多)→to=“/my” 可以匹配 /my /my/a /my/b …→只要是以/my开头的路径 都可以和 to="/my"匹配到

router-link-exact-active:精确匹配→to=“/my” 仅可以匹配 /my

3)自定义类名

router-link的两个高亮类名 太长了,可以自己定制

在创建路由对象时,额外配置两个配置项, linkActiveClasslinkExactActiveClass

// 创建了一个路由对象
const router = new VueRouter({
  routes: [
    ...
  ], 
  linkActiveClass: 'active', // 配置模糊匹配的类名
  linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})

在这里插入图片描述

4)跳转传参

两种传参方式

  1. 查询参数传参 (比较适合传多个参数)

语法格式如下

1.跳转:<router-link to="/path?参数名=值"></router-link>
2.获取:$router.query.参数名

2.动态路由传参 (优雅简洁,传单个参数比较方便)

1.配置动态路由:path: "/path/参数名"
2.跳转:to="/path/参数值"
3.获取:$route.params.参数名

在这里插入图片描述

路由重定向

问题:网页打开时,url默认是/路径,未匹配到组件时,会出现空白

在这里插入图片描述

解决方法:重定向→ 匹配 / 后, 强制跳转 /home 路径

// 创建了一个路由对象
const router = new VueRouter({
  routes: [
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/search/:words?', component: Search }
  ]
})
export default router
路由404

当路径找不到匹配时,应该给个提示页面→配在路由最后面

语法:path: “*” (任意路径) – 前面不匹配就命中最后这个

// 创建了一个路由对象
const router = new VueRouter({
  routes: [
    { path: '/', redirect: '/home' },
    { path: '/home', component: Home },
    { path: '/search/:words?', component: Search },
    { path: '*', component: NotFound }
  ]
})

export default router
路由模式

hash路由(默认) 例如: http://localhost:8080/#/home
lhistory路由(常用) 例如: http://localhost:8080/home (以后上线需要服务器端支持,开发环境webpack给规避掉了history模式的问题)

const router = new VueRouter({
  //一旦采用了history模式,地址栏就没有#了,需要后台配置访问规则
    mode:'histroy', //默认是hash
    routes:[]
})
编程式导航-两种路径跳转方式

编程式导航:用JS代码来进行跳转

  1. path 路径跳转(简易方便)
//简单写法
this.$router.push('路由路径')

//完整写法
this.$router.push({
  path: '路由路径'
})

2.name 命名路由跳转 (适合 path 路径长的场景)

路由规则,必须配置name配置项

// 2.通过命名路由的方式跳转,比较适合长路径
{ name: '路由名', path: '/path/xxx', component: XXX },

通过name来进行跳转

this.$router.push({
  name: '路由名'
})
编程式导航-两种路径跳转传参
  • 查询参数传参、动态路由传参
  • 两种跳转方式,对于两种传参方式都支持:
  • path 路径跳转传参; name 命名路由跳转传参

1)path路径跳转传参(query传参)

this.$router.push('/路径?参数名1=参数值1&参数2=参数值2')
this.$router.push({
  path: '/路径',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

2)path路径跳转传参(动态路由传参)

this.$router.push('/路径/参数值')
this.$router.push({
  path: '/路径/参数值'
})

3)name 命名路由跳转传参 (query传参)

this.$router.push({
  name: '路由名字',
  query: {
    参数名1: '参数值1',
    参数名2: '参数值2'
  }
})

4)name 命名路由跳转传参 (动态路由传参)

this.$router.push({
  name: '路由名字',
  params: {
    参数名: '参数值',
  }
})

VueCli 自定义创建项目

1.安装脚手架 (已安装)

npm i @vue/cli -g

2.创建项目

vue create exp-mobile
  • 选项
Vue CLI v5.0.8
? Please pick a preset:
  Default ([Vue 3] babel, eslint)
  Default ([Vue 2] babel, eslint)
> Manually select features     选自定义
  • 手动选择功能
    在这里插入图片描述

  • 选择vue的版本

  3.x
> 2.x
  • 是否使用history模式

在这里插入图片描述

  • 选择css预处理

在这里插入图片描述

  • 选择eslint的风格 (eslint 代码规范的检验工具,检验代码是否符合规范)
  • 比如:const age = 18; => 报错!多加了分号!后面有工具,一保存,全部格式化成最规范的样子

在这里插入图片描述

  • 选择校验的时机 (直接回车)

在这里插入图片描述

  • 选择配置文件的生成方式 (直接回车)
    在这里插入图片描述

  • 是否保存预设,下次直接使用? => 不保存,输入 N

在这里插入图片描述

  • 等待安装,项目初始化完成

在这里插入图片描述

  • 启动项目
npm run serve

ESlint代码规范修复

  1. eslint会自动高亮错误显示
  2. 通过配置,eslint会自动帮助我们修复错误

在这里插入图片描述

配置

// 当保存的时候,eslint自动帮我们修复错误
"editor.codeActionsOnSave": {
    "source.fixAll": true
},
// 保存代码,不自动格式化
"editor.formatOnSave": false

注意:eslint配置文件必须在根目录下,插件才能才能生效。打开项目必须以根目录打开,一次打开一个项目

settings.json

{
    "window.zoomLevel": 2,
    "workbench.iconTheme": "vscode-icons",
    "editor.tabSize": 2,
    "emmet.triggerExpansionOnTab": true,
    // 当保存的时候,eslint自动帮我们修复错误
    "editor.codeActionsOnSave": {
        "source.fixAll": true
    },
    // 保存代码,不自动格式化
    "editor.formatOnSave": false
}

Vuex

官方文档:https://vuex.vuejs.org/zh/

Vuex是一个插件,可以帮我们管理 Vue 通用的数据 (多组件共享的数据)

共同维护一份数据,数据集中化管理

例如:购物车数据 个人信息数

在这里插入图片描述

基本使用

在这里插入图片描述

1.安装:vuex是一个独立存在的插件,如果脚手架初始化没有选 vuex,就需要额外安装

yarn add vuex@3 或者 npm i vuex@3

2.新建 store/index.js 专门存放 vuex

为保持项目目录整洁,在src目录下新建一个store目录放置index.js文件(类似 router/index.js
在这里插入图片描述

3.创建仓库 store/index.js

// 导入 vue
import Vue from 'vue'
// 导入 vuex
import Vuex from 'vuex'
// vuex也是vue的插件, 需要use一下, 进行插件的安装初始化
Vue.use(Vuex)

// 创建仓库 store
const store = new Vuex.Store()

// 导出仓库
export default store

4在 main.js 中导入挂载到 Vue 实例上

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

Vue.config.productionTip = false

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

此刻,即成功创建一个空仓库

5.测试打印vuex

App.vue

created(){
  console.log(this.$store)
}

在这里插入图片描述

核心概念(state、mutation、actions、getters)

在这里插入图片描述

state状态

State 提供唯一的公共数据源,所有共享的数据都要统一放到 Store 中的 State 中存储

1)创建Store数据源,提供唯一公共数据源

// 创建仓库 store
const store = new Vuex.Store({
  // state 状态, 即数据, 类似于vue组件中的data,
  // 区别:
  // 1.data 是组件自己的数据, 
  // 2.state 中的数据整个vue项目的组件都能访问到
  state: {
    title: '大标题',
    count: 101
  }
})

2)访问数据

通过 store 直接访问→{{ $store.state.count }}
获取 store:
 1.Vue模板中获取 this.$store
 2.js文件中获取 import 导入 store

模板中:     {{ $store.state.xxx }}
组件逻辑中:  this.$store.state.xxx
JS模块中:   store.state.xxx

在模板中使用

<h1>state的数据 - {{ $store.state.count }}</h1>

组件逻辑中使用

<h1>state的数据 - {{ count }}</h1>

// 把state中数据,定义在组件内的计算属性中
  computed: {
    count () {
      return this.$store.state.count
    }
  }

js文件中使用

//main.js

import store from "@/store"

console.log(store.state.count)
辅助函数- mapState

通过辅助函数mapState 映射计算属性→{{ count }}

将store中的数据映射到组件的计算属性中

在这里插入图片描述

使用步骤:

1导入mapState (mapState是vuex中的一个函数)

import { mapState } from 'vuex'

2采用数组形式引入state属性

mapState(['count']) 

3利用展开运算符将导出的状态映射给计算属性

 computed: {
    ...mapState(['count'])
  }
 <div> state的数据:{{ count }}</div>
mutations

用于同步更新数据 (便于监测数据的变化, 更新视图等, 方便于调试工具查看变化)

基础使用

1定义 mutations 对象,对象中存放修改 state 的方法

2组件中提交调用 mutations

const store  = new Vuex.Store({
  state: {
    count: 0
  },
  // 定义mutations
  mutations: {
    // 方法里参数 第一个参数是当前store的state属性
    // payload 载荷 运输参数 调用mutaiions的时候 可以传递参数 传递载荷
    addCount (state) {
      state.count += 1
    }
  }
})

3组件中提交 mutations

this.$store.commit('addCount')
mutations传递参数

1提供函数

mutations: {
    // 所有mutation函数,第一个参数都是state
    addCount (state, n) {
      // 修改数据
      state.count += n
    },
    changeTitle (state, newTitle) {
      state.title = newTitle
    }
  },

2提交mutation

     handleAdd (n) {
      // 严格模式(有利于初学者检查代码,上线时需要关闭)
      // 错误代码(vue默认不会检测,检测需要成本)
      // this.$store.state.count++
      // console.log(this.$store.state.count)
      // 应该通过mutation核心概念,进行修改数据
      // 需要提交调用mutation
      //
      // console.log(n)
      // 调用带参数的mutation函数
      this.$store.commit('addCount', n)
      },
    changeFn () {
      this.$store.commit('changeTitle', '黑马')
    }

多参数传参,可传递一个对象

this.$store.commit('addCount', {
  count: n,
  msg: '哈哈'
})
辅助函数mapMutations

它和mapState类似,把mutations中的方法提取出来

//1.从Vuex中按需导入 mapMutations 函数
import  { mapMutations } from 'vuex'
//2.将指定的mutations函数,映射为当前组件的 methods 方法
methods: {
    ...mapMutations(['addCount'])
}

此时,即可调用

<button @click="addCount">+1</button>
actions

用于异步更新

基础使用

1提供actions 方法

mutations: {
  changeCount (state, newCount) {
    state.count = newCount
  }
}


// 3.处理异步,不能直接操作state,还是需要commit mutation
  actions: {
    // context上下文(此处未分模块,可以当成store仓库)
    //   context.commit('changeCount', 额外参数)
    changeCountAction (context, num) {
      // 这里时settimeou模拟异步,以后大部分是发送请求
      setTimeout(() => {
        context.commit('changeCount', num)
      }, 1000)
    }
  },

2页面中 dispatch 调用

handleChange () {
      // 调用action
      this.$store.dispatch('changeCountAction', 666)
    }

在这里插入图片描述

辅助函数mapActions

mapActions 是把位于 actions中的方法提取了出来,映射到组件methods中

Son2.vue

import { mapActions } from 'vuex'
methods: {
   ...mapActions(['changeCountAction'])
}

//mapActions映射的代码 本质上是以下代码的写法
//methods: {
//  changeCountAction (n) {
//直接通过 this.方法 就可以调用
//    this.$store.dispatch('changeCountAction', n)
//  },
//}
getters

可以对Store中的数据进行加工处理形成新的数据,类似于Vue的计算属性

基础使用

1定义getters

getters: {
    // getters函数的第一个参数是 state
    // 必须要有返回值
     filterList:  state =>  state.list.filter(item => item > 5)
  }

2使用getters

<div>{{ $store.getters.filterList }}</div>
辅助函数mapGetters

1通过 store 访问 getters

computed: {
    ...mapGetters(['filterList'])
}

2通过辅助函数 mapGetters 映射

<div>{{ filterList }}</div>
模块module(进阶语法)

若使用单一状态数,应用的所有状态会集中到一个比较大的对象

因此有了Vuex的模块化

在这里插入图片描述

语法:

定义两个模块 usersetting

user中管理用户的信息状态 userInfo modules/user.js

const state = {
  userInfo: {
    name: 'zs',
    age: 18
  }
}

const mutations = {}

const actions = {}

const getters = {}

export default {
  state,
  mutations,
  actions,
  getters
}

setting中管理项目应用的 主题色 theme,描述 desc, modules/setting.js

const state = {
  theme: 'dark'
  desc: '描述真呀真不错'
}

const mutations = {}

const actions = {}

const getters = {}

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

store/index.js文件中的modules配置项中,注册这两个模块

import user from './modules/user'
import setting from './modules/setting'

const store = new Vuex.Store({
    modules:{
        user,
        setting
    }
})

使用模块中的数据, 可以直接通过模块名访问 $store.state.模块名.xxx

Vuex模块化的使用

1直接使用

  • state --> $store.state.模块名.数据项名
  • getters --> $store.getters[‘模块名/属性名’]
  • mutations --> $store**.commit**(‘模块名/方法名’, 其他参数)
  • actions --> $store.dispatch(‘模块名/方法名’, 其他参数)

2借助辅助方法使用

1)import { mapXxxx, mapXxx } from ‘vuex’

computed、methods: {

​ // …mapState、…mapGetters放computed中;

​ // …mapMutations、…mapActions放methods中;

​ …mapXxxx(‘模块名’, [‘数据项|方法’]),

​ …mapXxxx(‘模块名’, { 新的名字: 原来的名字 }),

}

2)组件中直接使用 属性 {{ age }} 或 方法 @click="updateAge(2)"

综合案例-购物车

创建项目

脚手架新建项目 (注意:勾选vuex)

vue create vue-cart-demo
构建vuex-cart模块

1.新建 store/modules/cart.js

export default {
  namespaced: true,
  state () {
    return {
      list: []
    }
  },
}

2.挂载到 vuex 仓库上 store/cart.js

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

import cart from './modules/cart'

Vue.use(Vuex)

const store = new Vuex.Store({
  modules: {
    cart
  }
})

export default store
准备后端接口服务环境
  1. 安装全局工具 json-server (全局工具仅需要安装一次)
yarn global add json-server 或 npm i json-server  -g

2.代码根目录新建一个 db 目录

3.将资料 index.json 移入 db 目录

4.进入 db 目录,执行命令,启动后端接口服务 (使用–watch 参数 可以实时监听 json 文件的修改)

json-server  --watch  index.json
请求动态渲染数据

请求获取数据存入 vuex, 映射渲染

在这里插入图片描述

  1. 安装 axios
yarn add axios

2.准备actions 和 mutations

mutations: {
    updateList (state, newList) {
      state.list = newList
    },
    updateCount (state, obj) {
      // 根据id找到对应的对象,更新count属性即可
      const goods = state.list.find(item => item.id === obj.id)
      goods.count = obj.newCount
    }
  },
  actions: {
    // 请求方式:get
    // 请求地址:http://localhost:3000/cart
    async getList (context) {
      const res = await axios.get('http://localhost:3000/cart')
      // console.log(res)
      context.commit('updateList', res.data)
    },

    // 请求方式:patch
    async updateCountAsync (context, obj) {
      // 将修改更新同步到后台服务
      // console.log(obj)
      await axios.patch(`http://localhost:3000/cart/${obj.id}`, {
        count: obj.newCount
      })
      // 将修改更新同步到vuex
      context.commit('updateCount', {
        id: obj.id,
        newCount: obj.newCount
      })
    }
  },

3.App.vue页面中调用 action, 获取数据

import { mapState } from 'vuex'

export default {
  name: 'App',
  components: {
    CartHeader,
    CartFooter,
    CartItem
  },
  created () {
    this.$store.dispatch('cart/getList')
  },
  computed: {
    ...mapState('cart', ['list'])
  }
}

4.动态渲染

<!-- 商品 Item 项组件 -->
<cart-item v-for="item in list" :key="item.id" :item="item"></cart-item>

cart-item.vue

<template>
  <div class="goods-container">
    <!-- 左侧图片区域 -->
    <div class="left">
      <img :src="item.thumb" class="avatar" alt="">
    </div>
    <!-- 右侧商品区域 -->
    <div class="right">
      <!-- 标题 -->
      <div class="title">{{item.name}}</div>
      <div class="info">
        <!-- 单价 -->
        <span class="price">¥{{item.price}}</span>
        <div class="btns">
          <!-- 按钮区域 -->
          <button class="btn btn-light">-</button>
          <span class="count">{{item.count}}</span>
          <button class="btn btn-light">+</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  name: 'CartItem',
  props: {
    item: Object
  },
  methods: {

  }
}
</script>
修改数量

在这里插入图片描述

1.注册点击事件

<!-- 按钮区域 -->
<button class="btn btn-light" @click="onBtnClick(-1)">-</button>
<span class="count">{{item.count}}</span>
<button class="btn btn-light" @click="onBtnClick(1)">+</button>

2.页面中dispatch action

onBtnClick (step) {
  const newCount = this.item.count + step
  if (newCount < 1) return

  // 发送修改数量请求
  this.$store.dispatch('cart/updateCount', {
    id: this.item.id,
    count: newCount
  })
}

3.提供action函数

async updateCount (ctx, payload) {
  await axios.patch('http://localhost:3000/cart/' + payload.id, {
    count: payload.count
  })
  ctx.commit('updateCount', payload)
}

4.提供mutation处理函数

mutations: {
  ...,
  updateCount (state, payload) {
    const goods = state.list.find((item) => item.id === payload.id)
    goods.count = payload.count
  }
},
底部总价展示

1.提供getters

getters: {
    // 商品总数量 累加count
    total (state) {
      return state.list.reduce((sum, item) => sum + item.count, 0)
    },
    // 商品总价  累加count* price
    totalPrice (state) {
      return state.list.reduce((sum, item) => sum + item.count * item.price, 0)
    }
  }

2.动态渲染

<template>
  <div class="footer-container">
    <!-- 中间的合计 -->
    <div>
      <span>共 {{total}} 件商品,合计:</span>
      <span class="price">¥{{totalPrice}}</span>
    </div>
    <!-- 右侧结算按钮 -->
    <button class="btn btn-success btn-settle">结算</button>
  </div>
</template>

<script>
import { mapGetters } from 'vuex'
export default {
  name: 'CartFooter',
  computed: {
    ...mapGetters('cart', ['total', 'totalPrice'])
  }
}
</script>

VUE3

Vue2 选项式 API vs Vue3 组合式API

<script>
export default {
  data(){
    return {
      count:0
    }
  },
  methods:{
    addCount(){
      this.count++
    }
  }
}
</script>
<script setup>
import { ref } from 'vue'
const count = ref(0)
const addCount = ()=> count.value++
</script>

特点:

1.代码量变少

2.分散式维护变成集中式维护

VUE3优势:

在这里插入图片描述

使用create-vue搭建Vue3项目

1)create-vue是Vue官方新的脚手架工具,底层切换到了 vite

在这里插入图片描述

2)执行如下命令安装create-vue

npm init vue@latest

3)熟悉项目和关键文件

在这里插入图片描述

组合式API

setup选项

1)写法

<script>
  export default {
    setup(){
      
    },
    beforeCreate(){
      
    }
  }
</script>

2)执行机制(在beforeCreate钩子之前执行

在这里插入图片描述

3)复杂写法:在setup函数中写的数据和方法需要在末尾以对象的方式return,才能可模版使用

// setup执行时机,比beforeCreat还要早
  // 2.setup函数中,获取不到this(this是undefind)
  // 3.数据和函数需要在setup最好return,才能模板中应用
  // 问题:每次都要return,太麻烦
<script>
  export default {
    setup(){
      const message = 'this is message'
      const logMessage = ()=>{
        console.log(message)
      }
      // 必须return才可以
      return {
        message,
        logMessage
      }
    }
  }
</script>

4)语法糖写法:script标签添加 setup标记,不需要再写导出语句,默认会添加导出语句

<script setup>
  const message = 'this is message'
  const logMessage = ()=>{
    console.log(message)
  }
</script>

组合式API-reactive和ref函数

1)reactive:接受对象类型数据的参数传入并返回一个响应式的对象

<script setup>
 // 导入
 import { reactive } from 'vue'
 // 执行函数 传入参数 变量接收
 const state = reactive({
   msg:'this is msg'
 })
 const setSate = ()=>{
   // 修改数据更新视图
   state.msg = 'this is new msg'
 }
</script>

<template>
  {{ state.msg }}
  <button @click="setState">change msg</button>
</template>

2)ref:接收简单类型或者对象类型的数据传入并返回一个响应式的对象

//在原有传入数据的基础之上包了一层对象,包成了复杂类型
//注意点:脚本中,访问我们的数据,需要通过.value
//template中,.value不需要加
<script setup>
 // 1.导入
 import { ref } from 'vue'
 //2. 执行函数 传入参数 变量接收
 const count = ref(0)
 const setCount = ()=>{
   // 修改数据更新视图必须加上.value
   count.value++
 }
</script>

<template>
  <button @click="setCount">{{count}}</button>
</template>

3)区别

  1. reactive不能处理简单类型的数据
  2. ref参数类型支持更好,但是必须通过.value做访问修改
  3. ref函数内部的实现依赖于reactive函数

组合式API-computed

基本与VUE2保持一致,组合式API下只修改了API写法

<script setup>
  //1.导入
import { ref, computed } from 'vue'

// 声明数据
const list = ref([1, 2, 3, 4, 5, 6, 7, 8, 9])
//2.执行函数 变量接收  在回调参数中return计算值
const computedList = computed(() => {
  return list.value.filter(item => item > 2)
})
const addFn = () => {
  list.value.push(666)
}
</script>

<template>
  <div>
    <div>原始数据: {{ list }}</div>
    <div>计算后的: {{ computedList }}</div>
  </div>
  <button @click="addFn">+1</button>
</template>

组合式API-watch

侦听一个或多个数据的变化,数据变化时执行回调函数

1)侦听单个数据

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  // 2. 调用watch 侦听变化
  watch(count, (newValue, oldValue)=>{
    console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
  })
</script>

2)侦听多个数据:第一个参数可以改写成数组的写法

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  const name = ref('cp')
  // 2. 调用watch 侦听变化
  watch([count, name], ([newCount, newName],[oldCount,oldName])=>{
    console.log(`count或者name变化了,[newCount, newName],[oldCount,oldName])
  })
</script>

3)immediate(立即执行)

<script setup>
  // 1. 导入watch
  import { ref, watch } from 'vue'
  const count = ref(0)
  // 2. 调用watch 侦听变化
  watch(count, (newValue, oldValue)=>{
    console.log(`count发生了变化,老值为${oldValue},新值为${newValue}`)
  },{
    immediate: true
  })
</script>
  1. deep(深度侦听)
// 4.deep 深度监视,默认watch为浅层监视
// const 数据为简单类型,则直接监视
// 复杂类型,监视不到
<script>
const userInfo = ref({
  name:'zs',
  age:21
})
const setUserInfo = () => {
  userInfo.value.age++
}
watch(userInfo,(newValue) => {
  console.log(newValue)
}, {
  deep: true
})
  
  / 5.对于对象中的单个属性,进行监视
watch(() => userInfo.value.age, (newValue, oldValue) => {
  console.log(newValue, oldValue)
} )
</script>

<template>
  <div>{{ userInfo }}</div>
  <button @click="setUserInfo">修改userInfo</button>
</template>

组合式API - 生命周期函数

在这里插入图片描述

1)基本使用

//1.导入生命周期函数
import {onMounted } from 'vue'
//2.执行生命周期函数   传入回调
onMounted(() => {
//自定义逻辑
})

2)执行多次:按照顺序执行

// 如果有些代码需要在mounted生命周期中执行
onMounted(() => {
  console.log('mounted生命周期函数')
})

// 写成函数的调用方式,可以调用多次,并 不会冲突,先调用的先执行
onMounted(() => {
  console.log('mounted生命周期函数111')
})

组合式API - 父子通信

1)父传子

  1. 父组件中给子组件绑定属性
  2. 子组件内部通过props选项接收数据

在这里插入图片描述

2)子传父

  1. 父组件中给子组件标签通过@绑定事件
  2. 子组件内部通过 emit 方法触发事件
    在这里插入图片描述

组合式API - 模版引用

通过 ref标识 获取真实的 dom对象或者组件实例对象

1)基本使用

  1. 调用ref函数生成一个ref对象
  2. 通过ref标识绑定ref对象到标签
<script setup>
import {ref} from 'vue'
//1.调用ref函数生成一个ref对象
  const h1Ref = ref(null)
</script>
<template>
<!-- 2.通过ref标识绑定ref对象 -->
<h1 ref=“h1Ref”>我是dom标签h1<h1>
</template>

2)defineExpose

语法糖下组件内部的属性和方法不开放给父组件

可通过defineExpose编译宏指定哪些属性和方法允许访问

在这里插入图片描述

组合式API - provide和inject

顶层组件向任意的底层组件传递数据和方法,实现跨层组件通信

1)跨层传递普通数据

顶层组件通过 provide 函数提供数据

底层组件通过 inject 函数提供数据

在这里插入图片描述

2)跨层传递响应式数据

在调用provide函数时,第二个参数设置为ref对象
在这里插入图片描述

3)跨层传递方法:顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件的数据
在这里插入图片描述

APP.VUE

<script setup>

import CenterCom from '@/components/center-com.vue'
import { provide, ref } from 'vue'
// 1.跨层传递普通数据
provide('theme-color', 'pink')

// 2.跨出传递响应式数据
const count = ref(100)
provide('count',count)

setTimeout(() => {
  count.value = 500
}, 2000)

// 3.跨层级传递数据 =》 给子孙后代传递可以修改的数据
provide('changeCount', (newCount) => {
  count.value = newCount
})
</script>

<template>
  <div>
    <h1>我是顶层组件</h1>
    <CenterCom></CenterCom>
  </div>
</template>

bottom.vue

<script setup>
import { inject } from 'vue'
const themeColor = inject('theme-color')
const count = inject('count')
const changeCount = inject('changeCount')
const clickFn = () => {
  changeCount(1699)
}
</script>

<template>
  <div>
    <h3>我是底层组件 - {{ themeColor }} - {{ count }}</h3>
    <button @click="clickFn">更新count</button>
  </div>
</template>

vue3新特性

defineOptions

问题

1.在script setup 之前,要定义 props, emits→添加一个与setup平级的属性即可

2.在script setup 之后, setup 属性已没有→无法添加与其平级的的属性

3.引入了 defineProps 与 defineEmits 两个宏→只解决了 propsemits 这两个属性

4.若要定义组件的name或其它自定义属性,还是得回到原始用法——再添加一个script标签→非常奇怪,有两个script标签

解决办法

Vue 3.3 中新引入了 defineOptions 宏→用来定义 Options API 的任意选项

<script setup>
  defineOptions({
    name:'Foo',
    inheritAttrs: false,
    //...更多自定义属性
  })
</script>

defineModel

<script setup>
const modelValue = defineModel()
modelValue.value++
</script>

若要生效,还需配置vite.config.js

import { fileURLToPath, URL } from 'node:url'

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    vue({
      script: {
        defineModel: true
      }
    }),
  ],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Pina

简介

Pinia 是 Vue 的最新状态管理工具 ,是 Vuex 的 替代品
在这里插入图片描述

pinia基础使用

1.定义store

2.组件使用store

在这里插入图片描述

getters实现

直接使用 computed函数进行模拟→需要使用时把getters return出去

//数据(state)
const count = ref(0)

//getters
const doubleCount = computed(() => count.value*2)

action异步实现

异步action函数的写法和组件中获取异步数据的写法完全一致

//异步action
const getList = async () => {
   const res = await axios.request<接口数据类型>({})
}

storeToRefs工具函数

使用storeToRefs函数可以辅助保持数据(state + getter)的响应式解构

<script setup>
import { storeToRefs } from 'pinia'
const store = useCounterStore()
// `name` 和 `doubleCount` 是响应式的 ref
// 同时通过插件添加的属性也会被提取为 ref
// 并且会跳过所有的 action 或非响应式 (不是 ref 或 reactive) 的属性
const { name, doubleCount } = storeToRefs(store)
// 作为 action 的 increment 可以直接解构
const { increment } = store
</script>

Pinia的调试

Vue官方的 dev-tools 调试工具 对 Pinia直接支持,可以直接进行调试

在这里插入图片描述

Pinia持久化插件

官方文档:https://prazdevs.github.io/pinia-plugin-persistedstate/zh/

  1. 安装插件 pinia-plugin-persistedstate
npm i pinia-plugin-persistedstate

2.使用 main.js

import persist from 'pinia-plugin-persistedstate'
...
app.use(createPinia().use(persist))

3.配置 store/counter.js

import { defineStore } from 'pinia'
import { computed, ref } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  ...
  return {
    count,
    doubleCount,
    increment
  }
}, {
  persist: true
})
;