vue复习

script怎么写

  1. 开头必须写:import{ref)from 'vue'

  2. 变量用const名字=ref(初始值)

  3. 修改变量用:名字.value=新值

v-一系列

v-for=循环

v-if=条件显示

v-show=显示隐藏(不删标签)

v-model=输入框同步变量

:XXX=v-bind属性绑定

@XXX= v-on点击事件

快速上手

  1. index.js = 路线表(必须先写)

  2. @click = 触发动作

  3. router.push = 执行跳转

这三个加一起 = 你项目里所有页面跳转的逻辑

创建npm create vue@latest

一、Vue 快速上手

**1.**Vue 是一个用于构建用户界面的渐进式框架

**2.**创建Vue 实例

**3.**插值表达式

javascript 复制代码
<body>
 <div id="app">
    <!-- 这里将来会编写一些用于渲染的代码逻辑 -->
     <a href="">{{ count }}</a>
     <h1>{{ msg }}</h1>
  </div>

  <!-- 引入的是开发版本包 - 包含完整的注释和警告 -->
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>

  <script>
    // 一旦引入 VueJS核心包,在全局环境,就有了 Vue 构造函数
    const app = new Vue({
      // 通过 el 配置选择器, 指定 Vue 管理的是哪个盒子
      el:'#app',
      // 通过 data 提供数据
      data: {
        msg: 'hello 灰太狼',
        count: 666
      }
    })
  </script>
</body>

(1)作用:利用表达式进行插值,渲染到页面中

(2)语法:

(3)注意点:① 使用的数据要存在 (data)

② 支持的是表达式,而非语句 if ... for

③ 不能在标签属性里面使用

**4.**响应式特性

定义:数据改变,视图自动更新

(1)访问数据: "实例.属性名"

(2)修改数据: "实例.属性名" = "值"

二、Vue 指令

指令:带有 v- 前缀的特殊标签属性

**1.**动态解析标签

v-html = "表达式 " → 动态设置元素 innerHTML

**2.**v-show 和 v-if

(1)v-show

(2)v-if

javascript 复制代码
<body>
  <!-- 
    v-show 底层原理:切换 css 的 dispaly: 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.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        flag: false
      }
    })
  </script>
</body>

**3.**v-else 和 v-else-if

javascript 复制代码
<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.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        gender: 2,
        score: 100
      }
    })
  </script>
</body>

**4.**v-on

(1)普通

javascript 复制代码
<body>
  <div id="app">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button v-on:click="count++">+</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        count: 100
      }
    })
  </script>
</body>

(2)调用传参

javascript 复制代码
<body>
  <div id="app">
    <div class="box">
      <h3>灰太狼自动售货机</h3>
      <button @click="buy(5)">可乐5r</button>
      <button @click="buy(10)">咖啡10r</button>
    </div>
    <p>银行卡余额:{{ money }}r</p>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        money: 100,
      },
      methods: {
        buy (price) {
          console.log(price)
          this.money -= price
        }

      }
    })
  </script>

</body>

**5.**v-bind

javascript 复制代码
<body>
  <div id="app">
    <!-- v-bind:src  =>  :src -->
    <img :src="imgUrl" v-bind:title="msg" alt="">
  </div>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el:'#app',
      data: {
        imgUrl: '../考核JS/img/course1.png',
        msg: '哈喽,灰太狼'
      }
    })
  </script>
</body>

**6.**v-for

javascript 复制代码
<body>
  <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>
  <script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        list: ['灰太狼','蕉太狼','红太狼']
      }
    })
  </script>
</body>

v-for 中的 key

7.v-model

javascript 复制代码
  <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.7.16/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>

三、综合案例

一、指令补充

**1.**指令修饰符

(1)按键修饰符

@keyup.enter → 键盘回车监听

javascript 复制代码
<body>
  <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/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: ''
      },
      methods: {
        fn (e) {
          if(e.key === 'enter') {
            console.log('键盘回车的时候触发', this.username)
          }
        }
      }
    })
  </script>
</body>

(2)v-model 修饰符

(3)事件修饰符

javascript 复制代码
  <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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        age: '',
      },
      methods: {
        fatherFn () {
          alert('老父亲被点击了')
        },
        sonFn () { 
          alert('儿子被点击了')
        }
      }
    })
  </script>

**2.**v-bind 对于样式操作的增强

(1)操作 class

语法 :class = "对象/数组"

javascript 复制代码
  <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>
<body>

  <div id="app">
    <div class="box" :class="{ pink: false, big: true}">黑马程序员</div>
    <div class="box" :class="['pink', 'big']">黑马程序员</div>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>

(2)操作 style

语法 :style = "样式对象"

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

javascript 复制代码
<style>
    .box {
      width: 200px;
      height: 200px;
      background-color: rgb(187, 150, 156);
    }
  </style>

<body>
  <div id="app">
    <div class="box" :style="{ width: '400px', height: '400px', backgroundColor: 'skyblue'}"></div>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {

      }
    })
  </script>
</body>

**3.**v-model 应用于其他表单元素

javascript 复制代码
  <style>
    textarea {
      display: block;
      width: 240px;
      height: 100px;
      margin: 10px 0;
    }
  </style>


<body>
  <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="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        username: '',
        isSingle: true,
        gender: "2",
        cityId: '102',
        desc: ""
      }
    })
  </script>
</body>

二、computed 计算属性

**1.**计算属性

(1)定义:基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算。

(2)语法:

javascript 复制代码
<style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 240px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
  </style>

<body>

  <div id="app">
    <h3>小黑的礼物清单</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>礼物总数:{{ totalCount}} 个</p>
  </div>
  <script src="./vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        // 现有的数据
        list: [
          { id: 1, name: '篮球', num: 1 },
          { id: 2, name: '玩具', num: 2 },
          { id: 3, name: '铅笔', num: 5 },
        ]
      },
      computed: {
        totalCount () {
          // 基于现有的数据,编写求值逻辑
          // 计算属性函数内部,可以直接通过 this 访问到 app 实例
          // console.log(this.list)

          // 需求:对 this.list 数组里面的 num 进行求和  => reduce
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      }
    })
  </script>
</body>

**2.**computed 计算属性 与 methods 方法

(1)计算属性

(2)方法

javascript 复制代码
  <style>
    table {
      border: 1px solid #000;
      text-align: center;
      width: 300px;
    }
    th,td {
      border: 1px solid #000;
    }
    h3 {
      position: relative;
    }
    span {
      position: absolute;
      left: 145px;
      top: -4px;
      width: 16px;
      height: 16px;
      color: white;
      font-size: 12px;
      text-align: center;
      border-radius: 50%;
      background-color: #e63f32;
    }
  </style>

<body>

  <div id="app">
    <h3>小黑的礼物清单🛒<span>{{ totalCount }}</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>礼物总数:{{ totalCount }} 个</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 },
        ]
      },
      computed: {
        // 计算属性:有缓存的,一旦计算出来啊结果,就会立刻缓存
        // 下次读取 => 直接读缓存执行 => 性能特别高
        totalCount () {
          console.log('计算属性执行了')
          let total = this.list.reduce((sum, item) => sum + item.num, 0)
          return total
        }
      }
    })
  </script>
</body>

**3.**计算属性完整写法

语法:

javascript 复制代码
  <div id="app">
    姓:<input type="text" v-model="firstName"><br>
    名:<input type="text" v-model="lastName"><br>
    <p>{{ fullName}}</p>
    <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: '太狼',
      },
      computed: {
        // 简写 => 获取
        // fullName () {
        //   return this.firstName + this.lastName
        // }

        // 完整写法 => 获取 + 设置
        fullName: {
          // (1)当fullName计算属性,被获取求值时,执行get (有缓存,优先读缓存)
          //    会将返回值作为最终的求值结果
          get () {
            return this.firstName + this.lastName
          },
          // (2)当fullName计算属性,被修改赋值时,执行set
          //    修改的值,传递给set 方法的形参
          set (value) {
            this.firstName = value.slice(0, 1)
            this.lastName = value.slice(1)
          }
        }
      },
      methods: {
        changeName () {
          this.fullName = '红太狼'
        }
      }
    })
  </script>

4.综合案例

三、watch 侦听器

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

2.语法:

(1)简单写法

