关于javascript:Nextjs-项目最佳实践

40次阅读

共计 23921 个字符,预计需要花费 60 分钟才能阅读完成。

🔥🔥🔥 后方高能,干货满满,倡议点赞➕关注➕珍藏;后续还有该系列的 进阶教程 继续送上

什么是 Next.js

“Nest.js 通过提供所有生产环境须要的性能来给你最佳的开发体验:构建时预渲染,服务端渲染,TypeScript 反对,智能打包,路由预加载,零配置等等”

正如上文的介绍,Next.js 是一个十分全面的古代全栈利用构建计划。它蕴含了十分优雅的 TypeScript 和 React 反对,同时提供了古代利用常见的需要解决方案,例如:路由,API,PostCSS 工具和代码宰割等。

与此同时它也反对动态站点生成(用于能够在任何中央托管的高性能动态 HTML 页面)或者是通过 Vercel / AWS 等部署 Node.js 服务来进行数据按需加载的服务端渲染页面

Next.js 已迅速成为 Web 开发畛域最热门的技能之一。本教程旨在充当 Next.js 文档 的“实用”延长,并帮忙你应用大量最佳实际来开发我的项目,这将有利于你在今后对我的项目施行进一步的扩大。

介绍

本教程不是为了代替官网文档,因为官网文档曾经写得非常简单易懂了。我强烈推荐你在学习本文之前先大抵过一下 这一章 的内容,这样你对文中的术语和工具会比拟相熟,他们提供的一些组件与一般 HTML 组件类似,但通常是“更弱小”的版本。

我抵赖其中许多的是严格的并且带有主观色调的,如果其中任何一个对你没有吸引力,那么在大多数状况下能够简略地跳过这些局部并且应该依然可能实现本教程而不会遇到太多麻烦

当初,如果你曾经筹备好了,那就开始学习吧!

我的项目创立

咱们将应用 TypeScript 模版来创立一个默认的 Next.js 利用

npx create-next-app@latest --typescript nextjs-fullstack-app-template-zn

cd nextjs-fullstack-app-template-zn

// ESLInt : YES
// `src/` directory : YES
// `app/` directory : NO

首先咱们试试这个我的项目能不能失常运行。咱们在这个例子中会应用 yarn,当然你也能够用 NPM 或其余的工具

yarn dev

你能够关上 http://localhost:3000/ 看到这个 demo 曾经胜利运行

也举荐你运行

yarn build

来确保你的我的项目可能胜利打包。举荐(非必须)敞开开发服务器来运行 Next.js 的构建命令。大多数时候没有问题,但偶然构建会使你的开发服务器处于须要重新启动的奇怪状态。

构建胜利之后你能够在命令行看到这些绿色和红色文字的报告,构建过程是高效的,咱们将在开发的过程中尽量放弃这样的状态。

引擎锁定

咱们在本我的项目中应用的 v16 的 Node.js。你能够通过 node --version 查看版本。关上 package.json engines 字段是你指定所应用工具的特定版本的中央。

{
  "name": "nextjs-fullstack-app-template-zn",
  "version": "0.1.0",
  "private": true,
  "author": "YOUR_NAME",
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@next/font": "13.1.6",
    "@types/node": "18.11.18",
    "@types/react": "18.0.27",
    "@types/react-dom": "18.0.10",
    "next": "13.1.6",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "4.9.4"
  },
  "engines": {
    "node": ">=16.0.0",
    "yarn": ">=1.22.0"
  }
}

Git 配置

这将是咱们第一次提交到近程仓库的好时机,以确保更改失去备份,并遵循最佳实际将相干更改分组在一个提交中,而后再做新的批改。

默认状况下,你的 Next.js 我的项目曾经初始化了一个 repo。你能够应用 git status 查看你所在的分支。它应该会显示相似上面的文案

$ 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:   package.json

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

no changes added to commit (use "git add" and/or "git commit -a")

这通知咱们咱们在 main 分支上,咱们还没有暂存或做出任何提交

让咱们提交目前的变更

git add .

git commit -am "feat: project init"

第一个命令将在我的项目目录中增加并暂存所有在 .gitignore 中未被疏忽的文件。第二个将应用咱们在 -m 标记之后写的音讯来提交以后我的项目的状态

跳转到你喜爱的 git 托管服务提供商(比方 Github)并且创立一个新的仓库来寄存你的我的项目。

