vue基础第三天

124 阅读5分钟

vue第三天

侦听器

可以侦听data/computed属性值改变

语法:

  • watch: {
        "被侦听的属性名" (newVal, oldVal){
            
        }
    }
    

1652705962143.png

注:写在data同级

深度侦听和立即执行

侦听复杂类型, 或者立即执行侦听函数

immediate 可以再页面进来时马上触发一次当前监听回调

deep 可以监听到复杂数据内部的改变

1652712739497.png

示例:

<template>
  <div>
    <input type="text" v-model="msg" />
    <br />

    user.name
    <input type="text" v-model="user.name" />
    <br />

    user.age
    <input type="text" v-model="user.age" />

    <br />
    <input type="text" v-model="num1" />
    总数: {{ num2 }}
  </div>
</template>

<script>
export default {
  data() {
    return {
      msg: "jack",
      user: {
        name: "rose",
        age: 23,
      },
      num1: 2,
      num2: 3,
    };
  },
  watch: {
    // 以键值对的形式指定 被监听字段: 触发后回调函数
    msg(newVal, oldVal) {
      console.log(newVal, oldVal);
    },
    // 复杂类型无法直接监听, 需要多加额外配置
    user: {
      //deep 可以监听到复杂数据内部的改变
      deep: true,
      handler(newVal, oldVal) {
        console.log(newVal, oldVal);
      },
    },
    num1: {
      // immediate 可以再页面进来时马上触发一次当前监听回调
      immediate: true,
      handler(newVal) {
        this.num2 += newVal;
      },
    },
  },
};
</script>

<style>
</style>
案例:

品牌管理案例使用侦听器添加本地存储功能

<template>
  <div class="container">
    <div class="row">
      <div class="col-12 pt-3">
        <table class="table table-bordered">
          <thead>
            <tr>
              <th scope="col">编号</th>
              <th scope="col">资产名称</th>
              <th scope="col">价格</th>
              <th scope="col">创建时间</th>
              <th scope="col">操作</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="(item, index) in list" :key="item.id">
              <th scope="row">{{ item.id }}</th>
              <td>{{ item.name }}</td>
              <td :class="{ expensive: item.price > 100 }">{{ item.price }}</td>
              <td>{{ item.time | dataFilter }}</td>
              <td>
                <!-- 删除1.删除按钮,绑定事件 -->
                <!-- 删除2.绑定事件获取要删除的元素索引 -->
                <button
                  type="button"
                  class="btn btn-link"
                  @click="deleteProduct(index)"
                >
                  删除
                </button>
              </td>
            </tr>

            <tr class="bg-light">
              <th scope="row">统计</th>
              <td colspan="2">总价:{{ totalPrices }}</td>
              <td colspan="2">均价:{{ averagePrice }}</td>
            </tr>
          </tbody>
          <!-- 删除4.控制空状态显示 -->
          <tfoot v-show="list.length === 0">
            <tr>
              <td class="text-center" colspan="5">暂无数据</td>
            </tr>
          </tfoot>
        </table>
      </div>
    </div>

    <form class="row align-items-center">
      <div class="col-3">
        <input
          type="text"
          class="form-control"
          placeholder="资产名称"
          v-model="productName"
        />
      </div>

      <div class="col-3">
        <input
          type="text"
          class="form-control"
          placeholder="价格"
          v-model.number="productPrice"
        />
      </div>

      <div class="col-3">
        <button
          type="submit"
          class="btn btn-primary"
          @click.prevent="addProperty"
        >
          添加资产
        </button>
      </div>
    </form>
  </div>
</template>

