Vue 父组件通过 prop 传值给子组件 但子组件获取的值为空-解决记录

01 Bug 描述

笔者基于简化版的 vue-element-admin 前端框架 vue-admin-template 进行二次开发。

开发中遇到需要父组件向子组件传值的情景,笔者使用 Vue 官方文档提供的 通过 Prop 向子组件传递数据 的方法实现父组件向子组件的传值。但是在我的实例中,使用该方法传值,最终子组件获取到的数据为空值。

为了找到 Bug,我回顾了一遍通过 Prop 向子组件传递数据的过程。

02 通过 Prop 向子组件传递数据

笔者使用该方法实现父组件向子组件传值的过程如下:

父组件中操作:

  1. 在模板定义中父组件将自己要传递的数据,传递给子组件
  2. 子组件通过绑定属性来绑定父组件所传递的数据

子组件中操作:

  1. 子组件中将上面用于绑定数据的标签属性经过驼峰转换之后,定义到对应子组件模型属性定义的 props 中
  2. 子组件的标签属性可以使用插值表达式,显示在template模板里面

我的实例中父组件主要代码如下:

父组件在创建时调用 fetchData() 从后台获取一个集合数据并保存在 list 数据属性中,然后在模板定义中将 list 数据传递给子组件 ChildComponent

<template>
  <div class="app-container">    
  	   <!-- 父组件将 list 数据传递给子组件 -->
  	   <!-- 子组件通过绑定属性 child-list 绑定父组件数据 list -->
       <child-component :child-list="list"/>
  </div>
</template>

<script>
// 引入子组件 ChildComponent
import ChildComponent from './components/ChildComponent'
import { transactionList } from '@/api/remote-search'

export default {
  name: 'FatherComponent',
  components: {
    ChildComponent
  },
  data() {
    return{
      tableKey: 0,
      list: null,
      total: 0,
      listQuery: {
        page: 1,
        limit: 10,
        sort: '+id'
      }
    } 
  },
  created() {
  	// 创建时获取数据
    this.fetchData()
  },
  methods: {
    fetchData() {
      // 通过 api 方法 transactionList() 使用封装的 axios 请求从后台获取数据 
      transactionList(this.listQuery).then(response => {
          this.list = response.data.records
          this.total = response.data.total
          setTimeout(() => {
            this.listLoading = false
          }, 1.5 * 1000)
      })
    }
  }
}
</script>

我的实例中子组件主要代码如下:

子组件中将上面用于绑定数据的child-list经过驼峰转换之后childList,定义到对应子组件模型属性定义 props:["childList"],然后使用该数据计算集合元素总数num,最大值max,最小值min和平均值avg,并将这些计算结果使用插值表达式,显示在template模板中

<template>
<el-row>
  <el-row :gutter="40" class="panel-group">
    <el-col :xs="12" :sm="12" :lg="12" class="card-panel-col">
      <div class="card-panel-text">
      <!-- 使用插值表达式显示数据处理结果 -->
      总数:{{num}}
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="12" class="card-panel-col">
      <div class="card-panel-text">
      最大值:{{max}}
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="12" class="card-panel-col">
      <div class="card-panel-text">
      最小值:{{min}}
      </div>
    </el-col>
    <el-col :xs="12" :sm="12" :lg="12" class="card-panel-col">
      <div class="card-panel-text">
      平均值:{{avg}}
      </div>
    </el-col>
  </el-row>
</el-row>
</template>

<script>

export default {
  // 使用 prop 获取父组件传递过来的数据
  props:["childList"],
  data(){
    return{
      list: this.childList,
      num: 0,
      max: 0,
      min: 100,
      avg: 0,
    }
  },
  created(){
  	// 创建组件时调用该方法处理数据
    this.analysis()
  },
  methods: {
    analysis() {
      this.num = this.list.length
      let sum = 0
      for(let elem of this.list){
        if(elem.score > this.max){
          this.max = elem.score
        }
        if(elem.score < this.min){
          this.min = elem.score;
        }
        sum += elem.score;
      }
      this.avg = parseInt(sum/this.num)
    }
  }
}
</script>

03 发现 Bug

笔者的项目中是使用 axios 来请求后端数据的,这是父组件通过 prop 传值给子组件,子组件获取的值为空的Bug所在。

3.1 Axios 的异步请求机制

Axios 是一个异步请求技术 axios 中文网,往往基于 XMLHttpRequest 对象发起的请求都是异步请求。

异步请求的特点就是请求之后页面不动,响应回来的更新只是页面的局部,多个请求之间互不影响,并行执行。

3.2 Vue 的生命周期

Vue 实例的生命周期大概分为以下的几个阶段:

  • create实例创建阶段:钩子函数为 beforeCreate、created
  • compile模板编译阶段:该阶段无生命周期钩子函数
  • mount视图挂载阶段:钩子函数为 beforeMount、mounted
  • update更新阶段:钩子函数为 beforeUpdate、updated
  • destroy实例销毁阶段:钩子函数为 beforeDestroy、destroy

3.3 异步请求带来的问题

我们看根据 Vue 的生命周期来看父组件的实例化过程,第一步就是创建阶段,而在创建阶段中就已经调用了 fetchData() 向后端请求数据。