当初你已筹备好增加仓库的近程源并进行推送。Github 会在你创立的时候给你精确的阐明。你的语法可能与我的略有不同,具体取决于应用的是 HTTPS 还是 SSH。

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

git push -u origin {YOUR_BRANCH_NAME}

请留神,从这一点开始,咱们将应用 Conventional Commits 规范,特地是 此处形容 的 Angular 约定

起因与该我的项目中的许多其余性能一样,只是为所有开发人员设置一个 统一 的规范,以便在为我的项目做出奉献时最大水平地缩小培训工夫。我集体不太关怀抉择什么规范,只有每个人都批准遵循它,才是最重要的。

一致性就是所有!!!

代码格式化和品质工具

为了设定一个规范,供我的项目的所有贡献者应用,以放弃代码格调统一并遵循根本的最佳实际,咱们将应用两个工具:

  • eslint – 代码标准的最佳实际
  • prettier – 主动格式化代码文件

ESLint

咱们从 ESLint 开始,它非常简单因为在咱们创立 Next.js 我的项目的时候曾经主动装置好并且有了默认配置。

咱们仅须要增加少部分额定的配置就能够让它比默认配置更加严格。如果你不批准其中的任何一条规定配置,不必放心,咱们能够非常简单的手动敞开这些规定。咱们将所有的 ESLint 配置都写在 .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 3.48s.

如果你遇到任何谬误,那么 ESLint 十分善于分明地解释它们是什么。如果遇到你不喜爱的规定,你能够简略的将它从 1(告警)设置成 0(疏忽)来敞开它

"rules": {"no-unused-vars": 0,}

让咱们在这时候进行一次提交,带上信息 build: configure eslint

Prettier

prettier 会为咱们解决文件的主动格式化。让咱们将它增加到我的项目中

它只须要在开发过程中应用,所以须要增加到 devDependency

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 增加新的 script,而后咱们就能够运行 Prettier:

package.json

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

你能够运行

yarn prettier

主动格式化、修复和保留我的项目中你未疏忽的所有文件。默认状况下,我的格式化程序更新了大概 5 个文件。你能够在 VS Code 左侧的源代码治理选项卡中的已更改文件列表中看到它们。

让咱们在这时候进行一次提交,带上信息 build: implement prettier

Git Hooks

在咱们开始进行组件开发之前,还有一节是对于配置的。请记住,如果你要长期构建该我的项目,你将心愿该我的项目尽可能坚如磐石,尤其是与其余开发人员团队单干时。花工夫在一开始就把它做好是值得的。

咱们将应用一个叫做 Husky 的工具

Husky 是一个用于在 git 过程的不同阶段运行脚本的工具,例如 add、commit、push 等。咱们心愿可能设置某些条件,并且只有在咱们的代码满足这些条件时才容许提交和推送之类的事件胜利,假如这表明咱们的我的项目品质是能够承受的。

装置 Husky

yarn add -D husky

npx husky install

第二个命令将在你的我的项目中创立一个 .husky 目录。这就是你的 hooks 寄存的中央。确保此目录蕴含在你的代码仓库中,因为它也实用于其余开发人员,而不仅仅是你本人。

package.json 文件中增加 script

package.json

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

这将确保在其余开发人员运行该我的项目时主动装置 Husky

创立一个 hook

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

下面说为了让咱们的提交胜利,yarn lint 必须首先运行并胜利。在这种状况下,”胜利”意味着没有谬误。它将容许你有告警(请记住,在 ESLint 配置中,设置 1 是正告,设置 2 是谬误)

让咱们在这时候进行一次提交,带上信息 ci: implement husky。如果所有设置都实现,在你进行提交之前就会运行 lint script

让咱们再增加一个

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

以上确保咱们只有在代码构建胜利的时候才能够将代码推送到近程仓库中。这仿佛是一个相当正当的条件,不是吗?通过提交此更改并尝试推送来随便测试它。

最初,咱们将再增加一个工具。到目前为止,咱们始终在遵循所有提交音讯的规范约定,让咱们确保团队中的每个人都遵循它们(包含咱们本人!)。咱们能够为咱们的提交音讯增加一个 linter:

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

要配置它,咱们将应用一组规范默认值,但我喜爱将该列表显式蕴含在 commitlint.config.js 文件中,因为我有时会遗记可用的前缀:

commitlint.config.js

