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

Vue3 Composition API:从localStorage取对象始终返回undefined

解决Vue3 Composition API从localStorage获取数据返回undefined的问题

问题描述

使用Vue3 Composition API从localStorage读取存储的对象数据时,明明数据存在却始终返回undefined,报错信息:

Uncaught (in promise) TypeError: $setup.habits.value is undefined

localStorage中存储的habits数据为:

habits:"[{"habitName":"Dance","completedDates":["2023-12-27","2023-12-26","2023-12-28"]},{"habitName":"Act","completedDates":["2023-12-28"]}]"

问题根源

  1. 初始化habits时,若localStorage.getItem('habits')返回null(比如首次加载无数据、读取异常),JSON.parse(null)会得到null,此时habits.value为null,访问null.length会直接抛出错误。
  2. 模板中直接使用habits.value.length进行判断,未先校验habits.value是否有效;同时ref在模板中可自动解包,无需手动调用.value。
  3. 编辑功能中存在原生事件重复绑定的潜在问题,会导致多次触发回调。

修复方案

1. 初始化时设置默认值

给habits设置默认空数组,避免null状态:

let habits = ref(JSON.parse(localStorage.getItem('habits')) || [])

2. 完善watchEffect的逻辑

监听localStorage变化时,同样处理null情况:

watchEffect(() => {
    const storedHabits = localStorage.getItem('habits')
    habits.value = storedHabits ? JSON.parse(storedHabits) : []
})

3. 简化模板中的判断与循环

利用ref自动解包特性,去掉.value:

<div id="total-habits" v-if="habits.length">
    <div class="dropdown" v-for="habit in habits" :key="habit.habitName">
        <!-- 用可选链避免editingHabit为null时的报错 -->
        <p id="habitEntry" v-if="editingHabit?.habitName !== habit.habitName"> {{ habit.habitName }}</p>
        <input :id="habit.habitName" v-else v-model.lazy="editingHabit.habitName" type="text">                
        <!-- 其余内容不变 -->
    </div>
</div>

4. 修复编辑功能的事件绑定问题

复制原对象避免直接修改,同时解绑事件防止重复触发:

const editHabit = async (habit) => {
    // 复制对象,避免直接修改原数据
    editingHabit.value = { ...habit }

    await nextTick()
    const inputField = document.getElementById(habit.habitName)
    if (!inputField) return

    inputField.focus()
    
    const handleKeydown = (event) => {
        if (event.key === 'Enter') {
            const index = habits.value.findIndex(h => h.habitName === habit.habitName) 
            if (index !== -1) {
                habits.value[index].habitName = editingHabit.value.habitName
                localStorage.setItem('habits', JSON.stringify(habits.value))
            }
            
            editingHabit.value = null
            inputField.removeEventListener('keydown', handleKeydown)
        }
    }

    inputField.addEventListener('keydown', handleKeydown)
}

完整修复后的代码

<script setup>
import { ref, nextTick, watchEffect } from 'vue'
import AddHabit from '../components/EditHabits/AddHabit.vue'

// 初始化时设置默认空数组
let habits = ref(JSON.parse(localStorage.getItem('habits')) || [])
let editingHabit = ref()

watchEffect(() => {
    const storedHabits = localStorage.getItem('habits')
    habits.value = storedHabits ? JSON.parse(storedHabits) : []
})

const editHabit = async (habit) => {
    // 复制对象,避免直接修改原数据
    editingHabit.value = { ...habit }

    await nextTick()
    const inputField = document.getElementById(habit.habitName)
    if (!inputField) return

    inputField.focus()
    
    const handleKeydown = (event) => {
        if (event.key === 'Enter') {
            const index = habits.value.findIndex(h => h.habitName === habit.habitName) 
            if (index !== -1) {
                habits.value[index].habitName = editingHabit.value.habitName
                localStorage.setItem('habits', JSON.stringify(habits.value))
            }
            
            editingHabit.value = null
            inputField.removeEventListener('keydown', handleKeydown)
        }
    }

    inputField.addEventListener('keydown', handleKeydown)
}
</script>

<template>
    <div>
        <h4>Your Current Habits: </h4>
        <div id="total-habits" v-if="habits.length">
            <div class="dropdown" v-for="habit in habits" :key="habit.habitName">
                <p id="habitEntry" v-if="editingHabit?.habitName !== habit.habitName"> {{ habit.habitName }}</p>
                <input :id="habit.habitName" v-else v-model.lazy="editingHabit.habitName" type="text">                
                <div class="dropdown-content">
                    <div @click="editHabit(habit)">Edit</div>
                    <div>Pause</div>
                    <div>Delete</div>
                </div>
            </div>
        </div>
        <div v-else>No Habits Yet!</div>
        
        <h4>Hover on a habit to edit it!</h4>
    </div>
    
    <AddHabit/>
</template>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 10:08:14