Vue父子组件通信及相关进阶语法(附带详细案例)


发现宝藏

前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【宝藏入口】。

前言

为了巩固所学的知识,作者尝试着开始发布一些学习笔记类的博客,方便日后回顾。当然,如果能帮到一些萌新进行新技术的学习那也是极好的。作者菜菜一枚,文章中如果有记录错误,欢迎读者朋友们批评指正。

一、组件的三大组成部分(结构/样式/逻辑)

在这里插入图片描述

1. scoped样式冲突

在这里插入图片描述

在这里插入图片描述

2. data是一个函数

在这里插入图片描述

<template>
  <div class="base-count">
    <button @click="count--">-</button>
    <span>{
    
    {
    
     count }}</span>
    <button @click="count++">+</button>
  </div>
</template>

<script>
export default {
    
    
  // data() {
    
    
  //   console.log('函数执行了')
  //   return {
    
    
  //     count: 100,
  //   }
  // },
  data: function () {
    
    
    return {
    
    
      count: 100,
    }
  },
}
</script>

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

二、组件通信

1. 组件通信语法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 父传子

在这里插入图片描述

3. 子传父

在这里插入图片描述

4. props详解

1. 什么是 props

在这里插入图片描述
2. props 校验

在这里插入图片描述

在这里插入图片描述

  • 子组件
<template>
  <div class="base-progress">
    <div class="inner" :style="{ width: w + '%' }">
      <span>{
    
    {
    
     w }}%</span>
    </div>
  </div>
</template>

<script>
export default {
    
    
  // 1.基础写法(类型校验)
  // props: {
    
    
  //   w: Number,
  // },

  // 2.完整写法(类型、默认值、非空、自定义校验)
  props: {
    
    
    w: {
    
    
      type: Number,
      required: true,
      default: 0,
      validator(val) {
    
    
        // console.log(val)
        if (val >= 100 || val <= 0) {
    
    
          console.error('传入的范围必须是0-100之间')
          return false
        } else {
    
    
          return true
        }
      },
    },
  },
}
</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>
  • 父组件
<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>

3. prop & data、单向数据流
在这里插入图片描述

  • 子组件
<template>
  <div class="base-count">
    <button @click="handleSub">-</button>
    <span>{
    
    {
    
     count }}</span>
    <button @click="handleAdd">+</button>
  </div>
</template>

<script>
export default {
    
    
  // 1.自己的数据随便修改  (谁的数据 谁负责)
  // data () {
    
    
  //   return {
    
    
  //     count: 100,
  //   }
  // },
  // 2.外部传过来的数据 不能随便修改
  props: {
    
    
    count: {
    
    
      type: Number,
    },
  },
  methods: {
    
    
    handleSub() {
    
    
      this.$emit('changeCount', this.count - 1)
    },
    handleAdd() {
    
    
      this.$emit('changeCount', this.count + 1)
    },
  },
}
</script>

<style>
.base-count {
    
    
  margin: 20px;
}
</style>
  • 父组件
<template>
  <div class="app">
    <BaseCount :count="count" @changeCount="handleChange"></BaseCount>
  </div>
</template>

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

<style>

</style>

在这里插入图片描述

5. 非父子通信 - event bus 总线

在这里插入图片描述

  • EventBus.js
import Vue from 'vue'

const Bus  =  new Vue()

export default Bus
  • BaseA.vue