javascript 复制代码
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-size: 18px;
      }
      #app {
        padding: 10px 20px;
      }
      .query {
        margin: 10px 0;
      }
      .box {
        display: flex;
      }
      textarea {
        width: 300px;
        height: 160px;
        font-size: 18px;
        border: 1px solid #dedede;
        outline: none;
        resize: none;
        padding: 10px;
      }
      textarea:hover {
        border: 1px solid #1589f5;
      }
      .transbox {
        width: 300px;
        height: 160px;
        background-color: #f0f0f0;
        padding: 10px;
        border: none;
      }
      .tip-box {
        width: 300px;
        height: 25px;
        line-height: 25px;
        display: flex;
      }
      .tip-box span {
        flex: 1;
        text-align: center;
      }
      .query span {
        font-size: 18px;
      }

      .input-wrap {
        position: relative;
      }
      .input-wrap span {
        position: absolute;
        right: 15px;
        bottom: 15px;
        font-size: 12px;
      }
      .input-wrap i {
        font-size: 20px;
        font-style: normal;
      }
    </style>

  <body>
    <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">{{ result }}</div>
        </div>
      </div>
    </div>
    <script src="./vue.js"></script>
    <script src="./axios.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: ''
          },

          result: '', // 翻译结果
          // timer: null  // 延时器id
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
          // 该方法会在我数据变化时调用执行
          // newValue新值,oldValue老值(一般不用)
          // words (newValue) {
          //   console.log('变化了', newValue)
          // }

          'obj.words' (newValue) {
            // console.log('变化了', newValue)

            // 防抖:延迟执行 => 干啥事先等一等,延迟一会,一段时间内没有再次触发,才执行
            clearTimeout(this.timer)
            this.timer = setTimeout(async () => {
              const res = await axios({
                url: 'https://applet-base-api-t.itheima.net/api/translate',
                params: {
                  words: newValue
                }
              }, 300)
              this.result = res.data.data
            })
          }
        }
      })
    </script>
  </body>

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

javascript 复制代码
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-size: 18px;
      }
      #app {
        padding: 10px 20px;
      }
      .query {
        margin: 10px 0;
      }
      .box {
        display: flex;
      }
      textarea {
        width: 300px;
        height: 160px;
        font-size: 18px;
        border: 1px solid #dedede;
        outline: none;
        resize: none;
        padding: 10px;
      }
      textarea:hover {
        border: 1px solid #1589f5;
      }
      .transbox {
        width: 300px;
        height: 160px;
        background-color: #f0f0f0;
        padding: 10px;
        border: none;
      }
      .tip-box {
        width: 300px;
        height: 25px;
        line-height: 25px;
        display: flex;
      }
      .tip-box span {
        flex: 1;
        text-align: center;
      }
      .query span {
        font-size: 18px;
      }

      .input-wrap {
        position: relative;
      }
      .input-wrap span {
        position: absolute;
        right: 15px;
        bottom: 15px;
        font-size: 12px;
      }
      .input-wrap i {
        font-size: 20px;
        font-style: normal;
      }
    </style>

  <body>
    <div id="app">
      <!-- 条件选择框 -->
      <div class="query">
        <span>翻译成的语言:</span>
        <select v-model="obj.lang">
          <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">{{ result }}</div>
        </div>
      </div>
    </div>
    <script src="./vue.js"></script>
    <script src="./axios.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: '小黑',
            lang: 'italy'
          },

          result: '', // 翻译结果
        },
        // 具体讲解:(1) watch语法 (2) 具体业务实现
        watch: {
          obj: {
            deep: true, // 深度监视
            immediate: true, // 立刻执行,一进页面handler就立刻执行
            handler (newValue) {
              clearTimeout(this.timer)
              this.timer = setTimeout(async () => {
                const res = await axios({
                  url: 'https://applet-base-api-t.itheima.net/api/translate',
                  params: newValue
                }, 300)
                this.result = res.data.data
              })
            }
          }


          // 'obj.words' (newValue) {
          //   clearTimeout(this.timer)
          //   this.timer = setTimeout(async () => {
          //     const res = await axios({
          //       url: 'https://applet-base-api-t.itheima.net/api/translate',
          //       params: {
          //         words: newValue
          //       }
          //     }, 300)
          //     this.result = res.data.data
          //   })
          // }
        }
      })
    </script>
  </body>

四、综合案例

五、生命周期

**1.**生命周期 & 生命周期四个阶段

**2.**生命周期钩子

**3.**综合案例

javascript 复制代码
<body>

  <div id="app">
    <h3>{{ title }}</h3>
    <div>
      <button @click="count--">-</button>
      <span>{{ count }}</span>
      <button @click="count++">+</button>
    </div>
  </div>
  <script src="./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>
</body>

六、综合案例

一、工程化开发入门

**1.**工程化开发和脚手架

我采用的是npm.cmd

**2.**脚手架目录文件介绍和项目运行流程

一、组件通信

**1.**什么是prop

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

作用:向子组件传递数据

javascript 复制代码
/* App.vue */
<template>
  <div class="app">
    <UserInfo 
    :username="username"
    :age="age"
    :isSingle="isSingle"
    :car="car"
    :hobby="hobby"
    ></UserInfo>
  </div>
</template>

<script>
import UserInfo from './components/UserInfo.vue'
export default {
  data() {
    return {
      username: '小帅',
      age: 28,
      isSingle: true,
      car: {
        brand: '宝马',
      },
      hobby: ['篮球', '足球', '羽毛球'],
    }
  },
  components: {
    UserInfo,
  },
}
</script>

<style>
</style>
javascript 复制代码
/* UserInfo.vue */
<template>
  <div class="userinfo">
    <h3>我是个人信息组件</h3>
    <div>姓名:{{ username }}</div>
    <div>年龄:{{ age }}</div>
    <div>是否单身:{{ isSingle ? '是' : '否' }}</div>
    <div>座驾:{{ car.brand }}</div>
    <div>兴趣爱好 {{ hobby.join(' , ') }}</div>
  </div>
</template>

<script>
export default {
  props: ['username', 'age', 'isSingle', 'car', 'hobby']
}
</script>

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

**2.**prorps校验

语法:

javascript 复制代码
/* App.vue */
<template>
  <div class="app">
    <BaseProgress :w="width"></BaseProgress>
  </div>
</template>

<script>
import BaseProgress from './components/BaseProgress.vue'
export default {
  data() {
    return {
      width: 30,
    }
  },
  components: {
    BaseProgress,
  },
}
</script>

<style>
</style>
javascript 复制代码
/* BaseProgress.vue */
<template>
  <div class="base-progress">
    <div class="inner" :style="{ width: w + '%' }">
      <span>{{ w }}%</span>
    </div>
  </div>
</template>

<script>
export default {
  // props: ["w"],

  // 1.基础写法(类型校验)
  // props: {
  //   w: Number // Number String Boolean Array Object function 
  // }


  // 2.完整写法(类型、是否必填、默认值、自定义校验)
  props: {
    w: {
      type: Number,
      // required: true
      default: 0,  // 默认值
      validator (value) {
        // console.log(value)
        if (value >= 0 && value <= 100) {
          return true
        } else {
          console.error('传入的 prop w. 必须是0 ~ 100')
          return false
        }
      }
    }
  }
}
</script>

<style scoped>
.base-progress {
  height: 26px;
  width: 400px;
  border-radius: 15px;
  background-color: #272425;
  border: 3px solid #272425;
  box-sizing: border-box;
  margin-bottom: 30px;
}
.inner {
  position: relative;
  background: #379bff;
  border-radius: 15px;
  height: 25px;
  box-sizing: border-box;
  left: -3px;
  top: -2px;
}
.inner span {
  position: absolute;
  right: 0;
  top: 26px;
}
</style>

**3.**prop 和 data、单向数据流

javascript 复制代码
/* App.vue */
<template>
  <div class="app">
    <BaseCount 
    @changeCount="handleChange"
    :count="count">
    </BaseCount>
  </div>
</template>

<script>
import BaseCount from './components/BaseCount.vue'
export default {
  components:{
    BaseCount
  },
  data(){
    return {
      count:100
    }
  },
  methods:{
    handleChange (newCount) {
      // console.log(newCount)
      this.count = newCount
    }
  }
}
</script>

<style>

</style>
javascript 复制代码
/* BasCount.vue */
<template>
  <div class="base-count">
    <button @click="count--">-</button>
    <span>{{ count }}</span>
    <button @click="count++">+</button>
  </div>
</template>

<script>
export default {
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
  //   return {
  //     count: 100,
  //   }
  // },


  // 2.外部传过来的数据 不能随便修改
  props: {
    count: Number
  },
  methods: {
    handleAdd () {
      // 子传父 this.$emit(事件名, 参数)
      this.$emit('changeCount', this.count + 1)
    },
    handleSub () {
      this.$emit('changeCount', this.count - 1)
    }
  }
}

// 单向数据流,父组件的prop更新,会单向向下流动,影响到子组件
</script>

<style>
.base-count {
  margin: 20px;
}
</style>

二、综合案例

**1.**总结

**2.**非父子通信 (拓展) - event bus 事件总线

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

**3.**非父子通信 (拓展) - provide & inject

javascript 复制代码
<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: 'jtl',
        age: 18,
      },
    }
  },
  methods: {
    change () {
      // this.color = 'green'
      this.userInfo.name = 'htl'
    }
  },
  components: {
    SonA,
    SonB,
  },
}
</script>

<style>
.app {
  border: 3px solid #000;
  border-radius: 6px;
  margin: 10px;
}
</style>
javascript 复制代码
<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>

三、进阶语法

**1.**v-model 原理

(1)原理:v-model本质上是一个语法糖。如应用在输入框上,就是 value属性和input事件的合写

