什么是Next.js?

“Next.js为您提供了最佳的开发人员体验,其中包含生产所需的所有功能:混合静态和服务器渲染,TypeScript支持,智能捆绑,路由预取等。无需配置。

正如他们上面所说的那样,Next.js是一个多合一的全栈现代应用程序构建解决方案。它包括对Typescript和React的一流支持,同时为现代应用程序中一些最常见的需求(如路由,API,postCSS工具和代码拆分)提供了简单的解决方案。

它还支持静态站点生成(用于可以在任何地方托管的闪电般快速的静态HTML页面)或托管托管服务,如Vercel / AWS / etc,这些服务运行Node服务器并支持完整的按需数据加载和服务器端呈现页面。

Next.js已迅速成为Web开发领域最需要的技能之一。本教程旨在作为文档的"实用"扩展,并帮助您使用许多最佳实践来设置项目,这些最佳实践将提高您在扩展时保持所有内容管理的机会。

介绍

本教程不是要取代官方文档,这绝对是太棒了。我强烈建议您在开始本教程之前至少通读基本功能部分,这样您就可以熟悉术语和工具以及它们提供的一些组件,这些组件与原始 HTML 对应项相似,但通常"更强大”。

请查看目录,以了解我们将在此扩展教程中涉及的每个主题。我会坦率地承认其中许多是严格和固执己见的配置,如果其中任何一个对你没有吸引力,那么在大多数情况下,你可以简单地跳过这些部分,并且仍然应该能够完成教程而不会有太多的麻烦。

现在,说了这么多,如果你准备好了,让我们直接潜入!

项目设置

我们将首先使用 Typescript 模板创建一个默认的 Next.js 应用程序。

npx create-next-app --ts nextjs-fullstack-app-template

cd nextjs-fullstack-app-template

首先,我们将进行测试以确保应用程序正常工作。我们将使用此示例,但如果您愿意,也可以同样轻松地使用 NPM。yarn

yarn install

yarn dev

您应该会在 http://localhost:3000 上看到可用的演示应用程序

第一页加载

还建议运行

yarn build

确保您可以成功执行项目的生产生成。建议(但不是必需的)在运行 Next.js 生成时关闭开发服务器。大多数情况下没有问题,但有时生成可能会使您的开发服务器处于需要重新启动的奇怪状态。

您应该在命令行上获得一个漂亮的小报告,其中包含所有使用绿色文本构建的页面,这意味着它们很小且高效。在开发项目时,我们将尽量保持这种状态。

发动机锁定

我们希望所有从事此项目的开发人员都使用我们正在使用的相同 Node 引擎和包管理器。为此,我们创建了两个新文件:

  • .nvmrc- 将告诉项目的其他用途使用哪个版本的Node
  • .npmrc- 将告诉项目的其他用户使用哪个包管理器

我们正在为这个项目使用和,所以我们这样设置这些值:Node v14 Fermium``yarn

.nvmrc
lts/fermium
.npmrc
engine-strict=true

我们对 Node 使用 v14 而不是 v16 的原因是,在本教程的后面部分,我们将在 Vercel 上进行部署,不幸的是,Vercel 仍然不支持 Node 16。也许在你阅读本教程的时候,它可能会。您可以在此处跟踪进度。

您可以检查您的 Node 版本,并确保设置了正确的版本。可以在此处找到节点版本代号列表node --version

请注意,使用engine-strict``yarn``package.json

package.json
  "name": "nextjs-fullstack-app-template",
  "author": "YOUR_NAME",
  "description": "A tutorial and template for creating a production-ready fullstack Next.js application",
  "version": "0.1.0",
  "private": true,
  "license" : "MIT"
  "homepage": "YOUR_GIT_REPO_URL"
  "engines": {
    "node": ">=14.0.0",
    "yarn": ">=1.22.0",
    "npm": "please-use-yarn"
  },
  ...

在该字段中,您可以指定所使用工具的特定版本。如果您愿意,您也可以填写您的个人详细信息。engines

Git 设置

这将是对远程存储库进行首次提交、确保备份更改以及遵循最佳做法以在移动到新内容之前将相关更改分组到单个提交中的好时机。

默认情况下,Next.js 项目将已初始化存储库。您可以使用 检查您使用的分支。它应该这样说:git status

On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   README.md

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .npmrc
        .nvmrc

这告诉我们,我们处于分支上,但尚未暂存或进行任何提交。main

到目前为止,让我们提交更改。

git add .

git commit -m 'project initialization'

第一个命令将添加和暂存项目目录中未被忽略的所有文件.gitignore``-m

跳到您首选的 git 托管服务提供商(例如Github)并创建一个新的存储库来托管这个项目。确保将默认分支设置为与本地计算机上的分支相同的名称,以避免任何混淆。

在 Github 上,您可以通过以下方式将全局默认分支名称更改为您喜欢的任何名称:

Settings -> Repositories -> Repository default branch

现在您已准备好添加存储库的远程源并推送。当你创建它时,Github 会给你确切的说明。根据您使用的是 HTTPS 而不是 SSH,您的语法可能与我的略有不同。

