【前端-VUE】Vue3组件组成部分及组件通信(详解)

一、父子间通信

1.父传子:

2.子传父:

3.什么是prop

Prop定义:组件上注册的一些自定义属性

Prop作用:向子组件传递数据

特点

  • 可以传递任意数量的prop
  • 可以传递任意类型的prop

3.1 props校验

思考:组件的prop可以乱传么?

不可以

作用:为组件的prop指定验证要求,不符合要求,控制台就会有错误提示——>帮助开发者,快速发现错误

语法

①类型校验

②非空校验

③默认值

④自定义校验

props:{

校验的属性名: 类型 //Number String Boolean Array Object…

},

3.2更详细的props校验

props:{
    校验的属性名:{
        type:类型, // Number String Boolean …
        required: true, // 是否必填
        default: 默认值, // 默认值
        validator (value){
            // 自定义校验逻辑
            return 是否通过校验
        }
    }
},

4.prop & data、单向数据流

共同点:都可以给组件提供数据。

区别

  • data的数据是自己的——> 随便改
  • prop的数据是外部的 ——> 不能直接改,要遵循 单向数据流

总结:

谁的数据谁负责

1.data:自己的数据,随便改(谁的数据谁负责)

2.prop传过来的数据(外部的数据)不能直接改

3.单向数据流:父组件的prop更新,会单向向下流动,影响到子组件

4.1综合案例

4.2核心代码

App.vue

<template>
  <!-- 主体区域 -->
  <section id="app">
    <TodoHeader @add="handleAdd"></TodoHeader>
    <TodoMain @del="handleDel" :list="list"></TodoMain>
    <TodoFooter @clear="handleClear" :list="list"></TodoFooter>
  </section>
  <router-view></router-view>
</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(自己的数据自己负责)
// 删除功能:
// 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: '逛街' },
      ]
    }
  },
  methods: {
    handleAdd (todoName) {
      this.list.unshift({
        id: +new Date(),
        name: todoName
      })
    },
    handleDel (id) {
      this.list = this.list.filter(item => item.id !== id)
    },
    handleClear () {
      this.list = []
    }
  },
  watch: {
    list: {
      deep: true,
      handler (newValue) {
        localStorage.setItem('list', JSON.stringify(newValue))
      }
    }
  },
  components: {
    TodoHeader,
    TodoMain,
    TodoFooter
  }
}
</script>

TodoMain.vue

<template>
  <section class="main">
    <ul class="todo-list">
      <li v-for="(item, index) in list" class="todo">
        <div class="view">
          <span class="index">{{ index + 1 }}</span><label>{{ item.name }}</label>
          <button @click="handleDel(item.id)" class="destroy"></button>
        </div>
      </li>
    </ul>
  </section>
</template>
<script>
export default {
  props: {
    list: Array
  },
  methods: {
    handleDel (id) {
      this.$emit('del', id)
    }
  }
}
</script>

Todoheader.vue

<template>
  <header class="header">
    <h1>小黑笔记本</h1>
    <input @keyup.enter="handleAdd" v-model="todoName" placeholder="请输入任务" class="new-todo" />
    <button @click="handleAdd" class="add">添加任务</button>
  </header>
</template>
<script>
export default {
  data () {
    return {
      todoName: ''
    }
  },
  methods: {
    handleAdd () {
      // console.log(this.todoName)
      if (this.todoName.trim() === '') {
        alert('任务名称不能为空')
        return
      }
      this.$emit('add', this.todoName)
      this.todoName = ''
    }
  }
}
</script>

TodoFooter.vue

<template>
  <!-- 统计和清空 -->
  <footer class="footer">
    <!-- 统计 -->
    <span class="todo-count">合 计:<strong>{{ list.length }}</strong></span>
    <!-- 清空 -->
    <button @click="clear" class="clear-completed">
      清空任务
    </button>
  </footer>
</template>
<script>
export default {
  props: {
    list: Array
  },
  methods: {
    clear () {
      this.$emit('clear')
    }
  }
}
</script>

4.3总结

核心步骤:

①拆分基础组件

新建组件——> 拆分存放结构 ——> 导入注册使用

②渲染待办事项

提供数据(公共父组件)——> 父传子传递list ——> v-for渲染