所以代码层面,我们是在子组件完成编译之前就已经请求了数据,理论上子组件获取到的 list 不该为空。

这就是 axios 异步请求提高了请求效率之外,带来了一些问题:异步请求之后页面不动,响应回来的更新只是页面的局部,多个请求之间互不影响,并行执行。

父组件通过 props 向子组件传递异步数据时,再父实例完成模板编译之后,异步数据数据还未到达,这时子组件获取到的数据就为空

04 解决 Bug

我们已经发现子组件通过 prop 获取数据为空的问题是 axios 异步请求机制导致的,解决思路有两种:

  • 数据到达后再传递:即等父组件确认从后端获取到数据之后再通过 prop 传递给子组件
  • 监听父组件改变的 prop:在子组件中定义 watch 属性监听父组件通过 prop 传递数据的变化情况

4.1 数据到达后再传递

这种解决方式比较符合笔者项目的场景,也有两种方式:一种是使用 v-if 条件渲染;另一种是将异步请求使用 Promise 转化为同步请求获取后端数据。

这里仅介绍更为简单易用的 v-if 条件渲染方式:在子组件标签中增加 v-if="list != null" 判断要传递的数据是否为空,不为空时才渲染。

v-if 条件渲染会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。v-if 是惰性渲染:如果在初始渲染时条件为假,则什么也不做,直到条件第一次变为真时,才会开始渲染条件块。

笔者的项目中只需要实例创建时传递一次数据,所以 v-if 的惰性十分契合项目需求。修改父组件代码如下,在代码第4行添加v-if即可:

<template>
  <div class="app-container">    
  	   <!-- 父组件中增加 `v-if="list != null"` 判断要传递的数据是否为空,不为空时才渲染 -->
       <child-component v-if="list!=null" :child-list="list"/>
  </div>
</template>

<script>
// 引入子组件 ChildComponent
import ChildComponent from './components/ChildComponent'
import { transactionList } from '@/api/remote-search'

export default {
  name: 'FatherComponent',
  components: {
    ChildComponent
  },
  data() {
    return{
      tableKey: 0,
      list: null,
      total: 0,
      listQuery: {
        page: 1,
        limit: 10,
        sort: '+id'
      }
    } 
  },
  created() {
  	// 创建时获取数据
    this.fetchData()
  },
  methods: {
    fetchData() {
      // 通过 api 方法 transactionList() 使用封装的 axios 请求从后台获取数据 
      transactionList(this.listQuery).then(response => {
          this.list = response.data.records
          this.total = response.data.total
          setTimeout(() => {
            this.listLoading = false
          }, 1.5 * 1000)
      })
    }
  }
}
</script>

4.2 监听父组件改变的 prop

除了上述较为简单的方法,如果你的项目中父组件向子组件传递的数据会频繁动态改变,使用监听的方式可能更加适合你需要的应用场景。

在子组件中使用 watch 来监听父组件改变的prop,使用methods 来代替 created,本项目的代码不适用这种情况,使用如下子组件实例代码:

<template>
 <div>
  子组件<!--1-->
  <p>{{test}}</p>
 </div>
</template>
 
<script>
 export default {
  props: ['childObject'],
  data: () => ({
   test: ''
  }),
  watch: {
   'childObject.items': function (n, o) {
    this.test = n[0]
    this.updata()
   }
  },
  methods: {
   updata () { // created只会执行一次,但是又想监听改变的值做其他事情,就要使用methods 来代替 created
    console.log(this.test)
   }
  }
 }
</script>

05 解决后记 - 父组件和子组件互相传值

Bug 解决过程主要是父组件通过 prop 传值给子组件的过程,这里也记录一下子组件向父组件传值的方法,主要使用 $emit() 方法让父组件监听到自定义事件实现传值。

一个子组件和父组件互相传值简单例子如下:

子组件代码

<template>
    <div>
        <h1>接收到了来自父组件的数据:{{toChildData}}</h1>
        <button @click="toParent">向父组件传值</button>
    </div>
</template>
 
 
<script>
export default {
	// 接收来自父组件的数据
	props:['toChildData'],
    data() {
        return {
            childData: '子组件的数据'
        }
    },
    methods: {
        toParent:function() {
        	// 触发当前实例上的事件,向父组件发送数据
        	// 注意事件名为 listenEvent,父组件根据此名来监听事件
            this.$emit('listenEvent', this.data)
        }
    }
}
</script>

父组件代码


<template>
    <div>
        <h1>接收到了来自子组件的数据:{{fromChildData}}</h1>
        // @listenEvent="receiveData" 监听子组件的 listenEvent,并调用 receiveData() 接收来子组件的数据
        <child :to-child-data="fatherData" @listenEvent="receiveData"></child>
    </div>
</template>
 
 
<script>
import childComponent from './components/childComponent'
export default {
	components: {childComponent},
    data() {
        return {
            fatherData: '这是父组件的数据',
            fromChildData: null
        }
    },
    methods: {
        receiveData(data) {
            this.fromChildData = data.childData;
        }
    }
    
}
</script>

参考资料

通过 Prop 向子组件传递数据

详解vue父组件传递props异步数据到子组件的问题

vue条件语句v-if、v-else、v-else-if用法

  • 14
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王清欢Randy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值