VUE学习笔记(黑马2023版 第四天)

第四天

scoped样式冲突

默认情况: 写在组件中的样式会全局生效 →因此很容易造成多个组件之间的样式冲突

  1. 全局样式:默认组件中的样式会作用到全局
  2. 局部样式:可以给组件加上scoped属性,可以让样式只作用于当前组件

scoped原理

  1. 当前组件内的表圈都被添加"data-v-hash值"的属性
  2. css选择器都被添加[data-v-hash值]的属性选择器

data是一个函数

一个组件的data选项必须是一个函数→保证每个组件实例,维护独立的一份数据对象。
每次创建新的组件实例,都会新执行一次data函数,得到一个新对象

data () {
	return {
   	count:100
   }
},

组件通信

  1. 两种组件关系分类 和 对应的组件通信方案
    父子关系 → props & $emit
    非父子关系 → provide & inject 或 eventbys
    通用方案 → vuex
  2. 父子通信方案的核心流程
  • 父传子props:
    ①父中给子添加属性值 ②子props接受 ③使用
    在这里插入图片描述
  • 子传父$emit:
    ①子 $emit发送消息②父中给子添加消息监听③父中实现处理函数
    在这里插入图片描述

props详解

什么是prop

prop定义:组件上注册的一些自定义属性
prop作用:向子组件传递数据
特点:

  • 可以传递任意数量的prop
  • 可以传递任意类型的prop
    在这里插入图片描述

props校验

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

  1. 类型校验(最常用)
props:{
	校验的属性名:类型 //Number String...
},
  1. 非空校验
  2. 默认值
  3. 自定义校验
props:{
	校验的属性名:{
		type:类型,// Number String
		required:true,//是否必填
		default:默认值,//默认值
		validator(value){
			//自定义校验逻辑
			return 是否通过校验
		}
	}
}

prop & data 、单向数据流

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

  • data的数据是自己的 → 随便改
  • prop的数据是外部的 → 不能直接改,要遵循单向数据流
    单向数据流:父级prop的数据更新,会向下流动,影响子组件。这个数据流动是单向的。

实践案例:小黑记事本

效果图:
在这里插入图片描述

核心步骤:

  1. 拆分基础组件
    新建组件 → 拆分存放结构 → 导入注册使用
  2. 渲染待办任务
    提供数据(公共父组件)→ 父传子传递list → v-for 渲染
  3. 添加任务
    收集数据 v-model →监听事件 → 子传父传递任务 → 父组件unshift
  4. 删除任务
    监听删除携带id → 子传父传递id → 父组件filter删除
  5. 底部合计和清空功能
    底部合计:父传子传递list → 合计展示
    清空功能:监听点击 → 子传父通知父组件 → 父组件清空
  6. 持久化存储
    watch监视数据变化,持久化到本地

核心代码如下:

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

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

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
        })
      },
      handelDel (id) {
        this.list=this.list.filter(item => item.id !== id)
      },
      handelClear(){
        this.list=[]
      }
    },
    watch:{
      list:{
        deep:true,
        handler(newValue) {
          localStorage.setItem('list',JSON.stringify(newValue))
        }
      }
    },
    components: {
        TodoHeader,
        TodoMain,
        TodoFooter
    },
}
</script>

<style>

</style>

//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() {
            if(this.todoName.trim() === ''){
                alert('任务名不能为空')
                return
            }
            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 @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>

<style>

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

<style>

</style>

非父子通信(拓展) - event bus 事件总线

作用:飞父子组件之间,进行简易消息传递(复杂场景 → Vuex)

  1. 创建一个都能访问到的事件总线(空 Vue 实例) → utils/EventBus.js
import Vue from 'vue'
const Bus = new Vue()
export default Bus
  1. A组件(接收方),监听Bus实例的事件
created () {
	Bus.$on('sendMsg',(msg) => {
		this.msg = msg
	})
}
  1. B组件(发送方),出发Bus实例的事件
Bus.$emit('sendMsg','这是一个消息')

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

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

  1. 父组件 provide 提供数据
export default{
	provide () {
		return {
			//普通类型【非响应式】
			color: this.color,
			//复杂类型【响应式】(推荐)
			userInfo: this.userInfo,
		}
	}
}
  1. 子/孙组件 inject 取值使用
export default {
	inject: ['color','userInfo'],
	created () {
		console.log(this.color,this.userInfo)
	}
}

v-model 原理

原理:v-model本质上是一个语法糖。例如应用在输入框上,就是value属性和input属性的合写。
作用:提供数据的双向绑定
①数据变,视图跟这边 :value
②视图变,数据跟着变 @input
注意:¥event用于在模版中,获取事件的形参

<template>
  <div class="app">
    <input v-model="msg1" type="text" />
    <input :value="msg2" @input="msg2 = $event.target.value" type="text" >
  </div>
</template>

表单类组件封装

表单类组件封装 → 实现子组件和父组件数据的双向绑定
①父传子:数据应该是父组件props传递过来的,v-model拆解绑定数据
②子传父:监听输入,子传父传值给父组件修改
本质:实现了子组件和父组件数据的双向绑定

<!--父组件-->
<BaseSelect :cityId="selectId" @事件名="selectId = $event"></BaseSelect>
<!--子组件-->
<select :value="cityId" @change="handleChange">...</select>
//子组件
props: {
    cityId:String
  },
methods: {
  handleChange (e) {
    this.$emit('事件名', e.target.value)
   }
 }

v-model简化代码

父组件v-model简化代码,实现子组件和父组件数据双向绑定
①子组件中:props通过value接受,事件触发input
②父组件中:v-model给组件直接绑数据

<!--父组件-->
<BaseSelect v-model="selectId"></BaseSelect>
<!--子组件-->
<select :value="value" @change="handleChange">...</select>
//子组件
props: {
    value:String
  },
methods: {
  handleChange (e) {
    this.$emit('input', e.target.value)
   }
 }

.sync修饰符

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

<!--父组件-->
<BaseDialog :visible.sync="isShow"></BaseDialog>
//子组件
props:{
    visible:Boolean
  },
  methods:{
    close () {
      this.$emit('update:visible', false)
    }
  }

ref 和 $refs

作用:利用 ref 和 $refs 可以用于 获取 dom 元素,或 组件实例
特点:查找范围 → 当前组件内(更精确稳定)
①获取dom:

  1. 目标标签 - 添加 ref 属性
<div ref="chartRef">我是渲染图标的容器</div>
  1. 恰当实际,通过 this.$refs.xxx,获取目标标签
mounted() {
	console.log(this.$refs.chartRef)
},

②获取组件:

  1. 目标组件 - 添加ref属性
<BaseForm ref="baseForm"></BaseForm>

2.恰当实际,通过this.$refs.xxx,获取目标组件,就可以调用组件对象里面的方法

this.$refs.baseForm.组件方法()

Vue异步更新、$nextTick

  1. Vue是异步更新DOM的
  2. 想要在DOM更新完成之后做某件事,可以使用$nextTick
this.$nextTick(() => {
	//业务逻辑
})
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值