git remote add origin git@github.com:{YOUR_GITHUB_USERNAME}/{YOUR_REPOSITORY_NAME}.git

git push -u origin {YOUR_BRANCH_NAME}

请注意,从现在开始,我们将使用常规提交标准,特别是此处描述的 Angular 约定的Angular约定。

原因与该项目中的许多其他功能一样,只是为所有开发人员设置一个一致的标准,以便在为项目做出贡献时最大限度地减少培训时间。我个人很少关心选择什么标准,只要每个人都同意遵循它是最重要的。

一致性就是一切!

代码格式化和质量工具

为了制定一个可供项目所有贡献者使用的标准,以保持代码风格一致并遵循基本最佳实践,我们将实施两个工具:

  • eslint - 有关编码标准的最佳实践
  • 漂亮- 用于代码文件的自动格式化

艾斯林特

我们将从 ESLint 开始,这很容易,因为它会自动安装并预配置 Next.js 项目。

我们只是要添加一些额外的配置,并使其比默认的更严格一些。如果您不同意它设置的任何规则,无需担心,手动禁用任何规则都非常容易。我们配置根目录中应该已经存在的所有内容:.eslintrc.json

.eslintrc.json
{
  "extends": ["next", "next/core-web-vitals", "eslint:recommended"],
  "globals": {
    "React": "readonly"
  },
  "rules": {
    "no-unused-vars": [1, { "args": "after-used", "argsIgnorePattern": "^_" }]
  }
}

在上面的小代码示例中,我们添加了一些额外的默认值,我们说过React

我发现当您正在开发一个功能并想要准备变量以供以后使用但尚未达到实现它们的地步时,经常会出现这种情况。

你可以通过运行来测试你的配置:

yarn lint

您应该收到如下消息:

✔ No ESLint warnings or errors
Done in 1.47s.

如果你遇到任何错误,那么 ESLint 非常擅长清楚地解释它们是什么。如果遇到不喜欢的规则,可以在"规则"中禁用它,只需将其设置为 1(警告)或 0(忽略),如下所示:

  "rules": {
    "no-unused-vars": 0, // As example: Will never bug you about unused variables again
  }

此时,让我们使用消息进行提交build: configure eslint

漂亮

Prettier将负责为我们自动格式化我们的文件。现在让我们将其添加到项目中。

它仅在开发过程中需要,因此我将它添加为devDependency``-D

yarn add -D prettier

我还建议你获取Prettier VS Code扩展名,以便VS Code可以为你处理文件的格式设置,你不需要依赖命令行工具。在项目中安装和配置它意味着 VSCode 将使用项目的设置,因此仍需要在此处添加它。

我们将在根目录中创建两个文件:

.prettierrc
{
  "trailingComma": "es5",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true
}

这些值完全由您自行决定什么最适合您的团队和项目。

.prettierignore
.yarn
.next
dist
node_modules

在那个文件中,我放置了一个我不希望 Prettier 浪费任何资源的目录列表。如果您愿意,您还可以使用 *.html 之类的模式来忽略文件类型组。

现在我们添加一个新脚本,package.json

package.json
  ...
  "scripts: {
    ...
    "prettier": "prettier --write ."
  }

您现在可以运行

yarn prettier

自动格式化、修复和保存项目中您没有忽略的所有文件。默认情况下,我的格式化程序更新了大约 5 个文件。您可以在 VS Code 左侧的源代码控制选项卡中的已更改文件列表中看到它们。

让我们再次提交build: implement prettier

Git 钩子

在我们开始进行组件开发之前,还有一节是关于配置的。请记住,如果您要长期构建它,特别是与其他开发人员团队合作,您将希望这个项目尽可能坚如磐石。一开始就花时间把它做好是值得的。

我们将实现一个名为赫斯基的工具

Husky 是一个用于在 git 进程的不同阶段运行脚本的工具,例如 add、commit、push 等。我们希望能够设置某些条件,并且只有在我们的代码满足时才允许 commit 和 push 这样的事情成功这些条件,假设它表明我们的项目质量可以接受。

安装赫斯基运行

yarn add -D husky

npx husky install

第二个命令将.husky

将以下脚本添加到您的package.json

package.json
  ...
  "scripts: {
    ...
    "prepare": "husky install"
  }

这将确保赫斯基在其他开发人员运行项目时自动安装。

创建挂钩运行

npx husky add .husky/pre-commit "yarn lint"

上面说,为了使我们的提交成功,脚本必须首先运行并成功。在这种情况下,“成功"意味着没有错误。它将允许您有警告(请记住,在ESLint配置中,设置为1是警告,2是错误,以防您要调整设置)。yarn lint

让我们用 message 创建一个新的提交ci: implement husky

我们将添加另一个:

npx husky add .husky/pre-push "yarn build"

以上确保了我们不允许推送到远程仓库,除非我们的代码可以成功构建。这似乎是一个非常合理的条件,不是吗?通过提交此更改并尝试推送来随意测试它。


