vue2 vue3 组件传值的方式

组件间传值的方法

总结

  • 父组件传子组件
    • 父组件在使用子组件时通过设置属性传值,子组件使用props接收
  • 子组件传父组件
    • 自定义事件 父组件将自定义事件传递给子组件,子组件emit接受

什么是单向数据流

  1. 什么是单向数据流
    数据流是指组件之间数据的流向,单向数据流指数据只能从父组件向子组件传递,子组件无法改变父组件的props,如果想修改有其他的方式。
  2. 为什么不能是双向的
    父组件的数据发生改变,会通过props来通知子组件自动更新。 防止多个子组件都尝试修改父组件状态时,导致数据混乱

单向数据流的好处

  • 单向数据流会使所有状态的改变可记录、可跟踪,源头易追溯;
  • 所有数据只有一份,组件数据只有唯一的入口和出口,使得程序更直观更容易理解,有利于应用的可维护性。

父组件给子组件传值

方式1: props

通用说明
1.props是只读属性,子组件不可以修改传入的值。
2.props方式的传值是浅拷贝

传递方法
1.父组件上使用子组件,通过子组件的属性进行传值。
2.子组件props接受父组件的传值。

options API写法

  • 子组件通过props参数接收数据,数据优先被设置在vc组件上(vc.数据)
    • 如果子组件不使用props,子组件的$attrs里会存储传过来的属性,子组件的vc实例上不会存储。
    • 如果子组件使用props,子组件的vc实例上会直接存储传过来的属性,子组件的$attrs里不会存储
//1.父组件通过属性传值
<Student :name=name :sex=sex :age=age/>
//option API
//写法1:子组件接收值,简单接收
props:['name','sex','age']

//写法2:子组件接收值,设置传来值的类型
props:{
    
    
	name:String,
	sex:String,
	age:Number	
}

//写法3: 子组件接收值,完整写法
props:{
    
    
	name:{
    
    
		type:String,
		required:true //是否必须
		 default: "table" 
	},
	age:{
    
    
		type:Number,//类型
		default:99 //默认值
	},
	 rowClick: {
    
    
      type: Function,
      default: () =>{
    
    }
    },
     columns: {
    
    
      type: Array,
      default:() =>[]
    },
    api: {
    
    
      type: Object,
      default:()=>({
    
    })
    },
}
default默认值

1.使用default定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用默认值。
2.没有定义默认值时,如果父组件有传值,则用父值渲染。如果父组件没有传值,则使用的是该类型的默认值。

String ''
Number 0
Array []
Object {
    
    }

props 默认值只在没有传参时才会被读取,如果传入的是不完整对象,并不会为对象没有值的属性补齐默认值

在这里插入图片描述

所有props默认值(vue2和vue3)的写法都需要遵守
基本数据类型:直接赋值
对象数据类型:用函数赋值()=>{}

如果直接采用{}和[],当多个使用该组件的父组件都没有传递props而使用默认值。假设其中一个父组件修改了默认的值(不推荐,会报警告),其他的父组件由于指向的是同一个内存中的引用地址,也会发生改变。
使用了函数的形式去返回,保证每次函数执行出来的都是返回一个新的对象。

composition API | defineProps编译宏

defineProps()不需要定义和引用就可以直接使用
返回值:返回一个由接受属性组成的代理对象

说明

  • definePropsdefineEmits 都是只能在 <script setup> 中使用的编译器宏。他们不需要导入,且会随着 <script setup> 的处理过程一同被编译掉。
  • definePropsdefineEmits 要么使用运行时声明,要么使用类型声明。同时使用两种声明方式会导致编译报错。
  • 在模板中使用时,props可以省略
//第一种,简单接收
const props = defineProps(["name"]);
//第二种设置接收的类型
defineProps({
    
    
  page:Number
});
//第三种设置接收的类型和默认值
defineProps({
    
    
  page:{
    
    
    type:Number,
    default:2
  }
});
//第四种设置传来值的多种类型
defineProps({
    
    
  page:[String,Number]
})

运行时声明指对于 props 的类型的声明,这种声明方式 IED 是无法检测和给出提示的,只有在运行后才会给出提示,props API属于运行时声明。
类型声明在这里类型声明指基于 ts 的类型检查,对 props 进行类型的约束,支持IDE的类型推断和检查。因此,要使用类型声明,需要基于 ts,即 <script setup lang="ts">

composition API运行时声明的写法