// build: 影响构建零碎或内部依赖项的更改(示例范畴:gulp、broccoli、npm)// ci: 更改咱们的 CI 配置文件和脚本(示例范畴:Travis、Circle、BrowserStack、SauceLabs)// docs: 文档批改
// feat: 一个新的性能
// fix: 一个 bug 修复
// perf: 晋升性能的代码批改
// refactor: 既不修复谬误也不增加性能的代码更改
// style: 不影响代码含意的更改(空格、格局、短少分号等)// test: 增加缺失的测试或更正现有测试

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',
      ],
    ],
  },
};

而后应用 Husky 启用 commitlint:

npx husky add .husky/commit-msg 'npx --no -- commitlint --edit"$1"'
# 有的时候上述的命令会在某些命令行环境生效,也能够试试上面的命令
npx husky add .husky/commit-msg \"npx --no -- commitlint --edit'$1'\"
# 或者
npx husky add .husky/commit-msg "npx --no -- commitlint --edit $1"

我当初要应用音讯 ci: implement commitlint 创立一个新的提交

你能够在上面的屏幕截图中看到此设置的最终后果,心愿你的后果与此相似:

如果提交信息格式不正确的话,则会报错

VS Code 配置

当初咱们曾经实现了 ESLint 和 Prettier,咱们能够利用一些便当的 VS Code 性能让它们主动运行。

在我的项目的根目录中创立一个名为 .vscode 的目录和一个名为 settings.json 的文件。这将是一个笼罩已装置 VS 代码默认设置的值列表。

咱们想要将它们放在我的项目文件夹中的起因是咱们能够设置仅实用于该项目标特定设置,并且咱们能够通过将它们蕴含在代码仓库中来与咱们团队的其余成员共享它们。

在 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 左侧的小“谬误和播放图标”或按 Ctrl + Shift + D 拜访调试菜单。你能够抉择要运行的脚本并应用启动 / 进行按钮启动 / 进行它

除此之外,或者如果你没有应用 VS Code,咱们也能够在我的项目中设置一些有用的调试脚本

首先,咱们将装置 cross-env,如果你有共事在不同的环境(Windows、Linux、Mac 等)上工作,则有必要设置环境变量。

yarn add -D cross-env

装置完这个包之后,咱们能够更新下 package.json 文件中的 dev 脚本

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

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

在这个阶段,我将创立一个新的提交 build: add debugging configuration

目录构造

本节当初将介绍在咱们的我的项目中设置文件夹构造。这是许多人都会有十分强烈意见的话题之一,并且有充沛的理由!从久远来看,目录构造的确能够在我的项目失控时成就或毁坏我的项目,尤其是当团队成员不得不破费不必要的工夫来猜想将货色放在哪里(或找到货色)时。

我集体喜爱采纳相当简略的办法,基本上以类的 model / view 将事物离开。咱们将应用三个次要文件夹

PS: 原文没有 src 目录,我集体喜爱用 src 目录,所以加了 src 根目录

/components
/lib
/pages
  • components – 组成应用程序的各个 UI 组件将位于此处
  • lib – 业务 / 应用程序 / 畛域 逻辑将存在于此
  • pages – 我的项目的理论 路由 / 页面

除此之外,咱们还会有其余目录来反对该我的项目,但形成咱们的应用程序的简直所有内容的外围都将位于这三个目录中

components 中,咱们将有子目录,这些子目录将类似类型的组件组合在一起。你能够应用你喜爱的任何办法来执行此操作。我过来常常应用 MUI 库,所以我偏向于遵循他们在文档中用于组件的雷同组织

例如输入框、导航、工具办法、布局等。

你不须要提前创立这些目录并将它们留空。我会在构建组件时顺手创立它们

本节旨在解释我将如何设置这个我的项目,你能够抉择许多其余形式来组织你的我的项目,我激励你抉择最适宜你和团队的形式。

这里我再应用 feat: create directory structure 创立一个提交

增加 Storybook

咱们能够应用的一种很棒的古代工具叫做 Storybook

Storybook 为咱们提供了一个环境来展现和测试咱们在咱们正在应用它们的应用程序之外构建的 React 组件。它是将开发人员与设计人员分割起来并可能依据设计要求验证咱们开发的组件的外观和性能的好工具

请留神,Storybook 是一种可视化测试工具,稍后咱们将引入其余工具来进行性能单元测试和端到端测试

学习如何应用 Storybook 的最佳形式是装置并试用它!

npx sb init --builder webpack5

咱们将应用 webpack5 版本来与最新版本的 webpack 放弃同步(我不确定为什么它依然不是默认版本。兴许在你应用本教程时曾经是了)。

