Idr*_*ris 5 javascript reactjs server-side-rendering next.js react-query
Next JS 13 于上个月刚刚发布,完全改变了数据的获取方式,同时还通过使用根布局.js 提供了 _app.js 和 _document.js 的替代方案。以前在 Next JS 12 及更低版本中,要使用 Hydration 方法使用 React Query SSR 功能,您需要像这样设置 _app.js 文件:
import { Hydrate, QueryClient, QueryClientProvider } from '@tanstack/react-query';
import queryClientConfig from '../queryClientConfig';
export default function MyApp({ Component, pageProps }) {
const queryClient = useRef(new QueryClient(queryClientConfig));
const [mounted, setMounted] = useState(false);
const getLayout = Component.getLayout || ((page) => page);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<ErrorBoundary FallbackComponent={ErrorFallbackComponent}>
<QueryClientProvider client={queryClient.current}>
<Hydrate state={pageProps.dehydratedState}>
<AppProvider>
{getLayout(<Component {...pageProps} />)}
</AppProvider>
</Hydrate>
</QueryClientProvider>
</ErrorBoundary>
);
}
Run Code Online (Sandbox Code Playgroud)
要在 Next JS 的页面中使用 React Query SSR getServerSideProps,如下所示:
// Packages
import Head from 'next/head';
import { dehydrate, QueryClient } from '@tanstack/react-query';
// Layout
import getDashboardLayout from '../../layouts/dashboard';
// Parse Cookies
import parseCookies from '../../libs/parseCookies';
// Hooks
import { useFetchUserProfile } from '../../hooks/user';
import { fetchUserProfile } from '../../hooks/user/api';
import { getGoogleAuthUrlForNewAccount } from '../../hooks/auth/api';
import { fetchCalendarsOnServer } from '../../hooks/event/api';
import { useFetchCalendars } from '../../hooks/event';
// Store
import useStaticStore from '../../store/staticStore';
// `getServerSideProps function`
export async function getServerSideProps({ req, res }) {
const cookies = parseCookies(req);
const queryClient = new QueryClient();
try {
await queryClient.prefetchQuery(['fetchUserProfile'], () =>
fetchUserProfile(cookies.userAccessToken)
);
await queryClient.prefetchQuery(['fetchCalendars'], () => fetchCalendarsOnServer(cookies.userAccessToken));
await queryClient.prefetchQuery(['getGoogleAuthUrlForNewAccount'], () =>
getGoogleAuthUrlForNewAccount(cookies.userAccessToken)
);
} catch (error) {
}
return {
props: {
dehydratedState: dehydrate(queryClient),
},
};
}
function Home() {
const {
data: userProfileData, // This data is immediately made available without any loading as a result of the hydration and fetching that has occurred in `getServerSideProps`
isLoading: isUserProfileDataLoading,
error: userProfileDataError,
} = useFetchUserProfile();
const { data: savedCalendarsData } = useFetchCalendars(); // This data is immediately made available without any loading as a result of the hydration and fetching that has occurred in `getServerSideProps`
return (
<>
<Head>
<title>
{userProfileData.data.firstName} {userProfileData.data.lastName} Dashboard
</title>
<meta
name="description"
content={`${userProfileData.data.firstName} ${userProfileData.data.lastName} Dashboard`}
/>
<link rel="icon" href="/favicon.ico" />
</Head>
<PageContentWrapper
>
Page Content
</PageContentWrapper>
</>
);
}
Home.getLayout = getDashboardLayout; // This layout also needs data from userProfileData to be available. There is no problem and it never loads because the data is immediately available on mount.
export default Home;
Run Code Online (Sandbox Code Playgroud)
这是旧的 DashboardLayout 组件:
// Packages
import PropTypes from 'prop-types';
// Hooks
import { useFetchUserProfile } from '../../hooks/user';
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
};
function DashboardLayout({ children }) {
const { isLoading, error, data: userProfileData } = useFetchUserProfile(); // Data is immediately available and never loads because it has been fetched using SSR in getServerSideProps
if (isLoading)
return (
<div className="w-screen h-screen flex items-center justify-center text-white text-3xl font-medium">
Loading...
</div>
);
if (error) {
return (
<div className="w-screen h-screen flex items-center justify-center text-brand-red-300 text-3xl font-medium">
{error.message}
</div>
);
}
return (
<>
<div className="the-dashboard-layout">
{/* Start of Main Page */}
<p className="mb-2 text-brand-gray-300 text-sm leading-5 font-normal">
<span className="capitalize">{`${userProfileData.data.firstName}'s`}</span> Layout
</p>
</div>
</>
);
}
export default function getDashboardLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>;
}
Run Code Online (Sandbox Code Playgroud)
使用新的 Next JS 13,无法使用 React Query Hydration 方法,即使我能够使用新方法获取数据,在组件安装时仍然会重新获取数据,这会导致布局处于加载状态,因为数据不能立即可用。
在Next 13中,您只需要调用数据获取方法并将其直接传递给客户端组件,因为app目录现在直接支持服务器组件。
首先,根布局文件替换了 Next 13 中旧的 _app.js 和 _document.js 文件:值得注意的是pageProps,dehydratedState.
这是RootLayout服务器组件:
// Packages
import PropTypes from 'prop-types';
// Components
import RootLayoutClient from './root-layout-client';
RootLayout.propTypes = {
children: PropTypes.node.isRequired,
};
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<RootLayoutClient>{children}</RootLayoutClient>
</body>
</html>
);
}
Run Code Online (Sandbox Code Playgroud)
RootLayoutClient由于使用了客户端操作 Context 和 State,因此需要布局的客户端组件如下:
'use client';
// Packages
import React, { useRef, useEffect } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import PropTypes from 'prop-types';
// Context
import { AppProvider } from '../contexts/AppContext';
// Config
import queryClientConfig from '../queryClientConfig';
RootLayoutClient.propTypes = {
children: PropTypes.node.isRequired,
};
export default function RootLayoutClient({ children }) {
const queryClient = useRef(new QueryClient(queryClientConfig));
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
return (
<QueryClientProvider client={queryClient.current}>
<AppProvider>
{children}
</AppProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Run Code Online (Sandbox Code Playgroud)
getServerSideProps方法现已替换为使用 FETCH API 的基于 Promise 的普通数据获取方法。返回的获取数据现在可以传递到需要它的页面/组件中。
这是我的数据获取函数:
import { getCookie } from 'cookies-next';
export const fetchUserProfile = async (token) => {
if (token) {
try {
const response = await fetch(process.env.NEXT_PUBLIC_EXTERNAL_API_URL + FETCH_USER_PROFILE_URL, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`,
},
});
if (response.ok) {
const data = await response.json();
return data;
} else {
Promise.reject(response);
}
} catch (error) {
Promise.reject(error);
}
} else {
try {
const response = await fetch(process.env.NEXT_PUBLIC_EXTERNAL_API_URL + FETCH_USER_PROFILE_URL, {
method: 'GET',
headers: {
Authorization: `Bearer ${getCookie('userAccessToken')}`,
},
});
if (response.ok) {
const data = await response.json();
return data;
} else {
Promise.reject(response);
}
} catch (error) {
Promise.reject(error);
}
}
};
Run Code Online (Sandbox Code Playgroud)
以下是在主页中获取和使用数据的方式。请注意,主页位于应用程序目录中home/page.js::
import { cookies } from 'next/headers';
// Hooks
import { fetchUserProfile } from '../../../hooks/user/api';
// Components
import HomeClient from './home-client';
export default async function Page() {
const nextCookies = cookies();
const userAccessToken = nextCookies.get('accessToken');
const userProfileData = await fetchUserProfile(userAccessToken.value);
// This is essentially prop passing which was not needed using the previous hydration and getServerSideProps methods.
// Now, I have to pass this data down to a client component called `HomeClient` that needs the data. This is done because I may need to perform some client-side operations on the component.
return <HomeClient userData={userProfileData} />;
}
Run Code Online (Sandbox Code Playgroud)
这是HomeClient客户端组件:
'use client';
import { useEffect } from 'react';
import PropTypes from 'prop-types';
// Hooks
import { useFetchUserProfile } from '../../hooks/user';
HomeClient.propTypes = {
userData: PropTypes.any.isRequired,
calendarData: PropTypes.any.isRequired,
};
export default function HomeClient({ userData }) {
const { isLoading, error, data: userProfileData } = useFetchUserProfile();
useEffect(() => {
console.log(JSON.stringify(userData));
}, [userData]);
// This now loads instead of being immediately available. This can be mitigated by directly using the userData passed
// through props but I don't want to engage in prop drilling in case I need it to be passed into deeper nested child components
if (isLoading) {
return (
<div>Loading...</div>
)
}
return (
<>
<AnotherChildComponent profileData={userProfileData.data.profile}/>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
这是上面客户端组件中使用的 useFetchUserProfile 钩子函数HomeClient:
export const useFetchUserProfile = (conditional = true) => {
// Used to be immediately available as a result of the key 'fetchUserProfile' being used to fetch data on getServerSideProps but that's not available in the app directory
return useQuery(['fetchUserProfile'], () => fetchUserProfile(), {
enabled: conditional,
cacheTime: 1000 * 60 * 5,
});
};
Run Code Online (Sandbox Code Playgroud)
layout.js这是NextJS 13 共享通用布局所需的父文件。这layout.js也需要获取数据,但即使通过 也无法将数据传递给此props。过去,由于在中react-query进行水合,因此可以立即获得数据getServerSideProps
// Packages
import PropTypes from 'prop-types';
// Hooks
import { useFetchUserProfile } from '../../hooks/user';
DashboardLayout.propTypes = {
children: PropTypes.node.isRequired,
};
function DashboardLayout({ children }) {
const { isLoading, error, data: userProfileData } = useFetchUserProfile();
// Used to be that data was immediately available and never loaded because it has been fetched using SSR in getServerSideProps
// Now, it has to load the same data. This is even more complex because props can't be passed as there is no way or any abstraction method
// to share data between the layout and child components
if (isLoading)
return (
<div className="w-screen h-screen flex items-center justify-center text-white text-3xl font-medium">
Loading...
</div>
);
if (error) {
return (
<div className="w-screen h-screen flex items-center justify-center text-brand-red-300 text-3xl font-medium">
{error.message}
</div>
);
}
return (
<>
<div className="the-dashboard-layout">
{/* Start of Main Page */}
<p className="mb-2 text-brand-gray-300 text-sm leading-5 font-normal">
<span className="capitalize">{`${userProfileData.data.firstName}'s`}</span> Layout
</p>
</div>
</>
);
}
Run Code Online (Sandbox Code Playgroud)
如何消除重复的多个请求,并使数据在无需进行 prop-drill 的情况下获取相同数据的所有组件中可用?即使我想使用 props,我该如何解决无法将数据传递到父布局组件的限制。
先感谢您。
| 归档时间: |
|
| 查看次数: |
6969 次 |
| 最近记录: |