vue3父组件异步获取后端数据,子组件无法及时渲染问题分析、解决及使用suspense组件进行骨架屏优化

vue3中一个常见场景,父组件向后端异步获取数据,再父子传参给子组件,由子组件来渲染获取到的数据。

简单的props父子传参

只用props进行父子传参,子组件在其一系列生命周期开始时是获取不到数据的。父子组件的生命周期流程如下:
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
生命周期钩子是同步执行的,而向后端获取数据是异步的,如果在父组件的created中向后端异步获取数据,根据事件循环的顺序,子组件开始创建时是获取不到数据的。

做一个实现进行验证

//父组件,getArticleById向后端获取数据
const initArticleForm = () => {
  console.log('父组件created')
  getArticleById(articleId).then(res => {
    console.log('父组件获取到数据')
  })
}
initArticleForm()
onMounted(()=>{
  console.log('父组件Mounted')
})

在这里插入图片描述
可以看到放在父created中的异步获取要到父mounted后才能拿到数据。这样子组件是无法正常渲染的。

解决方案

1、使用v-if控制子组件开始挂载的时机

当异步获取到数据后再用v-if设置为true挂载子组件

<RichTextEditor v-if="flag" v-model="addArticleForm.content" ></RichTextEditor>
const initArticleForm = () => {
  console.log('父组件created')
  getArticleById(props.articleId).then(res => {
    flag.value = true //获取到数据改变flag
    let endTime = window.performance.now()
    console.log('父组件获取到数据')
  })
}
initArticleForm()

在这里插入图片描述
此时可以看到顺序是正确的,所以可以正常显示

性能测试

为了模拟卡顿的环境,将后端接口延迟3s返回

getArticleContentById: async (req, res) => {
        const {id} = req.query
        const data = await ArticleService.getArticleContentById(id)
        await setTimeout(()=>{
            res.header("Cache-Control", "no-cache, no-store, must-revalidate");
            res.send({
                code: 200,
                msg: "获取成功",
                data
            })
           }, 3000)
    },

然后在组件中v-for出1000个相同div用来展示父组件传来的数据

<div v-for="item in list">{{props.modelValue}}</div>
<script setup>
let list = []
for(let i = 0; i < 10000; i++){
  list.push(i)
}
</script>

子组件白屏时间计算:请求数据所需要的时间+子组件渲染的时间
在这里插入图片描述
共3104+109ms

2、子组件中用watch更新

用watch监视props变化,并通知试图对应的数据变化

let startTime = window.performance.now()
console.log('子组件created')
onMounted(()=>{
  console.log(`子组件Mounted`)
})
const props = defineProps({
  modelValue: {
    type: String,
    default: ""
  }
})
watch(()=>props.modelValue, (newValue, oldValue) => {
  valueEditor.value = props.modelValue//更新数据
  console.log('watch到新数据并更新页面')
  let endTime = window.performance.now()
  console.log(`子组件加载白屏时间:${endTime-startTime}`)
})

在这里插入图片描述
在这里插入图片描述

此时子组件白屏时间的计算方式不再是请求数据时间+子组件渲染时间,因为子组件会在数据请求到达之前渲染完毕,在数据到达之后再更新对应dom节点,此时白屏时间为111ms,等待视图更新成获取到的数据,时间总共为3132ms。

3、子组件中用computed更新

let valueEditor = computed(()=>{
  let endTime = window.performance.now()
  console.log(`子组件最终更新时间:${endTime-startTime}`)
  return props.modelValue
})

原理同watch基本一致,加载时间为58ms,最终更新时间为3114ms
在这里插入图片描述
在这里插入图片描述

可以看到使用computed的加载时间是要比watch快的,因为为了测试,在组件中v-for出1000个相同div用来展示父组件传来的数据,用computed有缓存这1000个div用的都是同一个缓存所以比较快。

骨架屏优化

使用suspense内置组件,要将原来的父组件放在suspense的插槽中,当数据没有加载到位时显示#fallback中的组件

<template>
  <Suspense>
    <template #default>
      <parent></parent>
    </template>
    <template #fallback>
      <div>loading...这里放骨架屏的组件</div>
    </template>
  </Suspense>
</template>

<script setup>
import {defineAsyncComponent} from "vue";
const parent= defineAsyncComponent(()=>import('./updateItem.vue'))
</script>

parent组件

<template>
  <el-form :model="addArticleForm" label-width="120px" ref="addArticleFormRef" :rules="addArticleFormRule">
    <el-form-item label="文章标题" class="form-item" prop="title">
      <el-input class="upload-input" v-model="addArticleForm.title" placeholder="请输入文章标题"/>
      <el-button type="primary" class="upload-btn" @click="upload(addArticleFormRef)">
        <span v-if="!loading">修改文章</span>
        <span v-else>上 传 中...</span>
        <el-icon class="el-icon--right"><Upload /></el-icon>
      </el-button>
    </el-form-item>
    <el-form-item label="文章分类" class="form-item" prop="tag">
      <el-select v-model="addArticleForm.tag" placeholder="请选择文章分类">
        <el-option v-for="key in user().articleTag.keys()" :label="key" :value="user().articleTag.get(key)"></el-option>
      </el-select>
    </el-form-item>
    <el-form-item label="文章内容" class="form-item">
      <RichTextEditor  v-model="addArticleForm.content" v-model:textValue="addArticleForm.abstract"></RichTextEditor>
    </el-form-item>
  </el-form>
</template>

<script setup>
import RichTextEditor from "../../../components/RichTextEditor.vue";
import {ref, reactive} from "vue";
import {user} from "../../../store/index.js";
import {addArticle} from "../../../api/system.js";
import {getArticleById} from '../../../api/article.js'
import {useRouter,useRoute} from "vue-router";
const route = useRoute()

const addArticleForm = reactive({
  tag: null, //Number类型
  title: '',
  content: null,
})

//请求数据,setup语法糖中使用顶层await,setup会自动在模块顶层加async,用await控制suspense组件中替换骨架屏的时机
let {data} = await getArticleById(route.params.articleId)//向后端请求
addArticleForm.tag = data[0].tag
addArticleForm.title = data[0].title
addArticleForm.content = data[0].content
</script>

此时父组件的子组件不再需要v-if ,watch,computed,可以直接用props中的数据

  • 5
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值