最后,我们将再添加一个工具。到目前为止,我们一直在遵循所有提交消息的标准约定,让我们确保团队中的每个人也都遵循它们(包括我们自己!)。我们可以为我们的提交消息添加一个 linter:

yarn add -D @commitlint/config-conventional @commitlint/cli

要配置它,我们将使用一组标准默认值,但我喜欢将该列表显式包含在commitlint.config.js

commitlint.config.js
// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
// docs: Documentation only changes
// feat: A new feature
// fix: A bug fix
// perf: A code change that improves performance
// refactor: A code change that neither fixes a bug nor adds a feature
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
// test: Adding missing tests or correcting existing tests

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'body-leading-blank': [1, 'always'],
    'body-max-line-length': [2, 'always', 100],
    'footer-leading-blank': [1, 'always'],
    'footer-max-line-length': [2, 'always', 100],
    'header-max-length': [2, 'always', 100],
    'scope-case': [2, 'always', 'lower-case'],
    'subject-case': [
      2,
      'never',
      ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
    ],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
    'type-case': [2, 'always', 'lower-case'],
    'type-empty': [2, 'never'],
    'type-enum': [
      2,
      'always',
      [
        'build',
        'chore',
        'ci',
        'docs',
        'feat',
        'fix',
        'perf',
        'refactor',
        'revert',
        'style',
        'test',
        'translation',
        'security',
        'changeset',
      ],
    ],
  },
};

然后使用赫斯基启用 commitlint,方法是:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'
# Sometimes above command doesn't work in some command interpreters
# You can try other commands below to write npx --no -- commitlint --edit $1
# in the commit-msg file.
npx husky add .husky/commit-msg \"npx --no -- commitlint --edit '$1'\"
# or
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

您可以随意尝试一些不遵守规则的提交,看看它们是如何不被接受的,并且您会收到旨在帮助您纠正它们的反馈。

我现在要创建一个新的提交,其中包含消息。ci: implement commitlint

您可以在下面的屏幕截图中看到此设置的完整高潮的结果,希望您的看起来相似:

开发体验

VS 代码配置

现在我们已经实现了ESLint和Prettier,我们可以利用一些方便的VS Code功能来自动运行它们。

在调用的项目的根目录中创建一个目录,并在名为 .这将是覆盖已安装 VS Code 的默认设置的值列表。.vscode``settings.json

我们希望将它们放在项目的文件夹中的原因是,我们可以设置仅适用于此项目的特定设置,并且可以通过将它们包含在代码存储库中来与团队的其他成员共享它们。

在里面,我们将添加以下值:settings.json

.vscode/settings.json
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.organizeImports": true
  }
}

上述内容将告诉 VS Code 使用您的 Prettier 扩展名作为默认格式化程序(如果您愿意,可以使用另一个格式化程序手动覆盖),并在每次保存时自动格式化文件并组织导入语句。

非常方便的东西,只是你不再需要考虑的另一件事,这样你就可以专注于重要的事情,比如解决业务问题。

我现在将使用消息进行提交。build: implement vscode project settings

调试

让我们设置一个方便的环境来调试我们的应用程序,以防我们在开发过程中遇到任何问题。

在目录内创建一个文件:.vscode``launch.json

launch.json
{
  "version": "0.1.0",
  "configurations": [
    {
      "name": "Next.js: debug server-side",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev"
    },
    {
      "name": "Next.js: debug client-side",
      "type": "pwa-chrome",
      "request": "launch",
      "url": "http://localhost:3000"
    },
    {
      "name": "Next.js: debug full stack",
      "type": "node-terminal",
      "request": "launch",
      "command": "npm run dev",
      "console": "integratedTerminal",
      "serverReadyAction": {
        "pattern": "started server on .+, url: (https?://.+)",
        "uriFormat": "%s",
        "action": "debugWithChrome"
      }
    }
  ]
}

使用该脚本后,您有三个调试选择。单击VS Code左侧的小"bug &play图标"或按以访问调试菜单。您可以选择要运行的脚本,并使用启动/停止按钮启动/停止它。Ctrl + Shift + D

VS 代码调试器

除此之外,或者如果你没有使用 VS Code,我们还可以在项目中设置一些有用的调试脚本。

首先,我们将安装跨环境;如果您的队友在不同的环境(Windows、Linux、Mac 等)上工作,则有必要设置环境变量。

yarn add -D cross-env

安装该软件包后,我们可以将package.json``dev

package.json
{
  ...
  "scripts": {
    ...
    "dev": "cross-env NODE_OPTIONS='--inspect' next dev",
  },
}

这将允许你在开发模式下工作时在浏览器中记录服务器数据,从而更轻松地调试问题。

在此阶段,我将使用消息进行新的提交build: add debugging configuration

目录结构

本节现在将介绍在我们的项目中设置文件夹结构。这是许多人会有非常强烈的意见的主题之一,并且有充分的理由!从长远来看,当项目失控时,目录结构确实可以成就或破坏项目,尤其是当团队成员不得不花费不必要的时间来猜测将东西放在哪里(或找到东西)时。

