vue(3)

108 阅读4分钟

vue基础第3天

一、侦听器watch

1.作用:

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

2.语法
watch: {
    "被侦听的属性名" (newVal,oldVal) {}
}

// 
<div>  
  <input type="text" v-model="name">
</div>

export default {
    data(){
        return {
            name:""
        }
    },
    watch: {
    name(newVal,oldVal) {
        console.log(newVal,oldVal)
    }
  }
}
3.深度侦听和立即执行

(1)作用:

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

(2)语法:

wtach: {
    "要侦听的属性名": {
        immediate:true// 立刻执行
        deep:true// 深度监听复杂类型的数据变化
        handler(newVal,oldVal) {
            
        }
    }
}
4.品牌案例-数据缓存

(1)监听list变化, 同步到浏览器本地

(2)需求1:

把品牌管理的数据实时同步到本地缓存

(3)分析:

① 在watch侦听list变化的时候, 把最新的数组list转成JSON字符串存入到localStorage本地

② data里默认把list变量从本地取值, 如果取不到给个默认的空数组

(4)效果:

新增/删除 – 刷新页面 – 数据还在

(5)关键代码

data() {
    return {
      list: JSON.parse(localStorage.getItem("list")) || [],
      type: "all",
      // 定义一个展示数组, 根据list 和 type 来显示结果
      // showList: []
    };
  },
  watch: {
    list: {
      deep: true,
      handler(newVal) {
        localStorage.setItem("list", JSON.stringify(newVal)); // 注意本地储存只能储存字符串格式
      },
    },
  },

二、vue组件基础

1.组件概念:

(1)组件是可复用的 Vue 实例, 封装标签, 样式和JS代码 --- 一个vue文件就是一个组件

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

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

(html, css和js)

2.作用:

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

3.组件的使用

(1)创建组件, 封装要复用的标签, 样式, JS代码

(2)注册组件

①全局注册:main.js文件中

import Vue from "vue"
import 组件对象 from "vue文件路径"
Vue.component("组件名",组件对象)

②局部注册:子组件.vue文件中

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

