Vue2:组件基础(上)

简介: Vue2:组件基础(上)

Vue2:组件基础(上)

Date: July 29, 2023

Sum: 生命周期、Vue-cli、组件的使用、小黑记账清单、小兔鲜首页


生命周期:

生命周期介绍

思考:


什么时候可以发送初始化渲染请求?(越早越好)


什么时候可以开始操作dom?(至少dom得渲染出来)


Vue生命周期:就是一个Vue实例从创建 到 销毁 的整个过程。


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


1.创建阶段:创建响应式数据


将普通数据转换成响应式数据


2.挂载阶段:渲染模板


结合数据渲染模版


3.更新阶段:修改数据,更新视图


数据修改与更新视图循环


4.销毁阶段:销毁Vue实例

c142fe46e012812ede49a010e79927c2.png

注意:创建和挂载阶段只有一次,但是更新阶段会循环多次


理解:了解了Vue的生命周期后,可以理解在何时去调用相应的代码


比如在创建阶段结束之后,我们才能发送初始化的渲染请求。在挂载阶段结束之后,才能操作dom


生命周期钩子

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

ebaee5b80d56dcfe5e031fe0128f07f2.png

注意:挂载阶段在mounted完成后,模版已经渲染完成了


注意:卸载阶段在你关闭浏览器时调用


案例:

fc8c6fafbba0e48440204f1955d074a5.png

Code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <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>
</body>
</html>

注:在生命周期中销毁后,以上的组件即使去按+或-也没有用了,因为以上DOM已于Vue无关了。


补充:在浏览器console中输入

app.$destory()

即可触发 beforeDestory 和 destroyed


生命周期案例

案例1:


需求:一进页面就发送请求,去获取对应的数据,来渲染出来


思考:在created钩子中发送初始化渲染请求, 采用axios方式请求数据

83c050ffe3101bc1dfb6a5cee15869d8.png

Code:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      list-style: none;
    }
    .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>
</head>
<body>
  <div id="app">
    <ul>
      <li v-for="(item, index) in list" :key="item.id" class="news">
        <div class="left">
          <div class="title">{{ item.title }}</div>
          <div class="info">
            <span>{{ item.source }}</span>
            <span>{{ item.time }}</span>
          </div>
        </div>
        <div class="right">
          <img :src="item.img" alt="">
        </div>
      </li>
    </ul>
  </div>
  <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>
    // 接口地址:http://hmajax.itheima.net/api/news
    // 请求方式:get
    const app = new Vue({
      el: '#app',
      data: {
        list: []
      },
      async created () {
        // 1. 发送请求获取数据
        const res = await axios.get('http://hmajax.itheima.net/api/news')
        // 2. 更新到 list 中,用于页面渲染 v-for
        this.list = res.data.data
      }
    })
  </script>
</body>
</html>

案例2:


需求:一进页面就获取焦点


思考:在mounted钩子上获取焦点

fa784127b5f12d688eede3ecfe9cb428.png

注意:挂载阶段在mounted完成后,模版已经渲染完成了


Code:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>示例-获取焦点</title>
  <!-- 初始化样式 -->
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reset.css@2.0.2/reset.min.css">
  <!-- 核心样式 -->
  <style>
    html,
    body {
      height: 100%;
    }
    .search-container {
      position: absolute;
      top: 30%;
      left: 50%;
      transform: translate(-50%, -50%);
      text-align: center;
    }
    .search-container .search-box {
      display: flex;
    }
    .search-container img {
      margin-bottom: 30px;
    }
    .search-container .search-box input {
      width: 512px;
      height: 16px;
      padding: 12px 16px;
      font-size: 16px;
      margin: 0;
      vertical-align: top;
      outline: 0;
      box-shadow: none;
      border-radius: 10px 0 0 10px;
      border: 2px solid #c4c7ce;
      background: #fff;
      color: #222;
      overflow: hidden;
      box-sizing: content-box;
      -webkit-tap-highlight-color: transparent;
    }
    .search-container .search-box button {
      cursor: pointer;
      width: 112px;
      height: 44px;
      line-height: 41px;
      line-height: 42px;
      background-color: #ad2a27;
      border-radius: 0 10px 10px 0;
      font-size: 17px;
      box-shadow: none;
      font-weight: 400;
      border: 0;
      outline: 0;
      letter-spacing: normal;
      color: white;
    }
    body {
      background: no-repeat center /cover;
      background-color: #edf0f5;
    }
  </style>
</head>
<body>
<div class="container" id="app">
  <div class="search-container">
    <img src="https://www.itheima.com/images/logo.png" alt="">
    <div class="search-box">
      <input type="text" v-model="words" id="inp">
      <button>搜索一下</button>
    </div>
  </div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
  const app = new Vue({
    el: '#app',
    data: {
      words: ''
    },
    // 核心思路:
    // 1. 等input框渲染出来 mounted 钩子
    // 2. 让input框获取焦点 inp.focus()
    mounted () {
      document.querySelector('#inp').focus()
    }
  })
</script>
</body>
</html>