我个人喜欢采取一种相当简单的方法,基本上以类模型/视图风格将事物分开。我们将使用三个主要文件夹:

/components
/lib
/pages
  • component- 构成应用程序的各个UI组件将位于此处
  • lib- 业务/应用程序/域逻辑将位于此处。
  • pages- 将是所需的实际路由/页面 Next.js 结构。

除此之外,我们还将有其他文件夹来支持该项目,但构成我们正在构建的独特应用程序的几乎所有内容的核心都将位于这三个目录中。

文档中用于组件的相同组织components

例如输入、表面、导航、实用程序、布局等。

您无需提前创建这些目录并将它们留空。我会在构建组件的同时创建它们。

本节旨在解释我将如何设置这个项目,您可以选择许多其他方式来组织您的项目,我鼓励您选择最适合您和您的团队的方式。

在这一点上,我将使用消息进行提交rfc: create directory structure

添加故事书

如果您还不熟悉它,我们可以使用的最棒的现代工具之一叫做故事

Storybook 为我们提供了一个环境来展示和测试我们在使用它们的应用程序之外构建的 React 组件。它是连接开发人员和设计人员并能够根据设计要求验证我们开发的组件外观和功能的好工具在一个隔离的环境中,没有应用程序其余部分的开销。

请注意,Storybook 是一种可视化测试工具,稍后我们将实现其他工具用于功能单元测试和端到端测试。

学习如何使用 Storybook 的最佳方法是安装并试用!

npx sb init --builder webpack5

我们将使用 webpack5 版本来与最新版本的 webpack 保持同步(我不确定为什么它还不是默认版本。也许在您使用本教程时就会出现)。

当 Storybook 安装时,它会自动检测关于你的项目的很多事情,比如它是一个 React 应用程序,以及你正在使用的其他工具。它应该注意所有配置本身。

如果您收到有关 eslintPlugin 的提示,您可以说"是”。不过,我们将手动配置它,所以如果您收到一条消息说它没有自动配置,请不要担心。

打开.eslintrc.json

.eslintrc.json
{
  "extends": [
    "plugin:storybook/recommended", // New
    "next",
    "next/core-web-vitals",
    "eslint:recommended"
  ],
  "globals": {
    "React": "readonly"
  },
  // New
  "overrides": [
    {
      "files": ["*.stories.@(ts|tsx|js|jsx|mjs|cjs)"],
      "rules": {
        // example of overriding a rule
        "storybook/hierarchy-separator": "error"
      }
    }
  ],
  "rules": {
    "no-unused-vars": [1, { "args": "after-used", "argsIgnorePattern": "^_" }]
  }
}

我添加了标记特定于故事书的两个新部分和行。// New

您会注意到Storybook还作为目录添加到项目的根目录中,其中包含许多示例。如果您不熟悉Storybook,我强烈建议您浏览它们并将它们留在那里,直到您习惯于在没有模板的情况下创建自己的模板。/stories

在运行它之前,我们需要确保我们使用的是webpack5。将以下内容添加到您的文件中:package.json

package.json
{
  ...
  "resolutions": {
    "webpack": "^5"
  }
}

然后运行

yarn install

确保安装了 webpack5。

接下来,我们必须更新文件:.storybook/main.js

