前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >next.js 源码解析 - dynamic 篇

next.js 源码解析 - dynamic 篇

作者头像
嘿嘿不务正业
发布2023-05-09 11:30:16
1.8K0
发布2023-05-09 11:30:16
举报
文章被收录于专栏:嘿嘿的专栏

上文我们一起看完了在 next.js 中如何解决 hydration fail 的错误和如何局部关闭 SSR 的几个方案,其中聊到了 next.jsdynamic API。老规矩,今天我们一起来看看 dynamic API 的源码实现。

API

因为昨天的文章中主要讲到如何使用 dynamic 关闭组件 SSR,并未讲到其它细节,所以先看下 dynamic 的具体 API 设计。dynamic 的设计很容易让人想到 React.lazy,事实上也确实差不多,不过 dynamicReact.load 多了一些功能。dynamic 除了 ssr 外,还支持 suspenseloading 参数。

suspensetrue 时类似 React.lazy 的常见写法,我们需要使用 Suspense 来包裹异步组件:

代码语言:javascript
复制
const DynamicHeader = dynamic(() => import('../components/header'), {
    suspense: true
});

export default function Home() {
    return (
        <Suspense fallback={`Loading...`}>
            <DynamicHeader />
        </Suspense>
    );
}

而当使用提供的 loading 参数时,我们则可以直接将 fallback 作为 loading 参数传入:

代码语言:javascript
复制
const DynamicHeader = dynamic(() => import('../components/header'), {
    loading: () => <div>Loading...</div>
});

这种情况下 next.js 会在组件加载过程中显示 loading 的内容来占位,这里其实在内部使用的是 react-loadable

源码

我们再来看下源代码,dynamic 所在的文件位置为 packages/next/shared/lib/dynamic.tsx,我们下面分块解析一下,先看下接口部分:

代码语言:javascript
复制
function dynamic<P = {}>(
    dynamicOptions: DynamicOptions<P> | Loader<P>,
    options?: DynamicOptions<P>
): React.ComponentType<P>;
export type DynamicOptions<P = {}> = LoadableGeneratedOptions & {
    loading?: (loadingProps: DynamicOptionsLoadingProps) => JSX.Element | null;
    loader?: Loader<P> | LoaderMap;
    loadableGenerated?: LoadableGeneratedOptions;
    ssr?: boolean;
    suspense?: boolean;
};

看接口就可以猜到其实 dynamic 可以只接受一个参数,将 loader 放在属性中就行了:

代码语言:javascript
复制
const DynamicHeader = dynamic({
    loading: () => <div>Loading...</div>,
    loader: () => import('../components/header')
});

loadingsuspensessr 参数我们上面都提到了,但是这里还有个 loadableGenerated 参数,别急我们一会就会看到。

代码语言:javascript
复制
import Loadable from './loadable';

let loadableFn: LoadableFn<P> = Loadable;

let loadableOptions: LoadableOptions<P> = options?.suspense
    ? {}
    : {
          loading: ({ error, isLoading, pastDelay }) => {
              if (!pastDelay) return null;
              if (process.env.NODE_ENV === 'development') {
                  if (isLoading) {
                      return null;
                  }
                  if (error) {
                      return (
                          <p>
                              {error.message}
                              <br />
                              {error.stack}
                          </p>
                      );
                  }
              }

              return null;
          }
      };

可以看到这里用到了 Loadable,其实就是 react-loadable 这个库,只是 next.js 将源码放在了自己的仓库中,然后根据是否为 suspense 初始化 loadableOptions。这里可以看到默认的 loading 参数,在开发环境下如果异步组件加载有报错将会进行展示。

然后 next.js 将会判断接收的参数类型将 dynamicOptionsoptions 参数合并到 loadableOptions

代码语言:javascript
复制
if (dynamicOptions instanceof Promise) {
    loadableOptions.loader = () => dynamicOptions;
} else if (typeof dynamicOptions === 'function') {
    loadableOptions.loader = dynamicOptions;
} else if (typeof dynamicOptions === 'object') {
    loadableOptions = { ...loadableOptions, ...dynamicOptions };
}
loadableOptions = { ...loadableOptions, ...options };

紧接着会对环境和参数进行参数检查,如 suspense 开启时不能关闭 ssrsuspense 时不能使用 loading,接着会处理我们上面看到的 loadableGenerated 参数:

代码语言:javascript
复制
if (loadableOptions.loadableGenerated) {
    loadableOptions = {
        ...loadableOptions,
        ...loadableOptions.loadableGenerated
    };
    delete loadableOptions.loadableGenerated;
}

loadableGenerated 会被合并到 loadableOptions 中。然后就到了最后一段逻辑:

代码语言:javascript
复制
if (typeof loadableOptions.ssr === 'boolean' && !loadableOptions.suspense) {
    if (!loadableOptions.ssr) {
        delete loadableOptions.ssr;
        return noSSR(loadableFn, loadableOptions);
    }
    delete loadableOptions.ssr;
}

return loadableFn(loadableOptions);

可以看到当 ssr 参数被设置为 false 时并且非 suspense 时,将会直接返回 noSSR,否则将会直接调用 react-loadable,将上面拼接出的 loadableOptions 进行传入,我们再看下 noSSR

代码语言:javascript
复制
const isServerSide = typeof window === 'undefined';
export function noSSR<P = {}>(
    LoadableInitializer: LoadableFn<P>,
    loadableOptions: DynamicOptions<P>
): React.ComponentType<P> {
    // Removing webpack and modules means react-loadable won't try preloading
    delete loadableOptions.webpack;
    delete loadableOptions.modules;

    if (!isServerSide) {
        return LoadableInitializer(loadableOptions);
    }

    const Loading = loadableOptions.loading!;
    return () => <Loading error={null} isLoading pastDelay={false} timedOut={false} />;
}

可以看到这里一样会使用 window 来判断代码环境,如果为客户端渲染,将会直接调用 react-loadable,而服务端将会使用 loading 参数进行渲染。

到这里源码解读就结束了,可能又同学会疑惑,在 ssr 关闭的情况下,客户端依旧会使用 react-loadable 进行渲染,而服务端则会直接渲染 Loading,那为啥不会出现 hydration fail 的错误?我一开始也愣了一下,想了想 react-loadable 在客户端初始化渲染的也是 loading 的内容,所以确实没问题的。😂

存疑

noSSR 中使用到两个参数 webpackmodules,看注释此处表示如果使用了 webpackmodules 参数,react-loadable 将会进行预加载,不过我目前没找到这两个参数是什么时候注入的,dynamic 中打断点确实存在,猜测为打包时注入的,先记录下。

总结

综上可以看出 next.jsdynamic 其实是将 React.lazyreact-loadable 两个方法进行了组合,本身代码量也并不算多,一定程度上对异步组件的使用进行了收口,有利于项目中的代码规范和代码的一致性。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-01-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 嘿前端 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • API
  • 源码
  • 存疑
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档