(2)作用:提供数据的双向绑定

(3)注意:$event 用于在模板中,获取事件的形参

javascript 复制代码
<template>
  <div class="app">
    <input v-model="msg1" type="text" />
    <br />
    <!-- 模板中获取事件的形参 => $event 获取 -->
    <input :value="msg2" @input="msg2 = $event.target.value" type="text" >
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg1: '',
      msg2: '',
    }
  },
}
</script>

<style>
</style>

**2.**表单类组件封装 & v-model 简化代码

(1)表单类组件封装 => 实现 子组件 和 父组件数据 的双向绑定

javascript 复制代码
<template>
  <div class="app">
    <BaseSelect
      :cityId="selectId"
      @changeId="selectId = $event"
    ></BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
     selectId: '102'
    }
  },
  components: {
    BaseSelect,
  },
  
}
</script>

<style>
</style>
javascript 复制代码
<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) {
      // console.log(e.target.value)
      this.$emit('changeId', e.target.value)
    }
  }
}
</script>

<style>
</style>

(2)父组件 v-model 简化代码,实现 子组件 和 父组件数据 双向绑定

javascript 复制代码
<template>
  <div class="app">
    <!-- v-model => :value + @input -->
    <BaseSelect
      v-model="selectId"
    ></BaseSelect>
  </div>
</template>

<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data() {
    return {
      selectId: '102',
    }
  },
  components: {
    BaseSelect,
  },
}
</script>

<style>
</style>
javascript 复制代码
<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: String
  },
  methods: {
    handleChange (e) {
      this.$emit('input', e.target.value)
    }
  }
}
</script>

<style>
</style>

3..sync 修饰符

(1)作用:可以实现 子组件 与 父组件数据 的 双向绑定,简化代码

(2)特点:prop属性名,可以自定义,非固定为 value

(3)场景:封装弹框类的基础组件, visible属性 true显示 false隐藏

(4)本质:就是 :属性名 和 @update:属性名 合写

4..ref 和 $refs

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

5. Vue异步更新、$nextTick

javascript 复制代码
<template>
<!-- 编辑状态 -->
  <div class="app">
    <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 () {
    // 1. 显示输入框 (异步 dom 更新)
    this.isShowEdit = true
    // 2. 让输入框获取焦点 ($nextTick等 dom 更新完,立刻执行准备的函数体)
    this.$nextTick(() => {
      // console.log(this.$refs.inp)
      this.$refs.inp.focus()
    })

    // setTimeout(() => {
    //   this.$refs.inp.focus()
    // }, 1000)
   }
  },
}
</script>

<style>
</style>

一、自定义指令

1.基础语法

自定义指令:自己定义的指令, 可以封装一些 dom 操作, 扩展额外功能

(1)全局注册

(2)局部注册

(3)总结:

javascript 复制代码
<template>
  <div>
    <h1>自定义指令</h1>
    <input v-focus ref="inp" type="text">
  </div>
</template>

<script>
export default {
  // mounted () {
  //   this.$refs.inp.focus()
  // }

  // 2. 局部注册指令
  directives: {
    // 指令名:指令的配置项
    focus: {
      inserted (el) {
        el.focus()
      }
    }
  }
}
</script>

<style>

</style>

2.指令的值

(1)语法:在绑定指令时,可以通过"等号"的形式为指令 绑定 具体的参数值

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

(3)总结:

javascript 复制代码
<template>
  <div>
    <h1 v-color = "color1">指令的值1测试</h1>
    <h1 v-color = "color2">指令的值2测试</h1>
  </div>
</template>

<script>
export default {
  data () {
    return {
      color1: 'red',
      color2: 'green'
    }
  },
  directives: {
    color: {
      // 1. inserted 提供的是元素被添加到页面中时的逻辑
      inserted (el, binding) {
        // console.log(el,binding.value)
        // binding.value 就是指令的值
        el.style.color = binding.value
      },
      // 2. update 指令的值修改的时候触发,提供值变化后,dom更新的逻辑
      update (el, binding) {
        console.log('指令的值修改了')
        el.style.color = binding.value
      }
    }
  }
}
</script>

<style>

</style>

3.v-loading 指令封装

(1)场景:实际开发过程中,发送请求需要时间,在请求的数据未回来时,

页面会处于空白状态 => 用户体验不好

(2)

(3)总结:

javascript 复制代码
<template>
  <div class="main">
    <div class="box" 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>
    <div class="box2"></div>
  </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>
.loading:before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: #fff url('./loading.gif') no-repeat center;
}

.box2 {
  width: 400px;
  height: 400px;
  border: 2px solid #000;
  position: relative;
}

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

二、插槽

1.默认插槽

(1)作用:让组件内部的一些结构支持自定义

(2)需求: 将需要多次显示的对话框, 封装成一个组件

(3)语法:

(4)总结

2.后备内容(默认值)

槽后备内容:封装组件时,可以为预留的 `<slot>` 插槽提供后备内容(默认内容)。

3.具名插槽

需求:一个组件内有多处结构,需要外部传入标签,进行定制

总结:

4.作用域插槽

作用域插槽: 定义slot插槽的同时, 是可以传值的。给插槽上可以绑定数据,将来使用组件时可以用

三、综合案例

一、路由入门

**1.**单页应用程序

**2.**路由概念

Vue中的路由是路径和组件的映射关系

**3.**VueRouter的介绍

(1)作用:修改地址栏路径时,切换显示匹配的组件

(2)说明:Vue 官方的一个路由插件,是一个第三方包

(3)官网:https://v3.router.vuejs.org/zh/

(4)5个基础步骤:

(5)2 个核心步骤:

**4.**组件目录存放问题

组件分类: .vue文件分2类; 页面组件 和 复用组件

注意:都是 .vue文件 (本质无区别)

分类:页面组件 - views文件夹 => 配合路由,页面展示

复用组件 - components文件夹 => 封装复用

二、路由进阶

**1.**路由模块封装

目标:将路由模块抽离出来。 好处:拆分模块,利于维护

绝对路径:@指代src目录,可以用于快速引入组件

**2.**声明式导航和导航高亮

router-link:能跳转,能高亮 (自带激活时的类名)

<router-link to="/路径值" ></router-link>

<a href="#/路径值" ></router-link>

**3.**精确匹配和模糊匹配

**4.**自定义高亮类名

javascript 复制代码
// router index.js
import Find from '@/views/Find'
import My from '@/views/My'
import Friend from '@/views/Friend'

import Vue from 'vue'
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 },
  ],
  // link自定义高亮类名
  linkActiveClass: 'active', // 配置模糊匹配的类名
  linkExactActiveClass: 'exact-active' // 配置精确匹配的类名
})

export default router

**5.**声明式导航传参 ( 查询参数传参和动态路由传参 )

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

① 语法格式如下 to="/path?参数名=值"

② 对应页面组件接收传递过来的值 $route.query.参数名

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

① 配置动态路由

② 配置导航链接 to="/path/参数值"

③ 对应页面组件接收传递过来的值 $route.params.参数名

(3)动态路由参数可选符

/search/:words 表示,必须要传参数。如果不传参数,也希望匹配,可以加个可选符 "?"

**6.**路由重定向

说明:重定向 → 匹配path后, 强制跳转path路径

语法: { path: 匹配路径, redirect: 重定向到的路径 }

**7.**路由404

作用:当路径找不到匹配时,给个提示页面

位置:配在路由最后

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

**8.**路由模式

**9.**编程式导航 - 基本跳转

(1)path 路径跳转 (简易方便)

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

javascript 复制代码
<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input v-model="inpValue" type="text">
      <button @click="goSearch">搜索一下</button>
    </div>
    <div class="hot-link">
      热门搜索:
      <router-link to="/search/黑马程序员">黑马程序员</router-link>
      <router-link to="/search/前端培训">前端培训</router-link>
      <router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FindMusic',
  data () {
    return {
      inpValue: ''
    }
  },
  methods: {
    goSearch () {
      // 1. 通过路径的方式跳转
      // (1) this.$router.push('路由路径') [简写]
      // this.$router.push('路由路径?参数名=参数值')
      // this.$router.push('/search')
      // this.$router.push(`/search?key=${this.inpValue}`)
      // this.$router.push(`?search/${this.inpValue}`)

      // (2) this.$router.push({  [完整写法] 更适合传参
      //         path: '路由路径'
      //         query: {
      //            参数名:参数值,
      //            参数名:参数值
      //         }
      // })
      // this.$router.push({
      //   path: '/search',
      //   query: {
      //     key: this.inpValue
      //   }
      // })
      this.$router.push({
        path: `/search/${this.inpValue}`
      })

      // 2. 通过命名路由的方式跳转(需要给路由取名字) 适合长路径
      //    this.$router.push({
      //        name: '路由名',
      //        query: {参数名:参数值},
      //        params: { 参数名:参数值}
      // })
      this.$router.push({
        name: 'search',
        // query: {
        //   key: this.inpValue
        // }
        params: {
          words: this.inpValue
        }
      })
    }
  }
}
</script>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