当 Storybook 装置时,它会自动检测无关我的项目的很多信息,比方它是一个 React 应用程序,以及正在应用的其余工具。它应该兼容好所有配置自身。

如果你收到无关 eslintPlugin 的提醒,你能够抉择“是”。不过,咱们将手动配置它,所以如果你收到一条音讯说它没有主动配置,请不要放心。

关上 eslintrc.json 文件并更新它

{
  "extends": [
    "plugin:storybook/recommended", // 新退出
    "next",
    "next/core-web-vitals",
    "eslint:recommended"
  ],
  "globals": {"React": "readonly"},
  // 新退出
  "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": "^_"}]
  }
}

我增加了 // 新退出 来标记 Storybook 特定的两个新局部和行。

咱们留神到,Storybook 也已将 /stories 目录增加到我的项目中,其中蕴含许多示例。如果你是 Storybook 的老手,我强烈建议你通读它们并将它们留在那里,直到你可能脱离模板自若地创立本人的示例。

在咱们运行它之前,咱们须要确保咱们应用的是 webpack5。将以下内容增加到 package.json 文件中:

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

而后运行

yarn install

确保 webpack5 曾经被装置

接下来更新 .storybook/main.js 文件

module.exports = {stories: ['../**/*.stories.mdx', '../**/*.stories.@(js|jsx|ts|tsx)'],
    /** 裸露 public 目录给到 stotrybook,作为动态资源目录 */
  staticDirs: ['../public'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
  ],
  framework: '@storybook/react',
  core: {builder: '@storybook/builder-webpack5',},
};

在这里,咱们更改了 stories 文件的模式,以便它能够在咱们的组件(或其余)目录中选取任何 .stories 文件。

咱们裸露了 Next.js 的“public”目录作为 Storybook 的动态资源目录,这样咱们就能够在 Storybook 中测试相似图片,视频等资源了

