简介:该教程兼容pc+移动端,如只需一端,可忽略兼容部分教程,根据需要运行的客户端构建项目

  1. antd官网:https://ant.design/components/overview-cn/
  2. antd-mobile官网:https://mobile.ant.design/zh
  3. next.js: https://www.nextjs.cn/
  4. react:https://react.zcopy.site/
  5. redux:https://react-redux.js.org/api/hooks#custom-context
一、介绍

Next.js,这是一个 React 的同构应用开发框架。

  1. 直观的、 基于页面 的路由系统(并支持 动态路由)
  2. 预渲染。支持在页面级的 静态生成 (SSG) 和 服务器端渲染 (SSR)
  3. 自动代码拆分,提升页面加载速度
  4. 具有经过优化的预取功能的 客户端路由
  5. 内置 CSS 和 Sass 的支持,并支持任何 CSS-in-JS 库
  6. 开发环境支持 快速刷新
  7. 利用 Serverless Functions 及 API 路由 构建 API 功能 完全可扩展
二、构建项目
yarn create next-app “文件名” --typescript
yarn dev

三、调整项目
  1. 文件目录
  2. _app.tsx
    import type { AppProps } from "next/app";export default function App({ Component, pageProps }: AppProps) {return <Component {...pageProps} />
    }
    
  3. index.tsx
    import {NextPage} from "next";const Home: NextPage = (props) => {  return <div>dsada</div>
    };
    export default Home
    
三、静态资源assets
  1. 创建assets>css、assets>font、assets>img
  2. 安装依赖
    yarn add sass
    
  3. 集成字体图标,下载阿里icon库,解压后把压缩包里的文件复制到assets>font
    <i class="iconfont icon-usename"></i>
    
  4. css文件下分别创建globals.scss、iframe.scss、normalize.scss、variable.scss
    //globals.scss 全局样式文件body{font-size: $font_size!important;
    }
    
     //iframe.scss 公共样式导入@import "./globals";
    @import "./normalize";
    @import "../font/iconfont.css";
    
    //normalize.scss 同一浏览器样式,下载后放入该文件中
    http://nicolasgallagher.com/about-normalize-css/
    https://github.com/necolas/normalize.css
    
    //variable.scss 全局变量文件$primary-color: red;
    /**
    * 字体大小
    */
    $font_size: 14px;//基础字体大小
    $sm_font_size: 12px;//小号字体
    $bg_font_size: 16px;//大号字体
    $xl_font_size: 20px;//超大号字体/**
    * icon 大小
    */
    $icon_size: $font_size;//默认字体
    $bg_icon_size: $bg_font_size;//大号字体
    $sm_icon_size: $sm_font_size;//小号字体
    $xl_icon_size: $xl_font_size;//超大号字体/**
    * button 颜色、大小
    */
    $btn_primary: #1677ff;
    $btn_danger: #ff4d4f;/**
    * h1-h5标签字体大小
    */
    $h1_font_size: 38px;//h1字体大小
    $h2_font_size: 30px;//h2字体大小
    $h3_font_size: 24px;//h3字体大小
    $h4_font_size: $xl_font_size;//h4字体大小
    $h5_font_size: $bg_font_size;//h5字体大小
    
  5. 配置引入路径tsconfig.json
    "paths": {..."@css/": ["./src/assets/css/"],"@img/": ["./src/assets/img/"],...
    }
    
  6. 引入全局样式,修改_app.tsx
    import type { AppProps } from "next/app";
    import "@css/iframe.scss";export default function App({ Component, pageProps }: AppProps) {return <Component {...pageProps} />
    }
  7. 引入全局sass变量,next.config.js
    const path = require("path");
    /** @type {import('next').NextConfig} */
    const nextConfig = {...sassOptions:{includePaths: [path.join(__dirname, "./src/assets/css")],prependData: "@import 'variable.scss';"},...
    }module.exports = nextConfig
  8. 配置antd-mobile主题,https://mobile.ant.design/zh/guide/theming,新建 css>antdMobileTheme.scss
    :root:root {--adm-color-primary: #ff4d4f;
    }
    
  9. 配置antd主题
  • 新建pages>antTheme.module.scss

    
    /* antd 主题配置
    * 详细配置可参考 https://ant.design/docs/react/customize-theme-cn*/:export {
    colorPrimary: $primary-color;
    fontSize: $font_size;
    fontSizeHeading1: $h1_font_size;
    fontSizeHeading2:$h2_font_size;
    fontSizeHeading3:$h3_font_size;
    fontSizeHeading4:$h4_font_size;
    fontSizeHeading5:$h5_font_size;
    fontSizeLG:$bg_font_size;
    fontSizeSM:$sm_font_size;
    fontSizeXL:$xl_font_size;
    fontSizeIcon:$sm_icon_size;
    }
    
  • 修改_app.tsx
    import type { AppProps } from "next/app";
    import {ConfigProvider} from "antd";
    import them from "./antTheme.module.scss";
    import "@css/iframe.scss";export default function App({ Component, pageProps }: AppProps) {return  <ConfigProvider theme={{token: them}}><Component {...pageProps}/></ConfigProvider>
    }
  1. 集成postcss
  • 安装依赖postcss-px-to-viewport-8-plugin

    yarn add postcss-px-to-viewport-8-plugin --dev
    
  • 根目录新建postcss.config.js
    //postcss.config.jsmodule.exports = {plugins: {"postcss-px-to-viewport-8-plugin": {viewportWidth: 375, // 视窗的宽度,对应的是我们设计稿的宽度viewportHeight: 912, // 视窗的高度,对应的是我们设计稿的高度,可以不设置unitPrecision: 3, // 指定`px`转换为视窗单位值的小数位数(很多时候无法整除)viewportUnit: 'vw', // 指定需要转换成的视窗单位,建议使用vwpropList: ['*'],selectorBlackList: [/^.pc/],minPixelValue: 1, // 小于或等于`1px`不转换为视窗单位,你也可以设置为你想要的值mediaQuery: false, // 允许在媒体查询中转换`px`,exclude: [/pc.module/,/antTheme.module.scss/,/braft-editor/], //设置忽略文件,用正则做目录名匹配}},
    };