10.编程式导航传参 ( 查询参数传参和动态路由传参)

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

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

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

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

javascript 复制代码
<template>
  <div class="home">
    <div class="logo-box"></div>
    <div class="search-box">
      <input v-model="inpValue" type="text">
      <button @click="goSearch">搜索一下</button>
    </div>
    <div class="hot-link">
      热门搜索:
      <router-link to="/search/黑马程序员">黑马程序员</router-link>
      <router-link to="/search/前端培训">前端培训</router-link>
      <router-link to="/search/如何成为前端大牛">如何成为前端大牛</router-link>
    </div>
  </div>
</template>

<script>
export default {
  name: 'FindMusic',
  data () {
    return {
      inpValue: ''
    }
  },
  methods: {
    goSearch () {
      // 1. 通过路径的方式跳转
      // (1) this.$router.push('路由路径') [简写]
      //     this.$router.push('路由路径?参数名=参数值')
      // this.$router.push('/search')
      // this.$router.push(`/search?key=${this.inpValue}`)
      // this.$router.push(`/search/${this.inpValue}`)

      // (2) this.$router.push({     [完整写法] 更适合传参
      //         path: '路由路径'
      //         query: {
      //            参数名: 参数值,
      //            参数名: 参数值
      //         }
      //     })
      // this.$router.push({
      //   path: '/search',
      //   query: {
      //     key: this.inpValue
      //   }
      // })
      // this.$router.push({
      //   path: `/search/${this.inpValue}`
      // })



      // 2. 通过命名路由的方式跳转 (需要给路由起名字) 适合长路径
      //    this.$router.push({
      //        name: '路由名'
      //        query: { 参数名: 参数值 },
      //        params: { 参数名: 参数值 }
      //    })
      this.$router.push({
        name: 'search',
        // query: {
        //   key: this.inpValue
        // }
        params: {
          words: this.inpValue
        }
      })
    }
  }
}
</script>

<style>
.logo-box {
  height: 150px;
  background: url('@/assets/logo.jpeg') no-repeat center;
}
.search-box {
  display: flex;
  justify-content: center;
}
.search-box input {
  width: 400px;
  height: 30px;
  line-height: 30px;
  border: 2px solid #c4c7ce;
  border-radius: 4px 0 0 4px;
  outline: none;
}
.search-box input:focus {
  border: 2px solid #ad2a26;
}
.search-box button {
  width: 100px;
  height: 36px;
  border: none;
  background-color: #ad2a26;
  color: #fff;
  position: relative;
  left: -2px;
  border-radius: 0 4px 4px 0;
}
.hot-link {
  width: 508px;
  height: 60px;
  line-height: 60px;
  margin: 0 auto;
}
.hot-link a {
  margin: 0 5px;
}
</style>

一、综合案例

**1.**分析

**2.**组件缓存 keep-alive

问题:从面经点到详情页,又点返回,数据重新加载了 → 希望回到原来的位置

原因:路由跳转后,组件被销毁了,返回回来组件又被重建了,所以数据重新被加载了

解决:利用 keep-alive 将组件缓存下来

(1)keep-alive定义

keep-alive是Vue的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们

keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中

(2)keep-alive的优点

在组件切换过程中把切换出去的组件保留在内存中,防止重复渲染DOM,

减少加载时间及性能消耗,提高用户体验性。

(3)语法

(4)keep-alive的三个属性

① include : 组件名数组,只有匹配的组件会被缓存

② exclude : 组件名数组,任何匹配的组件都不会被缓存

③ max : 最多可以缓存多少组件实例

(5)keep-alive的使用会触发两个生命周期函数

activated 当组件被激活(使用)的时候触发 → 进入这个页面的时候触发

deactivated 当组件不被使用的时候触发 → 离开这个页面的时候触发

组件缓存后就不会执行组件的created, mounted, destroyed 等钩子了

所以其提供了actived 和 deactived钩子,帮我们实现业务需求。

**3.**总结

**4.**自定义创建项目

**5.**ESlint 代码规范

规范说明:https://standardjs.com/rules-zhcn.html

自动修正:基于 vscode 插件 ESLint 高亮错误,并通过配置 自动 帮助我们修复错误。

二、Vuex的基本使用

1.vuex概述

**2.**构建 vuex [多组件数据共享] 环境

目标:基于脚手架创建项目,构建 vuex 多组件数据共享环境

**3.**创建一个空仓库

javascript 复制代码
// 这里面存放的就是 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'

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

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

// 导出给main.js使用
export default store

一、核心概念 - state 状态

目标:明确如何给仓库提供数据,如何使用仓库的数据

1.提供数据

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

对象中可以添加我们要共享的数据。

2.使用数据

① 通过 store 直接访问

② 通过辅助函数

**二、**核心概念 - mutations

**1.**目标:明确 vuex 同样遵循单向数据流,组件中不能直接修改仓库的数据

通过 strict: true 可以开启严格模式

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

(2)组件中提交调用 mutations

**2.**目标:掌握 mutations 传参语法

提交 mutation 是可以传递参数的 `this.$store.commit( 'xxx', 参数 )`

(1)提供 mutation 函数 (带参数 - 提交载荷 payload )

(2)页面中提交调用 mutation

Tips: 提交参数只能一个,如果有多个参数,包装成一个对象传递

**3.**核心概念 - mutations - 练习

(1)目标:减法功能,巩固 mutations 传参语法

(2)目标:实时输入,实时更新,巩固 mutations 传参语法

三、辅助函数 - mapMutations

目标:掌握辅助函数 mapMutations,映射方法

mapMutations和mapState很像,它是把位于mutations中的方法提取了出来,映射到组件methods

四、核心概念 - actions

目标:明确 actions 的基本语法,处理异步操作。

需求: 一秒钟之后, 修改 state 的 count 成 666。

说明:mutations 必须是同步的 (便于监测数据变化,记录调试)

五、辅助函数 - mapActions

目标:掌握辅助函数 mapActions,映射方法

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

六、核心概念 - getters

目标:掌握核心概念 getters 的基本语法 (类似于计算属性)

说明:除了state之外,有时我们还需要从state中派生出一些状态,这些状态是依赖state的,此时

会用到getters

例如:state中定义了list,为 1-10 的数组,组件中,需要显示所有大于5的数据

**1.**定义 getters

**2.**访问getters

(1)通过 store 访问 getters

(2)通过辅助函数 mapGetters 映射

七、核心概念 - 模块 module (进阶语法)

1.目标:掌握核心概念 module 模块的创建

(1)由于vuex使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复

杂时,store对象就有可能变得相当臃肿。(当项目变得越来越大的时候,Vuex会变得越来越

难以维护)

(2)模块拆分:user模块: store/modules/user.js

2.目标:掌握模块中 state 的访问语法

尽管已经分模块了,但其实子模块的状态,还是会挂到根级别的state中,属性名就是模块名使用

模块中的数据:

(1)直接通过模块名访问 $store.state.模块名.xxx

(2)通过 mapState 映射

默认根级别的映射 mapState([ 'xxx' ])

子模块的映射 mapState('模块名', ['xxx']) - 需要开启命名空间

3.目标:掌握模块中 getters 的访问语法

使用模块中 getters 中的数据:

(1)直接通过模块名访问 $store.getters['模块名/xxx ']

(2)通过 mapGetters 映射

默认根级别的映射 mapGetters([ 'xxx' ])

子模块的映射 mapGetters('模块名', ['xxx']) - 需要开启命名空间

4.目标:掌握模块中 mutation 的调用语法

注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模

块。 调用子模块中 mutation:

(1)直接通过 store 调用 $store.commit('模块名/xxx ', 额外参数)

(2)通过 mapMutations 映射

默认根级别的映射 mapMutations([ 'xxx' ])

子模块的映射 mapMutations('模块名', ['xxx']) - 需要开启命名空间

5.目标:掌握模块中 action 的调用语法 (同理 - 直接类比 mutation 即可)

注意:默认模块中的 mutation 和 actions 会被挂载到全局,需要开启命名空间,才会挂载到子模

块。 调用子模块中 action :

(1)直接通过 store 调用 $store.dispatch('模块名/xxx ', 额外参数)

(2)通过 mapActions 映射

默认根级别的映射 mapActions([ 'xxx' ])

子模块的映射 mapActions('模块名', ['xxx']) - 需要开启命名空间

javascript 复制代码
// ./store/index.js
// 这里面存放的就是 vuex 相关的核心代码
import Vue from 'vue'
import Vuex from 'vuex'
import user from './modules/user'
import setting from './modules/setting'

