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"]}]"
问题根源
- 初始化
habits时,若localStorage.getItem('habits')返回null(比如首次加载无数据、读取异常),JSON.parse(null)会得到null,此时habits.value为null,访问null.length会直接抛出错误。 - 模板中直接使用
habits.value.length进行判断,未先校验habits.value是否有效;同时ref在模板中可自动解包,无需手动调用.value。 - 编辑功能中存在原生事件重复绑定的潜在问题,会导致多次触发回调。
修复方案
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
相关产品推荐
相关产品推荐