最初,在咱们运行 Storybook 自身之前,让咱们在 .storybook/preview.js 中增加一些有用的值。这是咱们管制 stories 渲染形式默认值的文件。

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`,
        },
      },
    ];
  })
);

// 容许 Storybook 解决 Next 的 <Image> 组件
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 拜访到

如果你以前从未应用过这些示例,我心愿你尝试并相熟这些示例。

在这个阶段,我再创立一个提交 build: implement storybook

创立一个组件模版

是时候将咱们曾经实现的所有配置放在一起,看看如何应用咱们为本人设定的规范来创立和实现咱们的第一个组件了

咱们将创立一个简略的卡片。创立如下的目录

/src/components/template/base

在这个目录中创立 BaseTemplate.tsx。这将遵循文件名的规范模式,该模式与指向它的目录相匹配。例如,这容许咱们在卡片目录中领有其余类型的卡片,如 PhotoCardTextCard 等。

BaseTemplate.tsx

export interface IBaseTemplate {}

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

export default BaseTemplate;

咱们的每一个组件都将遵循这个确切的构造。即便它不应用 props,它依然会为组件导出一个空的 props 接口。这样做的起因是它将容许咱们在许多组件和文件中复制这个准确的构造,并应用雷同的模式替换组件,并且只查找 / 替换组件的名称。

当你开始应用 stories 和 mock props 时,就会明确为所有组件文件维护统一的命名计划和界面是如许不便和弱小。

这其中就遵循了咱们之前提及到的 一致性就是所有

接下来咱们会为组件创立款式文件。我集体更喜爱将款式文件寄存在各个组件的文件夹中

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

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,或者在 app 内,或者在咱们编写的单元测试中)中去测试它。快速访问数据来渲染组件将会很不便。

让咱们创立一个文件来存储这个组件的模仿数据,这些模仿数据是给后续测试应用的。

BaseTemplate.mocks.ts

import {IBaseTemplate} from './BaseTemplate';

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

export const mockBaseTemplateProps = {base,};

这个构造看起来有点点简单,但很快咱们将看到它的长处。我应用非常见名知意的统一命名模式,所以这个模板很容易复制并粘贴到你创立的每个新组件。

当初咱们为这个组件创立一个 story

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,
    argTypes: {},} as ComponentMeta<typeof BaseTemplate>;
  
const Template: ComponentStory<typeof BaseTemplate> = (args) => (<BaseTemplate {...args} />
);

export const Base = Template.bind({});

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

我不会通知你每一个 story 文件的具体细节,对你来说,做好的学习资源是 Storybook 的官网文档。

这里指标是创立一个容易复制粘贴的一致性的组件模版,以供组件进行开发和测试

当初测试一下

yarn storybook

如果一切顺利,你将会看到上面的界面(如果有问题,我倡议你再从新检查一下之前的配置正不正确)

当初咱们开始创立更多文件,最好养成在提交之前运行 yarn lint 的习惯,以确保一切都是洁净的并准备就绪。我在这里再创立一个提交 build: create BaseTemplate component

应用组件模版

既然咱们曾经有了组件模版,接下来咱们就创立一个实在的组件

创立 components/cards 目录。而后将 templates 目录下的 base 文件夹拷贝到 cards 上面,而后再将 base 重命名为 cat。咱们将创立一个 CatCard。重命名每个文件以匹配。实现后应该是这样的:

而后在 components/cards/cat 目录下,全局将 BaseTemplate 替换为 CatCard,如下

当初咱们筹备开始工作,咱们曾经有了一个洁净的预生成的模版,其中曾经为咱们的 Card 组件蕴含了 story 文件和模仿数据文件。相当的不便!让咱们开始开发 Card 组件吧:

CatCard.tsx

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

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



.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,};

留神这里从我的项目的 public 目录用了一张 🐱 的照片(/time-cat.jpg),你能够从我的项目的仓库中找到它

CatCard.stories 的批改就是须要将 story 的 title 从 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;

或者,你能够将头像图像放在 public 目录中,但为了学习应用内部域的过程,咱们将保留此设置。

当初运行 Storybook,如果你足够侥幸,你将会看到

这个组件能够很不便的搁置在理论我的项目中的任何地位。在短期内测试时应用 mock prop,并在筹备好后更换为实在 prop

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';

const Home: NextPage = () => {
    return (
      <div>
        <Head>
          <title>Create Next App</title>
          <meta name="description" content="Generated by create next app" />
          <link rel="icon" href="/favicon.ico" />
        </Head>
  
        <main>
          <h1>
            Welcome to <a href="<https://nextjs.org>">Next.js!</a>
          </h1>
  
          <div style={{display: 'flex'}}>
            <CatCard {...mockCatCardProps.base} />
            <CatCard {...mockCatCardProps.base} />
            <CatCard {...mockCatCardProps.base} />
            <CatCard {...mockCatCardProps.base} />
          </div>
        </main>
  
        <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>
              <Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
            </span>
          </a>
        </footer>
      </div>
    );
  };
  
  export default Home;

运行如下命令查看界面款式

    yarn dev

增加自定义文档

尽管在这个阶段没有必要,但你可能心愿对利用的 <head> 中的内容进行更细粒度的管制。在 pages 目录中创立自定义 _document.tsx

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;

请留神,我曾经移除了 components/cards/cat/CatCard.module.css 中的 @import 字体,并且把 google 字体放在这里进行预加载

您须要在 <head> 元素中执行或自定义的任何其余操作当初都能够在此文件中实现。

请留神,此 <Head> 与从 next/head 导入的不同。它们将一起工作,而这个仅用于你心愿在每个页面上加载的数据。

更多的对于自定义 _document,能够查看这个文档

增加 Layouts

Layouts 是 Next.js 中的重要概念。他们帮助咱们治理页面间的状态。对于本节,咱们将应用与官网示例 中提供的雷同的根本模板,并简略地对其进行自定义以适宜咱们的我的项目。

components 中创立新的目录 layouts。咱们将复制两次 templates/case 目录。一个叫做 primary,另一个叫做 sidebar。如下图所示

在每个文件中对 BaseTemplate 进行辨别大小写的查找 / 替换,并别离替换为 PrimaryLayoutSidebarLayout

如果你在这一步有遇到困难,能够间接参考这个[仓库的构造](https://github.com/zidanDirk/nextjs-fullstack-app-template-zn)

更新  PrimaryLayout.tsx 和  PrimaryLayout.module.css 文件:

components/layouts/primary/PrimaryLayout.tsx

import Head from 'next/head';
import styles from './PrimaryLayout.module.css';

export interface IPrimaryLayout extends React.ComponentPropsWithoutRef<'div'> {}

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;}

而后是 sidebar

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="/">
        Home
      </Link>
      <Link href="/about">
        About
      </Link>
      <Link href="/contact">
        Contact
      </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 的页面来展现如何应用共享 layouts 并在页面之间放弃组件状态。

首先,咱们须要增加一个类型来扩大默认的 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;
};

当你须要创立自定义 layouts 的页面,你能够应用 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 {NextPageWithLayout} from './page';

const Home: NextPageWithLayout = () => {
    return (
      <section >
        <h1>
          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>
    );
  };

并且在 pages 目录中创立一个新的 about 页面

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>
);
};

更新 _app.tsx

pages/_app.tsx

import type {AppProps} from 'next/app';
import './globals.css';
import {NextPageWithLayout} from './page';

interface AppPropsWithLayout extends AppProps {Component: NextPageWithLayout;}

function MyApp({Component, pageProps}: AppPropsWithLayout) {
    // 如果这个 layout 是可用的,则在页面中应用 
  const getLayout = Component.getLayout || ((page) => page);

  return getLayout(<Component {...pageProps} />);
}

export default MyApp;

最初我更新了 PrimaryLayout.mocks.ts 文件,为 IPrimaryLayout 增加了 children: '{{component}}’ 用于在 Storybook 中展现

同时我更新 layout 的 story title 从 templates/...变成 layouts/...

最初你能够保留测试一下

yarn dev

在侧边栏(Home 和 about)的按钮单击能够进行页面切换。请留神,所应用的布局将继续存在而无需从新加载(正如咱们的用意),并且用户将取得超疾速的体验。

在 Storybook 这边,咱们能够独立于利用预览和测试咱们的 layout 组件。这个 PrimaryLayout 组件在没有自组件的状况下没有什么作用,而侧边栏则能够完满的显示。

部署

最初一步将解说如何部署一个 Next.js 利用

咱们将应用 Vercel,因为它是 Next.js 应用程序最简略、最间接的部署解决方案。

请留神,Vercel 相对不是惟一的抉择,其余次要服务(如 AWS、Netlify 等)也都能够应用。

假如你应用的不是 动态站点生成 模式,那么实际上你仅仅须要找个服务来运行 next start 命令就行了

作为一个普通用户在 Vercel 上进行部署是完全免费的,咱们须要从创立账号开始

登录后,单击 + New Project 并授予 Vercel 拜访你的 Github 仓库的权限。你能够授予全局拜访权限,也能够仅抉择要部署的仓库。我将抉择 nextjs-fullstack-app-template-zn 这个仓库。

抉择它后,须要对其进行配置。在 Build and Output Settings 局部,确保将默认的 NPM 命令替换为 yarn 命令(除非你应用的是 NPM)。

咱们还没有应用任何环境变量,所以不须要增加

一旦实现,只需单击 Deploy 即可!就这么简略。

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

好消息是,你曾经配置了 yarn build 命令以确保在推送代码之前构建无效的生产版本,因而能够自信地推送代码,并假如部署会胜利。

惟一须要记住的是两个环境之间的差别。如果你的脚本不同(应用 NPM 而不是 yarn 或反之亦然),或者更常见的状况是短少环境变量,你的构建依然有可能在本地胜利但在 Vercel 上失败。

咱们将在当前的教程中增加 env 值,因而你须要确保在本地和生产环境中都配置了这些值,因为它们是秘密,永远不应提交给公共仓库。

下一步

我心愿你找到了本教程并学到了一些常识,为你和你的团队设置牢靠且可扩大的 Next.js 我的项目。

这是对于创立生产高质量 Next.js 应用程序的系列教程的第一局部。

上面是我对将来教程的一些想法,我心愿你能留下一些反馈,通知我哪些是你认为最有用的(如果你没有在上面看到它们,则能够留下其余反馈)

  • 如何应用 API 路由和 Tailwind CSS 构建全栈 Next.js 应用程序
  • 如何应用 Recoil 将全局状态管理器增加到 Next.js 应用程序
  • 如何应用 jest 和 playwright 在 Next.js 应用程序中施行单元测试和端到端测试
  • 如何应用 Github actions 和 Vercel 创立 CI/CD 流水线
  • 如何应用 NextAuth 和 i18next 在 Next.js 应用程序中实现 SSO 身份验证和国际化
  • 如何应用 Prisma 和 Supabase 将数据库连贯到 Next.js 应用程序
  • 如何应用 Next.js 和 Nx 在 monorepo 中治理多个应用程序

请持续关注,请不要犹豫,提出任何问题,如果能够的话,我很乐意答复!

其余

  • 代码仓库地址
  • 原文地址

感激观看,码字不易,欢送一键三连 ~~~ 🌹🌹🌹

正文完
 0