const props = defineProps({
    
    
foo: String,
bar: {
    
    
  type: Number,
  required: true
}
})
</script>

composition API类型声明的写法

<script setup lang='ts'>
interface List {
    
    
    id: number,
    content: string
}

const props = defineProps<{
    
    
  foo?: string
  list: List // 接口
}>()
</script>
props类型声明的默认值 | widthDefaults编译宏
<script setup lang="ts">
interface Props {
    
    
        msg?: string
        labels?: string[]
 }
 const props = withDefaults(defineProps<Props>(), {
    
    
       msg: 'hello',
       labels: () => ['one', 'two']
 })
</script>

方法2:组件身上的属性与事件

vue2 $attrs + $listeners

多层嵌套组件传递数据时,如果只是传递数据,而不做中间处理的话就可以用这个,比如父组件向孙子组件传递数据时

  • $attrs:包含父作用域里除 classstyle 除外的非 props 属性集合。通过 this.$attrs 获取父作用域中所有符合条件的属性集合,然后还要继续传给子组件内部的其他组件,就可以通过 v-bind="$attrs"
  • $listeners:包含父作用域里 .native 除外的监听事件集合。如果还要继续传给子组件内部的其他组件,就可以通过 v-on="$linteners"

vue3 useAttrs方法

useAttrs(): 获取组件身上的属性与事件,只能接受父作用域里除 classstyle 除外的非 props 属性集合,原生DOM事件和自定义事件都可以接受。

<template>
	<el-button :="$attrs"></el-button>
</template>

<script setup lang="ts">
import {
    
     useAttrs } from 'vue';
let $attrs  = useAttrs(); //返回的attrs是一个响应式代理对象 
</script>

:="$attrs"写法的意思,解构$attrs对象,key为属性名,value为属性值

<h1 v-bind="{a:1,b:2}">123</h1>
//页面中显示
<h1 a="1" b="2">123</h1>

方法3: $parent + defineExpose编译宏 | 子组件内部获取到父组件实例

$parent可以在子组件内部获取到父组件的实例

//子组件
<button @click="handler($parent)"></button>

//父组件暴露
defineExpose({
    
    })

使用 script setup 的组件是默认关闭的,通过模板 ref 或者 $parent 链获取到的组件的实例,不会暴露任何在 script setup 中声明数据。
需要使用 defineExpose 编译器宏将数据暴露

方法4:插槽 结构父 -> 子

默认情况引用组件后, 父组件模板里面使用组件,组件开闭标签内部的结构不会被渲染出来。如果想将其渲染出来,也就是由父组件传递html结构给子组件,就可以使用插槽。

插槽作用:让父组件可以向子组件指定位置插入html结构

插槽的种类

  • 默认插槽:父组件将html结构传递给子组件(相当于子组件留html坑,由父组件传递html填坑)。
  • 具名插槽(slot标签的name属性命名):由父组件决定将html结构传递哪个插槽。
  • 作用域插槽:父组件将html结构传递给子组件,子组件将数据传回父组件 。数据在子组件,但是根据数据生成的结构需要父组件来决定

默认插槽
同一组件默认插槽可以有多个

//Parent.vue
<Child>
<div>默认插槽</div>
</Child>

//Child.vue
<slot></slot> //默认插槽 <div>默认插槽</div>
<slot></slot>

具名插槽
使用<slot name="xxx">给插槽命名,父组件选择插入哪个插槽(①②都是vue3写法)
①使用<template v-slot:xxx>
②简写形式<template #xxx>

同一组件剧名插槽可以有多个

//Child.vue
<slot name="a"></slot>

//Parent.vue
<template #a>
    <div>我是填充具名插槽a位置结构</div>
</template>

作用域插槽
作用域插槽:数据: 子 -> 父 结构:父 -> 子
使用场景:数据在插槽所在的组件,但是根据数据生成的结构需要父组件来决定。

数据在子组件(作用域),结构由父组件传。

步骤
1.<slot :xxxx="yyy">将数据传给父组件,slot的固定写法。传递一个对象,key为xxx,值为yyy。
2.父组件使用作用插槽 <template v-slot="{xxxx}">,这里需要解构使用

<Child :todos="todos">
	//v-slot接受子组件传递回来的数据
   <template v-slot="{ row, index }">
     <p :style="{ color: row.done ? 'green' : 'red' }">
      {
    
    {
    
    row.title }}--{
    
    {
    
     index }}
     </p>
	</template>
