Vue2 Day3-Day4

302 阅读8分钟

watch侦听器(监视器)

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

2.语法:

(1) watch同样声明在跟data同级的配置项中

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

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

  data: { 
    words: '苹果',
    obj: {
      words: '苹果'
    }
  },

  watch: {
   //直接监视
    // 该方法会在数据变化时调用执行
    //newValue新值, oldValue老值(一般不用)
    //words (newValue){
    //console.log('变化了',newValue)
    //}
    
   //对象里面的子属性 通过访问路径的方式
    'obj.words' (newValue){
       console.log('变化了',newValue)
    '对象.属性名' (newValue, oldValue) {
     } 
    }
  })

防抖:延迟执行

axios?

背景介绍:Ajax(Asynchronous JavaScript and XML):
异步网络请求。Ajax能够让页面无刷新的请求数据。

实现ajax的方式有多种,如jQuery封装的ajax,原生的XMLHttpRequest,以及axios。但各种方式都有利弊:

  • 原生的XMLHttpRequest的配置和调用方式都很繁琐,实现异步请求十分麻烦

  • jQuery的ajax相对于原生的ajax是非常好用的,但是没有必要因为要用ajax异步网络请求而引用jQuery框架

Axios(ajax i/o system): 这不是一种新技术,本质上还是对原生XMLHttpRequest的封装,可用于浏览器和nodejs的HTTP客户端,只不过它是基于Promise的,符合最新的ES规范。具备以下特点:

  • 在浏览器中创建XMLHttpRequest请求
  • 在node.js中发送http请求
  • 支持Promise API
  • 拦截请求和响应
  • 转换请求和响应数据
  • 取消要求
  • 自动转换JSON数据
  • 客户端支持防止CSRF/XSRF(跨域请求伪造)

watch 完整写法 — 添加额外的配置项

  1. deep:true 对复杂类型深度监视
  2. immediate:true 初始化立刻执行一次handler方法
watch: {// watch 完整写法
  数据属性名: {
 deep: true, // 深度监视(针对复杂类型)
 immediate: true, // 是否立刻执行一次handler
    handler (newValue) {
      console.log(newValue)
    }
  }
}

Vue生命周期

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

生命周期四个阶段:创建—挂裁—更新—销毁

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

2.挂载阶段:渲染模板

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

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

image.png image.png

创建与挂裁阶段只会执行一次,更新阶段则会执行多次

Vue 生命周期函数(钩子函数)

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

  • beforeUpdate:数据修改了,视图还未更新
  • updated:数据修改了,视图已经更新

mounted 应用

mounted:模板渲染完成,可以开始操作DOM了

需求:已进入页面刷新之后,输入框可以获取焦点

核心思路:

  • 等输入框渲染出来
  • 让输入框获取焦点
 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;
    }
 <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: ''
      },
      mounted(){
        document.querySelector('#inp').focus()
      }
    })
  </script>

image.png

在mounted阶段之后再获取DOM

案例:小黑记账清单

1.基本渲染

  • 立刻发送请求获取数据 created
  • 拿到数据,存到data的响应式数据中
  • 结合数据,进行渲染 v-for
  • 消费统计 —> 计算属性

2.添加功能

  • 收集表单数据 v-model,使用指令修饰符处理数据
  • 给添加按钮注册点击事件,对输入的内容做非空判断,发送请求
  • 请求成功后,对文本框内容进行清空
  • 重新渲染列表

image.png

3.删除功能

  • 注册点击事件,获取当前行的id
  • 根据id发送删除请求
  • 需要重新渲染
     .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;
        }
      }

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

  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>
 

效果图如下:

当点击删除时,删除按钮所在的一行可以删除

image.png

4.饼图渲染

  • 初始化一个饼图 echarts.init(dom) mounted钩子中渲染
  • 根据数据试试更新饼图 echarts.setOptions({...})

思路:

  • 在绘图前需要为ECharts准备一个定义了高宽的DOM容器
  • 通过echarts.init方法初始化一个echarts实例 并通过setOption方法生成一个简单的柱状图
<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>
   
    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>
 

效果图如下:

image.png

工程化开发和脚手架

1.开发Vue的两种方式

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

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

image.png