四、集成redux
  1. 安装依赖

    yarn add redux react-redux redux-saga
    yarn add @types/react-redux @types/redux-saga next-redux-wrapper redux-devtools-extension --dev
    
  2. 创建redux>reducers、redux>sagas文件夹
  3. 配置引入路径tsconfig.json
    "paths": {..."@reducers/*": ["./src/redux/store/reducers/*"],"@sagas/*": ["./src/redux/store/sagas/*"],"@store/*": ["./src/redux/store/*"],...
    }
    
  4. 创建第一个store,redux>reducers>mobileStore.tsx
    /*** @description 该store,判断是否是移动端* *//*** @description 定义相关接口或者枚举* */
    export enum MobileStoreActionEnum {INIT="mobileStoreInit",CHANGE="mobileStoreChange"
    }export type MobileStoreStateType = boolean;interface MobileStoreActionInterface{type: MobileStoreActionEnum,payload:MobileStoreStateType
    }/*** @description store逻辑* */
    const mobileInitState:MobileStoreStateType = false;
    const mobileStore = (state:MobileStoreStateType =mobileInitState, action: MobileStoreActionInterface):MobileStoreStateType => {switch (action.type) {case MobileStoreActionEnum.INIT:return statecase MobileStoreActionEnum.CHANGE:return action.payloaddefault:return state}
    }
    export default mobileStore;
  5. 创建第一个sagaStore,redux>reducers>demoStore.tsx,异步store
    /*** @description 定义相关接口或者枚举* */export enum DemoStoreActionEnum{WATCH='watchDemoStore',CHANGE='demoStoreChange'
    }interface DemoStoreStateInterface {num:number
    }export interface DemoStoreActionInterface {type: DemoStoreActionEnumpayload: DemoStoreStateInterface
    }/*** @description store逻辑* */
    const initState:DemoStoreStateInterface = {num: 100
    }const demoStore = (state:DemoStoreStateInterface = initState, action: DemoStoreActionInterface):DemoStoreStateInterface => {switch (action.type) {case DemoStoreActionEnum.CHANGE:return action.payloaddefault:return state}
    };
    export default demoStore;
  6. 依次创建redux>sagas>demo.tsx、redux>sagas>mainSaga.tsx
  • saga的应用场景是复杂异步,如长时事务LLT(long live.transcation)等业务场景。
  • 方便测试,可以使用takeEvery打印logger。
  • 提供takeLatest/takeEvery/throttle方法,可以便利的实现对事件的仅关注最近事件、关注每一次、事件限频
  • 提供cancel/delay方法,可以便利的取消、延迟异步请求
  • 提供race(effects),[…effects]方法来支持竞态和并行场景
  • 提供channel机制支持外部事
    import { call, put, takeEvery, takeLatest,take,all,race,throttle,delay,fork,cacel,cancelled} from 'redux-saga/effects';
    takeEvery:被调用的任务无法控制何时被调用, 它们将在每次 action 被匹配时一遍又一遍地被调用。并且它们也无法控制何时停止监听。
    take:与takeEver相反,与 action 只会监听一次,使用一次就销毁
    takeLatest:每次 action 被匹配,当有action正在匹配,会放弃正在匹配的action,执行最新的
    call: saga通过 Generator函数实现,在yield函数后执行effect,其中call是用于执行某些异步操作的。
    put: 和上面的call一样,中间件提供put 来把action丢到中间件中去dispatch,好处同样是便于测试
    all: 同步执行多个任务使需要用到 yield all([call(fetch, '/users'),call(fetch, '/repos')])
    race: 和promise中的race一个概念,执行多个任务,受到响应后则继续执行 yield race({posts: call(fetchApi, '/posts'),timeout: call(delay, 1000)})
    fork:fork和take不同,take会和call一样阻塞代码的执行,知道结果返回,fork则不会,它会将任务启动并且不阻塞代码的执行,fork会返回一个task,可以用cacel(task)来取消任务
    cacel:来取消任务
    cancelled:如果当前任务,被cacel取消,则返回true
    throttle:节流
    
    //redux>sagas>demo.tsx
    import {call, put, takeEvery} from "@redux-saga/core/effects";
    import {DemoStoreActionEnum, DemoStoreActionInterface} from "@reducers/demoStore";// 延时器
    const delay = (ms:number) => new Promise(resolve => setTimeout(resolve, ms));function* asyncDemoSaga(action:DemoStoreActionInterface):Generator {yield call(delay,2000);yield put({ type: DemoStoreActionEnum.CHANGE,payload:action.payload})
    }function* watchDemoSaga():Generator {yield takeEvery(DemoStoreActionEnum.WATCH, asyncDemoSaga)
    }export default watchDemoSaga;
    //redux>sagas>mainSaga.tsximport {all} from "redux-saga/effects"
    import watchDemoSaga from "@sagas/demo";// saga中间件 主saga,用于区别是否需要saga来处理异步操作,如果没有异步,则放行
    function* mainSaga() {yield all([// 监听 saga 中有 userWatchSaga 操作,所以会拦截这个 actionwatchDemoSaga(),])
    }// 主saga要对外暴露出去
    export default mainSaga;
    
  1. 修改_app.tsx

    import type { AppProps } from "next/app";
    import {ConfigProvider} from "antd";
    import them from "./antTheme.module.scss";
    import "@css/iframe.scss";
    import {useEffect} from "react";
    import {useDispatch} from "react-redux";
    import { MobileStoreActionEnum} from "@reducers/mobileStore";
    import wrapper from "@/redux";
    import {Dispatch} from "redux";const App = ({ Component, pageProps }: AppProps) => {const dispatch:Dispatch = useDispatch();useEffect(():void => {//判断是哪个客户端(pc,mobile),主要用来兼容样式if (navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i)) {dispatch({type:  MobileStoreActionEnum.CHANGE,payload: true});//增加全局class,用于设置全局样式document.getElementsByTagName('html')[0].className = 'mobile';}else{//增加全局class,用于设置全局样式document.getElementsByTagName('html')[0].className = 'pc';}},[])return  <ConfigProvider theme={{token: them}}><Component {...pageProps}/></ConfigProvider>
    }export default wrapper.withRedux(App)
  2. 创建redux>index.tsx
    import { createWrapper, MakeStore } from "next-redux-wrapper";
    import { applyMiddleware, createStore, Store} from "redux";
    import createSagaMiddleware, {SagaMiddleware} from "redux-saga";
    import { composeWithDevTools } from "redux-devtools-extension/developmentOnly";import rootReducer from "@store/index";
    import mainSaga from "@sagas/mainSaga"; //异步初始化storeconst makeStore: MakeStore<Store> = () => {const sagaMiddleware:SagaMiddleware = createSagaMiddleware()const store:Store = createStore(rootReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))sagaMiddleware.run(mainSaga)return store
    }export default createWrapper<Store>(makeStore)
    
  3. 封装pc+移动端兼容性button组件,创建兼容性ui框架,需要把4. antd、antd-mobile二次封装,并合并一些共同的参数,修改成共同的样式;创建非ui框架的组件,只要注意像素单位的兼容就行,如:mobile.module.scss、pc.module.scss,postcss已限制pc.module 样式文件的转换
  • 修改.eslintrc.json

    {"extends": "next/core-web-vitals","rules": {"react/display-name": "off"}
    }
  • 创建components>antd>button
  • 创建pc端button组件button>pc.tsx、button>pc.module.scss
    //button>pc.tsx/*** @description pc端Button组件* *//**********第三方插件、组件引用**********/
    import React from "react";
    import {Button as PcButton, ButtonProps} from "antd";
    import {SizeType} from "antd/es/config-provider/SizeContext";
    import {ButtonType} from "antd/es/button";
    /**********当前目录文件*********/
    import styles from "./pc.module.scss";export interface PcButtonInterface {type?: ButtonType,size?: SizeType,onClick?: ButtonProps['onClick'],children?: React.ReactNode
    }const Button = React.memo((props:PcButtonInterface)=>{return <PcButton className={styles.button}type={props.type}size={props.size}onClick={props.onClick}>{ props.children }</PcButton >
    });export default Button;
    
  • 创建移动端button组件button>mobile.tsx、button>mobile.module.scss
    //button>mobile.tsx/*** @description 移动端Button组件* *//**********第三方插件、组件引用**********/
    import React from "react";
    import {Button as MobileButton, ButtonProps} from "antd-mobile";
    /**********当前目录文件*********/
    import styles from "./mobile.module.scss";export interface MobileButtonInterface {type?: ButtonProps['color'],size?: ButtonProps['size'],onClick?:ButtonProps['onClick'],children?: React.ReactNode;
    }const Button = React.memo((props:MobileButtonInterface)=>{return  <MobileButton className={styles.button}color={props.type}size={props.size}onClick={props.onClick}>{ props.children }</MobileButton>
    });export default Button;
    
  • 创建兼容pc+移动组件button>index.tsx、button>index.scss
    //button>index.tsx/*** @description 同时兼容pc、移动的Button组件* */import React, {useState} from "react";
    import PcButton, {PcButtonInterface} from "./pc";
    import MobileButton, {MobileButtonInterface} from "./mobile";
    import {useSelector, useStore} from "react-redux";
    import {Store} from "redux";interface ClientButtonInterface {type?: PcButtonInterface['type'] & MobileButtonInterface['type'],size?: PcButtonInterface['size'] & MobileButtonInterface['size'],onClick?: PcButtonInterface['onClick'] & MobileButtonInterface['onClick'],children?: React.ReactNode
    }const Button = React.memo((props: ClientButtonInterface) => {const store:Store = useStore();const storeState = store.getState() as any;const [mobile,setMobile]= useState(storeState.mobileStore)useSelector((state:any):void => {if(mobile!=state?.mobileStore){setMobile(state?.mobileStore);}});return <>{mobile ? <MobileButton {...props}/> : <PcButton {...props}/>}</>
    });export default Button;
    //button>index.scss.button{font-size: 14px;height: 32px;padding: 4px 15px;border-radius: 6px;
    }
    
  • 修改button>mobile.module.scss、button>pc.module.scss
    @import "./index";
    
  • 使用
    import Button from "@/components/antd/button";<Button type="primary">antd</Button>
    
  1. 持续储存
  • 安装依赖,https://www.npmjs.com/package/redux-persist

    yarn add redux-persist
    
  • 修改redux>index.tsx
    import { createWrapper, MakeStore } from "next-redux-wrapper";
    import { applyMiddleware, createStore, Store} from "redux";
    import createSagaMiddleware, {SagaMiddleware} from "redux-saga";
    import { composeWithDevTools } from "redux-devtools-extension/developmentOnly";
    import {persistStore, persistReducer} from "redux-persist";
    import storage from "redux-persist/lib/storage";import rootReducer from "@store/index";
    import mainSaga from "@sagas/mainSaga"; //异步初始化store//持久化储存配置
    const persistConfig = {key: 'root', //在localStorge中生成key为root的值storage,blacklist:['demoSaga'] //设置某个reducer数据不持久化
    }
    const makeStore: MakeStore<Store> = () => {const sagaMiddleware:SagaMiddleware = createSagaMiddleware();const rootPersistReducer = persistReducer(persistConfig, rootReducer)const store:Store = createStore(rootPersistReducer, composeWithDevTools(applyMiddleware(sagaMiddleware)))sagaMiddleware.run(mainSaga);persistStore(store);return store
    }export default createWrapper<Store>(makeStore)
    
五、页面配置
  1. 设置页面标题:_app.tsx

    import '@/assets/css/globals.scss';
    import type { AppProps } from 'next/app';
    import Head from 'next/head';
    import { ConfigProvider } from 'antd';
    import them from '@/pages/app.module.scss';export default function App({ Component, pageProps }: AppProps) {return <><Head><title>页面标题</title></Head><ConfigProvider theme={{token: them}}><Component {...pageProps}/></ConfigProvider></>
    }
    
  2. 设置页面框架代码:_document.tsx,只会在初始化预渲染,设置的内容只会在build后生效
    import {Html, Head, Main, NextScript} from 'next/document'export default function Document() {return (<Html lang="en"><Head><link rel="icon" href="/favicon.ico"></link><meta name="description" content="页面框架"></meta></Head><body><Main/><NextScript/></body></Html>)
    }
    
  3. 自定义404页面,pages下新建404.tsx页面
    export default function Custom_404(){return <>404页面</>
    }
    
六、图片引用
  1. 方法一:原生img,使用’ '可能会导致LCP变慢和带宽增加,build时会有此警告

    import idCard from '@img/idCard.png';
    <img src={idCard.src}/>
    
  2. 方法二:使用 next/image;简单的图片引用建议用原生img
    //建议用div包括起来,不单独使用,单独使用会自动生成很多自带的样式;Image会自适应div大小
    import idCard from '@img/idCard.png';
    import Image from 'next/image';
    <div><Image src={idCard} alt=""/></div>
    
  3. next/image 自带优化的api适用于SSR,SSG中无法使用 ,可以改动 next.config.js 配置解决
    const nextConfig = {reactStrictMode: true,swcMinify: true,images:{unoptimized:true}
    }module.exports = nextConfig
    
  4. 安装sharp包(高性能图片处理器),Next.js将自动使用它的图像优化
    yarn add sharp
    
七、动态路由
  1. 创建pages>demo文件夹
  2. 创建demo>index.tsx、demo>index.scss、demo>mobile.module.scss、demo>pc.module.scss
    //demo>index.tsximport Image from "next/image";
    import idCard from "@img/idCard.png";
    import useStyle from "@hook/styleHook";
    import mobileStyle from "./mobile.module.scss";
    import pcStyle from "./pc.module.scss";const Demo = () => {const styles = useStyle(pcStyle,mobileStyle);return <div className={styles.P_demo}><Image src={idCard} alt=""/></div>;
    };export default Demo
    //demo>index.scss.P_demo{img{width: 100px;height: 100px;}
    }
    
    //demo>mobile.module.scss、demo>pc.module.scss
    @import "./index";
    
  3. 修改page>index.tsx
    import {NextRouter, useRouter} from "next/router";const router:NextRouter = useRouter();
    <Button onClick={() => router.push('/demo')}>goDemo</Button>
    
  4. 自定义路由,改动 next.config.js 配置
    const nextConfig = {...//自定义路由,通常不需要自定义路由,适用于SSexportPathMap: async ()=>{return {'/':{page:'/index'}}},...
    }module.exports = nextConfig
    
  5. 动态路由
  • 修改demo>index.tsx为demo>[id].tsx

    import Image from "next/image";
    import idCard from "@img/idCard.png";
    import useStyle from "@hook/styleHook";
    import mobileStyle from "./mobile.module.scss";
    import pcStyle from "./pc.module.scss";
    import {NextRouter, useRouter} from "next/router";const Demo = () => {const styles = useStyle(pcStyle,mobileStyle);const router:NextRouter = useRouter();console.log(router.query)return <div className={styles.P_demo}><Image src={idCard} alt=""/></div>;
    };export default Demo
  • 修改pages>index.tsx
    <Button onClick={() => router.push('/demo/1')}>goDemo</Button>
    
  1. 路由嵌套
  • [id].tsx:paths 里面参数只有一个id ,/demo/1
  • […routers].tsx :paths 里面可以包含多个路径,以数组形式发挥参数,/demo/1/3
  • [id]/[comment].tsx:paths 里面可以包含多个路径,以json形式返回,/demo/1/3
八、接口api

NEXT.js存在CSR/SSR/SSG 三种请求方式,最多存在两种:1、CSR+SSR;2、CSR+SSG
CSR请求:常规前端项目中请求方式,由客户端浏览器端发送请求,拿到数据后再渲染道页面
SSR请求:由服务端发起请求(NEXT.js中的node.js),拿到数据后,组装HTML,再把HTML返回到客户端浏览器
SSG请求:与SSR请求类似,由服务端发起请求(NEXT.js中的node.js),拿到数据后,组装HTML,然后静态化输出。由于是完全静态化输出,当数据变化时,必须重新静态化才能更新页面

  1. 安装axios

    yarn add axios
    
  2. 封装axios
    /*** @description axios公共请求封装* */
    import axios, {AxiosResponse, InternalAxiosRequestConfig} from "axios";/*** @description 定义相关接口或者枚举* *///请求枚举
    export enum MethodEnum {Get='GET',Post='POST'
    }//返回结果
    export interface ResponseResultInterface<Body> {Header:{},Body: Body
    }//请求参数
    export interface RequestInterface<params> {url:string,params?:params,method?:MethodEnum
    }/*** 封装axios* */// 添加请求拦截器
    axios.interceptors.request.use( (config:InternalAxiosRequestConfig)=>{return config;
    }, (error)=>{return Promise.reject(error);
    });// 添加响应拦截器
    axios.interceptors.response.use( (response:AxiosResponse) => {return response;
    }, (error) => {return Promise.reject(error);
    });/*** @method useAxiosRequest axios请求封装* @param requestPar { RequestInterface } 请求url* @return Promise* */
    const baseRequest= async <params,responseData>(requestPar:RequestInterface<params>):Promise<responseData> => {const requestResult:AxiosResponse = await axios({method: requestPar.method || MethodEnum.Post,url: requestPar.url,data: requestPar.params});return requestResult.data as responseData;
    };export default baseRequest;
    
  3. 配置引入路径,修改 tsconfig.json
    "paths": {... "@common/*": ["./src/common/*"],"@api/*": [  "./src/pages/api/*"],...
    }
    
  4. 创建api服务,pages>api>demoApi.tsx
    // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
    import type { NextApiRequest, NextApiResponse } from "next";
    import {ResponseResultInterface} from "@common/baseRequest";export interface DemoInterface {id: number,name?: string
    }type ApiDemoType = ResponseResultInterface<DemoInterface>export default function demoApi(req: NextApiRequest,res: NextApiResponse<ApiDemoType>
    ):void {let data:ApiDemoType= {Header:{},Body:{id:-1}};if(req.method == "GET"){const id:number = Number(req.query.id);data.Body.id = id;switch (id) {case 1:data.Body.name = "我是API服务1"break;}res.status(200).json(data)}else{res.status(200).json(data)}
    }
    
  5. 修改pages>demo>[id].tsx
    import Image from "next/image";
    import {NextRouter, useRouter} from "next/router";
    import {GetServerSideProps} from "next";
    import {ParsedUrlQuery} from "querystring";
    import idCard from "@img/idCard.png";
    import useStyle from "@hook/styleHook";
    import baseRequest, {MethodEnum, RequestInterface} from "@common/baseRequest";
    import {DemoInterface} from "@api/demoApi";
    import mobileStyle from "./mobile.module.scss";
    import pcStyle from "./pc.module.scss";const Demo = (props: DemoInterface) => {const styles = useStyle(pcStyle,mobileStyle);const router:NextRouter = useRouter();console.log(router.query)console.log(props);return <div className={styles.P_demo}><Image src={idCard} alt=""/></div>;
    };/*** getServerSideProps 适用于SSR,不适用于SSG* getStaticProps SSR 和 SSG 均支持,但仅在网站build时候发起API请求* getServerSideProps 和 getStaticProps请求都是在服务端进行不涉及跨域* */export const getServerSideProps: GetServerSideProps = async (paths) => {const query:ParsedUrlQuery = paths.query;const requestOption:RequestInterface<undefined>={url:`http://localhost:3000/api/demoApi?id=${query.id}`,method:MethodEnum.Get}const requestResult = await baseRequest<DemoInterface>({url: requestOption.url,method:requestOption.method});return {props: requestResult.Body}
    }/*** SSG 静态生成* getStaticPaths build 时会生成多个页面* 只是用于getStaticProps* */
    // export const getStaticPaths: GetStaticPaths<DemoParams> = async () => {
    //     // const arr: string[] = ['1', '2'];
    //     // const paths = arr.map((id) => {
    //     //     return {
    //     //         params: { id },
    //     //     }
    //     // })
    //     // return {
    //     //     //这里的路由参数提供给getStaticProps使用
    //     //     paths,
    //     //     //不在以上参数路由将返回404
    //     //     fallback: false
    //     // }
    //     const id1:DemoParams = {id: '1'};
    //     const id2:DemoParams = {id: '2'};
    //     const staticPathOption = {
    //         //这里的路由参数提供给getStaticProps使用
    //         path: [{
    //             params: id1
    //         }, {
    //             params: id2
    //         }],
    //         //不在以上参数路由将返回404dc
    //         // fallback: false
    //     }
    //     return staticPathOption
    // }export default Demo
    

九、生成静态网站(SSG)
  1. 设置SSG的export命令,修改package.json

    "scripts": {"dev": "next dev","build": "next build && next export","start": "next start","lint": "next lint"
    },
    
  2. 然后执行yarn build,该命令回显执行next build 再执行 next export,输出目录为根目录下的out目录
  3. 设置静态资源的bassePath,如果发布在服务器二级目录需要设置,更改next.config.js 配置;/app为二级目录
    const nextConfig = {reactStrictMode: true,swcMinify: true,basePath: process.env.NODE_ENV == "development"? '':'/app'images:{unoptimized:true}
    }module.exports = nextConfig
    
  4. 设置输出目录export输出目录为app
    "scripts": {"dev": "next dev","build": "next build && next export -o app","start": "next start","lint": "next lint"
    },
    
十、以SSR模式运行项目
```
yarn build
yarn start -p 4000 //默认端口3000
```
十一·、以SSR模式运行项目
  1. 安装依赖

    npm install cross-env -g
    
  2. package.json
     "scripts": {"dev": "next dev","build": "next build && next export","start": "next start","lint": "next lint","customBuild": "cross-env BASE_PSTH=%npm_config_base% next build && next export -0 %npm_config_base%","customBuild": "cross-env BASE_PSTH=$npm_config_base next build && next export -0 $npm_config_base%"//mac},
    
  3. 运行npm run customBuild --base=/demo --out=demo
十二、多环境开发配置
  1. 安装依赖

    yarn add cross-env --dev
    
  2. 根目录下创建.env.production(生产环境)、.env.development(开发环境)、.env.test(测试环境)、.env.local(默认环境,始终覆盖默认设置)、.env(所有环境)
    //.env.testTEST=test //只有服务端可以获取到
    NEXT_PUBLIC_HOST=http://127.0.0.1:3000/ //变量暴漏给浏览器端,加NEXT_PUBLIC_
    
  3. 指定环境运行,修改package.json
      "scripts": {"dev:test": "cross-env NODE_ENV=test next dev",},页面打印:console.log(process.env.TEST);console.log(process.env.NEXT_PUBLIC_HOST);
    
十二、项目部署
  1. 安装nginx:https://nginx.org/en/download.html,下载合适的版本,解压到任意位置 nginx 配置
  • 启动cd到nginx目录运行 nginx.exe
  • 启动cd到nginx目录运行start nginx,访问http://localhost:80 正常访问nginx运行成功
  • 启动cd到nginx目录运行重新加载配置
  • 启动cd到nginx目录运行nginx -s stop 停止
  • 启动cd到nginx目录运行nginx -s quit 正常关闭
  • 启动cd到nginx目录运行nginx -s reopen 重新打开
  • 配置nginx.conf
    server {listen       9001;server_name  localhost;# server_name  btyhub.site, www.btyhub.site;# ssl两个文件,放在 nginx的conf目录中# ssl_certificate      btyhub.site_bundle.pem;# ssl_certificate_key  btyhub.site.key;# ssl_session_cache    shared:SSL:1m;# ssl_session_timeout  5m;# ssl_ciphers  HIGH:!aNULL:!MD5;# ssl_prefer_server_ciphers  on;# 代理到Next的服务,默认3000端口,也可以在start的时候指定location / {proxy_pass    http://127.0.0.1:3000/;}}
    
  1. 安装 pm2,node进程管理工具:npm install -g pm2
  2. 把打包后得.next,next.config.js 上传服务器
  3. package.json,运行yarn install
    {"name": "react_common","version": "0.1.0","private": true,"scripts": {"start": "next start"},"dependencies": {//项目下package.json 中dependencies},"devDependencies": {//项目下package.json 中devDependencies"child_process": "^1.0.2"
    }
  4. 安装child_process,运行yarn add child_process --dev,创建start.js
    let exec = require("child_process").exec;
    //yarn start -p 9003 指定端口运行项目
    exec("yarn start", {windowsHide: true});
    
  5. 运行pm2 start start.js --name projectName
十三、next.config.js
```
const configs = {// 编译文件的输出目录distDir: 'dest',// 是否给每个路由生成EtaggenerateEtags: true,// 页面内容缓存配置onDemandEntries: {// 内容在内存中缓存的时长(ms)maxInactiveAge: 25 * 1000,// 同时缓存多少个页面pagesBufferLength: 2,},// 在pages目录下那种后缀的文件会被认为是页面pageExtensions: ['jsx', 'js'],// 配置buildIdgenerateBuildId: async () => {if (process.env.YOUR_BUILD_ID) {return process.env.YOUR_BUILD_ID}// 返回null使用默认的unique idreturn null},// 手动修改webpack configwebpack(config, options) {return config},// 修改webpackDevMiddleware配置webpackDevMiddleware: config => {return config},// 可以在页面上通过 procsess.env.customKey 获取 valueenv: {customKey: 'value',},//CDN 前缀支持assetPrefix: 'https://cdn.mydomain.com',//静态优化指标devIndicators: {autoPrerender: false},//禁止etag生成generateEtags: false,//禁止x-powered-by,x-powered-by用于告知网站是用何种语言或框架编写的poweredByHeader: false//自定义路由exportPathMap: async ()=>{return {'/':{page:'/index'}}},images:{unoptimized:true},// 只有在服务端渲染时才会获取的配置serverRuntimeConfig: {mySecret: 'secret',secondSecret: process.env.SECOND_SECRET,},// 在服务端渲染和客户端渲染都可获取的配置publicRuntimeConfig: {staticFolder: '/static',},
}

next.js+react+typescript+antd+antd-mobile+axios+redux+sass react服务端渲染构建项目,从构建到发布,兼容pc+移动端相关推荐

  1. react开发教程(十)redux结合react

    描述 Redux 和 React 之间没有关系.Redux 可以搭配 React.Angular 甚至纯 JS.但是 Redux 还是比较适合和 React 搭配的,因为 React 允许你以 sta ...

  2. js 实现进度条,杠杆进度条,兼容pc移动端,滑块效果

    效果图: 创作不易,请多点赞支持.本版本为jquery实现,如果你的项目是vue,那么可以按照如下实现的逻辑改版,也可以继续使用jquery实现. 代码: <!DOCTYPE html> ...

  3. React 服务端渲染方案完美的解决方案

    最近在开发一个服务端渲染工具,通过一篇小文大致介绍下服务端渲染,和服务端渲染的方式方法.在此文后面有两中服务端渲染方式的构思,根据你对服务端渲染的利弊权衡,你会选择哪一种服务端渲染方式呢? 什么是服务 ...

  4. React服务端渲染(SSR)入门及项目搭建

    代码已经关联到github: 链接地址 文章有更新也会优先在这,觉得不错可以顺手点个star,这里会持续分享自己的开发经验(: 前言 服务端渲染是什么?我们什么情况下需要使用它?想要了解这些,需要简单 ...

  5. Node项目部署到阿里云服务器(ECS),以Nuxt.js服务端渲染项目为例

    1.前言 最近打算业余时间搭个网站,选择的技术栈为node+mongodb+Nuxt.js(基于vue,用于创建服务端渲染 (SSR) 应用),以下不会教科书式讲解,只是提供整体思路.参考资料以及关键 ...

  6. react开发教程(九)redux基础

    Readux基础 什么是redux? 简单点回答就是,一个管理数据的全局对象,但是它有单一状态树的概念,所谓的单一状态树,就是指"所有的 state都以一个对象树的形式储存在一个单一的 st ...

  7. antd + react model自定义footer_使用ESLint+Prettier规范React+Typescript项目

    项目开发过程中,大多数时候我们使用别人搭建好的脚手架编写代码,是项目的参与者.对于一些细节往往被忽略了.其中代码检测本身是一类非常简单的配置,但涉及不同框架和语言组合使用的时候,可能比想象中要困难,本 ...

  8. React全家桶+AntD 实战二

    React全家桶+AntD 单车后台管理系统开发(完整版) (第8章 单车业务之城市管理模块&) 第8章 单车业务之城市管理模块& src/router.js,通用路由 import ...

  9. React——UI框架 Antd 、Element

    [Ant Design of React] 官网:https://ant.design/docs/react/getting-started-cn 1. 安装依赖 cnpm i -S antd 2. ...

最新文章

  1. 将tensorflow训练好的模型移植到Android (MNIST手写数字识别)
  2. 趣谈设计模式 | 观察者模式(Observer) :消息的发布与订阅
  3. Linux文本编辑跳到指定行,Linux 命令每日一练:vi命令
  4. 点个外卖搞定「软中断」
  5. 在jMeter里如何创建用户定义的能生成随机数的变量
  6. How does UDO tool resolve objects
  7. leetcode115. 不同的子序列
  8. Python day2 数据类型 字符类型 文件处理
  9. python输出时间格式_python中日期和时间格式化输出的方法小结_python
  10. Calendar导入java,Java程序使用Calendar.add()方法将分钟添加到当前时间
  11. javac 命令的作用_机加工行业MES系统在生产调度中的重要作用
  12. 中数据库url怎么写_WIN10下怎么找到MYSQL数据库中存储数据的位置
  13. 自然语言处理实战——对电影评论进行情感分析(英文)
  14. R语言的四种数据结构---向量
  15. android 控制移动网络开关
  16. 电脑主机由哪几个重要的组成部分
  17. html中,将字符串对象数组转成对象数组
  18. sklearn学习笔记3:降维算法
  19. 如何开发辅助决策系统?
  20. 博彦科技4亿定增图谋境外软件外包 现金流考验

热门文章

  1. android百度手机助手,百度手机助手
  2. 《Segment as Points for Efficient Online Multi-Object Tracking and Segmentation》论文阅读笔记
  3. 创新工场发布人工智能战略白皮书 李开复看好金融、医疗、无人驾驶
  4. wireshark 下载
  5. 基于java框架的图书分享系统的设计与开发计算机毕业设计源码+系统+lw文档+mysql数据库+调试部署
  6. test word markdown
  7. jetson nano 安装 迈德威视 工业相机 mindvision SDK
  8. Unity Shader 视差映射
  9. 中科大2018计算机在安徽招生最低分,中国科学技术大学2020年录取分数线(附2017-2020年分数线)...
  10. 基金公司上报薪酬方案:5000万封顶?不少基金公司2021年年终奖待发