(3)使用组件(简写:<组件名/>

<template>
  <div>
      <组件名></组件名> 
      <组件名></组件名>
  </div>
</template>
4.组件-scoped作用

(1)准备: 会给当前组件内标签添加 data-v-hash值 的属性

(2)获取: css选择器都被添加 [data-v-hash值] 的属性选择器

三、vue组件通信-父传子

1.父传子:props

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

2.父传子基础

(1)父传子步骤

  • 父组件以属性的形式传入数据
  • 子组件 props 定义需要接受的数据
  • 直接当成 data 使用即可

(2)父传子代码

父组件:



// 局部写法
import 子组件名 from "子组件路径"
export default {
    components: {
        子组件名,
    }
}

子组件:

export default {
   props:[属性名,属性名...]
}    
3.组件通信_父向子-配合循环

(1)目标:父组件 -> 子组件 循环使用-传值

(2)代码

父组件:

<template>
   <div>
       <组件名 v-for="item in list" :key="list.id" :title="list.name" :name= "name"></组件名>
   </div>
</template>

<javascript>
  export default {
     data() {
        return {
          list:[
            { id:1 , name:"sss" , price:"xxx" },
            { id:1 , name:"sss" , price:"xxx" },
            { id:1 , name:"sss" , price:"xxx" },
          ]
        }
      }
    }
</javascript>

四、vue组件通信-子传父

1.单项数据流

子组件修改, 不通知父级, 造成数据不一致性,Vue规定props里的变量,本身是只读的

重点`:从父到子的数据流向, 叫单向数据流

2.子组件中自定义事件方法

目标:子组件中自定义事件方法

(1)子组件主动触发自定义事件 this.$emit('自定义事件名')

(2)父组件 监听 v-on 就是 @自定义事件名

(3)父组件给个对应的处理函数

父组件:

<组件名 @subprice="fn"></组件名>

export default {
    methods:{
        fn(index,price){
            
        }
    }
}

子组件:

<button @click="kanfn"> 砍价 </button>

export default {
    methods:{
        kanfn() {
            this.$emit("subprice",this.index,1)
        }
    }
}

五、组件通信-EventBus

1.作用:

App.vue里引入a.vue和b.vue,用于子组件之间数据传递

2.常用于跨组件通信时使用
3.语法

(1)src/EventBus/index.js – 创建空白Vue对象并导出

(2)在要传递值的组件(a.vue) eventBus.$emit('事件名', 值)

(3)在要接收值的组件(b.vue) eventBus.$on('事件名', 函数体)

a.vue

import eventBus from "../EventBus"
export default {
    methods:{
       sendFn() {
          eventBus.$emit("send","发送的数据")
      }
   }
}

b.vue

import eventBus from "../EventBus"
export default {
    created() {
        eventBus.$on("send",item=>{
            console.log("接受的数据",item)
      })
   }
}

六、todos案例=创建工程和组件

1.案例分析
(1)初始化todo工程
vue created 项目名称
(2)创建3个组件和里面代码(在预习资料.md复制)

在component文件夹中,新建三个文件:todoHeader、todoMain、todoFooter

(3)把styles的样式文件准备好(从预习资料复制)
(4)App.vue引入注册使用, 最外层容器类名todoapp
2.功能-注册组件
<template>
  <section class="todoapp">
    <TodoHeader></TodoHeader>
    <TodoMain></TodoMain>
    <TodoFooter></TodoFooter>
  </section>
</template>

<script>
  import TodoHeader from   "./components/TodoHeader";
  import TodoMain from "./components/TodoMain";
  import TodoFooter from   "./components/TodoFooter";

  export default {
      components: {
      TodoHeader,
      TodoMain,
      TodoFooter,
    }
  }
</script>
3.功能-todoHeader点击回车传输数据(子传父)

todoHeader.vue

<template>
  <header class="header">
    <h1>todos</h1>
    <input
      id="toggle-all"
      class="toggle-all"
      type="checkbox"
      v-model="isAllChecked"
    />
    <label for="toggle-all"></label>
    <input
      class="new-todo"
      placeholder="输入任务名称-回车确认"
      autofocus
      v-model="task"
      @keydown.enter="addTask"
    />
  </header>
</template> 


// 接收父list
props: ["list"],
data() {
   return {
     task: "",
  };
},
methods: {
    addTask() {
      if (!this.task) {
        alert("请输入内容");
        return;
      }
      // 每当回车通知父页面
      this.$emit("addTask", this.task);
      this.task = "";
    },
  },

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>


methods: {
    addTask(task) {
      this.list.push({
        id: Date.now(),
        name: task,
        isDone: false,
      });
    },
4.功能-todoMain点击删除

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>

delTask(id) {
  this.list = this.list.filter((item) => item.id !== id);
},

todoMain.vue

<template>
  <ul class="todo-list">
    <!-- completed: 完成的类名 -->
    <li :class="{ completed: item.isDone }" v-for="item in list" :key="item.id">
      <div class="view">
        <input class="toggle" type="checkbox" v-model="item.isDone" />
        <label>{{ item.name }}</label>
        <button class="destroy" @click="delTask(item.id)"></button>
      </div>
    </li>
  </ul>
</template>

methods: {
    // index版本
    // delTask(index) {
    //   this.$emit("delTask", index);
    // },

    // id版本
    delTask(id) {
      this.$emit("delTask", id);
    },
  },
5.功能-todofooter全部、未完成、已完成按钮点击功能

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>


changeType(type) {
   // 接收子传来的 点击状态
   this.type = type;
}

todoFooter.vue

<template>
  <footer class="footer">
    <span class="todo-count"
      >剩余<strong>{{ list.length }}</strong></span
    >
    <ul class="filters">
      <li>
        <a
          :class="{ selected: type === 'all' }"
          href="javascript:;"
          @click="type = 'all'"
          >全部</a
        >
      </li>
      <li>
        <a
          :class="{ selected: type === 'no' }"
          href="javascript:;"
          @click="type = 'no'"
          >未完成</a
        >
      </li>
      <li>
        <a
          :class="{ selected: type === 'yes' }"
          href="javascript:;"
          @click="type = 'yes'"
          >已完成</a
        >
      </li>
    </ul>
    <button class="clear-completed" @click="clearList">清除已完成</button>
  </footer>
</template>

watch: {
    type(newVal) {
      // 每当类型发生变化, 通知父页面
      this.$emit("changeType", newVal);
    },
  },
  data() {
    return {
      type: "all",
    };
  },
6.功能-todofooter清除按钮点击功能

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>


clearList() {
   // 点击清空已完成,其实就是过滤列表只留下 isDone 为 false 即可
   this.list = this.list.filter((item) => !item.isDone);
},

todofooter.vue

<template>
  <footer class="footer">
    <span class="todo-count"
      >剩余<strong>{{ list.length }}</strong></span
    >
    <ul class="filters">
      <li>
        <a
          :class="{ selected: type === 'all' }"
          href="javascript:;"
          @click="type = 'all'"
          >全部</a
        >
      </li>
      <li>
        <a
          :class="{ selected: type === 'no' }"
          href="javascript:;"
          @click="type = 'no'"
          >未完成</a
        >
      </li>
      <li>
        <a
          :class="{ selected: type === 'yes' }"
          href="javascript:;"
          @click="type = 'yes'"
          >已完成</a
        >
      </li>
    </ul>
    <button class="clear-completed" @click="clearList">清除已完成</button>
  </footer>
</template>



methods: {
    clearList() {
      this.$emit("clearList");
    },
  },
7.功能-todoHeader全选

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>

  computed: {
    showList() {
      // 声明一个函数, 函数名可以当做 data 直接渲染
      // 渲染的结果就是这里的返回值
      let showList = [];
      if (this.type === "all") {
        showList = this.list;
      } else if (this.type === "yes") {
        showList = this.list.filter((item) => item.isDone);
      } else {
        showList = this.list.filter((item) => !item.isDone);
      }
      return showList;
    },
  },

todoHeader.vue

<template>
  <header class="header">
    <h1>todos</h1>
    <input
      id="toggle-all"
      class="toggle-all"
      type="checkbox"
      v-model="isAllChecked"
    />
    <label for="toggle-all"></label>
    <input
      class="new-todo"
      placeholder="输入任务名称-回车确认"
      autofocus
      v-model="task"
      @keydown.enter="addTask"
    />
  </header>
</template>


computed: {
    // isAllChecked() {
    //   return this.list.every((item) => item.isDone);
    // },
    isAllChecked: {
      get() {
        return this.list.length > 0 && this.list.every((item) => item.isDone);
      },
      set(data) {
        this.list.forEach((element) => {
          element.isDone = data;
        });
      },
    },
  },
8.功能-储存本地数据

App.vue

<template>
  <section class="todoapp">
    <!-- 除了驼峰, 还可以使用-转换链接 -->
    <TodoHeader @addTask="addTask" :list="list"></TodoHeader>
    <!-- 接收子传来的delTask,并声明delTask方法 -->
    <TodoMain @delTask="delTask" :list="showList"></TodoMain>
    <TodoFooter
      @clearList="clearList"
      @changeType="changeType"
      :list="showList"
    ></TodoFooter>
  </section>
</template>



data() {
    return {
      list: JSON.parse(localStorage.getItem("list")) || [],
      type: "all",
      // 定义一个展示数组, 根据list 和 type 来显示结果
      // showList: []
    };
  },
  watch: {
    list: {
      deep: true,
      handler(newVal) {
        localStorage.setItem("list", JSON.stringify(newVal));
      },
    },
  },