综合案例-小黑记账清单

1-需求图示

de4e298de8565e9bac5f70f236e19203.png

2-需求分析


1.基本渲染


2.添加功能


3.删除功能


4.饼图渲染


3-思路分析


1.基本渲染


立刻发送请求获取数据 created

拿到数据,存到data的响应式数据中

结合数据,进行渲染 v-for

消费统计 —> 计算属性


2.添加功能


▪收集表单数据 v-model,使用指令修饰符处理数据

▪给添加按钮注册点击事件,对输入的内容做非空判断,发送请求

▪请求成功后,对文本框内容进行清空

▪重新渲染列表


3.删除功能


▪注册点击事件,获取当前行的id

▪根据id发送删除请求

▪需要重新渲染

4.饼图渲染


▪初始化一个饼图 echarts.init(dom) mounted钩子中渲染

▪根据数据试试更新饼图 echarts.setOptions({…})

affed8215a59e09b80f2858e108c85b1.png

实现基本渲染功能:


Code: 实现基本渲染

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- CSS only -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
      .red {
        color: red!important;
      }
      .search {
        width: 300px;
        margin: 20px 0;
      }
      .my-form {
        display: flex;
        margin: 20px 0;
      }
      .my-form input {
        flex: 1;
        margin-right: 20px;
      }
      .table > :not(:first-child) {
        border-top: none;
      }
      .contain {
        display: flex;
        padding: 10px;
      }
      .list-box {
        flex: 1;
        padding: 0 30px;
      }
      .list-box  a {
        text-decoration: none;
      }
      .echarts-box {
        width: 600px;
        height: 400px;
        padding: 30px;
        margin: 0 auto;
        border: 1px solid #ccc;
      }
      tfoot {
        font-weight: bold;
      }
      @media screen and (max-width: 1000px) {
        .contain {
          flex-wrap: wrap;
        }
        .list-box {
          width: 100%;
        }
        .echarts-box {
          margin-top: 30px;
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">
          <!-- 添加资产 -->
          <form class="my-form">
            <input type="text" class="form-control" placeholder="消费名称" />
            <input type="text" class="form-control" placeholder="消费价格" />
            <button type="button" class="btn btn-primary">添加账单</button>
          </form>
          <table class="table table-hover">
            <thead>
              <tr>
                <th>编号</th>
                <th>消费名称</th>
                <th>消费价格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ index + 1 }}</td>
                <td>{{ item.name }}</td>
                <td :class="{ red: item.price > 500}">{{ item.price.toFixed(2) }}</td>
                <td><a href="javascript:;">删除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="../echarts.min.js"></script>
    <script src="../vue.js"></script>
    <script src="../axios.js"></script>
    <script>
      /**
       * 接口文档地址:
       * https://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
       * 
       * 功能需求:
       * 1. 基本渲染
       * 2. 添加功能
       * 3. 删除功能
       * 4. 饼图渲染
       */
      const app = new Vue({
        el: '#app',
        data: {
          list: [],
        },
        computed: {
          totalPrice() {
            return this.list.reduce((sum, item) => sum += item.price, 0)
          }
        },
        async created() {
          // const res = await axios({
          //   url: 'https://applet-base-api-t.itheima.net/bill',
          //   method: 'get',
          //   params: {
          //     creator: 'Nathan',
          //   }
          // })
          const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
            params: {
              creator: 'Nathan'
            }
          })
          this.list = res.data.data
        }
      })
    </script> 
  </body>
</html>

实现添加功能:

2a41c2ef9aace907377f3cda3be3719e.png



重点:提交完数据后需要重新axios请求数据渲染页面,以下是建议封装一下axios请求


注意一下get和post的两种请求格式:


get后面的{}中需要加 params

async getList() {
  const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
    params: {
      creator: 'Nathan'
    }
  })
  this.list = res.data.data
},

post后面的{}中不需要加 params

async add() {
  if(!this.name) {
    alert('请输入消费名称')
    return
  }
  if(typeof this.price !== 'number') {
    alert('请输入正确的价格')
  }
  const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
    creator: 'Nathan',
    name: this.name,
    price: this.price,
  })
  this.getList()
  this.name = ''
  this.price = ''
}

Code:


         

实现删除功能:


Code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- CSS only -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
      .red {
        color: red!important;
      }
      .search {
        width: 300px;
        margin: 20px 0;
      }
      .my-form {
        display: flex;
        margin: 20px 0;
      }
      .my-form input {
        flex: 1;
        margin-right: 20px;
      }
      .table > :not(:first-child) {
        border-top: none;
      }
      .contain {
        display: flex;
        padding: 10px;
      }
      .list-box {
        flex: 1;
        padding: 0 30px;
      }
      .list-box  a {
        text-decoration: none;
      }
      .echarts-box {
        width: 600px;
        height: 400px;
        padding: 30px;
        margin: 0 auto;
        border: 1px solid #ccc;
      }
      tfoot {
        font-weight: bold;
      }
      @media screen and (max-width: 1000px) {
        .contain {
          flex-wrap: wrap;
        }
        .list-box {
          width: 100%;
        }
        .echarts-box {
          margin-top: 30px;
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">
          <!-- 添加资产 -->
          <form class="my-form">
            <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
            <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
            <button @click="add" type="button" class="btn btn-primary">添加账单</button>
          </form>
          <table class="table table-hover">
            <thead>
              <tr>
                <th>编号</th>
                <th>消费名称</th>
                <th>消费价格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ index + 1 }}</td>
                <td>{{ item.name }}</td>
                <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <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://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
       * 
       * 功能需求:
       * 1. 基本渲染
       *    (1) 立刻发送请求获取数据 created
       *    (2) 拿到数据,存到data的响应式数据中
       *    (3) 结合数据,进行渲染 v-for
       *    (4) 消费统计 => 计算属性
       * 2. 添加功能
       *    (1) 收集表单数据 v-model
       *    (2) 给添加按钮注册点击事件,发送添加请求
       *    (3) 需要重新渲染
       * 3. 删除功能
       *    (1) 注册点击事件,传参传 id
       *    (2) 根据 id 发送删除请求
       *    (3) 需要重新渲染
       * 4. 饼图渲染
       */
      const app = new Vue({
        el: '#app',
        data: {
          list: [],
          name: '',
          price: ''
        },
        computed: {
          totalPrice () {
            return this.list.reduce((sum, item) => sum + item.price, 0)
          }
        },
        created () {
          // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
          //   params: {
          //     creator: '小黑'
          //   }
          // })
          // this.list = res.data.data
          this.getList()
        },
        methods: {
          async getList () {
            const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
              params: {
                creator: '小黑'
              }
            })
            this.list = res.data.data
          },
          async add () {
            if (!this.name) {
              alert('请输入消费名称')
              return
            }
            if (typeof this.price !== 'number') {
              alert('请输入正确的消费价格')
              return
            }
            // 发送添加请求
            const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
              creator: '小黑',
              name: this.name,
              price: this.price
            })
            // 重新渲染一次
            this.getList()
            this.name = ''
            this.price = ''
          },
          async del (id) {
            // 根据 id 发送删除请求
            const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
            // 重新渲染
            this.getList()
          }
        }
      })
    </script>
  </body>
</html>

饼图渲染功能 :


关键:


1-在mounted处利用this把mychart绑定到Vue对象实例上

mounted() {
  // 利用 this 暂时存取一下myChart
  this.myChart = echarts.init(document.querySelector('#main'))
  // 指定图表的配置项和数据
  var option = {
    title: {
      text: 'Referer of a Website',
      subtext: 'Fake Data',
      left: 'center'
    },
    tooltip: {
      trigger: 'item'
    },
    legend: {
      orient: 'vertical',
      left: 'left'
    },
    series: [
      {
        name: 'Access From',
        type: 'pie',
        radius: '50%',
        data: [
          { value: 1048, name: 'Search Engine' },
          { value: 735, name: 'Direct' },
          { value: 580, name: 'Email' },
          { value: 484, name: 'Union Ads' },
          { value: 300, name: 'Video Ads' }
        ],
        emphasis: {
          itemStyle: {
            shadowBlur: 10,
            shadowOffsetX: 0,
            shadowColor: 'rgba(0, 0, 0, 0.5)'
          }
        }
      }
    ]
  };
  // 使用刚指定的配置项和数据显示图表。
  this.myChart.setOption(option);
}

2-引入echarts后,我们需要对getList()下的内容进行重新渲染,加上myChart.setOption,其关键点在于需要使用this来进行上下绑定。

this.myChart.setOption({
  series: [
    {
      // data: [
      //   { value: 1048, name: 'Search Engine' },
      //   { value: 735, name: 'Direct' },
      // ],
      data: this.list.map(item => ({ value: item.price, name: item.name }))
    }
  ]
})