基本介绍:

  • Vue CLI 是 Vue官方提供的一个全局命令工具
  • 可以帮助我们快速创建一个开发Vue项目的标准化基础架子【集成了webpack配置】

使用步骤:

  1. 全局安装(一次):yarn global add@vue/cli 或 npm i @vue/cli -g
  2. 查看Vue版本:vue --version
  3. 创建项目架子:vue create project-name(项目名不能用中文)
  4. 启动项目:yarn serve 或npm run serve(找package.json)

脚本架目录文件介绍 & 项目运行流程

image.png

  1. main.js 入口文件
  2. App.vue App根组件
  3. index.html 模板文件

main.js 文件核心作用:

  • 导入app.vue,基于app.vue创建结构 渲染 index.html

image.png

image.png

组件化开发 & 根组件

  1. 组件化:一个页面可以拆分成一个个组件,每个组件有着自己独立的结构、样式、行为。
  • 好处:便于维护,利于复用——提升开发效率

  • 组件分类:普通组件、跟组件

  1. 根组件:整个应用最上层的组件,包括所有普通小组件
image.png image.png

App.vue 文件(单文件组件)的三个组成部分

  1. 语法高亮插件
  2. 三部分组成
  • template:结构 (有且只能一个根元素)
  • script: js逻辑
  • style: 样式 (可支持less,需要装包)
  1. 让style支持less:

(1) 给style加上 lang="less"

(2)安装依赖包 less less-loader yarn add less less-loader -D(开发依赖)

<style lang ="less">
.App{
   width:400px;
   height:400px;
   background-color:pink;
   .box{
   width:100px;
   height:100px;
   background-color:lightblue;
   }
}
image.png

普通组件的注册使用(局部注册)

组件注册的两种方式:

  1. 局部注册:只能在注册的组件内使用
  • 创建.vue文件(三个部分组成)
  • 在使用的组件内导入并局部注册 components('组件名',组件对象)
  • 当组件名与组件对象相同时,组件名可省略
  1. 全局注册:所有组件内都能使用
  • 使用: 当成html标签使用 <组件名></组件名>

注意:

组件名规范—大驼峰命名法 如:HmHeader

补充:

Q:何为大驼峰命名法?

A:每一个单词的首字母都采用大写字母,例如:FirstName、LastName、CamelCase,也被称为 Pascal 命名法。

Q:什么是小驼峰命名法?

A: 第一个单词以小写字母开始,第二个单词的首字母大写 例如:firstName、lastName。

image.png

image.png

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

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

示例:

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

<script>
export default {

}
</script>

<style>
.hm-header {
  height: 100px;
  line-height: 100px;
  text-align: center;
  font-size: 30px;
  background-color: #8064a2;
  color: white;
}
</style>
//主体
<template>
  <div class="hm-main">
    我是hm-main
  </div>
</template>

<script>
export default {

}
</script>

<style>
.hm-main {
  height: 400px;
  line-height: 400px;
  text-align: center;
  font-size: 30px;
  background-color: #f79646;
  color: white;
  margin: 20px 0;
}
</style>
//底部
<template>
  <div class="hm-footer">
    我是hm-footer
  </div>
</template>

<script>
export default {

}
</script>

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

效果图如下:

image.png

普通组件的注册(全局注册)

  1. 创建.vue文件(/三个组成部分)
  2. main.js 中进行全局注册
    • 编写导入代码,往代码顶部编写
    • 进入全局注册—在所有的组件范围内都能直接使用
    • vue.component('组件名',组件对象)

:编写导入代码,往代码顶部编写

综合案例:小兔鲜首页

  1. 组件拆分

image.png

2.页面开发思路

(1)分析页面,按模块拆分组件,搭架子(局部或全局注册) 通常为局部注册 若一个模块多次使用也可全局注册

(2)根据设计图,编写组件html结构 css样式

(3)拆分封装通用小组将(局部或全局注册)

将来 ——通过js 动态渲染,实现功能

当内容太多时可使用快捷键:

  • 将所有都折叠 ctrl+k,ctrl+ 0
  • 将所有都展开 ctrl+k,ctrl+ j
  • ctrl +滚轮向下拖动:可复制需要的内容

组件多次渲染:v-for

<div class="bd">
  <ul>
     <BaseBrandItem v-for="item in 5":key="item">  <BaseBrandItem>
  </ul>
</div>