// 插件安装
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. 通过 mutation 可以提供修改数据的方法
  mutations: {
    // 所有 mutation 函数,第一个参数,都是 state
    // 注意点:mutation 参数有且只能有一个,如果需要多个参数,包装成一个对象
    addCount (state, n) {
      // 修改数据
      state.count += n
    },
    subCount (state, n) {
      state.count -= n
    },
    changeCount (state, newCount) {
      state.count = newCount
    },
    changeTitle (state, newTitle) {
      state.title = newTitle
    }
  },

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

  // 4.getters 类似于计算属性
  getters: {
    // 注意点:
    // 1. 形参第一个参数,就是state
    // 2. 必须有返回值,返回值就是getters的值
    filterList (state) {
      return state.list.filter(item => item > 5)
    }
  },

  // 5.modules 模块
  modules: {
    user,
    setting
  }
})

// 导出给main.js使用
export default store
javascript 复制代码
// ./App.vue
<template>
  <div id="app">
    <h1>
      根组件
      - {{ title }}
      - {{ count }}
    </h1>
    <input :value="count" @input="handleInput" type="text">
    <Son1></Son1>
    <hr>
    <Son2></Son2>
  </div>
</template>

<script>
import Son1 from './components/Son1.vue'
import Son2 from './components/Son2.vue'
import { mapState } from 'vuex'
// console.log(mapState(['count', 'title']))

export default {
  name: 'app',
  created () {
    // console.log(this.$router) // 没配
    console.log(this.$store.state.count)
  },
  computed: {
    ...mapState(['count', 'title'])
  },
  data: function () {
    return {

    }
  },
  methods: {
    handleInput (e) {
      // 1. 实时获取输入框的值
      const num = +e.target.value
      // 2. 提交 mutation ,调用 mutation 函数
      this.$store.commit('changeCount', num)
    }
  },
  components: {
    Son1,
    Son2
  }
}
</script>

<style>
#app {
  width: 600px;
  margin: 20px auto;
  border: 3px solid #ccc;
  border-radius: 3px;
  padding: 10px;
}
</style>
javascript 复制代码
// ./components/Son1.vue

<template>
  <div class="box">
    <h2>Son1 子组件</h2>
    从vuex中获取的值: <label>{{ $store.state.count }}</label>
    <br>
    <button @click="handleAdd(1)">值 + 1</button>
    <button @click="handleAdd(5)">值 + 5</button>
    <button @click="handleAdd(10)">值 + 10</button>
    <button @click="handleChange">1秒后修改成666</button>
    <button @click="changeFn">改标题</button>

    <hr>
    <!-- 计算属性getters -->
    <div>{{ $store.state.list }}</div>
    <div>{{ $store.getters.filterList }}</div>

    <hr>
    <!-- 测试访问模块中的state - 原生 -->
    <div>{{ $store.state.user.userInfo.name }}</div>
    <button @click="updateUser">更新个人信息</button>
    <button @click="updateUser2">1秒后更新个人信息</button>
    <div>{{ $store.state.setting.theme}}</div>
    <button @click="updateTheme">更新主题色</button>

    <hr>
    <!-- 测试访问模块中的getters - 原生 -->
    <div>{{ $store.getters['user/UpperCaseName'] }}</div>
  </div>
</template>

<script>
export default {
  name: 'Son1Com',
  created () {
    console.log(this.$store.getters)
  },
  methods: {
    updateUser () {
      // $store.commit('模块名/mutation名', 额外传参)
      this.$store.commit('user/setUser', {
        name: 'ytl',
        age: 20
      })
    },
    updateUser2 () {
      // 如何调用action dispatch
      this.$store.dispatch('user/setUserSecond', {
        name: 'qjl',
        age: 28
      })
    },
    updateTheme () {
      this.$store.commit('setting/setTheme', 'blue')
    },
    handleAdd (n) {
      // 错误代码(vue 默认不会检测,需要监测成本,开启严格模式即可)
      // this.$store.state.count++
      // console.log(this.$store.state.count)

      // 应该通过 mutation 核心概念,进行修改数据
      // 需要提供调用 mutation
      // this.$store.commit('addCount')

      // console.log(n)
      // 调用带参数的 mutation 函数
      this.$store.commit('addCount', n)
    },
    changeFn () {
      this.$store.commit('changeTitle', '灰太狼')
    },
    handleChange () {
      // 调用action
      // this.$store.dispatch('action名字', 额外参数)
      this.$store.dispatch('changeCountAction', 666)
    }
  }
}
</script>

<style lang="css" scoped>
.box{
  border: 3px solid #ccc;
  width: 400px;
  padding: 10px;
  margin: 20px;
}
h2 {
  margin-top: 10px;
}
</style>
javascript 复制代码
// ./components/Son2.vue
<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="subCount(10)">值 - 10</button>
    <button @click="chanageCountAction(888)">1秒后修改成888</button>
    <button @click="changeTitle('我是灰太狼')">改标题</button>

    <hr>
    <div>{{ filterList }}</div>

    <hr>
    <!-- 访问模块中的state -->
    <div>{{ user.userInfo.name }}</div>
    <div>{{ setting.theme }}</div>

    <hr>
    <!-- 访问模块中的state -->
    <div>user模块数据:{{ userInfo}}</div>
    <button @click="setUser({ name: 'hettl', age: 40})">更新个人信息</button>
    <button @click="setUserSecond({ name: 'hettl', age: 40})">1秒后更新个人信息</button>
    <div>setting模块数据:{{ theme }} - {{ desc }}</div>
    <button @click="setTheme('blue')">更新主题色</button>

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

<script>
import { mapState, mapMutations, mapActions, mapGetters } from 'vuex'
export default {
  name: 'Son2Com',
  computed: {
    // mapState 和 mapActions 都是映射属性
    ...mapState(['count', 'user', 'setting']),
    ...mapState('user', ['userInfo']),
    ...mapState('setting', ['theme', 'desc']),
    ...mapGetters(['filterList']),
    ...mapGetters('user', ['UpperCaseName'])
  },
  methods: {
    // mapState 和 mapActions 都是映射方法
    // 全局级别的映射
    ...mapMutations(['subCount', 'changeTitle']),
    ...mapActions(['chanageCountAction']),

    // 分模块的映射
    ...mapMutations('setting', ['setTheme']),
    ...mapMutations('user', ['setUser']),
    ...mapActions('user', ['setUserSecond'])
    // handleSub (n) {
    //   this.subCount(n)
    // }
  }
}
</script>