Code:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <!-- CSS only -->
    <link
      rel="stylesheet"
      href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
    />
    <style>
      .red {
        color: red!important;
      }
      .search {
        width: 300px;
        margin: 20px 0;
      }
      .my-form {
        display: flex;
        margin: 20px 0;
      }
      .my-form input {
        flex: 1;
        margin-right: 20px;
      }
      .table > :not(:first-child) {
        border-top: none;
      }
      .contain {
        display: flex;
        padding: 10px;
      }
      .list-box {
        flex: 1;
        padding: 0 30px;
      }
      .list-box  a {
        text-decoration: none;
      }
      .echarts-box {
        width: 600px;
        height: 400px;
        padding: 30px;
        margin: 0 auto;
        border: 1px solid #ccc;
      }
      tfoot {
        font-weight: bold;
      }
      @media screen and (max-width: 1000px) {
        .contain {
          flex-wrap: wrap;
        }
        .list-box {
          width: 100%;
        }
        .echarts-box {
          margin-top: 30px;
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <div class="contain">
        <!-- 左侧列表 -->
        <div class="list-box">
          <!-- 添加资产 -->
          <form class="my-form">
            <input v-model.trim="name" type="text" class="form-control" placeholder="消费名称" />
            <input v-model.number="price" type="text" class="form-control" placeholder="消费价格" />
            <button @click="add" type="button" class="btn btn-primary">添加账单</button>
          </form>
          <table class="table table-hover">
            <thead>
              <tr>
                <th>编号</th>
                <th>消费名称</th>
                <th>消费价格</th>
                <th>操作</th>
              </tr>
            </thead>
            <tbody>
              <tr v-for="(item, index) in list" :key="item.id">
                <td>{{ index + 1 }}</td>
                <td>{{ item.name }}</td>
                <td :class="{ red: item.price > 500 }">{{ item.price.toFixed(2) }}</td>
                <td><a @click="del(item.id)" href="javascript:;">删除</a></td>
              </tr>
            </tbody>
            <tfoot>
              <tr>
                <td colspan="4">消费总计: {{ totalPrice.toFixed(2) }}</td>
              </tr>
            </tfoot>
          </table>
        </div>
        <!-- 右侧图表 -->
        <div class="echarts-box" id="main"></div>
      </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/echarts@5.4.0/dist/echarts.min.js"></script>
    <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://www.apifox.cn/apidoc/shared-24459455-ebb1-4fdc-8df8-0aff8dc317a8/api-53371058
       * 
       * 功能需求:
       * 1. 基本渲染
       *    (1) 立刻发送请求获取数据 created
       *    (2) 拿到数据,存到data的响应式数据中
       *    (3) 结合数据,进行渲染 v-for
       *    (4) 消费统计 => 计算属性
       * 2. 添加功能
       *    (1) 收集表单数据 v-model
       *    (2) 给添加按钮注册点击事件,发送添加请求
       *    (3) 需要重新渲染
       * 3. 删除功能
       *    (1) 注册点击事件,传参传 id
       *    (2) 根据 id 发送删除请求
       *    (3) 需要重新渲染
       * 4. 饼图渲染
       *    (1) 初始化一个饼图 echarts.init(dom)  mounted钩子实现
       *    (2) 根据数据实时更新饼图 echarts.setOption({ ... })
       */
      const app = new Vue({
        el: '#app',
        data: {
          list: [],
          name: '',
          price: ''
        },
        computed: {
          totalPrice () {
            return this.list.reduce((sum, item) => sum + item.price, 0)
          }
        },
        created () {
          // const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
          //   params: {
          //     creator: '小黑'
          //   }
          // })
          // this.list = res.data.data
          this.getList()
        },
        mounted () {
          this.myChart = echarts.init(document.querySelector('#main'))
          this.myChart.setOption({
            // 大标题
            title: {
              text: '消费账单列表',
              left: 'center'
            },
            // 提示框
            tooltip: {
              trigger: 'item'
            },
            // 图例
            legend: {
              orient: 'vertical',
              left: 'left'
            },
            // 数据项
            series: [
              {
                name: '消费账单',
                type: 'pie',
                radius: '50%', // 半径
                data: [
                  // { value: 1048, name: '球鞋' },
                  // { value: 735, name: '防晒霜' }
                ],
                emphasis: {
                  itemStyle: {
                    shadowBlur: 10,
                    shadowOffsetX: 0,
                    shadowColor: 'rgba(0, 0, 0, 0.5)'
                  }
                }
              }
            ]
          })
        },
        methods: {
          async getList () {
            const res = await axios.get('https://applet-base-api-t.itheima.net/bill', {
              params: {
                creator: '小黑'
              }
            })
            this.list = res.data.data
            // 更新图表
            this.myChart.setOption({
              // 数据项
              series: [
                {
                  // data: [
                  //   { value: 1048, name: '球鞋' },
                  //   { value: 735, name: '防晒霜' }
                  // ]
                  data: this.list.map(item => ({ value: item.price, name: item.name}))
                }
              ]
            })
          },
          async add () {
            if (!this.name) {
              alert('请输入消费名称')
              return
            }
            if (typeof this.price !== 'number') {
              alert('请输入正确的消费价格')
              return
            }
            // 发送添加请求
            const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
              creator: '小黑',
              name: this.name,
              price: this.price
            })
            // 重新渲染一次
            this.getList()
            this.name = ''
            this.price = ''
          },
          async del (id) {
            // 根据 id 发送删除请求
            const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
            // 重新渲染
            this.getList()
          }
        }
      })
    </script>
  </body>
</html>

注意事项:


1-如果在箭头函数中需要返回一个对象,那么需要在{}外在加上一对()

data: this.list.map(item => ({ value: item.price, name: item.name }))

工程化开发入门

工程化开发和脚手架

工程化开发模式

概念:


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


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

918f1d9cb775b4ecc6b1d1c59aa6863b.png

根据上图,通过工程化开发模式能够将不同的源码转成不同版本的js代码


工程化开发模式优点:


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


工程化开发模式问题:


webpack配置不简单

雷同的基础配置

缺乏统一的标准


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


脚手架 Vue-cli