③添加任务

收集数据 v-model ——>监听事件 ——> 子传父传递任务 ——> 父组件unshift

④删除任务

监听删除携带id ——> 子传父传递id ——> 父组件filter删除

⑤底部合计 和 清空功能

底部合计:父传子传递list ——> 合计展示

清空功能:监听点击 ——>子传父通知父组件 ——> 父组件清空

⑥持久化存储

watch监视数据变化,持久化到本地

二、非父子通信

1.非父子通信- event bus 事件总线

作用:非组件之间,进行简易消息传递。(复杂场景——>Vuex)

1.创建一个能够访问到的事件总栈(空Vue实例)——>utils/EventBus.js

improt Vue form 'vue'
const Bus = new Vue()
export default Bus

2.A组件(接收方),监听Bus实例的事件

created (){
	Bus.$on('sendMsg',(msg)=>{
		this.msg = msg
	})
}

3.B组件(发送方),触发Bus实例的事件

Bus.$emit('sendMsg','这是一个消息')

代码:

App.vue

<template>
  <div>
    <BaseA></BaseA>
    <BaseB></BaseB>
    <BaseC></BaseC>
  </div>
</template>
<script>
import BaseA from './components/BaseA.vue'
import BaseB from './components/BaseB.vue'
import BaseC from './components/BaseC.vue'
export default {
  components: {
    BaseA,
    BaseB,
    BaseC
  }
}
</script>

 BaseA.vue

<template>
  <div class="baseA">
    我是A组件(接收方)
    <p>{{ msg }}</p>
  </div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
  created () {
    // 2.在A组件(接收方),进行监听(订阅消息)
    Bus.$on('sendMsg', (msg) => {
      this.msg = msg
    })
  },
  data () {
    return {
      msg: ''
    }
  }
}
</script>
<style>
.baseA {
  width: 200px;
  height: 150px;
  padding: 10px;
  margin-top: 10px;
  border: 3px solid #000;
  border-radius: 5px;
  font-size: 20px;
}
</style>

 BaseB.vue

<template>
  <div class="baseB">
    我是B组件(发布方)
    <button @click="clickSend">发布通知</button>
  </div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
  methods: {
    clickSend () {
      // 3.B组件(发件方)触发事件的方式传递参数(发布消息)
      Bus.$emit('sendMsg', '今日晴天,适合郊游')
    }
  }
}
</script>

BaseC.vue

<template>
  <div class="baseC">
    我是C组件(接收方)
    <p>{{ msg }}</p>
  </div>
</template>
<script>
import Bus from '../utils/EventBus'
export default {
  created () {
    // 2.在C组件(接收方),进行监听(订阅消息)
    Bus.$on('sendMsg', (msg) => {
      this.msg = msg
    })
  },
  data () {
    return {
      msg: ''
    }
  }
}
</script>
<style>
.baseA {
  width: 200px;
  height: 150px;
  padding: 10px;
  margin-top: 10px;
  border: 3px solid #000;
  border-radius: 5px;
  font-size: 20px;
}
</style>

EventBus.js

// 1.创建一个都能访问到的事件总线(空的Vue实例)
import { createApp } from 'vue';
 
const Bus = createApp({}); // 创建一个空的Vue实例
export default Bus

2.非父子通信(拓展)-provide & inject

provide & inject 作用:跨层级共享数据

1.父组件provide提供数据

export default {
	provide (){
		return {
			//普通类型【非响应式】
			color: this.color,
			// 复杂类型【响应式】
			userInfo: this.userInfo,
		}
	}
}

2./孙组件inject取值使用

export default {
	inject: ['color','userInfo'],
	created(){
		console.log(this.color,this.userInfo)
	}
}

代码:

App.vue

<template>
  <div class="app">
    <span>我是App组件</span>
    <button @click="change">修改数据</button>
    <SonA></SonA>
    <SonB></SonB>
  </div>
</template>
<script>
import SonA from './components/SonA.vue'
import SonB from './components/SonB.vue'
export default {
  provide () {
    return {
      color: this.color,
      userInfo: this.userInfo
    }
  },
  data () {
    return {
      color: 'pink', // 简单类型(非响应式)
      userInfo: { // 复杂类型(响应式) - 推荐
        name: 'zs',
        age: 18
      }
    }
  },
  methods: {
    change () {
      this.color = 'green'
      this.userInfo.name = 'ls'
    }
  },
  components: {
    SonA,
    SonB
  }
}
</script>