storybook/main.js
module.exports = {
  stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'],
  /** Expose public folder to storybook as static */
  staticDirs: ['../public'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/react',
  core: {
    builder: '@storybook/builder-webpack5',
  },
};

在这里,我们更改了故事文件的模式,以便它将拾取组件(或其他)目录中的任何文件。.stories

我们还将 Next.js 的"公共"文件夹公开为静态目录,以便我们可以在 Storybook 中测试图像、媒体等内容。

最后,在我们运行 Storybook 本身之前,让我们在storybook/preview.js

storybook/preview.js
import '../styles/globals.css';
import * as NextImage from 'next/image';

const BREAKPOINTS_INT = {
  xs: 375,
  sm: 600,
  md: 900,
  lg: 1200,
  xl: 1536,
};

const customViewports = Object.fromEntries(
  Object.entries(BREAKPOINTS_INT).map(([key, val], idx) => {
    console.log(val);
    return [
      key,
      {
        name: key,
        styles: {
          width: `${val}px`,
          height: `${(idx + 5) * 10}vh`,
        },
      },
    ];
  })
);

// Allow Storybook to handle Next's <Image> component
const OriginalNextImage = NextImage.default;

Object.defineProperty(NextImage, 'default', {
  configurable: true,
  value: (props) => <OriginalNextImage {...props} unoptimized />,
});

export const parameters = {
  actions: { argTypesRegex: '^on[A-Z].*' },
  controls: {
    matchers: {
      color: /(background|color)$/i,
      date: /Date$/,
    },
  },
  viewport: { viewports: customViewports },
};

上面有一些个人偏好,但您可以根据需要进行配置。请务必将默认断点设置为与应用中对你很重要的任何内容相匹配。我们还添加了一个处理程序,以便 Storybook 可以处理 Next 的组件而不会崩溃。<Image>

现在我们准备测试它。跑:

yarn storybook

如果一切顺利,您将在主机中看到一条消息,如下所示:

故事书开始

您将能够在http://localhost:6006

故事书主

如果您以前从未使用过它,我会鼓励您尝试并熟悉这些示例。

在这个阶段,我将使用 message 进行提交build: implement storybook

创建组件模板

现在是时候将我们所做的所有配置整合在一起,看看我们如何使用我们为自己设定的标准来创建和实现我们的第一个组件。

我们只需创建一个简单的卡片。创建以下目录结构:

/components/templates/base

在该目录中,我们将创建 .这将遵循文件名的标准模式,该模式与导致它的目录相匹配。例如,这允许我们在目录中拥有其他类型的卡,例如等。BaseTemplate.tsx``cards``PhotoCard``TextCard

BaseTemplate.tsx
export interface IBaseTemplate {}

const BaseTemplate: React.FC<IBaseTemplate> = () => {
  return <div>Hello world!</div>;
};

export default BaseTemplate;

我们的每一个组件都将遵循这个精确的结构。即使它不使用 props,它仍然会为组件导出一个空的 props 接口。这样做的原因是它将允许我们在许多组件和文件中复制这种精确的结构,并使用相同的预期模式交换组件/导入,并且只需查找/替换组件的名称。

当您开始使用故事和模拟道具等时,您很快就会发现为所有组件文件维护一致的命名方案和界面是多么方便和强大。

这可以追溯到我们之前提出的所有观点的一致性。

接下来,我将制作一个位于组件旁边的样式模块文件。默认情况下,Next.js 为您提供了一个/styles

BaseTemplate.module.css
.component {
}

作为一个标准的空模板,您的顶级样式将在您的组件上使用。您可以按以下方式更新您BaseTemplate

BaseTemplate.tsx
import styles from './BaseTemplate.module.css';

export interface IBaseTemplate {}

const BaseTemplate: React.FC<IBaseTemplate> = () => {
  return <div className={styles.container}>Hello world!</div>;
};

export default BaseTemplate;

现在我们有一个干净的样式模板。

让我们向模板中添加一个示例 prop,以便我们可以处理我们将用于组件 prop 的标准:

BaseTemplate.tsx
import styles from './BaseTemplate.module.css';

export interface IBaseTemplate {
  sampleTextProp: string;
}

const BaseTemplate: React.FC<IBaseTemplate> = ({ sampleTextProp }) => {
  return <div className={styles.container}>{sampleTextProp}</div>;
};

export default BaseTemplate;

对于我们创建的每个组件,我们都需要一种非常快速和简单的方法来在不同的环境中测试它(例如 Storybook,还有应用程序,也许还有我们的单元测试)。快速访问数据以呈现组件会很方便。

让我们创建一个文件来存储该组件用于测试的一些模拟数据:

BaseTemplate.mocks.ts
import { IBaseTemplate } from './BaseTemplate';

const base: IBaseTemplate = {
  sampleTextProp: 'Hello world!',
};

export const mockBaseTemplateProps = {
  base,
};

这种结构可能看起来有点复杂,但我们很快就会看到好处。我使用非常有意的一致命名模式,因此此模板非常容易复制并粘贴到您创建的每个新组件中。

现在,让我们为此组件创建一个创建故事:

BaseTemplate.stories.tsx
import { ComponentStory, ComponentMeta } from '@storybook/react';
import BaseTemplate, { IBaseTemplate } from './BaseTemplate';
import { mockBaseTemplateProps } from './BaseTemplate.mocks';

export default {
  title: 'templates/BaseTemplate',
  component: BaseTemplate,
  // More on argTypes: https://storybook.js.org/docs/react/api/argtypes
  argTypes: {},
} as ComponentMeta<typeof BaseTemplate>;

// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof BaseTemplate> = (args) => (
  <BaseTemplate {...args} />
);

export const Base = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args

Base.args = {
  ...mockBaseTemplateProps.base,
} as IBaseTemplate;

我不打算详细介绍文件的每个不同部分需要什么,因为您最好的资源是官方的故事书文档。stories

这里的目标是创建一个一致的、易于复制/粘贴的组件构建和测试模式。

让我们试试这个。跑步:

yarn storybook

如果一切顺利,您将看到外观精美的基本组件(如果不是,我建议您重新访问上一节并检查是否错过了任何配置)。

故事书基础模板

现在我们开始创建更多文件,最好养成在提交之前运行的习惯,以确保一切都很干净并准备就绪。我将使用消息进行提交。yarn lint``build: create BaseTemplate component

使用组件模板

由于我们有了模板,让我们来看看使用它来创建一个真实组件的过程。

创建目录。然后将整个目录从 复制到 并重命名它。我们将制作一个.重命名每个文件以进行匹配。完成后,它应该看起来像这样:components/cards``base``templates``cards``cat``CatCard

组件目录结构

现在,您可以在 VS Code 中按(或等效 mac)来执行完整的项目搜索和替换。仅包含并执行替换 以替换 。它应如下所示:ctrl + shift + F``components/cards/cat``CatCard``BaseTemplate

