如何解决ReactJs / NextJs-有条件地渲染互斥组件,而不会影响加载时间
我正在尝试提高React Web应用程序的加载速度。
我有两个组件导入-一个用于移动设备,一个用于台式机(不良设计?我想是这样):
import Posts from '../components/post/posts';
import PostsMobile from '../components/post/postsMobile';
这很容易开发,因为我不必努力使同一组件与台式机和移动设备兼容。
然后检查屏幕尺寸并加载适当的组件,我这样做:
const largeScreen = useMediaQuery(theme => theme.breakpoints.up('sm'));
...
{largeScreen? (
<Posts />
) :
(
<PostsMobile />
)
}
您可以在此处调整浏览器的大小以查看两个组件的负载:Link to home page showing the two components
解决方法
经典的条件渲染,这是执行此操作的适当方法,在任何情况下,都只会将一个组件添加到DOM。附带说明一下,为移动视图和桌面视图使用两个不同的组件并不是最好的主意,通常您的html结构应该相同,并且您应该使用CSS进行布局更改(根据Google的建议-https://web.dev/responsive-web-design-basics/)
,查看@artsy/fresnel,他们有一个非常简单的示例,展示了如何configure Next.js and Gatsby.js在SSR环境中实现依赖屏幕宽度的控制
- 注意:它几乎不会增加开销大小;目前,我在我的产品组合中将它与tailwindcss结合使用,并且事实证明它是一个了不起的工具。易于配置,实现起来很简单。这是example of conditionally rendering the same svg icon four times的屏幕尺寸函数,用于相应地自定义样式(xs(移动设备),sm,md,大于md(桌面))
(1)在您的组件目录中创建一个窗口宽度的文件,以配置@artsy/fresnel
进行全局共享
-
components/window-width.jsx
或components/window-width.tsx
import { createMedia } from '@artsy/fresnel';
const PortfolioMedia= createMedia({
breakpoints: {
xs: 0,sm: 768,md: 1000,lg: 1200,},})
// Generate CSS to be injected in the head using a styles tag (pages/_document.jsx or pages/_document.tsx)
export const mediaStyles = PortfolioMedia.createMediaStyle();
export const { Media,MediaContextProvider } = PortfolioMedia;
// https://github.com/artsy/fresnel/tree/master/examples/nextjs
- 请注意,您可以根据需要自定义断点; the following breakpoints are from my portfolio's configuration file。它们与Tailwind的断点重叠,以使它们保持良好的游戏状态
// ...
const PortfolioMedia = createMedia({
breakpoints: {
xs: 0,sm: 640,md: 768,lg: 1024,xl: 1280
}
});
// ...
(2)用pages/index.jsx
组件包裹pages/index.tsx
或MediaContextProvider
// ...
import { MediaContextProvider } from 'components/window-width';
interface IndexProps {
allPosts: Post[];
allAbout: AboutType[];
allBlog: BlogType[];
}
const Index = ({ allPosts,allAbout,allBlog }: IndexProps) => {
const morePosts = allPosts.slice(0);
const moreAbout = allAbout.slice(0);
const moreBlog = allBlog.slice(0);
return (
<Fragment>
<MediaContextProvider>
<Lead />
<Head>
<title>{`${CLIENT_NAME} landing page`}</title>
</Head>
<div className='max-w-cardGridMobile md:max-w-cardGrid my-portfolioH2F grid mx-auto content-center justify-center items-center text-center'>
{morePosts.length > 0 && <Cards posts={morePosts} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreAbout.length > 0 && <AboutCoalesced abouts={allAbout} />}
</div>
<div className='max-w-full my-portfolioH2F block mx-auto content-center justify-center items-center text-left'>
{moreBlog.length > 0 && <BlogCoalesced blogs={allBlog} />}
</div>
<Footer />
</MediaContextProvider>
</Fragment>
);
};
export default Index;
// ...
(3)最后,将生成的mediaStyles
CSS注入到Next的style
中的text/css
类型的Head
标记中
-
pages/_document.jsx
或pages/_document.tsx
import Document,{
Html,Head,Main,NextScript,DocumentContext
} from 'next/document';
import { mediaStyles } from 'components/window-width';
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const initialProps = await Document.getInitialProps(ctx);
return { ...initialProps };
}
render() {
return (
<Html lang='en-US'>
<Head>
<meta charSet='utf-8' />
<link rel='stylesheet' href='https://use.typekit.net/cub6off.css' />
<style type='text/css' dangerouslySetInnerHTML={{ __html: mediaStyles }} />
</Head>
<body className='root'>
<script src='./noflash.js' />
<Main />
<NextScript />
</body>
</Html>
);
}
}
(4)利润;配置完成
- 仅此而已。为了清楚起见,我在下面放入了actual example from one of my projects。
(5)奖金示例-有条件地将ArIcon渲染为设备大小的函数
-
components/lead-arIcon.tsx
import { ArIcon } from 'components/svg-icons';
import Link from 'next/link';
import { Media } from 'components/window-width';
import { Fragment } from 'react';
import DarkMode from 'components/lead-dark-mode';
const ArIconConditional = (): JSX.Element => {
const arIconXs: JSX.Element = (
<Media at='xs'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='18vw' height='18vw' />
</a>
</Link>
</Media>
);
const arIconSm: JSX.Element = (
<Media at='sm'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='15vw' height='15vw' />
</a>
</Link>
</Media>
);
const arIconMd: JSX.Element = (
<Media at='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full '
id='top'
aria-label='top'
>
<ArIcon width='12.5vw' height='12.5vw' />
</a>
</Link>
</Media>
);
const arIconDesktop: JSX.Element = (
<Media greaterThan='md'>
<Link href='/'>
<a
className='container block pl-portfolio pt-portfolio justify-between mx-auto w-full min-w-full'
id='top'
aria-label='top'
>
<ArIcon
width='10vw'
height='10vw'
classNames={[
` antialised w-svgIcon max-w-svgIcon transform transition-all`,' stroke-current',` fill-primary`
]}
/>
</a>
</Link>
</Media>
);
const DarkModeToggler = (): JSX.Element => (
<div className='pt-portfolio text-customTitle transition-all transform -translate-y-mdmxSocial col-span-4 text-right -translate-x-portfolioPadding'>
<DarkMode />
</div>
);
const ArIconsCoalesced = (): JSX.Element => (
<Fragment>
<div className='relative block justify-between lg:w-auto lg:static lg:block lg:justify-start transition-all w-full min-w-full col-span-2'>
{arIconXs}
{arIconSm}
{arIconMd}
{arIconDesktop}
</div>
</Fragment>
);
return (
<Fragment>
<div className='select-none relative z-1 justify-between pt-portfolioDivider navbar-expand-lg grid grid-cols-6 min-w-full w-full container overflow-y-hidden overflow-x-hidden transform'>
<ArIconsCoalesced />
<DarkModeToggler />
</div>
</Fragment>
);
};
export default ArIconConditional;
,
您可以尝试延迟加载组件。
posts;
componentDidMount() {
if (largeScreen) {
import('../components/post/posts').then(({ default: Posts }) => {
// ^^^^ make sure it has a default export
this.posts = Posts;
this.forceUpdate();
});
} else {
// here load the other component for lower screens
}
}
然后,在内部渲染:
const Posts = this.posts;
return largeScreen? (
<Posts />
) : (
<PostsMobile />
);
注意:您还必须添加一个调整大小的侦听器,因此,如果屏幕达到特定宽度,则会加载另一个组件并进行渲染。
注意2 :如果您不关心SSR,则可以尝试将React.lazy
与Suspense
一起使用:https://en.reactjs.org/docs/code-splitting.html#reactlazy
我认为台式机和移动设备的用法非常有效,例如,尽管CSS版本几乎总是首选,但对于在手机,移动设备上无法使用的拖放组件,这可能就没有意义了仅汉堡菜单等。
反应的lazy loading是必经之路(除非您需要SSR):
import React,{ Suspense } from 'react';
const Posts = React.lazy(() => import('../components/post/posts');
const PostsMobile = React.lazy(() => import('../components/post/postsMobile');
function MainComponent() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
{ largeScreen? ( <Posts />) : <PostsMobile /> }
</Suspense>
</div>
);
}
这将确保仅在首次渲染组件时才加载该组件。温馨提示:如果将loading
div更改为animated loading placeholder,则应用程序的UI可能会更令人愉悦。
没有答案与使用Nextjs的SSR兼容,因此我最终使用了动态导入功能。看起来非常强大但简单。
https://nextjs.org/docs/advanced-features/dynamic-import
import dynamic from 'next/dynamic'
const Posts = dynamic(() => import('../components/post/posts'),{ loading: () => <LinearProgress /> });
const PostsMobile = dynamic(() => import('../components/post/postsMobile'),{ loading: () => <LinearProgress /> });
这为我节省了几毫秒
我不确定是否有更好的选择,所以希望人们发表评论。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。