<template>
  <div class="base-a">
    我是A组件(接受方)
    <p>{
    
    {
    
    msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
    
    
  data() {
    
    
    return {
    
    
      msg: '',
    }
  },
  created() {
    
    
    Bus.$on('sendMsg', (msg) => {
    
    
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

<style scoped>
.base-a {
    
    
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
  • BsaeB.vue
<template>
  <div class="base-b">
    <div>我是B组件(发布方)</div>
    <button @click="sendMsgFn">发送消息</button>
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
    
    
  methods: {
    
    
    sendMsgFn() {
    
    
      Bus.$emit('sendMsg', '今天天气不错,适合旅游')
    },
  },
}
</script>

<style scoped>
.base-b {
    
    
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>
  • BaseC.vue
<template>
  <div class="base-c">
    我是C组件(接受方)
    <p>{
    
    {
    
    msg}}</p>  
  </div>
</template>

<script>
import Bus from '../utils/EventBus'
export default {
    
    
  data() {
    
    
    return {
    
    
      msg: '',
    }
  },
  created() {
    
    
    Bus.$on('sendMsg', (msg) => {
    
    
      // console.log(msg)
      this.msg = msg
    })
  },
}
</script>

<style scoped>
.base-c {
    
    
  width: 200px;
  height: 200px;
  border: 3px solid #000;
  border-radius: 3px;
  margin: 10px;
}
</style>

在这里插入图片描述

6. 非父子通信 - provide & inject

在这里插入图片描述

在这里插入图片描述

三、 综合案例:小黑记事本(组件版)

在这里插入图片描述

1. 拆分组件

在这里插入图片描述

2. 渲染

在这里插入图片描述

在这里插入图片描述

3. 添加

在这里插入图片描述

4. 删除

在这里插入图片描述

5. 统计&清空&持久化

在这里插入图片描述

6. 完整代码

  • TodoHeader.vue
<template>
   <!-- 输入框 -->
  <header class="header">
    <h1>小黑记事本</h1>
    <input placeholder="请输入任务" class="new-todo" v-model="todoName" @keyup.enter="handleAdd"/>
    <button class="add" @click="handleAdd">添加任务</button>
  </header>
</template>

<script>
export default {
    
    
  data(){
    
    
    return {
    
    
      todoName:''
    }
  },
  methods:{
    
    
    handleAdd(){
    
    
      // console.log(this.todoName)
      this.$emit('add',this.todoName)
      this.todoName = ''
    }
  }
}
</script>

<style>

</style>
  • TodoMain.vue
<template>
  <!-- 列表区域 -->
  <section class="main">
    <ul class="todo-list">
      <li class="todo" v-for="(item, index) in list" :key="item.id">
        <div class="view">
          <span class="index">{
    
    {
    
     index + 1 }}.</span>
          <label>{
    
    {
    
     item.name }}</label>
          <button class="destroy" @click="handleDel(item.id)"></button>
        </div>
      </li>
    </ul>
  </section>
</template>

<script>
export default {
    
    
  props: {
    
    
    list: {
    
    
      type: Array,
    },
  },
  methods: {
    
    
    handleDel(id) {
    
    
      this.$emit('del', id)
    },
  },
}
</script>

<style>
</style>
  • TodoFooter.vue
<template>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count"
      >合 计:<strong> {
    
    {
    
     list.length }} </strong></span
    >
    <!-- 清空 -->
    <button class="clear-completed" @click="clear">清空任务</button>
  </footer>
</template>

<script>
export default {
    
    
  props: {
    
    
    list: {
    
    
      type: Array,
    },
  },
  methods:{
    
    
    clear(){
    
    
      this.$emit('clear')
    }
  }
}
</script>

<style>
</style>
  • App.vue
<template>
  <!-- 主体区域 -->
  <section id="app">
    <TodoHeader @add="handleAdd"></TodoHeader>
    <TodoMain :list="list" @del="handelDel"></TodoMain>
    <TodoFooter :list="list" @clear="clear"></TodoFooter>
  </section>
</template>

<script>
import TodoHeader from './components/TodoHeader.vue'
import TodoMain from './components/TodoMain.vue'
import TodoFooter from './components/TodoFooter.vue'

// 渲染功能:
// 1.提供数据: 提供在公共的父组件 App.vue
// 2.通过父传子,将数据传递给TodoMain
// 3.利用 v-for渲染

// 添加功能:
// 1.手机表单数据  v-model
// 2.监听事件(回车+点击都要添加)
// 3.子传父,讲任务名称传递给父组件 App.vue
// 4.进行添加 unshift(自己的数据自己负责)
// 5.清空文本框输入的内容
// 6.对输入的空数据 进行判断

// 删除功能
// 1.监听事件(监听删除的点击) 携带id
// 2.子传父,讲删除的id传递给父组件的App.vue
// 3.进行删除filter(自己的数据 自己负责)

// 底部合计:父传子  传list 渲染
// 清空功能:子传父  通知父组件 → 父组件进行更新
// 持久化存储:watch深度监视list的变化 -> 往本地存储 ->进入页面优先读取本地数据
export default {
    
    
  data() {
    
    
    return {
    
    
      list: JSON.parse(localStorage.getItem('list')) || [
        {
    
     id: 1, name: '打篮球' },
        {
    
     id: 2, name: '看电影' },
        {
    
     id: 3, name: '逛街' },
      ],
    }
  },
  components: {
    
    
    TodoHeader,
    TodoMain,
    TodoFooter,
  },
  watch: {
    
    
    list: {
    
    
      deep: true,
      handler(newVal) {
    
    
        localStorage.setItem('list', JSON.stringify(newVal))
      },
    },
  },
  methods: {
    
    
    handleAdd(todoName) {
    
    
      // console.log(todoName)
      this.list.unshift({
    
    
        id: +new Date(),
        name: todoName,
      })
    },
    handelDel(id) {
    
    
      // console.log(id);
      this.list = this.list.filter((item) => item.id !== id)
    },
    clear() {
    
    
      this.list = []
    },
  },
}
</script>

<style>
</style>

在这里插入图片描述

四、进阶语法

1. V-model原理

在这里插入图片描述

2. v-model应用于组件

在这里插入图片描述

在这里插入图片描述

  • 父组件 App.vue
<template>
  <div class="app">
    <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>
  • 子组件 BaseSelect
<template>
  <div>
    <select :value="value" @change="selectCity">
      <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: {
    
    
    selectCity(e) {
    
    
      this.$emit('input', e.target.value)
    },
  },
}
</script>

<style>
</style>

3. sync修饰符

在这里插入图片描述

  • 父组件 App.vue
<template>
  <div class="app">
    <button @click="openDialog">退出按钮</button>
    <!-- isShow.sync  => :isShow="isShow" @update:isShow="isShow=$event" -->
    <BaseDialog :isShow.sync="isShow"></BaseDialog>
  </div>
</template>

<script>
import BaseDialog from './components/BaseDialog.vue'
export default {
    
    
  data() {
    
    
    return {
    
    
      isShow: false,
    }
  },
  methods: {
    
    
    openDialog() {
    
    
      this.isShow = true
      // console.log(document.querySelectorAll('.box')); 
    },
  },
  components: {
    
    
    BaseDialog,
  },
}
</script>

<style>
</style>
  • 子组件 BaseDialog
<template>
  <div class="base-dialog-wrap" v-show="isShow">
    <div class="base-dialog">
      <div class="title">
        <h3>温馨提示:</h3>
        <button class="close" @click="closeDialog">x</button>
      </div>
      <div class="content">
        <p>你确认要退出本系统么?</p>
      </div>
      <div class="footer">
        <button>确认</button>
        <button>取消</button>
      </div>
    </div>
  </div>
</template>

<script>
export default {
    
    
  props: {
    
    
    isShow: Boolean,
  },
  methods:{
    
    
    closeDialog(){
    
    
      this.$emit('update:isShow',false)
    }
  }
}
</script>

<style scoped>
.base-dialog-wrap {
    
    
  width: 300px;
  height: 200px;
  box-shadow: 2px 2px 2px 2px #ccc;
  position: fixed;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 0 10px;
}
.base-dialog .title {
    
    
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 2px solid #000;
}
.base-dialog .content {
    
    
  margin-top: 38px;
}
.base-dialog .title .close {
    
    
  width: 20px;
  height: 20px;
  cursor: pointer;
  line-height: 10px;
}
.footer {
    
    
  display: flex;
  justify-content: flex-end;
  margin-top: 26px;
}
.footer button {
    
    
  width: 80px;
  height: 40px;
}
.footer button:nth-child(1) {
    
    
  margin-right: 10px;
  cursor: pointer;
}
</style>

4. ref和 $refs

在这里插入图片描述
在这里插入图片描述

  • 父组件 App.vue
<template>
  <div class="app">
    <div class="base-chart-box">
      这是一个捣乱的盒子
    </div>
    <BaseChart></BaseChart>
  </div>
</template>

<script>
import BaseChart from './components/BaseChart.vue'
export default {
    
    
  components:{
    
    
    BaseChart
  }
}
</script>

<style>
.base-chart-box {
    
    
  width: 300px;
  height: 200px;
}
</style>
  • 子组件 BaseChart.vue
<template>
  <div class="base-chart-box" ref="baseChartBox">子组件</div>
</template>

<script>
import * as echarts from 'echarts'

export default {
    
    
  mounted() {
    
    
    // 基于准备好的dom,初始化echarts实例
    // document.querySelector 会查找项目中所有的元素
    // $refs只会在当前组件查找盒子
    // var myChart = echarts.init(document.querySelector('.base-chart-box'))
    var myChart = echarts.init(this.$refs.baseChartBox)
    // 绘制图表
    myChart.setOption({
    
    
      title: {
    
    
        text: 'ECharts 入门示例',
      },
      tooltip: {
    
    },
      xAxis: {
    
    
        data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子'],
      },
      yAxis: {
    
    },
      series: [
        {
    
    
          name: '销量',
          type: 'bar',
          data: [5, 20, 36, 10, 10, 20],
        },
      ],
    })
  },
}
</script>

<style scoped>
.base-chart-box {
    
    
  width: 400px;
  height: 300px;
  border: 3px solid #000;
  border-radius: 6px;
}
</style>

5. $nextTick

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

总结

欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下。
(博客的参考源码可以在我主页的资源里找到,如果在学习的过程中有什么疑问欢迎大家在评论区向我提出)

猜你喜欢

转载自blog.csdn.net/HHX_01/article/details/133962717