Laravel SPA中Inertia.js与Vue.js嵌套组件响应性问题
Inertia.js + Laravel + Vue3 SPA 新增Todo后组件无法自动更新数据
问题场景
使用Inertia.js构建Laravel+Vue3单页应用时,原本在Home页面直接渲染todos列表,新增Todo后数据能正常更新;但拆分出TodoList组件负责渲染后,新增的Todo必须手动刷新页面才会显示,TodoList接收的todos props无法响应式更新,尝试用ref包裹props也无效。
核心原因
当前后端store方法执行完成后直接重定向回首页,Inertia默认会复用之前页面的props缓存,不会重新请求最新的todos数据,导致组件拿到的仍是旧数据。
解决方案(两种可选)
方案1:后端重定向时强制刷新数据
修改TodoController.php的store方法,在重定向时添加X-Inertia-Refresh请求头,让Inertia重新获取首页的最新props:
class TodoController extends Controller { public function index() { return Inertia::render('Home', [ 'todos' => Todo::latest()->get()->toArray(), ]); } public function store(Request $request) { Todo::create([ 'task' => $request->input('task'), ]); return redirect()->route('index')->withHeaders([ 'X-Inertia-Refresh' => true, ]); } }
此方式会让Inertia重新调用index方法获取最新todos,页面组件自动更新数据。
方案2:前端提交成功后手动更新本地数据(更优SPA体验)
无需重定向,直接在前端将新创建的Todo添加到本地列表,避免页面重新加载:
- 修改后端
store方法,返回新创建的Todo数据:
public function store(Request $request) { $todo = Todo::create([ 'task' => $request->input('task'), ]); return response()->json($todo, 201); }
- 修改
Home.vue,将props转为响应式变量并定义更新方法:
<template> <TodoList :todos="todos" @add-todo="addTodoToList" /> </template> <script setup> import TodoList from "./TodoList.vue"; import { ref } from 'vue'; const props = defineProps(["todos"]); // 将props中的todos转为本地响应式数组 const todos = ref([...props.todos]); // 向列表头部添加新Todo(对应后端latest()排序) const addTodoToList = (newTodo) => { todos.value.unshift(newTodo); }; </script>
- 修改
TodoList.vue,传递事件到子组件:
<template> <div class="w-2/3 flex flex-col justify-center"> <!-- 必须添加key,确保Vue正确跟踪列表项变化 --> <Todo v-for="todo in todos" :key="todo.id" :todo="todo" @add-todo="$emit('add-todo', $event)" /> </div> </template> <script setup> import Todo from "./Todo.vue"; defineProps(["todos"]); defineEmits(['add-todo']); </script>
- 修改
Todo.vue,提交成功后触发事件传递新Todo:
<template> <form @submit.prevent="addTodo" class="relative"> <input class="p-3 pr-12 w-full rounded-md" type="text" name="todo" id="todo" placeholder="Create a new todo..." v-model="todoForm.task" /> <button class="absolute text-white flex justify-center items-center top-1 right-1" > <span class="text-white bg-indigo-600 rounded-full text-5xl h-10 w-10 leading-[30px]" >+</span > </button> </form> </template> <script setup> import { useForm } from "@inertiajs/vue3"; const emit = defineEmits(['add-todo']); const todoForm = useForm({ task: "", }); const addTodo = () => { todoForm.post("/todos", { onSuccess: (response) => { todoForm.reset("task"); // 将新Todo传递给父组件更新列表 emit('add-todo', response.data); }, }); }; </script>
内容的提问来源于stack exchange,提问作者Med Amine Grid
相关产品推荐
相关产品推荐

