我们有一个简单的设置,涉及包装_app.tsx
页面的Context API提供程序。我们试图实现的流程是:
- 在一个页面上,我们使用
getServerSideProps
从API收集一些数据 - 我们通过道具将数据发送到页面
- 我们从页面更新上下文(如上所述,上下文提供者正在包装
_app.tsx
) - 上下文已更新,所有子组件都可以访问其中的数据
所以我们有一个非常标准的设置,可以在客户端正确工作。问题是更新后的上下文值没有被用来对页面进行SSR。
上下文API
const ContextValue = createContext();
const ContextUpdate = createContext();
export const ContextProvider = ({children}) => {
const [context, setContext] = useState({});
return <ContextValue.Provider value={context}>
<ContextUpdate.Provider value={setContext}>
{children}
</ContextValue.Provider>
</ContextUpdate.Provider>
}
export const useContextValue = () => {
const context = useContext(ContextValue);
return context;
}
export const useContextUpdate = () => {
const update = useContext(ContextUpdate);
return update;
}
然后我们在_app.jsx
:上
...
return <ContextProvider>
<ContextApiConsumingComponent />
<Component {...pageProps} />
</Context>
在任何页面中,如果我们可以使用上面提供的钩子来更新上下文。例如:
export function Index({customData, isSSR}) {
const update = useContextUpdate();
if(isSSR) {
update({customData})
}
return (
<div>...</div>
);
}
export async function getServerSideProps(context) {
...
return {
props: { customData , isSSR}
};
};
我们可以在ContextApiConsumingComponent
:中使用上下文
export function ContextApiConsumingComponent() {
const context = useContextValue()
return <pre>{JSON.stringify(context?.customData ?? {})}</pre>
}
上面的(非常简化的)代码在客户端上运行良好。但在SSR过程中,如果我们检查服务器发送到浏览器的HTML,我们会注意到<pre></pre>
标签是空的,即使上下文值在__NEXT_DATA__
脚本部分正确地发送到浏览器(它们在应用程序加载后准确地填充了浏览器上的数据)。
我已经建立了一个GitHub回购与最低限度的复制。
我是不是错过了什么?
据我所知,React不会等待SSR期间执行异步操作。而setState是一个异步操作。
如果您希望它在SSR期间呈现,则需要为useState钩子提供initialValue
。
export const ContextProvider = ({children, customData}) => {
const [context, setContext] = useState(customData);
// ...
}
不确定您是否有其他理由使用上下文将这些数据获取到api使用者中,但使用nextjs,getStaticProps
或getServerSideProps
当然会馈送到路由组件中,但可能您错过了以下细节?
Component道具是活动页面,因此每当您在路线之间导航时,Component都会更改为新页面因此,您发送到组件的任何道具都将被页面接收。
pageProps
是一个具有初始道具的对象,这些道具是通过我们的一种数据获取方法为您的页面预加载的,否则它是一个空对象。
https://nextjs.org/docs/advanced-features/custom-app
因此,_app.tsx
可以作用于路线组件通常从getStaticProps
或getServerSideProps
接收的任何道具
types.ts
// or whatever union type you need to indicate that myriad of "custom datas" you might be pushing out of getStaticProps
export type MaybeCustomDataFromGetStaticProps = Map<string, string> | undefined;
export type PagePropsWithCustomData = {
customData: MaybeCustomDataFromGetStaticProps
isSSR?: boolean;
}
pages/_app.tsx
import type { PagePropsWithCustomData } from '../types';
const App = ({
Component,
pageProps: { customData, isSSR, ...pageProps},
router,
}: AppProps<PagePropsWithCustomData & Record<string, unknown>>) => {
return (
<ContextProvider value={{ isSSR, customData }}>
<ContextApiConsumingComponent />
<Component {...pageProps} />
</ContextProvider>
)
}
pages/somepage.tsx
import type { NextPage, GetServerSideProps } from "next";
import type { PagePropsWithCustomData } from '../types';
import { magicallyGetCustomData } from '../services/Api';
import { isSSR } from '../core/Routing';
type IndexRouteProps = PagePropsWithCustomData & {
isSSR?: boolean;
}
const IndexRoute:NextPage<IndexRouteProps> = ({ customData, isSSR }) => {
// no longer need to do anything with customData, or isSSR here
return (
<div>stuff</div>
);
}
export const getServerSideProps:GetServerSideProps<IndexRouteProps> = async (context) => {
const customData = await magicallyGetCustomData();
return {
props: { customData , isSSR: isSSR(context) }
};
};
export default IndexRoute