**概念:**vue-cli是 vue 官方提供的、快速生成 vue 工程化项目的工具。【集成了webpack配置】


特点:

1.开箱即用,零配置

2.内置babel等工具

3.标准化的webpack配置


标准化:你的不同项目可以使用相同的架子


vue-cli 的中文官网首页:https://cli.vuejs.org/zh/


Vue-cli使用步骤

安装 vue-cli

vue-cli 是基于 Node.js 开发出来的工具,因此需要使用 npm 将它安装为全局可用的工具:

# 全局安装 vue-cli
npm install -g @vue/cli
# 查看 vue-cli 的版本,检查 vue-cli 是否安装成功
vue --version

解决 Windows PowerShell 不识别 vue 命令的问题:


默认情况下,在PowerShell 中执行 vue --version 命令会提示如下的错误消息:

3dee6cbfe92ccb53853a4550d6f76241.png

解决方案如下:


① 以管理员身份运行 PowerShell


② 执行 set-ExecutionPolicy RemoteSigned 命令


③ 输入字符 Y ,回车即可

9bf226289df70f7fad219106f5b11542.png

创建项目

vue-cli 提供了创建项目的两种方式:

# 基于 [命令行] 的方式创建 vue 项目
vue create 项目名称
# OR
# 基于 [可视化面板] 创建 vue 项目
vue ui

基于 vue ui 创建 vue 项目

步骤1:在终端下运行 vue ui 命令,自动在浏览器中打开创建项目的可视化面板:

e48514eea59bb22970bb911ac260048e.png

步骤2:在详情页面填写项目名称

53f08e5caafbab332383e2d2f9934a73.png

步骤3:在预设页面选择手动配置项目:

9d1e388b809dc1811ab19c95eb672d68.png

步骤4:在功能页面勾选需要安装的功能(Choose Vue Version、Babel、CSS 预处理器、使用配置文件):

2d02f7aea38272aa2ffdd6b2333c7c77.png

注意:最后一项是把插件的配置信息单独存放到一个配置文件


步骤5:在配置页面勾选 vue 的版本和需要的预处理器:

18af7366a6325a82e080a48b16c76b56.png

步骤6:将刚才所有的配置保存为预设(模板),方便下一次创建项目时直接复用之前的配置:

2cec6cb42f4cedda31605b2e5e87b252.png

步骤7:创建项目并自动安装依赖包:

8c2d240194e362188ebf0b6546690022.png

vue ui 的本质:通过可视化的面板采集到用户的配置信息后,在后台基于命令行的方式自动初始化项目:

b5771f08fc6b62e54b3743ccd50c75a9.png

注意:预设在.vuerc配置文件中


项目创建完成后,自动进入项目仪表盘:

94581314358699efd28524bc10970e1c.png

基于命令行创建 vue 项目

步骤1:在终端下运行 vue create demo2 命令,基于交互式的命令行创建 vue 的项目:

2639050e9671d4fdd974730c642adcb1.png

步骤2:选择要安装的功能:

52b2557936871bad42bbfb38ca6eacbd.png

步骤3:使用上下箭头选择 vue 的版本,并使用回车键确认选择:

867611746ac83c2c0b25d753b61b9f9a.png

步骤4:使用上下箭头选择要使用的 css 预处理器,并使用回车键确认选择:

39664b3636337753899f6f7f1b272109.png

步骤5:使用上下箭头选择如何存储插件的配置信息,并使用回车键确认选择

7b45c237ce15149a00533cf2f2d82562.png

步骤6:是否将刚才的配置保存为预设:

e46ae8d4c741a7609f7a7d5e47243ecb.png

步骤7:选择如何安装项目中的依赖包

08d8db3679c6d11edc6dce796d193c4e.png

步骤8:开始创建项目并自动安装依赖包

51d60b4538ced08955852889899cf022.png

步骤9:项目创建完成:

ae3b9df4a8e738a0a72d0686c7beb319.png

总结安装流程:

流程:

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

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

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

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


根据下图中的红框处修改:

理解:


前两步搞定之后,以后创建Vue项目就只需要3和4两步来创建项目了


项目目录介绍和运行流程

项目目录介绍:

d02ab0aaac308f94d777ef3422d0f5d1.png

虽然脚手架中的文件有很多,目前咱们只需认识三个文件即可


main.js 入口文件

App.vue App根组件

index.html 模板文件

运行流程及作用:

main.js作用:导入 App.vue, 并基于 App.vue 创建结构渲染 index.html


App.vue:决定页面的呈现内容

be34169a1660cc0d7642336cba89aeb4.png

main.js文件详解:

// 1. 导入 Vue 核心包
import Vue from 'vue'
// 2. 导入 App.vue 根组件
import App from './App.vue'
// 提示:当前处于什么环境(生产环境 / 开发环境)
Vue.config.productionTip = false
// 3. Vue实例化,提供render方法 -> 基于App.vue创建结构并渲染index.html
new Vue({
  render: h => h(App),
}).$mount('#app')

补充:


提示环境信息:

815253ed08d865d8c394c34c434d813f.png