<script>
import moment from "moment";
export default {
  name: "App",
  data() {
    return {
      // 运用短路运算  如果有本地存储则取用本地存储渲染页面   没有则使用空数组渲染页面
      list: JSON.parse(localStorage.getItem("list")) || [],
      productName: "",
      productPrice: 0,
    };
  },
  watch: {
    // 添加侦听器
    list(newVal) {
      // 通过侦听器添加本地存储功能
      localStorage.setItem("list", JSON.stringify(newVal));
    },
  },
  computed: {
    //计算总价业务相关代码
    totalPrices() {
      let total = 0;
      this.list.forEach((item) => {
        total += item.price;
      });
      return total;
    },
    // 计算均价业务相关代码
    averagePrice() {
      let average = this.totalPrices / this.list.length;
      return average;
    },
  },
  filters: {
    dataFilter(time) {
      return moment(time).format("YYYY-MM-DD");
    },
  },
  methods: {
    addProperty() {
      if (!this.productName || !this.productPrice) {
        alert("资产名称或价格不能为空");
        return;
      }
      //删除5.处理空数据情况下新增逻辑异常
      let id;
      if (this.list.length > 0) {
        id = this.list[this.list.length - 1].id + 1;
      } else {
        id = 100;
      }
      this.list.push({
        id,
        name: this.productName,
        price: this.productPrice,
        time: new Date(),
      });
      (this.productName = ""), (this.productPrice = 0);
    },
    deleteProduct(index) {
      //删除3.删除元素
      // console.log(index);
      this.list.splice(index, 1);
    },
  },
};
</script>

<style scoped>
.expensive {
  color: red;
}
</style>

vue组件

组件概念

组件是可复用的 Vue 实例, 封装标签, 样式和JS代码

组件化 :封装的思想,把页面上 可重用的部分 封装为 组件,从而方便项目的 开发 和 维护

一个页面, 可以拆分成一个个组件,一个组件就是一个整体, 每个组件可以有自己独立的 结构 样式 和 行为(html, css和js)

1652713157577.png

组件基础使用-注册和使用

每个组件都是一个独立的个体, 代码里体现为一个独立的.vue文件

口诀: 哪部分标签复用, 就把哪部分封装到组件内

==(重要): 组件内template只能有一个根标签==

==(重要): 组件内data必须是一个函数, 独立作用域==

创建组件

封装标签+样式+js - 组件都是独立的, 为了复用

在app.vue同级目录文件下新建文件夹components(文件夹名可自定义---建议命名为components)

在该文件夹内创建组件

1652713780026.png

注册组件
全局注册使用

全局入口在main.js, 在new Vue之上注册

语法:

  • import Vue from 'vue'
    import 组件对象 from 'vue文件路径'
    
    Vue.component("组件名", 组件对象)
    
// @/  表示在src文件下开始查找  
import ProdItem from "@/components/ProdItem"
Vue.component("ProdItem", ProdItem)

全局注册ProdItem组件名后, 就可以当做标签在任意Vue文件中template里用

单双标签都可以或者小写加-形式, 运行后, 会把这个自定义标签当做组件解析, 使用==组件里封装的标签替换到这个位置==

组件名要两个或两个以上单词 采用驼峰命名或者-分隔

局部注册使用

语法:在script内注册

  • import 组件对象 from 'vue文件路径'
    
    export default {
        components: {
            "组件名": 组件对象
        }
    }
    

1652714346669.png

1652714741776.png

组件注册使用总结:

  • (创建)封装html+css+vue到独立的.vue文件中

  • (引入注册)组件文件 => 得到组件配置对象

  • (使用)当前页面当做标签使用

组件-scoped的作用

目的: 解决多个组件样式名相同, 冲突问题

在style上加入scoped属性, 就会在此组件的标签上加上一个随机生成的data-v开头的属性

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

1652714700529.png

总结: style上加scoped, 组件内的样式只在当前vue组件生效

scoped 可以将当前样式限定在这个文件内, 对多可以穿透到下一层的根元素,其他不受影响(即:只会影响到组件内最外层的同类名元素,组件内同类名的子元素则不受影响 ---组件内多套一层父元素,则不会影响到组件内同类名的元素---)

vue组件通信--传递方式

从一个组件内, 给另外一个组件传值

父传子

目标:父组件 -> 子组件 传值

首先明确父和子是谁, 在父引入子 (被引入的是子)

  • 父: App.vue

  • 子: MyProduct.vue

例如: App.vue(父) ProdItem.vue(子)

父:父组件作为属性将数据传交给子组件标签

1652715794257.png