<style lang="css" scoped>
.box {
  border: 3px solid #ccc;
  width: 400px;
  padding: 10px;
  margin: 20px;
}
h2 {
  margin-top: 10px;
}
</style>
javascript 复制代码
// ./store/modules/user.js
// user模块
const state = {
  userInfo: {
    name: 'htl',
    age: 18
  },
  score: 100
}
const mutations = {
  setUser (state, newUserInfo) {
    state.userInfo = newUserInfo
  }
}
const actions = {
  setUserSecond (context, newUserInfo) {
    // 将异步在action中进行封装
    setTimeout(() => {
      // 调用mutation context上下文,默认提交的就是直接模块的action和mutation
      context.commit('setUser', newUserInfo)
    }, 1000)
  }
}
const getters = {
  // 分模块后,state指代子模块的state
  UpperCaseName (state) {
    return state.userInfo.name.toUpperCase()
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions,
  getters
}
javascript 复制代码
// ./store/modules/setting.js
// setting模块
const state = {
  theme: 'light', // 主题色
  desc: '测试demo'
}
const mutations = {
  setTheme (state, newTheme) {
    state.theme = newTheme
  }
}
const actions = {}
const getters = {}

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

**1.**项目演示

目标:查看项目效果,明确功能模块 → 完整的电商购物流程

**2.**创建项目

在cmd(win + R)中输入 vue create project-name

选中自定义(第三个)

**3.**调整初始化目录

目标:将目录调整成符合企业规范的目录

**4.**vant 组件库

目标:认识第三方 Vue组件库 vant-ui

组件库:第三方 封装 好了很多很多的 组件,整合到一起就是一个组件库。

链接:https://vant-contrib.gitee.io/vant/v2/#/zh-CN/

**5.**其他 Vue 组件库

目标:了解其他 Vue 组件库

Vue的组件库并不是唯一的,vant-ui 也仅仅只是组件库的一种。

一般会按照不同平台进行分类:

(1)PC端: element-ui (element-plus) ant-design-vue

(2)移动端:vant-ui Mint UI(饿了么) Cube UI(滴滴)

**6.**vant 全部导入(方便) 和 按需导入(性能)【推荐】

(1)目标:阅读文档,掌握 全部导入 的基本使用

全部导入:

(2)目标:阅读文档,掌握 按需导入 的基本使用

按需导入:

**7.**项目中的 vw 适配

目标:基于 postcss 插件 实现项目 vw 适配

px => vx

**8.**路由设计配置

(1)目标:分析项目页面,设计路由,配置一级路由

但凡是单个页面,独立展示的,都是一级路由

(2)目标:阅读vant组件库文档,实现底部导航 tabbar

tabbar标签页:

(3)目标:基于底部导航,完成二级路由配置

**9.**登录页静态布局

(1)request模块 - axios 封装

目标:将 axios 请求方法,封装到 request 模块

使用 axios 来请求后端接口, 一般都会对 axios 进行 一些配置 (比如: 配置基础地址,请求响应

拦截器等) 所以项目开发中, 都会对 axios 进行基本的二次封装, 单独封装到一个 request 模块

中, 便于维护使用

接口文档地址:

https://apifox.com/apidoc/shared-12ab6b18-adc2-444c-ad11-0e60f5693f66/doc-2221080

基地址:http://cba.itlike.com/public/index.php?s=/api/

(2)图形验证码功能完成

目标:基于请求回来的 base64 图片,实现图形验证码功能

说明:

需求:

(3)api 接口模块 -封装图片验证码接口

目标:将请求封装成方法,统一存放到 api 模块,与页面分离

(4)Toast 轻提示

目标:阅读文档,掌握 toast 轻提示

(5)短信验证倒计时

目标:实现短信验证倒计时功能

步骤分析:

(6)登录功能

目标:封装api登录接口,实现登录功能

步骤分析:

(7)响应拦截器统一处理错误提示

目标:通过响应拦截器,统一处理接口的错误提示

(8)登录权证信息存储

目标:vuex 构建 user 模块存储登录权证 (token & userId)

补充说明:

(9)storage存储模块 - vuex 持久化处理

目标:封装 storage 存储模块,利用本地存储,进行 vuex 持久化处理

(10)添加请求 loading 效果

目标:统一在每次请求后台时,添加 loading 效果

背景:有时候因为网络原因,一次请求的结果可能需要一段时间后才能回来, 此时,需要

给用户 添加 loading 提示。

添加 loading 提示的好处:

实操步骤:

**10.**页面访问拦截

目标:基于全局前置守卫,进行页面访问拦截处理

说明:智慧商城项目,大部分页面,游客都可以直接访问, 如遇到需要登录才能进行的操作,提示并跳转到登录 但是:对于支付页,订单页等,必须是登录的用户才能访问的,游客不能进入该页面,需要做拦截处理

**11.**首页 - 静态结构准备 & 动态渲染

目标:实现首页静态结构,封装接口,完成首页动态渲染

**12.**搜索 - 历史记录管理

目标:构建搜索页的静态布局,完成历史记录的管理

**13.**搜索列表 - 静态布局 & 动态渲染

目标:实现搜索列表页静态结构,封装接口,完成搜索列表页的渲染

14.商品详情- 静态布局 & 渲染

目标:实现商品详情静态结构,封装接口,完成商品详情页渲染

**15.**加入购物车 - 唤起弹层

目标:点击加入购物车,唤起弹层效果

16.加入购物车 - 封装数字框组件

目标:封装弹层中的数字框组件

17.加入购物车 - 判断 token 添加登录提示

目标:给未登录的用户,添加登录提示

**18.**加入购物车 - 封装接口进行请求

目标:封装接口,进行加入购物车的请求

**19.**购物车模块

说明:购物车 数据联动关系 较多,且通常会封装一些 小组件, 所以为了便于维护,一般都会将购物车的数据基于 vuex 分模块管理

**20.**订单结算台

说明:所有的结算,本质上就是 跳转到 "订单结算台",并且,跳转的同时,需要 携带上对应的订单相关参数, 具体需要哪些参数,基于 "订单结算台" 的需求来定。

(1)确认订单信息

目标:封装通用的订单信息确认接口

(2)购物车结算

目标:购物车结算跳转,传递参数,调用接口渲染订单结算台

(3)立即购买结算

目标:购物车结算跳转,传递参数,调用接口渲染订单结算台

21.提交订单并支付

目标:封装 API 请求方法,提交订单并支付

**22.**订单管理 & 个人中心 (快速实现)

目标:基于笔记,快速实现 订单管理 和 个人中心 跑通流程

**23.**打包发布

(1)目标:明确打包的作用

说明:vue脚手架只是开发过程中,协助开发的工具,当真正开发完了 => 脚手架不参与上线

打包的作用: ① 将多个文件压缩合并成一个文件 ② 语法降级 ③ less sass ts 语法解析

(2)目标:打包的命令 和 配置

说明:vue脚手架工具已经提供了打包命令,直接使用即可。

命令:npm run build

结果:在项目的根目录会自动创建一个文件夹`dist`, dist中的文件就是打包后的文件,只需要

放到服务器中即可。

配置:默认情况下,需要放到服务器根目录打开,如果希望双击运行,需要配置publicPath

配成相对路径

24.打包优化:路由懒加载

目标:配置路由懒加载,实现打包优化

说明:当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同 的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。

一、Vue3的优势

二、create-vue搭建Vue3项目

**1.**认识 create-vue

create-vue是Vue官方新的脚手架工具,底层切换到了vite(下一代构建工具),为开发提供极速响应

**2.**使用create-vue创建项目

(1)前提环境条件

已安装 16.0 或更高版本的 Node.js

使用:node -v 查询

(2)创建一个Vue应用

npm init vue@latest

这一指令将会安装并执行 create-vue这一指令将会安装并执行 create-vue

三、熟悉项目目录和关键文件

项目目录和关键文件

四、组合式API - setup选项

**1.**setup选项的写法和执行时机

**2.**setup选项中写代码的特点

3.<script setup>语法糖

**4.**总结

javascript 复制代码
<!-- <script>
export default {
  // setup
  // 1. 执行时机,比beforeCreate要早
  // 2. setup函数中,获取不到this (this是undefined)
  // 3. 数据 和 函数,需要在 setup 最后 return 才能在模板中应用
  // 问题:每次都要return太麻烦了
  setup () {
    // console.log('set函数')

    // 数据
    const message = 'hello Vue3'
    // 函数
    const logMessage = () => {
      console.log(message)
    }

    return {
      message,
      logMessage
    }
  },
  beforeCreate () {
    console.log('beforeCreate函数')
  }
}
</script> -->

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


<template>
  <div>{{ message }}</div>
  <button @click="logMessage">按钮</button>
</template>

五、组合式API - reactive和ref函数

**1.**reactive()

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

(2)核心步骤:

从 vue 包中导入 reactive 函数

在 <script setup> 中执行 reactive 函数并传入类型为对象的初始值,并使用变量接收返回值

**2.**ref()

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

(2)核心步骤:

从 vue 包中导入 ref 函数

在 <script setup> 中执行 ref 函数并传入初始值,使用变量接收 ref 函数的返回值

**3.**总结

javascript 复制代码
<script setup>
// 1. reactive: 接收一个对象型的数据,返回一个响应式的对象
// 问题:如果是简单类型,怎么办呢?
// import { reactive } from 'vue'
// const state = reactive({
//   count: 100
// })
// const setCount = () => {
//   state.count++
// }

// 2. ref: 接收简单类型 或 复杂类型,返回一个响应式的对象
//    本质:是在原有传入数据的基础上,外层包了一层对象,包成了复杂类型
//    底层,包成复杂类型之后,再借助 reactive 实现的响应式
//    注意点:
//    1. 访问数据,需要通过 .value
//    2. 在 template 中, .value不需要加(帮我们扒了一层)

// 推荐: 以后声明数据,统一用 ref  => 统一了编码规范
import { ref } from 'vue'
const count = ref(0)
const setCount = () => {
  count.value++
}
</script>


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

六、组合式API - computed

**1.**computed计算属性函数

(1)计算属性基本思想和Vue2的完全一致,组合式API下的计算属性只是修改了写法

(2)核心步骤:

导入computed函数

执行函数 在回调参数中return基于响应式数据做计算的值,用变量接收

**2.**总结

javascript 复制代码
<script setup>
// const 计算属性 = computed(() => {
//    return 计算返回结果
// })

import { computed, ref } from 'vue'
// 声明数据
const list =ref([1, 2, 3, 4, 5, 6, 7, 8])
console.log(list)  // 对象
console.log(list.value)  // 数组


// 基于list派生一个计算属性,从list中过滤出 > 2
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>
    <button @click="addFn" type="button">修改</button>
  </div>
</template>

七、组合式API - watch

**1.**watch函数

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

俩个额外参数:1. immediate(立即执行) 2. deep(深度侦听)

**2.**基础使用 - 侦听单个数据

(1)导入watch函数

(2)执行watch函数传入要侦听的响应式数据(ref对象)和回调函数

**3.**基础使用 - 侦听多个数据

说明:同时侦听多个响应式数据的变化,不管哪个数据变化都需要执行回调

**4.**immediate

说明:在侦听器创建时立即触发回调, 响应式数据变化之后继续执行回调

**5.**deep

默认机制:通过watch监听的ref对象默认是浅层侦听的,直接修改嵌套的对象属性不会触发回调执

行,需要开启deep 选项

**6.**精确侦听对象的某个属性

需求:在不开启deep的前提下,侦听age的变化,只有age变化时才执行回调

**7.**总结

javascript 复制代码
<script setup>
import { ref, watch } from "vue";
const count = ref(0)
const nickname = ref('灰太狼')

const changeCount = () => {
  count.value++
}
const changeNickname = () => {
  nickname.value = '红太狼'
}

// 1. 监视单个数据的变化
//    watch(ref对象, (newValue, oldValue) => { ... })
// watch(count, (newValue, oldValue) => {
//   console.log(newValue, oldValue)
// })

// 2. 监视多个数据的变化
//    watch([ref对象1, ref对象2],(newArr, oldArr) => { ... })
// watch([count, nickname], (newArr, oldArr) => {
//   console.log(newArr, oldArr)
// })

// 3. immediate 立刻执行
// watch(count, (newValue, oldValue) => {
//   console.log(newValue, oldValue)
// }), {
//   immediate: true,
// }

// 4. deep 深度监视,默认 watch 进行的是 浅层监视
//    const ref1 = ref(简单类型) 可以直接监视
//    const ref2 = ref(复杂类型) 监视不到复杂类型内部数据的变化
const userInfo = ref({
  name: 'htl',
  age: 18
})
const setUserInfo = () => {
  // 修改了 userInfo.value 修改了对象的地址,才能监视到
  // userInfo.value = { name: 'hettl', age: 20 }
  userInfo.value.age++
}

// deep 深度监视
// watch(userInfo, (newValue) => {
//   console.log(newValue)
// }, {
//   deep: true
// })

// 5. 对于对象中的属性,进行监视
watch(() => userInfo.value.age, (newValue, oldValue) => {
  console.log(newValue, oldValue)
})
</script>


<template>
  <div>{{ count }}</div>
  <button @click="changeCount">改数字</button>
  <div>{{ nickname }}</div>
  <button @click="changeNickname">改昵称</button>
  <div>-----------------------</div>
  <div>{{ userInfo }}</div>
  <butto @click="setUserInfo">修改userInfo</button>
</template>

八、组合式API - 生命周期函数

**1.**Vue3的生命周期API (选项式 VS 组合式)

**2.**生命周期函数基本使用

(1)导入生命周期函数

(2)执行生命周期函数 传入回调

**3.**执行多次

生命周期函数是可以执行多次的,多次执行时传入的回调会在时机成熟时依次执行

**4.**总结

javascript 复制代码
<script setup>

// beforeCreate 和 created 的相关代码
// 一律放在 setup 中执行

import { onMounted } from "vue"

const getList = () => {
  setTimeout(() => {
    console.log('发送请求,获取数据')
  }, 2000)
}
// 一进入页面的请求
getList()

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

// 写成函数的调用方式,可以调用多次,并不会冲突,而是按照顺序依次输出
onMounted (() => {
  console.log('mounted生命周期函数 -逻辑2')
})
</script>


<template>
</template>

九、组合式API - 父子通信

**1.**组合式API下的父传子

(1)基本思想

父组件中给子组件绑定属性

子组件内部通过props选项接收

(2)defineProps 原理:就是编译阶段的一个标识,实际编译器解析时,遇到后会进行编译转换

**2.**组合式API下的子传父

基本思想

(1)父组件中给子组件标签通过@绑定事件

(2)子组件内部通过 emit 方法触发事件

**3.**总结

javascript 复制代码
<!-- 父组件 -->
<script setup>
// 父传子
// 1. 给子组件,添加属性的方式传值
// 2. 在子组件,通过props接收

// 子传父
// 1. 在子组件内部,emit触发事件(编译器宏获取)
// 2. 在父组件,通过 @ 监听

// 局部组件(导入进来就能用)
import { ref } from 'vue'
import SonCom from '@/components/son-com.vue'

const money = ref(100)
const getMoney = () => {
  money.value += 10
}
const changeFn = (newMoney) => {
  money.value = newMoney
}
</script>


<template>
  <div>
    <h3>
      父组件 - {{ money }}
      <button @click="getMoney">挣钱</button>
    </h3>
    <!-- 给子组件,添加属性方式传值 -->
    <SonCom 
    @changeMoney="changeFn"
    car="宝马车" 
    :money="money">
  </SonCom>
  </div>
</template>
javascript 复制代码
<script setup>
// 子组件
// 注意:由于写了 setup,所以无法直接配置 props 选项
// 所以:此处需要借助于"编译器宏"函数来接收子组件传递的数据
const props = defineProps({
  car: String,
  money: Number
})
const emit = defineEmits(['changeMoney'])
console.log(props.car)
console.log(props.money)

const buy = () => {
  // 需要 emit 触发事件
  emit('changeMoney', 5)
}
</script>


<template>
  <!-- 对于props传递过来的数据,模板中可以直接使用 -->
  <div class="son">
    我是子组件 - {{ car }} - {{ money }}
    <button @click="buy">花钱</button>
  </div>
</template>

<style scoped>
.son {
  border: 1px solid #000;
  padding: 30px;
}
</style>

十、组合式API - 模版引用

**1.**模板引用的概念

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

**2.**如何使用(以获取dom为例 组件同理)

(1)调用ref函数生成一个ref对象

(2)通过ref标识绑定ref对象到标签

**3.**defineExpose()

默认情况下在<script setup> 语法糖下组件内部的属性和方法是不开放给父组件访问的

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

**4.**总结

javascript 复制代码
<script setup>
import TestCom from '@/components/test-com.vue'
import { onMounted, ref } from 'vue'

// 模板引用(可以获取dom,也可以获取组件)
// 1. 调用ref函数,生成一个ref对象
// 2. 通过ref标识,进行绑定
// 3.通过ref对象.value即可访问到绑定的元素(必须渲染完成后,才能拿到)
const inp = ref(null)

// 生命周期钩子 onMounted
onMounted(() => {
  // console.log(inp.value)
  // inp.value.focus()
})

const clickFn = () => {
  inp.value.focus()
}

// ------------------------------
const testRef = ref(null)
const getCom = () => {
  console.log(testRef.value.count)
  testRef.value.sayHi()
}
</script>


<template>
  <div>
    <input ref="inp" type="text">
    <button @click="clickFn">点击让输入框聚焦</button>
  </div>
  <TestCom ref="testRef"></TestCom>
  <button @click="getCom">获取组件</button>
</template>

十一、组合式API - provide和inject

**1.**作用和场景

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

**2.**跨层传递普通数据

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

(2)底层组件通过inject函数获取数据

**3.**跨层传递响应式数据

在调用provide函数时,第二个参数设置为ref对象

(1)顶层组件

(2)底层组件

**4.**跨层传递方法

顶层组件可以向底层组件传递方法,底层组件调用方法修改顶层组件中的数据

(1)顶层组件

(2)底层组件

**5.**需求解决思考

**6.**总结

javascript 复制代码
<script setup>
import CenterCom from '@/components/center-com.vue'
import { provide } from 'vue'

// 1. 跨层传递普通数据
provide('theme-color', 'blue')
// 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>
javascript 复制代码
<script setup>
import { inject } from 'vue'

const themColor = inject('theme-color')
const count = inject('count')
const changeCount = inject('changeCount')
const clickFn = () => {
  changeCount(1000)
}
</script>


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

十二、Vue3.3新特性-defineOptions

十三、Vue3.3新特性-defineModel

Vue3 中的 v-model 和 defineModel

javascript 复制代码
<script setup>
import MyInput from '@/components/my-input.vue'
import { ref } from 'vue'
const txt = ('123456')
</script>


<template>
  <div>
    <MyInput v-model="txt"></MyInput>
    {{ txt }}
  </div>
</template>

一、Pinia 快速入门

**1.**什么是Pinia

Pinia 是 Vue 的最新 状态管理工具 ,是 Vuex 的 替代品

(1)提供更加简单的API (去掉了 mutation )

(2)提供符合,组合式风格的API (和 Vue3 新语法统一)

(3)去掉了 modules 的概念,每一个 store 都是一个独立的模块

(4)配合 TypeScript 更加友好,提供可靠的类型推断

**2.**手动添加Pinia到Vue项目

(1)使用 Vite 创建一个空的 Vue3 项目

npm create vue@latest

(2)按照官方文档 安装 pinia 到项目中

**3.**Pinia基础使用 - 计数器案例

(1)定义store

(2)组件使用store

**4.**getters实现

Pinia中的 getters 直接使用 computed函数 进行模拟, 组件中需要使用需要把 getters return出去

**5.**action异步实现

(1)编写方式:异步action函数的写法和组件中获取异步数据的写法完全一致

(2)接口地址:http://geek.itheima.net/v1_0/channels

(3)需求:在Pinia中获取频道列表数据并把数据渲染App组件的模板中

**6.**storeToRefs工具函数

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

**7.**Pinia的调试

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

**8.**Pinia持久化插件

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

(2)安装插件 pinia-plugin-persistedstate

npm i pinia-plugin-persistedstate

(3)main.js 使用

import persist from 'pinia-plugin-persistedstate'

...

app.use(createPinia().use(persist))

(4)store仓库中,persist: true 开启

**9.**总结

二、Vue3 大事件管理系统

**1.**大事件项目介绍 和 创建

(1)Vue3 大事件管理系统

①在线演示:https://fe-bigevent-web.itheima.net/login

②接口文档: https://apifox.com/apidoc/shared-26c67aee-0233-4d23-aab7-08448fdf95ff/api-93850835

③基地址:http://big-event-vue-api-t.itheima.net

(2)pnpm 包管理器 - 创建项目

一些优势:比同类工具快2倍左右、节省磁盘空间... https://www.pnpm.cn/

安装方式:npm install -g pnpm

创建项目:pnpm create vue

(3)创建项目

① 进入项目目录 ② 安装依赖 ③ 启动项目

=>

**2.**Eslint 配置代码风格

配置文件 .eslintrc.cjs

(1)prettier 风格配置 https://prettier.io

① 单引号 ② 不使用分号 ③ 宽度80字符

④ 不加对象|数组最后逗号 ⑤ 换行符号不限制(win mac 不一致)

(2)vue组件名称多单词组成(忽略index.vue)

(3)props解构(关闭)

提示:安装Eslint且配置保存修复,不 要开启默认的自动保存格式化

**3.**配置代码检查工作流

(1)提交前做代码检查

① 初始化 git 仓库,执行 git init 即可

② 初始化 husky 工具配置,执行pnpm dlx husky-init && pnpm install即可

https://typicode.github.io/husky/

③ 修改 .husky/pre-commit 文件

问题:pnpm lint 是全量检查,耗时问题,历史问题

(2)暂存区 eslint 校验

① 安装 lint-staged 包pnpm i lint-staged -D

② package.json 配置 lint-staged 命令

③ .husky/pre-commit 文件修改

=>

(3)总结

**4.**目录调整

**5.**vue-router4 路由代码解析

(1)路由初始化

=>

  1. 创建路由实例由 createRouter 实现

  2. 路由模式

① history 模式使用 createWebHistory()

② hash 模式使用 createWebHashHistory()

③参数是基础路径,默认/

(2)总结

创建一个路由实例,路由模式是history模式,路由的基础地址是 vite.config.js中的 base 配置的值,默认是 / , 环境变量地址:https://cn.vitejs.dev/guide/env-and-mode.html

**6.**引入 Element Plus 组件库

(1)按需引入 Element Plus

① 安装: pnpm add element-plus

② 配置按需导入:

官方文档:https://element-plus.org/zh-CN/guide/quickstart.html

③ 直接使用组件

④ 彩蛋:默认 components 下的文件也会被 自动注册~

**7.**Pinia 构建仓库 和 持久化

**8.**Pinia 仓库统一管理

(1)pinia 独立维护

现在:初始化代码在 main.js 中,仓库代码在 stores 中,代码分散职能不单一

优化:由 stores 统一维护,在 stores/index.js 中完成 pinia 初始化,交付 main.js 使用

(2)仓库 统一导出

现在:使用一个仓库 import { useUserStore } from `./stores/user.js` 不同仓库路径不一致

优化:stores/index.js统一导出,导入路径统一`./stores`,而且仓库维护在stores/modules 中

**9.**数据交互 - 请求工具设计

**10.**整体路由设计

1.登录注册页面 [element-plus 表单 & 表单校验]

功能需求说明:

(1)注册登录 静态结构 & 基本切换

(2)注册功能 (校验 + 注册)

(3)登录功能 (校验 + 登录 + 存token)

2.首页 layout 架子 [element-plus 菜单组件]

功能需求说明:

(1)基本架子拆解 (菜单组件的使用)

(2)登录访问拦截

(3)用户基本信息获取&渲染

(4)退出功能 [element-plus 确认框]

3.文章分类页面 - [element-plus 表格]

功能需求说明:

(1)基本架子 - PageContainer 封装

(2)文章分类渲染 & loading 处理

(3)文章分类添加编辑 [element-plus 弹层]

(4)文章分类删除

源代码见C:\Users\lin\day13-big-event-admin

javascript 复制代码
<template>
  <div class="carousel-container">
    <!-- 轮播图区域 -->
    <div class="carousel-slides" @mouseenter="stopAutoPlay" @mouseleave="startAutoPlay">
      <!-- 根据 currentIndex 显示当前图片 -->
      <div
        v-for="(item, index) in data"
        :key="index"
        class="slide"
        v-show="currentIndex === index"
        :style="{ backgroundColor: item.color }"   <!-- 动态背景色 -->
      >
        <img :src="item.url" :alt="item.title" />
        <div class="title">{{ item.title }}</div>
      </div>

      <!-- 左右按钮 -->
      <button class="carousel-btn prev" @click="prevSlide">❮</button>
      <button class="carousel-btn next" @click="nextSlide">❯</button>

      <!-- 指示器小圆点(显示标题) -->
      <div class="indicators">
        <span
          v-for="(item, index) in data"
          :key="index"
          class="indicator"
          :class="{ active: currentIndex === index }"
          :style="{ backgroundColor: currentIndex === index ? item.color : '#ccc' }"
          @click="goToSlide(index)"
        >
          {{ item.title }}
        </span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

// 你提供的数据(已修正最后一项的括号)
const data = [
  { url: './image/1.jpg', title: '111', color: 'green' },
  { url: './image/2.jpg', title: '222', color: 'yellow' },
  { url: './image/3.jpg', title: '333', color: 'blue' },
  { url: './image/4.jpg', title: '444', color: 'pink' },
  { url: './image/5.jpg', title: '555', color: 'red' },
  { url: './image/6.jpg', title: '666', color: 'brown' },
  { url: './image/7.jpg', title: '777', color: 'grey' },
  { url: './image/8.jpg', title: '888', color: 'skyblue' }
];

// 当前显示第几张(从0开始)
const currentIndex = ref(0);
let timer = null;

// 下一张
const nextSlide = () => {
  if (currentIndex.value === data.length - 1) {
    currentIndex.value = 0;
  } else {
    currentIndex.value++;
  }
};

// 上一张
const prevSlide = () => {
  if (currentIndex.value === 0) {
    currentIndex.value = data.length - 1;
  } else {
    currentIndex.value--;
  }
};

// 跳转到指定图片
const goToSlide = (index) => {
  currentIndex.value = index;
};

// 自动播放
const startAutoPlay = () => {
  if (timer) return;
  timer = setInterval(() => {
    nextSlide();
  }, 3000);
};

const stopAutoPlay = () => {
  if (timer) {
    clearInterval(timer);
    timer = null;
  }
};

// 组件挂载后启动自动播放,卸载时清除定时器
onMounted(() => {
  startAutoPlay();
});

onUnmounted(() => {
  stopAutoPlay();
});
</script>

<style scoped>
.carousel-container {
  width: 800px;
  margin: 0 auto;
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.carousel-slides {
  position: relative;
  width: 100%;
  background-color: #f0f0f0;
}

.slide {
  width: 100%;
  transition: opacity 0.5s ease-in-out;
  position: relative;
  text-align: center;
}

.slide img {
  width: 100%;
  height: auto;
  display: block;
}

.title {
  position: absolute;
  bottom: 20px;
  left: 20px;
  background-color: rgba(0,0,0,0.6);
  color: white;
  padding: 8px 16px;
  border-radius: 4px;
  font-size: 18px;
}

/* 左右按钮 */
.carousel-btn {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  background-color: rgba(0, 0, 0, 0.5);
  color: white;
  border: none;
  font-size: 24px;
  padding: 10px 16px;
  cursor: pointer;
  border-radius: 50%;
  transition: background-color 0.3s;
}

.carousel-btn:hover {
  background-color: rgba(0, 0, 0, 0.8);
}

.prev {
  left: 20px;
}

.next {
  right: 20px;
}

/* 指示器(带标题的小圆点) */
.indicators {
  position: absolute;
  bottom: 20px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 12px;
}

.indicator {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  color: white;
  cursor: pointer;
  transition: all 0.3s;
  background-color: #ccc;
}

.indicator.active {
  transform: scale(1.1);
  box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
</style>
相关推荐
普通网友6 分钟前
JavaScript:ESLint+Prettier 规范代码格式
开发语言·javascript·ecmascript
jiayong2320 分钟前
第 38 课:任务列表里高亮当前正在查看详情的任务
开发语言·前端·javascript·vue.js·学习
anOnion40 分钟前
构建无障碍组件之Spinbutton Pattern
前端·html·交互设计
程序员Better1 小时前
前端成功转型AI全栈,我踩过的坑都替你填上了
前端·后端·ai编程
兔子零10241 小时前
GPT-5.5 与 DeepSeek-V4:大模型竞争的本质,正在从“谁更强”变成“谁让成本更低”
前端·javascript·后端
Daybreak1 小时前
幽灵依赖:本地跑得好好的,线上部署却炸了
前端
无心使然云中漫步1 小时前
Openlayers调用ArcGis地图服务之一 —— 地图切片(/tile)
前端·arcgis·vue·数据可视化
火山口车神丶1 小时前
如何借助AI进行模块封装DIY
javascript·人工智能·算法
angushine1 小时前
Python常用方法
开发语言·前端·python
C澒1 小时前
AI 生码 - D2C:Figma to Code 全流程实现
前端·低代码·ai编程·figma