关于Vue实例化的理解:下面两种方式的效果是一致的

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

render的完全写法:

render: h => h(App),
render: (creatElement) => {
  return creatElement(App)
}

组件化开发思想

组件化概念:

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


好处:便于维护,利于复用 → 提升开发效率。


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


比如:下面这个页面,若把所有的代码都写在一个页面中会显得代码比较混乱,难易维护。


咱们可以按模块进行组件划分:

b0b0d6f9971bd640b121e6c35b277e12.png

案例网站:http://www.ibootstrap.cn/

dae07b8acc30d7aac069346e78ab3416.png

理解:比如我们把轮播图封装成一个组件,哪个项目需要用,我们就导入到哪个项目中去。


vue 中的组件化开发


vue 是一个完全支持组件化开发的框架。vue 中规定组件的后缀名是 .vue。之前接触到的 App.vue 文件本质上就是一个 vue 的组件。


根组件概念:

概念:整个应用最上层的组件,包裹所有普通小组件

4142d2b7d114226de8143b88ce19f4a4.png

语法高亮插件:Vetur

组件构成:

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

—script:js逻辑

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


其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。


让组件支持less:


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


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

3086d73df1e778ad32024ee900857bf7.png

组件的构成:

组件的 template 节点

vue 规定:每个组件对应的模板结构,需要定义到节点中。

<template>
  <!-- 当前组件的DOM结构,需要定义到template标签的内部>
</template>

注:是vue提供的容器标签,只起到包裹性质的作用,它不会被渲染为真正的DOM元素


在 template 中使用指令


在组件的节点中,支持使用前面所学的指令语法,来辅助开发者渲染当前组件的 DOM 结构。


在 template 中定义根节点


在 vue 2.x 的版本中,节点内的DOM结构仅支持单个根节点:


代码示例如下:

<template>
  <div>
    <h1>App根组件</h1>
    <p>{{ num }}</p>
  </div>
</template>

理解:如图所示,就是在h1 h2的外面得需要包裹一个div,否则h1 h2就算两个根节点


但是 ,在 vue 3.x 的版本中,中支持定义多个根节点:

<template>
    <h1>App根组件</h1>
    <h2>这是副标题</h2>
</template>

理解:在h1 h2的外面就算没有包裹一个统一的根节点(比如用div包裹他们两)也没有问题


总结:


总结:template节点


template里面支持使用vue的指令来渲染DOM结构

template不会被渲染成真正的DOM结构,它只起到包裹性质的作用

在template内部,vue3支持多个根结点,vue2仅支持单个根节点


组件的 script 节点

vue 规定:组件内的

---
### **script 中的 name 节点**
可以通过 name 节点为当前组件定义一个名称:
```jsx
<script>
export default {
  // name 属性指向带式当前组件的名称(建议: 每个单词的首字母大写)
  name: 'App',
}
</script>

在使用 vue-devtools 进行项目调试的时候,自定义的组件名称可以清晰的区分每个组件:

33523472603d9ec79095491b069a1768.png

script 中的 data 节点

vue 组件渲染期间需要用到的数据,可以定义在 data 节点中:

<script>
export default {
  name: 'App',
  // 组件的数据(data方法中return出去的对象,就是当前组件渲染期间需要用到的数据对象)
  data() {
    return {
      username: 'test'
    }
  }
}
</script>

组件中的 data 必须是函数


vue 规定:组件中的 data 必须是一个函数,不能直接指向一个数据对象。因此在组件中定义 data 数据节点时,下面的方式是错误的:

data: { //组件中,不能直接让 data 指向一个数据对象(会报错)
    count: 0
}

script 中的 methods 节点

组件中的事件处理函数,必须定义到 methods 节点中,示例代码如下:

export default {
  name: 'App',
  data() {
    return {
      count: 0,
    }
  },
  methods: {
    addCount() {
      this.count++
    }
  }
}

methods指向一个对象,在methods指向的对象中,我们可以声明对应的事件处理函数,在事件处理函数中,this指向当前组件的实例。值得注意的是,当前组件中的数据(如data),可以通过this访问,如图中,我们可以通过this.count++来让实例中的data中的count自增加一。


组件的 style 节点

vue 规定:组件内的

其中 <style> 标签上的 lang="css" 属性是可选的,它表示所使用的样式语言。默认只支持普通的 css 语法,可选值还有 less、scss 等。
---
### 让 style 中支持 less 语法
如果希望使用 less 语法编写组件的 style 样式,可以按照如下两个步骤进行配置:
① 运行 npm install less -D 命令安装依赖包,从而提供 less 语法的编译支持
② 在 <style> 标签上添加 lang="less" 属性,即可使用 less 语法编写组件的样式
```jsx
<style lang="less">
h1 {
  font-weight: normal;
  i {
    color: red;
    font-style: normal;
  }
}
</style>

个人总结:

vue组件的组成结构:


每个 .vue 组件都由 3 部分构成,分别是:


template -> 组件的模板结构 script -> 组件的 JavaScript 行为 style -> 组件的样式