VS 代码查找替换

现在你已经准备好工作了,你有一个干净的预生成的模板,包括故事和卡片的模拟数据。相当方便!让我们让它看起来像一张真正的卡:

(为了记录,我没有创建这张漂亮的卡片,它基于才华横溢的里昂Etyo在这里创建的示例)

CatCard.tsx
import styles from './CatCard.module.css';
import Image from 'next/image';

export interface ICatCard {
  tag: string;
  title: string;
  body: string;
  author: string;
  time: string;
}

const CatCard: React.FC<ICatCard> = ({ tag, title, body, author, time }) => {
  return (
    <div className={styles.container}>
      <div className={styles.card}>
        <div className={styles.card__header}>
          <Image
            src="/time-cat.jpg"
            alt="card__image"
            className={styles.card__image}
            width="600"
            height="400"
          />
        </div>
        <div className={styles.card__body}>
          <span className={`${styles.tag} ${styles['tag-blue']}`}>{tag}</span>
          <h4>{title}</h4>
          <p>{body}</p>
        </div>
        <div className={styles.card__footer}>
          <div className={styles.user}>
            <Image
              src="https://i.pravatar.cc/40?img=3"
              alt="user__image"
              className={styles.user__image}
              width="40"
              height="40"
            />
            <div className={styles.user__info}>
              <h5>{author}</h5>
              <small>{time}</small>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};

export default CatCard;

设置样式:

CatCard.module.css
@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap');

.container {
  margin: 1rem;
}

.container * {
  box-sizing: border-box;
  padding: 0;
  margin: 0;
}

.card__image {
  max-width: 100%;
  display: block;
  object-fit: cover;
}

.card {
  font-family: 'Quicksand', sans-serif;
  display: flex;
  flex-direction: column;
  width: clamp(20rem, calc(20rem + 2vw), 22rem);
  overflow: hidden;
  box-shadow: 0 0.1rem 1rem rgba(0, 0, 0, 0.1);
  border-radius: 1em;
  background: #ece9e6;
  background: linear-gradient(to right, #ffffff, #ece9e6);
}

.card__body {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.tag {
  align-self: flex-start;
  padding: 0.25em 0.75em;
  border-radius: 1em;
  font-size: 0.75rem;
}

.tag-blue {
  background: #56ccf2;
  background: linear-gradient(to bottom, #2f80ed, #56ccf2);
  color: #fafafa;
}

.card__body h4 {
  font-size: 1.5rem;
  text-transform: capitalize;
}

.card__footer {
  display: flex;
  padding: 1rem;
  margin-top: auto;
}

.user {
  display: flex;
  gap: 0.5rem;
}

.user__image {
  border-radius: 50%;
}

.user__info > small {
  color: #666;
}

并设置模拟数据:

CatCard.mocks.ts
import { ICatCard } from './CatCard';

const base: ICatCard = {
  tag: 'Felines',
  title: `What's new in Cats`,
  body: 'Lorem ipsum dolor sit amet consectetur adipisicing elit. Sequi perferendis molestiae non nemo doloribus. Doloremque, nihil! At ea atque quidem!',
  author: 'Alex',
  time: '2h ago',
};

export const mockCatCardProps = {
  base,
};

请注意,这使用了项目公共目录中的猫的图像。您可以在项目存储库中找到它。(/time-cat.jpg)

我们唯一需要更新的是将故事标题从更改为CatCard.stories``templates/CatCard``cards/CatCard

我们确实需要更新我们的域名,因为我们正在使用一个我们没有明确声明为允许的域名(对于头像)。只需将配置文件更新为如下所示:next.config.js

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ['i.pravatar.cc'],
  },
};

module.exports = nextConfig;

或者,您可以将头像图像放在您自己的公共目录中,但为了学习使用外部域的过程,我们将保留此设置。

现在,修女故事书,如果你幸运的话,你会受到以下欢迎:

故事书猫卡

然后,可以轻松地将此组件放在实际应用程序中的任何位置。在测试时短期内使用道具,并在准备就绪时更换为真正的道具!mock

pages/index.tsx
import type { NextPage } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import CatCard from '../components/cards/cat/CatCard';
import { mockCatCardProps } from '../components/cards/cat/CatCard.mocks';
import styles from '../styles/Home.module.css';

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>

        <div className={styles.grid}>
          <CatCard {...mockCatCardProps.base} />
          <CatCard {...mockCatCardProps.base} />
          <CatCard {...mockCatCardProps.base} />
          <CatCard {...mockCatCardProps.base} />
        </div>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by{' '}
          <span className={styles.logo}>
            <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
          </span>
        </a>
      </footer>
    </div>
  );
};

export default Home;

让我们来看看最终的杰作:

yarn dev

最终杰作

添加自定义文档

尽管在此阶段没有必要,但你可能希望对应用中的内容进行更精细的控制。通过在目录中创建自定义设置,您可以执行此操作。立即创建该文件。<head>``_document.tsx``pages

pages/_document.tsx
import Document, { Head, Html, Main, NextScript } from 'next/document';

