项目初始化 1 npx create-next-app@latest --typescript
安装完成后,根据指示开启开发模式的 server。然后尝试编辑 pages/index.tsx
并在浏览器查看结果。
开发规范 commitlint 1 npx @luozhu/create-commitlint
prettier 安装依赖 1 yarn add prettier eslint-config-prettier eslint-plugin-prettier @luozhu/prettier-config -D
配置 .eslintrc.json 1 2 3 4 5 6 7 8 { "extends" : [ "next/core-web-vitals" , "plugin:prettier/recommended" , "prettier" ] , "rules" : { "no-unused-vars" : 1 , "react-hooks/exhaustive-deps" : 0 , "@next/next/no-img-element" : 0 } }
配置 .prettierrc.js 1 module .exports = require ('@luozhu/prettier-config' );
.editorconfig 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # EditorConfig is awesome: http://EditorConfig.org # top-most EditorConfig file root = true # Unix-style newlines with a newline ending every file [*] quote_type = single # Fix Prettier "prettier.singleQuote" not working in 1.40 vs code indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false [Makefile] indent_style = tab
lint-staged 安装 lint-staged:
在 package.json
文件中如下配置即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 { "gitHooks" : { "pre-commit" : "lint-staged" , "commit-msg" : "commitlint -e -V" } , "lint-staged" : { "**/*.{js,jsx,ts,tsx}" : [ "eslint --fix" ] , "**/*.{md,json}" : [ "prettier --write" ] } }
antd 开发 安装依赖 NextJS 使用 Less 编译 Antd 使用 yarn 安装 next-with-less 包,并顺带最新版本的 less 和 less-loader:
1 yarn add next-with-less less less-loader -D
并修改 next.config.js
配置文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 const withLess = require ("next-with-less" );let config = { reactStrictMode : true }; config = withLess ({ ...config, lessLoaderOptions : { lessOptions : { modifyVars : { "@primary-color" : "#f74a49" , "@border-radius-base" : ".5em" } } } }); module .exports = config
引入 antd less 在 pages/_app.tsx
中引入 less
1 2 import 'antd/lib/style/themes/default.less' ;import 'antd/dist/antd.less'
使用 dayjs 替换 moment.js 按照 自定义组件 的方式自定义组件在 Next.js 中是不够的,需要使用 next-transpile-modules 做进一步的处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const withTM = require ('next-transpile-modules' )([ 'rc-picker' , 'rc-util' , 'antd' , 'rc-pagination' , 'rc-notification' , '@ant-design/icons' , ]); let config = { reactStrictMode : true }; config = withTM (config); module .exports = config
在 _app.tsx
中初始化 dayjs:
1 2 3 4 5 import dayjs from 'dayjs' ;import 'dayjs/locale/zh-cn' ;dayjs.locale ('zh-cn' );
布局 布局使用的是 antd 的 Layout,我选用的是侧边栏布局。
Layout.tsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import { Layout , Menu , Breadcrumb } from 'antd' ;import Sider from './Sider' ;const { Header , Content , Footer } = Layout ;const AppLayout = ({children} ) => { return ( <Layout style ={{ minHeight: '100vh ' }}> <Sider /> <Layout className ="site-layout" > <Header style ={{ padding: 0 , backgroundColor: '#ffffff ' }} /> <Content style ={{ margin: '0 16px ' }}> {children}</Content > <Footer style ={{ textAlign: 'center ' }}> Crypto Meta ©2021 Powered by{' '} <a className ="default" href ="https://nextjs.org/" target ="_blank" rel ="noreferrer" > Next.js </a > </Footer > </Layout > </Layout > ) }
Sider.tsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 import { Layout , Menu } from 'antd' ;import React , { useState } from 'react' ;import { useRouter } from 'next/router' ;import Link from 'next/link' ;import menus from 'config/menus' ;const { Sider } = Layout ;const AppSider = ( ) => { const router = useRouter (); const [collapsed, setCollapsed] = useState (false ); const onCollapse = (collapsed: boolean ) => { setCollapsed (collapsed); }; return ( <> <Sider breakpoint ="lg" collapsible collapsed ={collapsed} onCollapse ={onCollapse} > <Menu theme ="dark" mode ="inline" selectedKeys ={[router.pathname]} > {menus.map(menu => ( <Menu.Item key ={menu.route} icon ={menu.icon} > <Link href ={menu.route} > <a > {menu.name}</a > </Link > </Menu.Item > ))} </Menu > </Sider > </> ); }; export default AppSider ;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 export default [ { route : '/cryptoyou' , name : 'Crypto You' , icon : ( <img src ="https://cdn.jsdelivr.net/gh/youngjuning/images/202112081051602.png" width ="30" alt ="thecryptoyou" /> ), }, { route : '/squid' , name : 'Squid NFT' , icon : ( <img src ="https://cdn.jsdelivr.net/gh/youngjuning/images/202112082111091.png" width ="30" alt ="thecryptoyou" /> ), }, ];
国际化 antd 提供了一个 React 组件 ConfigProvider 用于全局配置国际化文案。新建 components/Layout/Provider.tsx
:
1 2 3 4 5 6 7 8 9 import React from 'react' ;import { ConfigProvider } from 'antd' ;import zhCN from 'antd/lib/locale/zh_CN' ;const Provider = ({ children } ) => { return <ConfigProvider locale ={zhCN} > {children}</ConfigProvider > ; }; export default Provider ;
然后在 components/Layout/index.tsx
中引入:
1 2 3 4 5 6 7 8 import Provider from './Provider' ;export default function AppLayout ({ children } ) { return ( <Provider > // ... </Provider > ); }
面包屑导航 参考 Breadcrumbs and NextJS 封装了一个 Breadcrumb 组件:
1 2 3 4 5 6 import React from 'react' ;import Breadcrumbs from 'nextjs-antd-breadcrumbs' ;const Example = ( ) => { return <Breadcrumbs rootLabel ="Home" omitRootLabel ={false}/ > ; };
Next.js 开发 基于根目录导入模块 baseUrl
配置选项允许您直接从项目的根目录导入。
1 2 3 4 5 6 { "compilerOptions" : { "baseUrl" : "." } }
模块路径别名 在 tsconfig.json 中加入以下配置:
1 2 3 4 5 6 7 8 { "compilerOptions" : { "baseUrl" : "." , "paths" : { "@/components/*" : [ "./components/*" ] } } }
其他的地址依葫芦画瓢加到 paths 对象中即可。
redirects 永久重定向 永久重定向不同于重写路由,它会在 url 中表现出来,在 Next.js 中重定向是在 next.config.js
中配置的:
1 2 3 4 5 6 7 8 9 10 11 12 13 let config = { async redirects ( ) { return [ { source : '/' , destination : '/cryptoyou' , permanent : true , }, ]; }, }; module .exports = config;
在 pages 目录包含非页面文件 要把测试文件、生成的文件或其他组件使用的文件放在 pages
目录中,你可以在扩展名前加上类似 page
的字样。
打开 next.config.js
并添加 pageExtensions
配置:
1 2 3 module .exports = { pageExtensions : ['page.tsx' , 'page.ts' , 'page.jsx' , 'page.js' ], }
然后重命名你的页面,使其有一个包括 .page
的文件扩展名(例如,重命名 MyPage.tsx
为MyPage.page.tsx
)。
注意:确保你也重命名 _document.js
、_app.js
、_middleware.js
,以及 pages/api/
下的文件。
styled-jsx next.js 内置支持 styled-jsx,我们要做的是配置支持 sass,首先安装 @styled-jsx/plugin-sass
:
1 yarn add @styled-jsx/plugin-sass sass node-sass -D
然后配置 .babelrc
:
1 2 3 4 5 6 7 8 9 10 11 12 { "presets" : [ [ "next/babel" , { "styled-jsx" : { "plugins" : [ "@styled-jsx/plugin-sass" ] } } ] ] }
注意:开启 babel 后,会自动禁用 swc,目前还没有 swc 的支持方案,进度请关注 [Improvement] Next.JS 12 support + SWC
自定义错误页面 404.tsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import { Result , Button } from 'antd' ;import Link from 'next/link' ;export default function Custom404 ( ) { return ( <Result status ="404" title ="404" subTitle ="Sorry, the page you visited does not exist." extra ={ <Button type ="primary" > <Link href ="/" > <a > 返回首页</a > </Link > </Button > } /> ); }
500.tsx 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import { Result , Button } from 'antd' ;import Link from 'next/link' ;export default function Custom500 ( ) { return ( <Result status ="500" title ="500" subTitle ="Sorry, something went wrong." extra ={ <Button type ="primary" > <Link href ="/" > 返回首页</Link > </Button > } /> ); }
Dapp 小狐狸钱包 小狐狸钱包MetaMask新手使用教程 使用MetaMask连接到币安智能链(BSC) @usedapp/core 安装依赖 1 $ yarn add @usedapp/core
设置 Provider 你需要做的第一件事是用可选的配置设置 DAppProvider,并将你的整个应用程序包裹在其中。本文中我们编辑 components/Layout/Provider.tsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import React from 'react' ;import { DAppProvider , Config , ChainId } from '@usedapp/core' ;const config : Config = { readOnlyChainId : ChainId .BSC , readOnlyUrls : { [ChainId .BSC ]: 'https://bsc-dataseed1.binance.org/' , }, }; const Provider = ({ children } ) => { return ( <DAppProvider config ={config} > {children}</DAppProvider > ); }; export default Provider ;
连接钱包 然后你需要使用 activateBrowserWallet 来激活 provider。最好是在用户点击 “Connect” 按钮时进行。本文中我们新建组件 components/ConnectButton.tsx
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import React from 'react' ;import { Button } from 'antd' ;import { useEthers } from '@usedapp/core' ;import { LoginOutlined } from '@ant-design/icons' ;function ConnectButton ( ) { const { activateBrowserWallet } = useEthers (); function handleConnectWallet ( ) { activateBrowserWallet (); } return ( <Button size ="large" type ="primary" icon ={ <LoginOutlined /> } onClick={handleConnectWallet}> Connect MetaMask </Button > ); } export default ConnectButton ;
钱包余额 useEtherBalance(address: string)
提供一种获取账户余额的方法。以账户地址为参数,当数据不可用时(即未连接),返回 ·BigNumber
或 undefined
。要获得当前连接的账户,请使用 useEthers()
。
1 2 3 4 5 6 7 8 9 10 11 12 import { formatEther } from '@ethersproject/units' export function EtherBalance ( ) { const { account } = useEthers () const etherBalance = useEtherBalance (account) return ( <div > {etherBalance && <p > Balance: {formatEther(etherBalance)}</p > } </div > ) }
代币余额 useTokenBalance(address: string, tokenAddress: string)
提供一种方法来获取由 tokenAddress
指定的 ERC20 代币在所提供地址的余额。当数据不可用时,返回 BigNumber
或 undefined
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import { formatUnits } from '@ethersproject/units' const BABY = '0x53e562b9b7e5e94b81f10e96ee70ad06df3d2657' export function TokenBalance ( ) { const { account } = useEthers () const tokenBalance = useTokenBalance (BABY , account) return ( <div > {tokenBalance && <p > BABY: {formatUnits(tokenBalance, 18)}</p > } </div > ) }
参考 部署 GitHub Pages 如果是 vercel 付费用户,推荐使用 vercel。GitHub Pages 只能部署静态内容,所以需要使用 next export
将静态内容导出部署。
首先配置 npm scripts:
1 2 3 4 "scripts" : { "preexport" : "yarn build" , "export" : "next export" } ,
然后添加 .github/workflows/gh-pages.yml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 name: github pages on: push: branches: - main jobs: deploy: runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 - uses: c-hive/gha-yarn-cache@v2 - name: Install Dependencies run: yarn --non-interactive --silent --ignore-scripts --production=false - name: Build Website run: yarn export - name: Deploy Website uses: peaceiris/actions-gh-pages@v3 with: personal_token: ${{ secrets.PERSONAL_TOKEN }} external_repository: crypto-meta/crypto-meta.github.io publish_dir: ./out