</Child>
let todos = ref([
  {
    
     id: 1, title: "ranran", done: true },
  {
    
     id: 2, title: "ranran1", done: false },
  {
    
     id: 3, title: "ranran2", done: true },
  {
    
     id: 4, title: "ranran3", done: false },
]);


//Child.vue
//子组件回传数据给父组件
<ul>
   <li v-for="(item, index) in todos" :key="item.id">
    <!--作用域插槽:可以讲数据回传给父组件-->
    <slot :row="item" :index="index"></slot>
   </li>
</ul>

<script setup lang="ts">
//通过props接受父组件传递数据
defineProps(["todos"]);
</script>

子组件给父组件传递

自定义事件与原生DOM事件

// 在vue2中,这种写法为自定义事件
// 在vue3中,这种写法为原生DOM事件
<Child @click=""></Child>
  • 组件在绑定事件时,vue2默认绑定的是自定义事件。在vue2中,可以使用.native标识符比如@click.native,将自定义事件变为原生的DOM事件,将事件绑定在子组件的根节点上。
  • vue3原生的DOM事件不管放在标签上、组件上都是原生事件。
    虽然vue3的原生DOM事件绑定在组件上也是原生事件,但是只要子组件通过defineEmits接受了父组件传递过来的函数,该事件会被当作自定义事件触发。

方法1:自定义事件/原生DOM事件 + emit 方法

options API写法 | $emit()

  1. 将自定义事件绑定在子组件的实例vc上,回调函数是在父组件中,通过回调函数接收参数
  • 写法1: 在父组件中使用@v-on:将回调函数绑定在子组件的vc上@自定义事件=’事件回调'
  • 写法2: 在父组件中this.$refs.xxx获取到子组件的实例,采用$on(‘事件名’,回调函数)绑定自定义事件
  1. 子组件$emit()触发自定义事件并传递参数,子组件this.$off()解绑自定义事件
//父组件
<template>
  <div>
    <h1>我是父组件</h1>
    <Son :info="info" @change="fn"></Son>
  </div>
</template>

<script>
import Son from "./Son.vue";
export default {
    
    
  data() {
    
    
    return {
    
    
      info: "我是父组件中的数据",
    };
  },
  components: {
    
    
    Son,
  },
  methods: {
    
    
    fn(info) {
    
    
      this.info = info +  "我是父组件中点击修改后的数据";
    },
  },
};
</script>

//子组件
<template>
  <div>
    <h2>我是子组件</h2>
    <p>{
    
    {
    
     info }}</p>
    <button @click="fn">修改数据(子)</button>
  </div>
</template>

<script>
export default {
    
    
  props: ["info"], //父传子
  methods: {
    
    
    fn() {
    
    
      //这种直接赋值prop是不可取的,vue会直接报错
      //this.info=this.info+"子组件直接赋值prop"
      // 修改数据
      this.$emit('change',this.info + ",我现在被子组件emit了"); //触发自定义事件并传值,父组件自定义事件的回调函数触发
    },
  },
};
</script>

composition API写法 | defineEmits编译宏

defineEmits接受父组件传递过来的自定义函数
返回值:(event, ...args) => instance.emit(event, ...args) 返回一个函数,第一个参数为自定义事件名,第二个参数开始为传递给自定义函数的参数。

运行时声明的写法

//子组件
<template>
  <button @click="butFn">改变page值:{
    
    {
    
    page}}</button>
</template>


<script setup>
import {
    
     defineEmits } from "vue";
const emit = defineEmits(["pageFn"]);   //定义一个变量来接收父组件传来的方法
const butFn=()=>{
    
    
    emit("pageFn",5)//触发该方法,5为传递的参数
}
</script>

composition API类型声明的写法

