使用useIntersectionObserver实现无限滚动后提交表单触发replaceState错误
问题描述
我使用useIntersectionObserver实现无限滚动功能,滚动时一切正常,但滚动后提交帖子表单时,出现以下错误:
错误信息
DOMException: Failed to execute 'replaceState' on 'History'
相关代码
web.php
Route::get('/dashboard', [DashboardController::class, 'index'])->middleware(['auth', 'verified'])->name('dashboard');
Dashboard.vue
<template> <Head title="Dashboard" /> <AuthenticatedLayout> <div class="py-2"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="overflow-hidden"> <div class="py-6 text-gray-900 flex"> <section class="basis-2/5"> <h3>Profile</h3> <h3>Logout</h3> </section> <section class="basis-3/5 px-3"> <Form /> <div v-if="posts.data.length" class="mt-12" id="posts"> <Post v-for="post in posts.data" :key="post.id" :post="post" /> <div ref="bottom" class="-translate-y-32"></div> </div> </section> </div> </div> </div> </div> </AuthenticatedLayout> </template> <script setup> import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'; import { Head } from '@inertiajs/vue3'; import Form from '@/Components/Post/Form.vue'; import Post from '@/Components/Post.vue'; import { onMounted, ref } from 'vue'; import { useIntersectionObserver } from '@vueuse/core'; import axios from 'axios'; const props = defineProps({ posts: Object }); const bottom = ref(null); const { stop } = useIntersectionObserver(bottom, ([{ isIntersecting }]) => { if (!isIntersecting) { return; } axios.get(`${props.posts.meta.path}?cursor=${props.posts.meta.next_cursor}`) .then((response) => { props.posts.data = [...props.posts.data, ...response.data.data]; props.posts.meta = response.data.meta; if (!response.data.meta.next_cursor) { stop(); } }); }); onMounted(() => { document.addEventListener('click', function (event) { if (event.target.matches('.post-body .mention')) { if (parseInt(event.target.dataset.id) > 0) { alert(event.target.outerText); } } }); }); </script>
Form.vue
<template> <div> <form @submit.prevent="post"> <div> <Tiptap v-model="form.body" placeholder="Got something to share?" /> <!-- <TextArea id="body" type="text" class="mt-4 block w-full" rows="3" placeholder="Got something to share?" v-model="form.body" required /> --> <InputError class="mt-2" :message="form.errors.body" /> </div> <div class="mt-3"> <PrimaryButton :disabled="form.processing || form.body.length < 1">Post</PrimaryButton> </div> </form> </div> </template> <script setup> import TextArea from '@/Components/TextArea.vue'; import InputError from '@/Components/InputError.vue'; import PrimaryButton from '@/Components/PrimaryButton.vue'; import { useForm } from '@inertiajs/vue3'; import Tiptap from '../Tiptap.vue'; const form = useForm('PostForm', { body: '' }); const post = () => { form.post(route('posts.store'), { preserveScroll: true, preserveState: false, onSuccess: (page) => { form.reset(); console.log(page.props) //page.props.posts.data = [[], ...page.props.posts.data]; }, onFinish: (visit) => { console.log(visit) } }); }; </script>
解决方案
错误原因
直接修改Inertia传递的props.posts(包括data和meta字段)破坏了Inertia的单向数据流规则。Inertia期望props是不可变的,直接修改会干扰其内部状态管理,导致表单提交后执行history.replaceState时出错。
修复步骤
1. 在Dashboard.vue中用本地ref维护帖子数据
不要直接操作props,复制初始数据到本地响应式变量中:
// 替换原props定义后的代码 const props = defineProps({ posts: Object }); // 初始化本地状态,复制props数据 const localPosts = ref({ data: [...props.posts.data], meta: { ...props.posts.meta } }); const bottom = ref(null); const { stop } = useIntersectionObserver(bottom, ([{ isIntersecting }]) => { if (!isIntersecting) { return; } axios.get(`${localPosts.value.meta.path}?cursor=${localPosts.value.meta.next_cursor}`) .then((response) => { // 修改本地状态而非props localPosts.value.data = [...localPosts.value.data, ...response.data.data]; localPosts.value.meta = response.data.meta; if (!response.data.meta.next_cursor) { stop(); } }); });
同时更新模板中的循环源:
<div v-if="localPosts.data.length" class="mt-12" id="posts"> <Post v-for="post in localPosts.data" :key="post.id" :post="post" /> <div ref="bottom" class="-translate-y-32"></div> </div>
2. 表单提交后同步新帖子到本地列表
在Form.vue中添加emit,通知父组件新增的帖子:
// Form.vue的script部分添加emit定义 const emit = defineEmits(['postCreated']); // 修改onSuccess回调 onSuccess: (page) => { form.reset(); // 假设新创建的帖子在返回数据的第一个位置 emit('postCreated', page.props.posts.data[0]); }
在Dashboard.vue中监听这个事件并更新本地列表:
<!-- 修改Form组件的引用 --> <Form @post-created="addNewPost" />
// 在Dashboard.vue的script中添加方法 const addNewPost = (newPost) => { // 将新帖子插入列表顶部 localPosts.value.data.unshift(newPost); };
内容的提问来源于stack exchange,提问作者Blues Clues
相关产品推荐
相关产品推荐