子:

  • 子组件内props, 定义变量, 准备接收, 然后使用变量
  • 数据接收后直接可以使用了

1652715817204.png

总结: 组件封装复用的标签和样式, 而具体数据要靠外面传入

父传子--配合循环

把数据循环分别传入给组件内显示

例:

父:

<template>
  <div>
    <div class="item">app.vue文件里面的 item</div>

    <!-- 通过变量传递参数 -->
    <ProdItem
      v-for="item in list"
      :key="item.id"
      :title="item.proname"
      :price="item.proprice"
      :info="item.info"
    />
  </div>
</template>

<script>
//局部注册组件演示
import ProdItem from "@/components/ProdItem.vue";

export default {
  data() {
    return {
      list: [
        {
          id: 1,
          proname: "超级好吃的棒棒糖",
          proprice: 18.8,
          info: "开业大酬宾, 全场8折",
        },
        {
          id: 2,
          proname: "超级好吃的大鸡腿",
          proprice: 34.2,
          info: "好吃不腻, 快来买啊",
        },
        {
          id: 3,
          proname: "超级无敌的冰激凌",
          proprice: 14.2,
          info: "炎热的夏天, 来个冰激凌了",
        },
      ],
    };
  },
  components: {
    ProdItem,
  },
};
</script>

<style scoped>
.item {
  color: orange;
}
div {
  margin-top: 15px;
}
</style>

子:

<template>
  <div>
    <!-- 组件命名要以两个以上单词取名,否则报错 -->
    <!-- 要封装的东西放入到组件内(html/css/js) -->
    <div>
      <!-- 父传子3.数据直接可以使用了 -->
      <!-- 被声明过 props 接收的数据, 就能当做 data 一样使用 -->
      <div class="item" @click="handleClick">
        <h2>{{ title }}</h2>
        <div>售价:{{ price }}</div>
        <div>{{ info }}</div>
      </div>
    </div>
  </div>
</template>

<script>
export default {
  // 父传子2.子元素声明一个接收的配置  props
  // 父组件可以传数据, 但是没有权限控制子组件怎么渲染
  // 如果子组件愿意接收什么数据, 就以字符串数组的形式声明 props
  // 比如这里我愿意接收 title price, info 三个属性
  // props 可以声明接收任意数据, 但是父组件有没有传, 就不是子组件控制的了
  props: ["title", "price", "info", "index"],
  methods: {
    handleClick() {
      // console.log("被点击");
      // 子传父1.子组件促发自定义事件,传递参数(可选)
      this.$emit("kanjia", this.index, 3);
    },
  },
};
</script>

<style>
.item {
  color: salmon;
  border: 1px solid #ccc;
}
</style>

单向数据流

在vue中需要遵循单向数据流原则

1. 父组件的数据发生了改变,子组件会自动跟着变
2. 子组件不能直接修改父组件传递过来的props  props是只读的

==父组件传给子组件的是一个对象,子组件修改对象的属性,是不会报错的,对象是引用类型, 互相更新==

1652717797539.png

总结: props的值不能重新赋值, 对象引用关系属性值改变, 互相影响

子传父

目标:子组件向父组件传递数据

  • 子组件自定义事件方法,用户触发该事件时把数据传递出去
  1. 对应标签绑定监听:@时间名="函数"、
  2. 在methods声明函数:

​ methods:{函数() {this.$emit('自定义事件',参数1,参数2)}:}

例:

1652875044350.png

  • 父组件监听子组件自定义的事件方法,绑定响应处理函数
  1. 监听子组件自定义事件: @自定义事件="处理函数"
  2. 在methods中声明处理函数:

1652875301604.png

1652875304463.png

组件是一个vue实例, 封装标签, 样式和JS代码

便于复用, 易于扩展

一方主动传递, 另一方表明愿意接受, 最后用起来

  • 父传子
    • 父组件以属性的形式传入数据
    • 子组件 props 定义需要接受的数据
    • 直接当成 data 使用即可
  • 子传父
    • 子组件主动触发自定义事件 this.$emit('自定义事件名')
    • 父组件 监听 v-on 就是 @自定义事件名
    • 父组件给个对应的处理函数