其中,每个组件中必须包含 template 模板结构,而 script 行为和 style 样式是可选的组成部分。


组件的 template 节点:


vue 规定:每个组件对应的模板结构,需要定义到节点中。

<template>
  <!-- 当前组件的DOM结构,需要定义到template标签的内部>
</template>

1.template里面支持使用vue的指令来渲染DOM结构

2.template不会被渲染成真正的DOM结构,它只起到包裹性质的作用

3.在template内部,vue3支持多个根结点,vue2仅支持单个根节点


组件的 script 节点:


组件内的

<script>
  export default {
    // 组件的名称
    name: 'MyApp',
    // 组件的数据
    data() {
      return {
        username: 'Jack'
      }
    },
    //组件的方法
    methods: {
      addCount() {
        this.count++
      }
    }
  }
</script>

export default:


组件相关的 data 数据,methods 方法等,都需要定义到 export default 所导出的对象中。


data() :


data方法中return 出去的对象,就是当前组件渲染期间所需要用到的数据对象


组件的 style 节点:


组件内的

<style lang="css">
  h1 {
    font-weight: normal;
  }
</style>

组件的基本使用

组件的注册

概念:组件之间可以进行相互的引用,例如:vue 中组件的引用原则:先注册后使用。

29a21ae8cc3b3d362904a60598f68f6b.png

vue 中组件的引用原则:先注册后使用。


使用方式:当成html标签使用即可 <组件名></组件名>


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


组件注册命名方式:

在进行组件的注册时,定义组件注册名称的方式有两种:


① 使用 kebab-case 命名法(俗称短横线命名法,例如 my-swiper 和 my-search)


② 使用 PascalCase 命名法(俗称帕斯卡命名法或大驼峰命名法,例如 MySwiper 和 MySearch)


短横线命名法的特点:


必须严格按照短横线名称进行使用


帕斯卡命名法的特点:


既可以严格按照帕斯卡名称进行使用,又可以转化为短横线名称进行使用


注意:在实际开发中,推荐使用帕斯卡命名法为组件注册名称,因为它的适用性更强。


案例:

//kebab-case
app.component('my-swiper', Swiper)
//PascalCase
app.component('MyTest', Test)

补充:


在template中写组件采用tab快速补齐

<HmHeader></HmHeader>

56df467c883a84963522aeb011ff8309.png


注册组件的两种方式:

vue 中注册组件的方式分为“全局注册”和“局部注册”两种,其中:


被全局注册的组件,可以在全局任何一个组件内使用


被局部注册的组件,只能在当前注册的范围内使用

62b23ec498718ea120077bcfeaa0e039.png

如图,在全局注册的组件 Swiper,既可以在Home组件中使用,也可以在About组件中使用,


而在Home组件中局部注册的组Search组件,只能在Home组件中使用。


全局注册组件

全局注册组件方式:app.component(’被注册的组件名称’, 组件)

在main.js中操作:


使用app.component进行注册

a228c33dd09174ed72a387519a06475b.png

或者,使用Vue.component进行注册

b2e284e5a05d2bb994061d8c4b9da7b2.png

使用全局注册组件


使用 app.component() 方法注册的全局组件,直接以标签的形式进行使用即可,例如:

ab46ca63920bff09d200344896a6decc.png

按照上面的说明配置main.js和App.vue

c86fac49712df2a15bd08bb05dc18ebc.png


局部注册组件

操作流程:


在组件App中导入组件search, 然后在components中注册组件search

7bbf32522771a2e87d6aa1303797d353.png

具体操作:

f72d23a4b495d3f0edeb1a33787679a3.png

全局注册和局部注册的区别

被全局注册的组件,可以在全局任何一个组件内使用


被局部注册的组件,只能在当前注册的范围内使用


应用场景:


如果某些组件在开发期间的使用频率很高,推荐进行全局注册;


如果某些组件只在特定的情况下会被用到,推荐进行局部注册。


通过 name 属性注册组件

在注册组件期间,除了可以直接提供组件的注册名称之外,还可以把组件的 name 属性作为注册后组件的名称,示例代码如下:

b219fc839fbf60b32b7ae3a3fca8c921.png

注意事项:


如果注册组件的命名与要导入的组件一致,我们可以省略组件的命名:


如下所示,如果我们需要注册MyList组件,我们可以往components中填入:

components: {
  'MyList': MyList
}

由于组件的命名与导入组件的名称一致,所以可以简化为如下形式:

components: {
  MyList
}

案例:

b24009e9df940f6601017bea32c58bf4.png


个人总结:

组件的注册:先注册,后使用


注册方式:全局注册和局部注册


组件注册名称:推荐用帕斯卡命名法

//PascalCase
app.component('MyTest', Test)

全局注册组件:被全局注册的组件,可以在全局任何一个组件内使用

a63695e7ff87163191bc81af4ecb7238.png

局部注册组件:被局部注册的组件,只能在当前注册的范围内使用

7b302b8f4c19fbd7198b4d5b3a9846a2.png

解决样式冲突问题:


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


