新手一个,最近在学习nextjs14,基于App Router做一个小的笔记本demo.
大致想要的效果如下:
- 简介:
这里面有两个组件,一个是SideBarNoteList,控制笔记列表,一个是Editor,书写内容,提交内容。Editor的父组件是动态路的page.tsx。笔记的数据源只是一个静态json数据,想简单的模拟一下。结构大致如此:
我的需求也很简单,就是在提交笔记表单后,action会把数据添加到静态数据里,然后我希望左边的SideBarList能把新的数据刷出来。
按照我之前的react经验,我使用了redux来管理状态,创建了一个store,我的想法是使用useState/useEffect/useSelector/dispatch,在表单提交后讲数据存到store中,SideBarList监听store中的表单状态,有表单提交时刷新自己,重新获取新的数据并渲染。 代码片段
- SideBarList的代码:
export default function SideBarNoteList() {
const getFormCode = useSelector(formCode)
const [articleData, setArticleData] = useState<Article[]>(listAllArticles())
if (articleData.length === 0)
return <div className="notes-empty">No notes created yet!</div>
useEffect(() => {
// 监听表单状态变化
if (getFormCode === EditorFormStateCode.Success) {
// 表单提交成功后,重新获取数据
console.log('表单提交成功后,重新获取数据')
setArticleData(listAllArticles())
}
}, [getFormCode])
- 动态路由下的page.tsx包含了Editor组件:
export default function Page({ params }: { params: { id: string } }) {
const noteId = params.id
const note = getArticleById(noteId)
if (!note) {
return (
<div className="note--empty-state">
<span className="note-text--empty-state">
Click a note on the left to view something! 🥺
</span>
</div>
)
}
return (
<Editor
noteId={noteId}
initialTitle={note.title}
initialBody={note.content}
/>
)
}
Editor组件监听了store中的formstate:
export enum EditorFormStateCode {
Success = 'success',
Error = 'error',
}
export default function Editor({
noteId,
initialTitle,
initialBody,
}: {
noteId: string | null
initialTitle: string
initialBody: string
}) {
const [title, setTitle] = useState(initialTitle)
const [body, setBody] = useState(initialBody)
const [saveState, saveFormAction] = useFormState<EditorFormState, FormData>(
saveNote,
initialState,
)
const [deleteState, deleteFormAction] = useFormState<EditorFormState, FormData>(
deleteNote,
initialState,
)
useEffect(() => {
if (saveState?.code === EditorFormStateCode.Success) {
// 派发表单提交事件
store.dispatch(articleSliceActions.formSubmit(saveState))
}
if (deleteState?.code === EditorFormStateCode.Success) {
// 派发表单提交事件
store.dispatch(articleSliceActions.formSubmit(deleteState))
}
}, [saveState, deleteState])
我的根Layout.tsx:
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en">
<body>
<Provider store={store}>
<div className="container">
<div className="main">
<Sidebar />
<section className="col note-viewer">{children}</section>
</div>
</div>
</Provider>
</body>
</html>
)
}
* 我的store.ts:
export const store = configureStore({
reducer: rootReducer,
devTools: process.env.NODE_ENV !== 'production',
// middleware: getDefaultMiddleware => getDefaultMiddleware({
// serializableCheck: false, // 禁用序列化检查
// }).concat(logger),
})
export type AppDispatch = typeof store.dispatch
export type RootState = ReturnType<typeof rootReducer>
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>
一个很简单的slice:
export interface EditorFormState {
message: string
code: string
errors?: ZodIssue[]
}
export const initialState: EditorFormState = {
message: '',
code: EditorFormStateCode.Error,
errors: [],
}
const articleSlice = createSlice({
name: 'articles',
initialState,
reducers: {
formSubmit(state, { payload }: PayloadAction<EditorFormState>) {
state.message = payload.message
state.code = payload.code
state.errors = payload.errors
},
},
})
export const articleSliceActions = articleSlice.actions
export const formCode = createSelector(
(state: RootState) => state.articles.code,
code => code,
)
// 导出reducer属性
export default articleSlice.reducer
现在我的代码启动后报如下错误:
⨯ ./components/Editor.tsx
Error:
× You're importing a component that needs useState. It only works in a Client Component but none of its parents are marked with "use client", so they're Server Components by default. │ Learn more: https://nextjs.org/docs/getting-started/react-essentials
看了问题说明,似乎是说被next判定为Server component 的不能使用useSate/useEffect等。
其中我的Editor是被判定成了Server component
于是我尝试在Editor开头加上'use client',试图让Editor被判定为一个Client Component,加上之后错误变成了:
⨯ Error: Cannot access Error.Symbol(immer-state) on the server. You cannot dot into a client module from a server component. You can only pass the imported name through.
nextjs的组件判定让我头大,我的问题是:我应该如何修改这段代码以实现我想要的更新效果?
我处理类似问题的思路是不是本身就有问题,应该怎么做才对呢?
感谢。
不需要用状态管理,SideBarList组件数据从服务端正常获取,
表单提交成功后调用nextjs的api刷新下路由数据就行,
像nextjs官方的AI演示项目,侧边栏就是用
router.refresh
更新的revalidatePath
router.refresh