You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vue3+Go项目中页面刷新后响应式对象数据丢失及toRef取值undefined问题

Vue3 + Go 项目开发问题解决方案

我来帮你解决这两个实际开发中常见的问题,咱们一步步拆解:


问题1:刷新页面后项目数据丢失

背后的原因

页面刷新时,Vue的整个实例会被销毁,内存里reactive存储的数据直接清空。虽然你在onMounted里请求了API,但网络请求需要时间,这期间页面会处于无数据状态,看起来就像“数据丢失”。另外,原代码没处理加载状态,也没做任何数据持久化,导致刷新后必须等请求完成才能看到内容。

解决方案

我给你两个优化方向,结合起来用体验最好:

  1. 添加加载状态和错误提示:让用户明确知道页面正在加载,避免误以为数据没了;
  2. 可选本地缓存:把非敏感的项目数据存在localStorage里,刷新后先展示缓存内容,再用最新API数据更新(适合不需要实时同步的场景)。

修改后的父组件代码:

<script>
import { reactive, onMounted } from 'vue';
import { useRoute } from 'vue-router';

export default {
  components: { EditProject },
  setup() {
    const route = useRoute();
    // 初始化项目数据结构,避免子组件读取属性时出现undefined
    const project = reactive({
      id_project: null,
      name: '',
      description: '',
      start_date: '',
      end_date: '',
      created_at: '',
      updated_at: ''
    });
    const isLoading = reactive({ value: true });
    const error = reactive({ value: null });

    const fetchProject = async () => {
      try {
        isLoading.value = true;
        // 先读本地缓存(可选)
        const cachedProject = localStorage.getItem(`project_${route.params.id}`);
        if (cachedProject) {
          Object.assign(project, JSON.parse(cachedProject));
        }
        // 请求最新数据
        const res = await fetch(`http://localhost:8000/api/v1/projects/${route.params.id}`);
        if (!res.ok) throw new Error('获取项目数据失败');
        const data = await res.json();
        Object.assign(project, data);
        // 更新本地缓存(可选)
        localStorage.setItem(`project_${route.params.id}`, JSON.stringify(data));
        error.value = null;
      } catch (err) {
        console.error(err);
        error.value = err.message;
      } finally {
        isLoading.value = false;
      }
    };

    onMounted(() => {
      fetchProject();
    });

    return { project, isLoading, error };
  },
};
</script>

问题2:子组件中toRef访问value返回undefined

背后的原因

这是时机+用法错误导致的:

  1. 父组件的project初始是空对象,子组件挂载时props.project还没被API数据填充,此时调用toRef(props.project, 'name'),因为props.project.name根本不存在,所以Ref的value就是undefined;
  2. 你原代码里用const content = toRef(props, 'project')是多此一举,props.project本身就是对象,之后再用toRef(content.value, 'name'),初始时content.value是空对象,自然拿不到属性;
  3. 刷新后父组件重新请求API,子组件挂载时props.project为空的时间更长,所以问题更明显。

解决方案

正确的做法是用toRefs把props.project的属性转成响应式Ref,再用computed兜底默认值,确保属性不存在时不会返回undefined。另外要统一API返回的字段名(比如你原代码里用endProject,但API返回的是end_date,这里要对齐)。

修改后的子组件代码:

<script>
import { ref, toRefs, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';

export default {
  props: {
    project: {
      type: Object,
      required: true,
      // 给props加默认结构,避免初始为空时的问题
      default: () => ({
        name: '',
        description: '',
        end_date: '',
        user: { id_user: '' }
      })
    },
  },
  setup(props) {
    // 用toRefs把props.project的属性转成响应式Ref
    const { name: rawName, description: rawDescription, end_date: rawEndDate } = toRefs(props.project);
    
    // 用computed加默认值,彻底避免undefined
    const name = computed(() => rawName.value || '');
    const description = computed(() => rawDescription.value || '');
    // 把API返回的ISO日期转成YYYY-MM-DD格式,方便表单使用
    const endProject = computed(() => rawEndDate.value ? rawEndDate.value.split('T')[0] : '');
    // 可选链操作符避免user不存在时报错
    const user = computed(() => props.project.user?.id_user || '');

    const users = ref([]);
    const route = useRoute();
    const router = useRouter();

    const fetchUsers = async () => {
      try {
        const res = await fetch(`http://localhost:8000/api/v1/users/selected_user`);
        if (!res.ok) throw new Error('获取用户列表失败');
        users.value = await res.json();
      } catch (error) {
        console.error(error);
      }
    };

    const editProject = async () => {
      // 校验必填字段
      if (!name.value || !description.value || !endProject.value || !user.value) {
        console.log('请填写所有必填字段');
        alert('请填写所有必填字段');
        return;
      }
      try {
        const res = await fetch(`http://localhost:8000/api/v1/projects/${route.params.id}`, {
          method: 'PUT',
          body: JSON.stringify({
            name: name.value,
            description: description.value,
            // 替换所有横杠,避免日期解析错误
            end_date: new Date(endProject.value.replace(/-/g, '/')).toISOString(),
            user: { id_user: user.value },
          }),
          headers: { 'Content-Type': 'application/json' },
        });
        if (!res.ok) throw new Error('更新项目失败');
        // 更新本地缓存(如果父组件用了缓存)
        localStorage.setItem(`project_${route.params.id}`, JSON.stringify({
          ...props.project,
          name: name.value,
          description: description.value,
          end_date: new Date(endProject.value.replace(/-/g, '/')).toISOString(),
          updated_at: new Date().toISOString()
        }));
        router.push({ name: 'Project' });
      } catch (err) {
        console.error(err);
        alert('更新项目失败,请重试');
      }
    };

    fetchUsers();

    return { name, description, endProject, user, users, editProject };
  },
};
</script>

额外注意事项

  • 字段名统一:一定要确保API返回的字段和你代码里用的字段一致(比如end_date vs endProject),不然会出现属性找不到的问题;
  • 日期处理:new Date()解析YYYY-MM-DD在部分浏览器可能有问题,所以用replace(/-/g, '/')转成YYYY/MM/DD更稳妥;
  • 响应式传递:父组件的project是响应式的,子组件用toRefs或computed可以确保属性变化时子组件实时更新;
  • 错误处理:给所有API请求加错误捕获,不仅要打印控制台,还要给用户友好提示,提升体验。

内容的提问来源于stack exchange,提问作者cosmos multi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 03:37:40