为每个组件分配唯一的自定义属性,在编写组件样式时,通过属性选择器来控制样式的作用域:

7f86987437f0d19d6f306b2c38f1a642.png

防止组件间样式冲突:

<style scoped> </style>

组件的 props:


props 是组件的自定义属性,使用者通过 props 把数据传递到子组件内部供使用。


作用:父组件通过 props 向子组件传递数据


示例如下:

// 这是父组件 App.vue
<template>
  <div>
    <h1>这是 App.vue 根组件</h1>
    <hr>
    <!-- 通过自定义属性props,把文章标题和作者,传送到 my-article 组件中 -->
    <my-article :title="info.title" :author="info.author" :MyTest="info.MyTest"></my-article>
  </div>
</template>
<script>
import MyArticle from './Article.vue'
export default {
  name: 'MyApp',
  data() {
    return {
      info: {
        title: 'abc',
        author: '123',
        MyTest: 'test'
      }
    }
  },
  components: {
    MyArticle,
  }
}
</script>
// 这是子组件 Article.vue
<template>
  <div>
    <h3>标题:{{title}}</h3>
    <h5>作者:{{author}}</h5>
    <h6>发布时间:{{pubTime}}</h6>
  </div>
</template>
<script>
export default {
  name: 'MyArticle',
  // 外界可以传递指定的数据,到当前的组件中
  props: ['title', 'author', 'pubTime']
}
</script>

Class 与 Style 绑定:


vue 允许开发者通过 v-bind 属性绑定指令,为元素动态绑定 class 属性的值和行内的 style 样式


综合案例-小兔仙首页

拆分模块-局部注册

d14c19ac16370014fa61724e799588db.png

这里单独讲个模块:新鲜好物模块

a1a8620f8f631694a6bb60c4d4d97d32.png

我们把商品项拆成模块,并进行全局注册复用:

8719bd7540b5ab40286881f8413b4dec.png

先搞定商品模块的代码:


Code:

<template>
  <div>
    <li class="base-goods-item">
      <a href="#">
        <div class="pic"><img src="@/assets/images/goods1.png" alt="" /></div>
        <div class="txt">
          <h4>KN95级莫兰迪色防护口罩</h4>
          <p>¥ <span>79</span></p>
        </div>
      </a>
    </li>
  </div>
</template>
<script>
export default {
}
</script>
<style>
base-goods-item li {
  width: 304px;
  height: 404px;
  background-color: #EEF9F4;
}
base-goods-item li {
  display: block;
}
base-goods-item li .pic {
  width: 304px;
  height: 304px;
}
base-goods-item li .txt {
  text-align: center;
}
base-goods-item li h4 {
  margin-top: 17px;
  margin-bottom: 8px;
  font-size: 20px;
}
base-goods-item li p {
  font-size: 18px;
  color: #AA2113;
}
base-goods-item li p span {
  font-size: 22px;
}
</style>

再在main.js中进行全局注册:

import BaseGoodsItem from './components/BaseGoodsItem.vue'
Vue.component('BaseGoodsItem', BaseGoodsItem)

最后在XtxNewGoods.vue中进行使用:

<ul>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
  <BaseGoodsItem></BaseGoodsItem>
</ul>

补充:多次生成组件

<BaseGoodsItem v-for="item in 5" :key="item"></BaseGoodsItem>

注:这里的item是从1开始的数字

相关实践学习
Serverless极速搭建Hexo博客
本场景介绍如何使用阿里云函数计算服务命令行工具快速搭建一个Hexo博客。
相关文章
|
22天前
|
JavaScript
在 Vue 中处理组件选项与 Mixin 选项冲突的详细解决方案
【10月更文挑战第18天】通过以上的分析和探讨,相信你对在 Vue 中使用 Mixin 时遇到组件选项与 Mixin 选项冲突的解决方法有了更深入的理解。在实际开发中,要根据具体情况灵活选择合适的解决方案,以确保代码的质量和可维护性。
75 7
|
4天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
4天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
21天前
|
缓存 JavaScript UED
Vue 的动态组件与 keep-alive
【10月更文挑战第19天】总的来说,动态组件和 `keep-alive` 是 Vue.js 中非常实用的特性,它们为我们提供了更灵活和高效的组件管理方式,使我们能够更好地构建复杂的应用界面。深入理解和掌握它们,以便在实际开发中能够充分发挥它们的优势,提升我们的开发效率和应用性能。
43 18
|
16天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
20天前
|
前端开发 UED
vue3知识点:Suspense组件
vue3知识点:Suspense组件
29 4
|
19天前
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
23 1
|
20天前
|
JavaScript 前端开发 Java
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
26 2
|
20天前
|
Java
vue3知识点:Teleport组件
vue3知识点:Teleport组件
25 1
|
23天前
|
存储 JavaScript
Vue 组件间通信的方式有哪些?
Vue组件间通信主要通过Props、Events、Provide/Inject、Vuex(状态管理)、Ref、Event Bus等实现,支持父子组件及跨级组件间的高效数据传递与状态共享。