class MyDocument extends Document {
  render() {
    return (
      <Html>
        <Head>
          <link rel="preconnect" href="https://fonts.googleapis.com" />
          <link rel="preconnect" href="https://fonts.gstatic.com" />
          <link
            href="https://fonts.googleapis.com/css2?family=Quicksand:wght@300..700&display=swap"
            rel="stylesheet"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

请注意,我已经从组件/卡片/cat/CatCard.module中删除了@importURL字体.css并将Google字体放在头部以预加载。

现在,您需要在元素中执行或自定义的任何其他操作都可以在此文件中完成。<head>

请注意,这与从 中导入的那个不同。它们都将协同工作,并且此数据仅用于您希望在每个页面上加载的数据。<Head>``next/head

有关如何使用自定义的更多信息,请参阅文档。_document

添加布局

布局是 Next.js 中的一个重要概念。它们可帮助您管理页面之间的状态。对于本节,我们将使用与官方示例中提供的相同的基本模板,并简单地对其进行自定义以适应我们的项目。

创建一个名为 的新目录。我们将再次复制我们的目录两次。一个要呼叫,一个呼叫。完成后,它应如下所示:layouts``components``templates/case``primary``sidebar

布局文件夹

执行区分大小写的查找/替换BaseTemplate``PrimaryLayout``SidebarLayout

如果您对这一步有任何困难,请随时从 repo 中获取结构

这些布局模板的结构归功于 Vercel 的*leerobJJ Kasper*

更新 和 的内容PrimaryLayout.tsx``PrimaryLayout.module.css

components/layouts/primary/PrimaryLayout.tsx
import Head from 'next/head';
import styles from './PrimaryLayout.module.css';

export interface IPrimaryLayout {}

const PrimaryLayout: React.FC<IPrimaryLayout> = ({ children }) => {
  return (
    <>
      <Head>
        <title>Primary Layout Example</title>
      </Head>
      <main className={styles.main}>{children}</main>
    </>
  );
};

export default PrimaryLayout;
components/layouts/primary/PrimaryLayout.module.css
.main {
  display: flex;
  height: calc(100vh - 64px);
  background-color: white;
}

.main > section {
  padding: 32px;
}

然后是侧边栏:

components/layouts/sidebar/SidebarLayout.tsx
import Link from 'next/link';
import styles from './SidebarLayout.module.css';

export interface ISidebarLayout {}

const SidebarLayout: React.FC<ISidebarLayout> = () => {
  return (
    <nav className={styles.nav}>
      <input className={styles.input} placeholder="Search..." />
      <Link href="/">
        <a>Home</a>
      </Link>
      <Link href="/about">
        <a>About</a>
      </Link>
      <Link href="/contact">
        <a>Contact</a>
      </Link>
    </nav>
  );
};

export default SidebarLayout;
components/layouts/sidebar/SidebarLayout.module.css
.nav {
  height: 100%;
  display: flex;
  flex-direction: column;
  width: 250px;
  background-color: #fafafa;
  padding: 32px;
  border-right: 1px solid #eaeaea;
}

.nav > a {
  margin: 8px 0;
  text-decoration: none;
  background: white;
  border-radius: 4px;
  font-size: 14px;
  padding: 12px 16px;
  text-transform: uppercase;
  font-weight: 600;
  letter-spacing: 0.025em;
  color: #333;
  border: 1px solid #eaeaea;
  transition: all 0.125s ease;
}

.nav > a:hover {
  background-color: #eaeaea;
}

.input {
  margin: 32px 0;
  text-decoration: none;
  background: white;
  border-radius: 4px;
  border: 1px solid #eaeaea;
  font-size: 14px;
  padding: 8px 16px;
  height: 28px;
}

现在这些模板已经创建,我们需要使用它们。我们将更新主页并创建另一个页面,该页面调用以显示如何使用共享布局并在页面之间保留组件状态。about.tsx

首先,我们需要添加一个扩展默认接口的类型,因为出于某种原因,它不包括开箱即用的功能。创建一个自定义类型文件,该文件将受此解决方案的启发为我们处理NextPage``getLayout

pages/page.d.ts
import { NextPage } from 'next';
import { ComponentType, ReactElement, ReactNode } from 'react';

export type NextPageWithLayout<P = {}> = NextPage<P> & {
  getLayout?: (_page: ReactElement) => ReactNode;
  layout?: ComponentType;
};

现在,您可以使用该界面代替需要创建具有自定义布局的页面的时间。NextPageWithLayout``NextPage

现在让我们更新我们的主页:

pages/index.tsx
import CatCard from '../components/cards/cat/CatCard';
import { mockCatCardProps } from '../components/cards/cat/CatCard.mocks';
import PrimaryLayout from '../components/layouts/primary/PrimaryLayout';
import SidebarLayout from '../components/layouts/sidebar/SidebarLayout';
import styles from '../styles/Home.module.css';
import { NextPageWithLayout } from './page';

const Home: NextPageWithLayout = () => {
  return (
    <section className={styles.main}>
      <h1 className={styles.title}>
        Welcome to <a href="https://nextjs.org">Next.js!</a>
      </h1>
      <CatCard {...mockCatCardProps.base} />
    </section>
  );
};

export default Home;

Home.getLayout = (page) => {
  return (
    <PrimaryLayout>
      <SidebarLayout />
      {page}
    </PrimaryLayout>
  );
};

并在目录中创建一个新页面:about``pages

pages/about.tsx
import PrimaryLayout from '../components/layouts/primary/PrimaryLayout';
import SidebarLayout from '../components/layouts/sidebar/SidebarLayout';
import { NextPageWithLayout } from './page';

const About: NextPageWithLayout = () => {
  return (
    <section>
      <h2>Layout Example (About)</h2>
      <p>
        This example adds a property <code>getLayout</code> to your page,
        allowing you to return a React component for the layout. This allows you
        to define the layout on a per-page basis. Since we&apos;re returning a
        function, we can have complex nested layouts if desired.
      </p>
      <p>
        When navigating between pages, we want to persist page state (input
        values, scroll position, etc.) for a Single-Page Application (SPA)
        experience.
      </p>
      <p>
        This layout pattern will allow for state persistence because the React
        component tree is persisted between page transitions. To preserve state,
        we need to prevent the React component tree from being discarded between
        page transitions.
      </p>
      <h3>Try It Out</h3>
      <p>
        To visualize this, try tying in the search input in the{' '}
        <code>Sidebar</code> and then changing routes. You&apos;ll notice the
        input state is persisted.
      </p>
    </section>
  );
};

export default About;

About.getLayout = (page) => {
  return (
    <PrimaryLayout>
      <SidebarLayout />
      {page}
    </PrimaryLayout>
  );
};

最后,在我更新的文件中,我将其用作占位符值,以在Storybook中显示组件的位置,并且我已经删除了模拟属性(尽管我没有删除该文件,因此我已经准备好了界面,以防我需要添加prop)。mocks``PrimaryLayout.mocks.ts``children: '{{component}}'``SidebarLayout.mocks.ts

我还将故事标题从 更改为 。templates/...``layouts/...

最后,我们可以测试一下。保存并运行

yarn dev

在边栏上的两个路线(“主页"和"关于”)之间单击以在页面之间切换。请注意,使用的布局将持续存在,而无需重新加载(正如我们的意图一样),您将获得超级快速和快速的体验。

下一个布局 01

下一个布局 02

在 Storybook 方面,我们甚至可以独立于应用程序查看和测试布局组件。如果没有内容,它就不会太有用,但是侧边栏非常好。PrimaryLayout

yarn storybook

故事书侧边栏

最后一步是展示 Next.js 应用的部署过程。

我们将使用 Vercel,因为它是 Next.js 应用程序最简单、最直接的部署解决方案(主要是因为 Vercel 拥有 Next,因此人们总是可以假设它们将提供一流的支持)。

请注意,Vercel 绝对不是唯一的选择,如果您选择走这条路,其他主要服务(如断续器网易等)都可以正常工作。

next start

作为爱好用户在 Vercel 上进行部署是完全免费的。首先,我们将在 Vercel 上创建一个帐户

登录后,单击+ New Project``nextjs-fullstack-app-template

选择它后,您需要对其进行配置。在该Build and Output Settings

下一步配置

我们尚未使用任何环境变量,因此无需添加任何环境变量。

一旦这样,只需点击一下,你就完成了!就是这么简单。Deploy

成功部署

(上面的截图有点过时了,我最初在布局部分之前写了部署部分,但你明白了)

您的站点不仅现在已部署,而且每次您提交到主分支时,它都会继续自动重新部署。如果您不想要这种行为,那么很容易在 Vercel 仪表板中进行配置。

好消息是,在推送代码之前,您已经配置了yarn build

您唯一需要记住的是两个环境之间的差异。如果您的脚本不同(使用 NPM 而不是 yarn,反之亦然),或者如果您缺少环境变量,则更常见的是,您的构建仍然有可能在本地成功但在 Vercel 上失败。

我们将env

下一步

我希望您找到了本教程,并了解了有关为您和您的团队设置可靠且可扩展的 Next.js 项目的知识。

这是创建生产质量 Next.js 应用程序的多部分系列的第一部分。

下面是我对未来分期付款的一些想法,我鼓励您留下一些反馈,说明您认为哪些最有用(或者如果您在下面看不到它们,也可以留下一些反馈)。

  • 如何使用 API 路由和 Tailwind CSS 构建全栈 Next.js 应用程序
  • 如何使用 Recoil 将全局状态管理器添加到 Next.js 应用程序
  • 如何在 Next.s 应用程序中使用 jest 和 剧作家 实现单元和端到端测试
  • 如何使用 Github 操作和 Vercel 创建 CI/CD 管道
  • 如何使用 NextAuth 和 i18next 在 Next.js 应用程序中实现 SSO 身份验证和国际化
  • 如何使用 Prisma 和 Supabase 将数据库连接到 Next.js 应用程序
  • 如何使用 Next.js 和 Nx 在 monorepo 中管理多个应用程序