一壶茶的时间带你了解vue中父组件异步数据通过props方式传递给子组件,子组件接收不到的问题

154 阅读3分钟

在项目中,我们经常会用到父子组件的传值操作,但是有时候我们获取到的数据是通过请求接受到的异步数据,但是子组件在接收父组件值的过程中会遇到一个问题,那就是虽然页面视图已经更改了,但是在调试工具打印的时候还是之前的值,这就造成了一个困扰,今天我来带大家了解一下为什么会发生这种情况,以及解决方案。

父组件: App.vue

<template>
  <div>
    <button @click="change">传值</button>
    <div>
      <asyncPage :num="num"></asyncPage>
    </div>
  </div>
</template>

<script>
// 我们先正常引入 子组件
import asyncPage from './components/HelloWorld.vue'

export default {
  name: 'DemApp',
  components:{
    asyncPage
  },
  data() {
    return {
      num:1,
    };
  },
  methods: {
    change(){
      // 首先我们定义一个定时器,模拟项目中异步请求得到的异步数据
      setTimeout(()=>{
        // 点击让数据发生改变
        this.num = 12
      },0)
    },
  
  },
};
</script>

子组件:compoent.vue

<template>
  <div>
    {{num}}
  </div>
</template>

<script>
export default {
  name: 'DemHelloWorld',

  props:{
    num:{
      type:Number,
      require:true
    }
  },
 
  data() {
    return {
    };
  },
  created(){
    console.log(this.num,11111111111);
  },
  methods: {

  },
};
</script>

<style lang="scss" scoped>

</style>

打印结果:

image.png

可以看到,我们在触发点击事件后,拿到的值是父组件之前定义好的值1,但是视图中的数据已经发生了改变。造成这样的原因是因为,父子组件在搭建关系的时候,他们的生命周期已经开始了执行。执行的顺序为:

父组件 beforeCreate > 父组件 Created > 父组件 beforeMount > 子组件 beforeCreated > 子组件 Created > 子组件 beforeMount > 子组件 Mounted > 父组件 Mounted

当我们触发点击事件函数执行的时候。子组件的生命周期早已经开始执行,所以 Created 函数打印出来的值是之前已经定义好的数据。但是当函数执行过后值确实发生了改变,基于 Vue 响应式的原理视图,也对应发生了改变。如若想在 created 拿到该值的话,你可以这样操作

父组件App.vue

<template>
  <div>
    <button @click="change">传值</button>
    <div>
      <asyncPage :num="num"></asyncPage>
    </div>
  </div>
</template>

<script>
 // 我改变一下引入方式,用异步的方式去引入
  const asyncPage = () => ({
  component:import('./components/HelloWorld.vue'),
  // 展示加载时组件的延时时间
  delay: 1000,
  // 如果提供了超时时间且组件加载也超时了,
  timeout: 3000,
})

export default {
  name: 'DemApp',
  components:{
    asyncPage
  },
  beforeCreate(){
    console.log('我是父组件中的生命周期:beforeCreate');
  },
  created(){
    console.log('我是父组件中的生命周期:created');
  },
  beforeMount(){
    console.log('我是父组件中的生命周期:beforeMount');
  },
  mounted(){
    console.log('我是父组件中的生命周期:mounted');
  },
  data() {
    return {
      num:1
    };
  },
};
</script>

<style lang="scss" scoped>

</style>
    

打印结果:

image.png

可以发现,执行顺序确实发生了改变,那我们再来看看拿到的值,会不会因为异步方式引入而发生改变呢,

App.vue

<template>
  <div>
    <button @click="change">传值</button>
    <div>
      <asyncPage :num="num"></asyncPage>
    </div>
  </div>
</template>

<script>
// 我们改变一下引入方式,用异步的方式去引入
  const asyncPage = () => ({
  component:import('./components/HelloWorld.vue'),
  // 展示加载时组件的延时时间
  delay: 1000,
  // 如果提供了超时时间且组件加载也超时了,
  // 则使用加载失败时使用的组件。默认值是:`Infinity`
  timeout: 3000,
})

export default {
  name: 'DemApp',
  components:{
    asyncPage
  },
  beforeCreate(){
    console.log('我是父组件中的生命周期:beforeCreate');
  },
  created(){
    console.log('我是父组件中的生命周期:created');
  },
  beforeMount(){
    console.log('我是父组件中的生命周期:beforeMount');
  },
  mounted(){
    console.log('我是父组件中的生命周期:mounted');
  },
  data() {
    return {
      num:1
    };
  },
  methods:{
    change(){
      setTimeout(() => {
        this.num = 12
      }, 0);
    }
  }
};
</script>

<style lang="scss" scoped>

</style> 

子组件:compoent.vue

<template>
  <div>
    {{num}}
  </div>
</template>

<script>
export default {
  name: 'DemHelloWorld',

  props:{
    num:{
      type:Number,
      require:true
    }
  },
  beforeCreate(){
    console.log('我是子组件中的生命周期:beforeCreate');
  },
  created(){
    console.log(this.num,12123132);
    console.log('我是子组件中的生命周期:created');
  },
  beforeMount(){
    console.log('我是子组件中的生命周期:beforeMount');
  },
  mounted(){
    console.log('我是子组件中的生命周期:mounted');
  },
  data() {
    return {
    };
  },
  methods: {

  },
};
</script>

<style lang="scss" scoped>

</style>

打印结果:

image.png

看到这里有人可能会有疑惑,我不是已经异步引入组件了吗?为什么 created 拿到的值还是没有改变呢,首先明白一点,异步引入不是不加载的意思,而是说他的执行速度相对于其他同步加载的组件速度会慢一些,会优先执行别的组件,就好比 JavaScript 中的事件队列,优先执行同步任务,然后再执行异步任务,因为我们是通过点击事件触发的回调函数对值进行的修改,所以无论是异步任务还是同步任务我们获取到的数据永远都是父组件最开始定义的数据值,因为我们在更新值的前提是通过点击事件触发的回调函数,来操控值的改变,但是假如我们直接在父组件created 函数中调用 change 事件,模拟我们项目中请求数据的异步操作,再来看看结果

打印结果:

image.png

我们拿到的值就是改变后的值,原因是因为生命周期的顺序发生了改变,在父组件中的 Mounted 已经完成之后,再去执行子组件的生命周期自然而然,拿到的值是已经发生过改变的值

总结

说到底这是一个组件之间生命周期执行顺序的问题,解决这种问题思路就是,改变生命周期就行了

这是异步组件的用处之一,其次它也是为了优化项目,我们都知道 vue是单页面应用,首次加载的速度会比较慢,假如在项目过大,你直接全程引入动态组件,网不好那项目还没加载完直接嘎了。其实不使用异步组件的话,也可以在created获取值,直接通过 v-if 来对组件进行显示隐藏,从而获取上面的操作,但是确实不太推荐,损耗性太大,直接使用异步引入不香吗?优化了性能的同时,还解决了问题。

好啦,讲到这里,我们的内容就结束了,有不懂的地方,大家可以留言,我会尽心尽力解答,有说的不对的地方,欢迎各位大佬指正