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

  1. antd官网https://ant.design/components/overview-cn/
  2. antdmobile官网https://mobile.ant.design/zh
  3. next.js: https://www.nextjs.cn/
  4. reacthttps://react.zcopy.site/
  5. reduxhttps://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>cssassets>fontassets>img
  2. 安装依赖
    yarn add sass
    
  3. 集成字体图标下载阿里icon库,解压后把压缩包里的文件复制到assets>font
    <i class="iconfont icon-usename"></i>
    
  4. css文件下分别创建globals.scssiframe.scssnormalize.scssvariable.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. 配置antdmobile主题https://mobile.ant.design/zh/guide/theming,新建 css>antdMobileTheme.scss
    :root:root {
     --adm-color-primary: #ff4d4f;
    }
    
  9. 配置antd主题
  1. 集成postcss
四、集成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>reducersredux>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
    /**
     * @descriptionstore判断是否是移动端
     * */
    
    
    /**
     * @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 state
            case MobileStoreActionEnum.CHANGE:
                return action.payload
            default:
                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: DemoStoreActionEnum
        payload: DemoStoreStateInterface
    }
    
    /**
     * @description store逻辑
     * */
    const initState:DemoStoreStateInterface = {
        num: 100
    }
    
    const demoStore = (state:DemoStoreStateInterface = initState, action: DemoStoreActionInterface):DemoStoreStateInterface => {
        switch (action.type) {
            case DemoStoreActionEnum.CHANGE:
                return action.payload
            default:
                return state
        }
    };
    export default demoStore;
    
    
  6. 依次创建redux>sagas>demo.tsx、redux>sagas>mainSaga.tsx
  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"; //异步初始化store
    
    const 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 样式文件转换
  1. 持续储存
五、页面配置
  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.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";
    
    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 = {
      ...
      //自定义路由,通常不需要自定义路由,适用于SS
      exportPathMap: 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. 路由嵌套
八、接口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 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; 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.test
    
    TEST=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. 安装nginxhttps://nginx.org/en/download.html下载合适的版本解压任意位置 nginx 配置
  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.jsondependencies
      },
      "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',
  // 是否给每个路由生成Etag
  generateEtags: true,
  // 页面内容缓存配置
  onDemandEntries: {
    // 内容内存缓存的时长(ms)
    maxInactiveAge: 25 * 1000,
    // 同时缓存多少个页面
    pagesBufferLength: 2,
  },
  // 在pages目录下那种后缀的文件会被认为是页面
  pageExtensions: ['jsx', 'js'],
  // 配置buildId
  generateBuildId: async () => {
    if (process.env.YOUR_BUILD_ID) {
      return process.env.YOUR_BUILD_ID
    }

    // 返回null使用默认unique id
    return null
  },
  // 手动修改webpack config
  webpack(config, options) {
    return config
  },
  // 修改webpackDevMiddleware配置
  webpackDevMiddleware: config => {
    return config
  },
  // 可以在页面上通过 procsess.env.customKey 获取 value
  env: {
    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',
  },  
}

原文地址:https://blog.csdn.net/randy521520/article/details/130066048

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_43926.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注