const emit = defineEmits<{
    
    
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+:另一种更简洁的语法
const emit = defineEmits<{
    
    
  change: [id: number] // 具名元组语法
  update: [value: string]
}>()

方法2 ref + defineExpose编译宏 | 父组件获取子组件的值/方法

ref可以获取真实的DOM节点,也可以获取子组件实例VC
$parent可以在子组件内部获取到父组件的实例

setup中获取子组件的实例
1.父组件中设置子组件属性ref
2.同名变量接受

//父组件
<Son ref='son'> </son>

<script setup lang="ts">
const son= ref(null);
</script>

存在问题:ref可以获取子组件实例,但是获取不到子组件数据
原因:组件内部数据对外关闭
解决办法:组件defineExpose()对外暴露值或方法

//子组件暴露
defineExpose({
    
    
  money,
})
//父组件使用
<Son ref='son'> </son>

<script setup lang="ts">
const son= ref(null);
conselo.log(son.value);
</script>

父子组件数据同步

vue2 v-model的原理

v-model语法糖 = props(父传子) + 绑定事件监听@input/触发事件监听emit(子传父)

v-model实现原理
默认传递的属性是value,自定义事件名为input

<!--v-model简写-->
<Son v-model="msg" />
<!--原始写法-->
<Son :value="msg" @input= "val => msg=val "/>

text 和 textarea 元素使用 value property 和 input 事件;
checkbox 和 radio 使用 checked property 和 change 事件;
select 字段使用 value property和change 作为事件。

vue2 sync 修饰符原理

sync 修饰符 = props(父传子) + 绑定事件监听@update:属性名/触发事件监听emit (子传父)

.sync修饰符可以实现和v-model同样的功能,而且它比v-model更加灵活。
v-model一个组件只能用一个.sync可以有多个。

xxx.sync的原理
①父组件给字子组件传递props:属性名
②给当前子组件绑定了一个自定义事件,事件名为update:属性名,该事件会更新xxx的值

// 正常父传子: 
<son :info="str" :title="str2"></son>

// 加上sync之后父传子(.sync没有数量限制): 
<son :info.sync="str" .title.sync="str2"></son> 

// 它等价于
<son
:info="str" @update:info="val=>str=val"
:title="str2" @update:title="val=>str2=val">
</son> 

//子组件emit触发事件
this.$emit('update:info',this.info + ",我现在被子组件emit了")

vue3 v-model原理

vue3 v-model的原理是vue2中sync修饰符的原理,特点也一样,一个组件可以有多个v-model

绑定多个v-model:v-model:value = 值,每个v-model之间互不影响。

<Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1>

祖先组件向子孙组件传值

provide/inject

  • provide提供数据:指定想要提供给后代组件的数据或方法
  • inject在组件中注入数据:在任何后代组件中使用inject接收provide提供的数据或方法,不管组件嵌套多深都可以直接拿来用

vue2中的使用

injectdata/props之前初始化,providedata/props之后初始化,注入内容时,是将内容注入到当前vc的__provide
inject配置key先在当前组件读取内容(__provide),读取不到则取它的父组件读取,找到最终内容保存到当前实例(vc)中,这样可以直接通过this读取到inject注入的内容。

vue 不会对 provide 中的变量进行响应式处理。所以,要想 inject 接受的变量是响应式的,provide 提供的变量本身就需要是响应式的。

// 父组件
export default {
    
    
  provide: {
    
    
    name: "父组件数据",
    say() {
    
    
      console.log("say say say");
    },
  },
  // 当需要用到this的时候需要使用函数形式,调用时this指向当前组件
  provide() {
    
    
    return {
    
    
      todoLength: this.todos.length
    }
  },
}

// 子组件
<template>
  <div>
    <div>provide inject传递过来的数据: {
    
    {
    
     name }}</div>
    <div>provide inject传递过来的方法<button @click="say">say</button></div>
  </div>
</template>

<script>
export default {
    
    
  inject: ['name', 'say'],
  },
}
</script>

vue3中的使用

  • provide()方法,第一个参数需要是一个独一无二的标识(不允许和组件内部的变量重名),第二个参数为祖先组件提供的数据(值/方法)
  • inject()方法,第一个数据为标识,通过标识获取祖先组件提供的数据,第二个参数为默认值,如果没有从祖先组件获取到标识数据就使用默认值。
//祖先组件
<script setup lang="ts">
import {
    
    provide] from 'vue'
const age = ref(18);
provide("keyName",age )
</script>

//孙子组件
<script setup lang="ts">
import {
    
    inject} from 'vue';
let car = inject('keyName'); // car为ref引用对象
console.log(car.value)
const updateCar = ()=>{
    
    
   car.value  = '自行车';
}
</script>

说明
1.上述案例中,孙子组件中接受的数据和祖先组件注入的数据指向同一个ref引用对象。所以在孙子组件中修改car ,祖先组件中同名数据也会被修改。在祖先组件中修改值,孙祖组件中也会响应。

2.在模板中使用,可以直接使用参数标识

<script setup lang="ts">
import {
    
    inject} from 'vue';
</script>
<template>
	<span>{
    
    {
    
    keyName}}</span>
</template>

猜你喜欢

转载自blog.csdn.net/qq_41370833/article/details/132484031