SonA.vue

<template>
  <div class="SonA">
    我是SonA组件
    {{ color }} - {{ userInfo.name }} - {{ userInfo.age }}
  </div>
  <GrandSon></GrandSon>
</template>
<script>
import GrandSon from './GrandSon.vue';
export default {
  inject: ['color', 'userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  },
  components: {
    GrandSon
  }
}
</script>

SonB.vue

<template>
  <div class="SonB">
    我是SonB组件
    {{ color }} - {{ userInfo.name }} - {{ userInfo.age }}
  </div>
</template>
<script>
export default {
  inject: ['color', 'userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  }
}
</script>

GrandSon.vue

<template>
  <div class="GrandSon">
    我是GrandSon组件
    {{ color }} - {{ userInfo.name }} - {{ userInfo.age }}
  </div>
</template>
<script>
export default {
  inject: ['color', 'userInfo'],
  created () {
    console.log(this.color, this.userInfo)
  }
}
</script>

 

三、v-model原理

1.原理:

v-model本质上是一个语法糖。例如应用在输入框上,就是value属性和input事件的合写。

作用:提供数据的双向绑定

①数据变,视图跟着变 :value

②页面输入改变,数据会自动改变 @input

注意:$event用于模板中,获取时间中的形参

<template>
  <div class="app">
    <input v-model="msg" type="text">
	// 等价于
    <input :value="msg" @input="msg = $event.target.value" type="text">
  </div>
</template>

2.表单类组件封装&v-model简化代码

2.1表单类组件 封装

①父传子:数据 应该是父组件props传递过来的,v-model拆解 绑定数据

②子传父:监听输入,子传父传值给父组件修改

本质:实现了子组件 和 父组件数据 的双向绑定

2.2 父组件v-model 简化代码,实现 子组件 和父组件数据 双向绑定

①子组件中:props通过value接收,事件触发input

②父组件中:v-model给组件直接绑数据 (:value + @input)

代码:

App.vue

<template>
  <div class="app">
    <!-- <BaseSelect :cityId="selectId" @changeId="selectId = $event"></BaseSelect> -->
    <BaseSelect v-model="selectId"></BaseSelect>
  </div>
</template>
<script>
import BaseSelect from './components/BaseSelect.vue'
export default {
  data () {
    return {
      selectId: '104'
    }
  },
  components: {
    BaseSelect
  }
}
</script>

BaseSelect.vue

<template>
  <div>
    <!-- <select :value="cityId" @change="handleChange"> -->
    <select :value="value" @change="handleChange">
      <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: {
    // cityId: String
    value: String
  },
  methods: {
    handleChange (e) {
      // console.log(e.target.value)
      // this.$emit('changeId', e.target.value)
      this.$emit('input', e.target.value)
    }
  }
}
</script>

四、sync修饰符

  • 作用:可以实现 子组件 与 父组件数据 的双向绑定,简化代码
  • 特点:prop属性名,可以自定义,非固定为value
  • 场景:封装弹窗类的基础组件,visible属性 true显示 false隐藏
  • 本质:就是 :属性名 和 @update:属性名 合写

代码:

App.vue

<template>
  <div class="app">
    <button class="logout" @click="isShow = true">退出按钮</button>
    <!-- :visible.sync => :visible + @update:visible -->
    <BaseDialog :visible.sync="isShow"></BaseDialog>
  </div>
</template>
<script>
import BaseDialog from './components/BaseDialog.vue';
export default {
  data () {
    return {
      isShow: false
    }
  },
  components: {
    BaseDialog
  }
}
</script>

BaseDialog.vue

<template>
  <div v-show="visible">
    <div>
      <div>
        <h3>温馨提示:</h3>
        <button class="close" @click="close"></button>
      </div>
      <div>
        <p>你确定要退出本系统吗?</p>
      </div>
      <div>
        <button @click="close">确认</button>
        <button @click="close">取消</button>
      </div>
    </div>
  </div>
</template>
<script>
export default {
  props: {
    visible: Boolean
  },
  methods: {
    close () {
      this.$emit('update:visible', false)
    }
  }
}
</script>

五、ref和$refs

作用利用ref$refs可以用于获取dom(文档对象模型中代表HTML元素的JavaScript对象)元素,或组件实例

特点:查找范围——>当前组件内(更精准稳定)

获取dom

1.目标标签-添加ref属性

<div ref="chartRef">我是渲染表的容器</div>

2.恰当时机,通过this.$refs.xxx,获取目标标签

mounted(){
console.log(this.$refs.chartRef)
},

代码:

App.vue

<template>
  <div class="app">
    <BaseChart></BaseChart>
  </div>
</template>
<script>
import BaseChart from './components/BaseChart.vue'
export default {
  data () {
    return {
    }
  },
  components: {
    BaseChart
  }
}
</script>

BaseChart.vue

<template>
  <div ref="mychart" id="base-chart-box" style="width: 600px; height: 400px"></div>
</template>
<script>
// 装包:yarn add echarts
import * as echarts from 'echarts';
export default {
  mounted () {
    // const myChart = echarts.init(document.querySelector('.base-chart-box'))
    const myChart = echarts.init(this.$refs.mychart)
    // 指定图表的配置项和数据
    const option = {
      title: { text: 'My Chart' },
      xAxis: { data: ["衬衫", "羊毛衫", "裤子", "高跟鞋", "袜子"] },
      yAxis: {},
      series: [{ type: 'bar', data: [5, 20, 36, 10, 10] }],
    }
    // 使用刚指定的配置项和数据显示图表。
    myChart.setOption(option)
  }
}
</script>

效果图:

代码:

App.vue

<template>
  <div class="app">
    <BaseForm ref="baseForm"></BaseForm>
    <button @click="handleGet">获取数据</button>
    <button @click="handleReset">重置数据</button>
  </div>
</template>
<script>
import BaseForm from './components/BaseForm.vue'
export default {
  data () {
    return {
    }
  },
  methods: {
    handleGet () {
      console.log(this.$refs.baseForm.getValues())
    },
    handleReset () {
      this.$refs.baseForm.resetValues()
    }
  },
  components: {
    BaseForm
  }
}
</script>

BaseForm.vue

<template>
  <form action="">
    账号:<input type="text" v-model="account" />
    密码:<input type="password" v-model="password" />
  </form>
</template>
<script>
export default {
  data () {
    return {
      account: '',
      password: ''
    }
  },
  methods: {
    // 方法1:收集表单数据,返回对象
    getValues () {
      return {
        account: this.account,
        password: this.password
      }
    },
    // 方法2:重置表单
    resetValues () {
      this.account = ''
      this.password = ''
    }
  }
}
</script>

六、Vue异步更新、$nextTick

需求:编辑标题,编辑框自动聚焦

1.点击编辑,显示编辑框

2.让编辑框立刻获取焦点

this.isShowEdit = true // 显示输入框
this.$refs.inp.focus() // 获取焦点

问题:“显示之后”,立刻获取焦点是不能成功的!

原因:vue是异步更新DOM(提升性能)

$nextTick:DOM更细后,才会触发执行此方法里的函数体

语法:this.$nextTick(函数体)

this.$nextTick(()=>{
this.$refs.inp.focus()
})

代码:

App.vue

<template>
  <div class="app">
    <div v-if="isShowEdit">
      <input ref="inp" v-model="editValue" type="text">
      <button>确认</button>
    </div>
    <!-- 默认状态 -->
    <div v-else>
      <span>{{ title }}</span>
      <button @click="handleEdit">编辑</button>
    </div>
  </div>
</template>
<script>
export default {
  data () {
    return {
      title: '大标题',
      editValue: '',
      isShowEdit: false
    }
  },
  methods: {
    handleEdit () {
      // 1.显示输入框 (异步dom更新,获取不到数据)
      this.isShowEdit = true
      // 2.让 输入框 获取焦点($nextTick等dom更新完,立刻执行准备的函数体)
      this.$nextTick(() => {
        this.$refs.inp.focus()
      })
    }
  }
}
</script>

总结:

1.Vue是异步更新DOM的

2.想要在DOM更新完成之后做某件事,可以使用$nextTick

  • 16
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值