整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript高級 第二集

          JavaScript高級 第二集

          1-面向對象-三大特性

          封裝

          把一些比較散的, 單一的值, 有結構的組裝成為一個整體

          把一些值隱藏在內部, 不暴漏給外界

          案例

          · 表述兩個學生的姓名,年齡,學號 --- 學習

          ? 直接定義變量形式

          ? 弊端

          ? 過于分散

          ? 所有變量暴漏外界, 不安全

          ? 易被修改或產生沖突

          ? 表述含義的變量名無法統一

          ? 使用對象封裝優化

          繼承

          獲取已經存在的對象已有屬性和方法的一種方式

          多態

          多態表現為

          · 同一操作,作用于不同的對象,會產生不同的解釋和行為。

          · 例如: toString()

          ? 不同的對象, 調用相同的方法, 產生不同的結果

          多用于強類型語言中,JavaScript具備與生俱來的多態特性。

          · // 弱類型: 不同類型之間運算, 會存在隱式轉換

          · // 強類型: 不同類型之間運算, 需要顯示的轉換為同一個類型進行運算

          02-面向對象-構造函數使用-注意事項1

          01-構造函數設置屬性和方法

          實例屬性/實例方法

          · 都是綁定在使用構造函數創建出來的對象p上; 最終使用的時候也是使用對象p來進行訪問;

          · 案例

          ? function Person(name, age, doFunc) {

          ? this.name=name;

          ? this.age=age;

          ? this.doFunc=doFunc;

          ? }

          靜態屬性/靜態方法

          · 綁定在函數身上的屬性和方法

          · 注意:

          ? 函數本質也是一個對象, 既然是個對象, 那么就可以動態的添加屬性和方法

          ? 只要函數存在, 那么綁定在它身上的屬性和方法, 也會一直存在

          · 應用場景

          ? 記錄總共創建了多少個人對象

          ? 方案1

          ? 全局變量

          ? 方案2

          ? 實例屬性

          ? ?

          ? 方案3

          ? 靜態屬性/靜態方法

          概念補充

          · 實例化

          ? 通過構造函數, 構造出對象這個過程

          · 實例

          ? 被構造出來的對象

          02-關于創建出來的對象類型獲取

          01 獲得內置對象的類型

          · {}

          · [1,2,3]

          02 獲取根據自己聲明的構造函數創建的對象

          · p.constructor.name

          · constructor

          ? 對象的構造器

          ? 類似于, 一個產品上, 關于廠家的標識

          03-關于創建出來的對象類型驗證

          instanceof

          03-面向對象-構造函數使用-注意事項2

          構造函數的調用

          標準調用

          · var p=new Person();

          · var p=new Person

          ? 針對無參數的情況

          錯誤調用

          · var p=Person();

          ? this會變

          實例方法的調用

          p.run();

          var tmp=p.run;

          · tmp();

          · ?

          總結:

          針對于函數內部的this

          如果這個函數當做一般函數來調用

          · this則代表這個函數的調用者

          如果這個函數, 被當做構造函數來使用

          · this, 代表構造出來的對象

          04-面向對象-構造函數-優化-方案1

          問題

          每個實例的方法一般都是相同的;

          方法本質也是一個對象

          但是, 之前的方案

          · 針對于每個對象, 都會產生一個新的方法對象

          · 造成資源的浪費

          優化方案

          方案1:

          · 抽取函數對象到全局

          · 構造函數內部直接賦值

          05-面向對象-構造函數-優化-方案2

          方案1的問題

          ① 全局變量增多,造成污染

          ② 代碼結構混亂,不易維護

          ③ 函數可以不通過對象直接調用,封裝性不好

          優化方案-原型對象方法擴展

          什么是原型對象?

          · 1. 每當我們聲明了一個函數(本質對象); 那么這個函數上就會被附加很多屬性來描述這個函數

          · 2. 其中有個屬性 叫 prototype , 是一個引用類型, 指向著的對象, 就被成為原型對象

          · 3. 原型對象中, 有一個屬性 叫 constructor , 指向著關聯的函數

          原型對象有什么作用?

          · 每次我們通過一個構造函數創建一個對象的時候, 被創建的對象, 就會自動添加一個屬性 __proto__ 來指向構造函數的原型對象

          · 這個指向有什么用?

          ? 當我們調用一個對象的屬性,或者方法時, 會先到對象內部查找, 如果查找到了, 就直接調用

          ? 如果查找不到, 則,根據這條線, 到原型對象上面去查找

          ? 原型對象中如果存在該屬性或方法,那么就直接使用,如果不存在指定的屬性則返回undefined,如果不存在指定的方法則報錯

          · 原型對象, 可以說是各個對象公共的區域

          · 舉例

          ? 數組

          具體步驟

          · 代碼

          ? Person.prototype.run=function () {

          ? console.log(this.name, '跑吧, 熊孩子');

          ? }

          · 01-給原型對象增加一個方法

          ? 所有的對象, 都可以根據關聯的線查找到這個方法

          · 02- 內部的this, 是調用者

          注意:

          如果原型對象和構造對象的屬性和方法,沖突,會有什么效果?

          · 就近原則

          通常在創建對象"之前"設置構造函數的原型對象(提供共享的屬性|方法)

          訪問原型對象的正確方法是 構造函數.prototype 而不是 對象.prototype

          設置原型對象的屬性和方法

          · 通過原型對象來修改

          · 不要通過對象來修改

          ? ?

          再次概念區分

          實例屬性/方法

          · 構造函數內部, 綁定的屬性和方法

          靜態屬性/方法

          · 構造函數自身, 綁定的屬性和方法

          原型屬性/方法

          · 原型對象, 綁定的屬性和方法

          概念聲明

          函數的原型對象

          對象的原型對象

          06-面向對象-對原型對象擴展屬性和方法

          方式1:

          直接借助對象的動態性, 添加屬性和方法

          問題:

          · 如果大批量的添加, 冗余代碼比較多, 看起來比較亂

          方式2:

          替換原型對象

          · 原型對象本身就是一個對象

          · 它與函數, 之間, 是通過函數的prototype屬性進行關聯

          · 所以可以直接創建一個對象,當做是原型對象, 然后修改函數的prototype指針指向

          注意:

          · 注意替換原型對象,和創建對象的先后順序

          ? 替換在前

          ? 創建在后

          · 替換總結

          ? ① 當替換構造函數的原型對象的時候,已經使用構造函數創建出來的對象指向的原型對象不會發生改變

          ? ② 如果是替換了構造函數的原型對象,那么構造函數的新的原型對象和舊的原型對象之間沒有任何關系

          07-面向對象-原型對象屬性-方法的讀取和設置

          設置原型對象屬性/方法

          對象.屬性=xxx

          · 如果該屬性在對象中已經存在,則修改該屬性的值

          · 如果該屬性在對象中尚未存在,則新增該屬性

          · 錯

          原型對象.屬性=xxx

          · 一定要獲取到原型對象來設置

          · 函數名.prototype

          特例

          · 如果原型對象的屬性, 是一個引用類型的

          · 那么通過對象也可以修改(操作對象內部屬性)

          ? 影響所有對象

          訪問原型對象屬性/方法

          構造函數創建出來的對象在訪問屬性的時候,會先在實例內查找,如果沒有找到則進一步到對應的原型對象中查找

          08-面向對象-訪問函數原型對象的方式

          1. 通過 函數名.prototype

          2. 通過對象中的__proto__屬性訪問

          注意點說明

          __proto__是一個非標準屬性

          即ECMAScript中并不包含該屬性,這只是某些瀏覽器為了方便開發人員開發和調試而提供的一個屬性,不具備通用性

          建議:在調試的時候可以使用該屬性,但不能出現在正式的代碼中

          09-面向對象-hasOwnProperty和in屬性操作

          in 判斷一個對象, 是否擁有某個屬性(如果對象身上沒有, 會到原型對象里面查找)

          hasOwnProperty: 只到對象自身查找

          思考

          怎樣判斷一個屬性僅僅是原型對象屬性

          10-面向對象-isPrototypeOf和instanceOf

          isPrototypeOf: 判斷一個對象, 是否是某個實例的原型對象

          Person.prototype.isPrototypeOf(p)

          instanceOf : 判斷一個對象, 是否是某個構造函數的原型鏈上

          11-面向對象-原型完善-constructor

          用于獲取一個對象的真實類型

          提問

          畫出構造函數, 實例, 原型對象之間的關系

          怎樣給原型對象擴展屬性和方法?

          1. 直接借助對象的動態特性

          · 拿到原型對象

          函數的宿主可以有哪些?根據不同的宿主可以稱為什么方法?

          篇文章是為ReactJs小白準備的,希望他們快速抓住ReactJs的要點并能在實踐中隨機應變。

          兩句話版本

          • ReactJs把視圖更新簡化為一個render函數
          • render函數接收兩個參數,分別是配置項和狀態

          長版本

          ReactJs是一個專注于View的Web前端框架。Web前端的View就是瀏覽器中的Dom元素,改變View的唯一途徑就是修改瀏覽器中的Dom元素,因此ReactJs的核心任務就是如何修改Dom元素,作為一個成功的框架,ReactJs使修改Dom元素變得高效而又簡單。

          ReactJs把修改Dom的操作簡化成一個函數renderInto(parentDom, props, states)=>htmlString,這個函數的意圖就是根據props,states計算出視圖對應的html字符串并添加為parentDom的子節點。props和states就是普通的javascript對象,這個函數的核心邏輯就是計算html元素的機構及元素屬性然后拼接成字符串返回。作為框架,ReactJs用JSX形式的DSL解決了拼接html的任務并接管了更新到parentDom的職責。看一個例子,理解這個函數并理解ReactJs怎么使用這個函數你就可以一個人開始ReactJs之旅了。

          var props={name: 'myname'};  
          var states={amount: 1000};  
          
          
          function render(props, states) {  
            var title=’Hello, ' + props.name;  
            var description='You have ' + states.amount + ' score now.';  
          
          
            return (  
              <div className="score-board">  
                 <h1>{title}</h1>  
                 <p>{description}</p>  
              </div>  
            );  
          }
          

          函數第一行根據props計算title,第二行根據states計算description,最后以JSX形式返回拼接好的html字符串。

          如果你用過AngularJs,EmberJs等類似的前端框架,你可能會覺得沒什么了不起,不就是把模板和邏輯放到一起嗎?是的,沒錯,但這不僅僅是組織形式上的改變,而是編程隱喻的轉變—從復雜的MVC或MVVM模式到簡單的render函數。還有一點不同是JSX最終編譯成調用react-dom的javascript語句,而不是直接生成字符串。

          render函數還只是ReactJs這座冰山的一角,”React”會在render函數的輸入變化時再次調用這個函數。再看一個例子。

          var props={name: 'myname'};  
          var states={amount: 1000};  
          
          
          function handleClickAdd() {  
            states={amount: states.amount + 1};  
          }  
          
          
          function render(props, states) {  
            var title=’Hello, ' + props.name;  
            var description='You have ' + states.amount + ' score now.';  
          
          
            return (  
              <div className="score-board">  
                 <h1>{title}</h1>  
                 <p>{description}</p>  
                 <button onClick={handleClickAdd}>+1</button>  
              </div>  
            );  
          }
          

          這個例子增加了一個”+1”按鈕,當用戶點擊按鈕時會修改states,ReactJs在states變化時的”React”就是再次調用render函數,然后用新輸出更新瀏覽器的dom。

          可能你會問,props和states不就是Model嗎?是的,可以理解成Model,但此Model非彼Model,props和states都是為View服務的而非和View平起平坐。

          可能你還會問,為啥不把props和states合并成一個對象?要回答這個問題,就涉及到復雜視圖的場景。想想看,當視圖內的元素不斷增加時,代碼上如何處理,還要在一個render函數里折騰嗎?肯定不會。我猜你已經想到了,要把大問題拆小。ReactJs給出的解決方法就是把大視圖拆成若干個小視圖,每個視圖都有自己的render函數,在JSX中可以直接使用視圖標簽。看一個例子。

          var Score=React.createClass({  
            initialState: function() {  
              return {amount: 1000};  
            },  
          
          
            function handleClickAdd() {  
              this.setState({amount: this.states.amount + 1});  
            }  
          
          
            render: function() {  
              var title=’Hello, ' + this.props.name;  
              var description='You have ' + this.states.amount + ' score now.';  
          
          
              return (  
                <div className="score-board">  
                   <h1>{title}</h1>  
                   <p>{description}</p>  
                   <button onClick={handleClickAdd}>+1</button>  
                </div>  
              );  
            }  
          });  
          
          
          var ScoreList=React.createClass({  
            render() {  
              return (  
                <ul className="score-list">  
                  <li><Score name="Tom" /></li>  
                  <li><Score name="Jerry" /></li>  
                </ul>  
              );  
            }  
          });  
          
          
          ReactDOM.render(  
            <ScoreList />,  
            document.getElementById('content')  
          );
          

          這個例子中有兩類View,分別是Score和ScoreList。ScoreList的render函數中使用Score標簽并給出配置項name的值。詳細看一下Score,ReactJs提供createClass方法定義視圖,在render函數中通過this.props訪問外部傳入的配置項,通過this.states訪問視圖內部的狀態。從意圖上看,props外部傳入視圖的配置項,擁有者是父視圖,視圖內部只能讀取配置項,states的擁有者是視圖自身。

          區分props和states的結果就是,子視圖沒辦法直接改變父視圖,視圖改變一定是自觸發改變的視圖開始向子視圖傳播。對上面的例子,當Tom的Score改變時,ScoreList其他部分一定不會改變,所以視圖更新從Tom的Score視圖開始就可以,這就保證了能更高效地計算視圖變化,再加上VirtualDom的使用,使ReactJs的效率大大超過其他框架。

          當子視圖需要改變父視圖時,也一定是從父視圖開始向下更新。假如上面的例子中ScoreList還有平均分的視圖,當Tom的分數改變時,需要更新ScoreList中的平均分。這就需要Score視圖在處理”+1”輸入時把變化通知到ScoreList,做法時給Score增加配置項,ScoreList中定義更新平均分的函數并把函數作為配置項傳給Score。當ScoreList更新時,因為Jerry的props和states都沒變化,所以Jerry的Score視圖不需要更新。

          這就是ReactJs的全部秘密了(不過Web前端本身是一個復雜系統,你還需要了解更多其他內容)。

          介:該教程兼容pc+移動端,兼容原理:同時封裝了pc框架antd、和移動端框架antdMobile,根據不同客戶端類型渲染不同的組件,同一個組件需要根據客戶端類型同時封裝pc和移動端,如果覺得開發麻煩,可忽略兼容部分教程,根據需要分別構建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 的同構應用開發框架。

          • 直觀的、 基于頁面 的路由系統(并支持 動態路由)
          • 預渲染。支持在頁面級的 靜態生成 (SSG) 和 服務器端渲染 (SSR)
          • 自動代碼拆分,提升頁面加載速度
          • 具有經過優化的預取功能的 客戶端路由
          • 內置 CSS 和 Sass 的支持,并支持任何 CSS-in-JS 庫
          • 開發環境支持 快速刷新
          • 利用 Serverless Functions 及 API 路由 構建 API 功能
          • 完全可擴展

          二、構建項目

          yarn create next-app “文件名” --typescript
          yarn dev

          三、調整項目

          1. 文件目錄

          1. _app.tsx
          import type { AppProps } from "next/app";
          
          export default function App({ Component, pageProps }: AppProps) {
            return <Component {...pageProps} />
          }
          
          1. 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
          1. 集成字體圖標,下載阿里icon庫,解壓后把壓縮包里的文件復制到assets>font
          <i class="iconfont icon-usename"></i>
          1. 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字體大小
          1. 配置引入路徑tsconfig.json
          "paths": {
              ...
              "@css/": [
                  "./src/assets/css/"
              ],
              "@img/": [
                  "./src/assets/img/"
              ],
              ...
          }
          1. 引入全局樣式,修改_app.tsx
          import type { AppProps } from "next/app";
          import "@css/iframe.scss";
          
          export default function App({ Component, pageProps }: AppProps) {
            return <Component {...pageProps} />
          }
          
          1. 引入全局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
          
          1. 配置antd-mobile主題,https://mobile.ant.design/zh/guide/theming,新建 css>antdMobileTheme.scss
          :root:root {
            --adm-color-primary: #ff4d4f;
          }
          1. 配置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.js
          
          module.exports={
              plugins: {
                  "postcss-px-to-viewport-8-plugin": {
                      viewportWidth: 375, // 視窗的寬度,對應的是我們設計稿的寬度
                      viewportHeight: 912, // 視窗的高度,對應的是我們設計稿的高度,可以不設置
                      unitPrecision: 3, // 指定`px`轉換為視窗單位值的小數位數(很多時候無法整除)
                      viewportUnit: 'vw', // 指定需要轉換成的視窗單位,建議使用vw
                      propList: ['*'],
                      selectorBlackList: [/^.pc/],
                      minPixelValue: 1, // 小于或等于`1px`不轉換為視窗單位,你也可以設置為你想要的值
                      mediaQuery: false, // 允許在媒體查詢中轉換`px`,
                      exclude: [/pc.module/,/antTheme.module.scss/,/braft-editor/], //設置忽略文件,用正則做目錄名匹配
                  }
              },
          };
          

          參數

          說明

          類型

          默認值

          unitToConvert

          需要轉換的單位,默認為 px

          string

          px

          viewportWidth

          設計稿的視口寬度,如傳入函數,函數的參數為當前處理的文件路徑,函數返回

          undefind

          跳過轉換

          number | Function

          320

          unitPrecision

          單位轉換后保留的精度

          number

          5

          propList

          能轉化為 vw 的屬性列表

          string[]

          ['*']

          viewportUnit

          希望使用的視口單位

          string

          vw

          fontViewportUnit

          字體使用的視口單位

          string

          vw

          selectorBlackList

          需要忽略的 CSS 選擇器,不會轉為視口單位,使用原有的 px 等單位

          string[]

          []

          minPixelValue

          設置最小的轉換數值,如果為 1 的話,只有大于 1 的值會被轉換

          number

          1

          mediaQuery

          媒體查詢里的單位是否需要轉換單位

          boolean

          false

          replace

          是否直接更換屬性值,而不添加備用屬性

          boolean

          true

          landscape

          是否添加根據

          landscapeWidth

          生成的媒體查詢條件

          @media (orientation: landscape)

          boolean

          false

          landscapeUnit

          橫屏時使用的單位

          string

          vw

          landscapeWidth

          橫屏時使用的視口寬度,,如傳入函數,函數的參數為當前處理的文件路徑,函數返回

          undefind

          跳過轉換

          number

          568

          exclude

          忽略某些文件夾下的文件或特定文件,例如 node_modules 下的文件,如果值是一個正則表達式,那么匹配這個正則的文件會被忽略,如果傳入的值是一個數組,那么數組里的值必須為正則

          Regexp

          undefined

          include

          需要轉換的文件,例如只轉換 'src/mobile' 下的文件 (

          include: /\/src\/mobile\//

          ),如果值是一個正則表達式,將包含匹配的文件,否則將排除該文件, 如果傳入的值是一個數組,那么數組里的值必須為正則

          Regexp

          undefined

          四、集成redux

          1. 安裝依賴
          yarn add redux react-redux redux-saga
          yarn add @types/react-redux @types/redux-saga next-redux-wrapper redux-devtools-extension --dev
          1. 創建redux>reducers、redux>sagas文件夾
          2. 配置引入路徑tsconfig.json
          "paths": {
              ...
              "@reducers/*": [
                "./src/redux/store/reducers/*"
              ],
              "@sagas/*": [
                "./src/redux/store/sagas/*"
              ],
              "@store/*": [
                "./src/redux/store/*"
              ],
              ...
          }
          1. 創建第一個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 state
                  case MobileStoreActionEnum.CHANGE:
                      return action.payload
                  default:
                      return state
              }
          }
          export default mobileStore;
          
          1. 創建第一個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;
          
          1. 依次創建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.tsx
          
          import {all} from "redux-saga/effects"
          import watchDemoSaga from "@sagas/demo";
          
          // saga中間件 主saga,用于區別是否需要saga來處理異步操作,如果沒有異步,則放行
          function* mainSaga() {
              yield all([
                  // 監聽 saga 中有 userWatchSaga 操作,所以會攔截這個 action
                  watchDemoSaga(),
              ])
          }
          
          // 主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)
          
          1. 創建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)
        1. 封裝pc+移動端兼容性button組件,創建兼容性ui框架,需要把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>
            </>
          }
          1. 設置頁面框架代碼:_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>
              )
          }
          
          1. 自定義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}/>
          1. 方法二:使用 next/image;簡單的圖片引用建議用原生img
          //建議用div包括起來,不單獨使用,單獨使用會自動生成很多自帶的樣式;Image會自適應div大小
          import idCard from '@img/idCard.png';
          import Image from 'next/image';
          <div><Image src={idCard} alt=""/></div>
          1. next/image 自帶優化的api適用于SSR,SSG中無法使用 ,可以改動 next.config.js 配置解決
          const nextConfig={
            reactStrictMode: true,
            swcMinify: true,
            images:{
                unoptimized:true
            }
          }
          
          
          module.exports=nextConfig
          1. 安裝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";
          1. 修改page>index.tsx
          import {NextRouter, useRouter} from "next/router";
          
          const router:NextRouter=useRouter();
          <Button onClick={()=> router.push('/demo')}>goDemo</Button>
          1. 自定義路由,改動 next.config.js 配置
          const nextConfig={
            ...
            //自定義路由,通常不需要自定義路由,適用于SS
            exportPathMap: async ()=>{
              return {
                '/':{
                  page:'/index'
                }
              }
            },
            ...
          }
          
          
          module.exports=nextConfig
          1. 動態路由
          • 修改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
          1. 封裝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;
          
          1. 配置引入路徑,修改 tsconfig.json
          "paths": {
               ... 
               "@common/*": [
                    "./src/common/*"
               ],
               "@api/*": [  
                   "./src/pages/api/*"
               ],
               ...
          }
          1. 創建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)
              }
          }
          1. 修改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"
          },
          1. 然后執行yarn build,該命令回顯執行next build 再執行 next export,輸出目錄為根目錄下的out目錄

          1. 設置靜態資源的bassePath,如果發布在服務器二級目錄需要設置,更改next.config.js 配置;/app為二級目錄
          const nextConfig={
            reactStrictMode: true,
            swcMinify: true,
            basePath: process.env.NODE_ENV=="development"? '':'/app'
            images:{
                unoptimized:true
            }
          }
          
          
          module.exports=nextConfig
          1. 設置輸出目錄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

          十一、動態生成項目目錄

          1. 安裝依賴
          npm install cross-env -g
          1. 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
            },


          1. 運行npm run customBuild --base=/demo --out=demo

          十二、多環境開發配置

          1. 安裝依賴
          yarn add cross-env --dev
          1. 根目錄下創建.env.production(生產環境)、.env.development(開發環境)、.env.test(測試環境)、.env.local(默認環境,始終覆蓋默認設置)、.env(所有環境)
          //.env.test
          
          TEST=test //只有服務端可以獲取到
          NEXT_PUBLIC_HOST=http://127.0.0.1:3000/ //變量暴漏給瀏覽器端,加NEXT_PUBLIC_
          1. 指定環境運行,修改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"
          }
          
          1. 安裝child_process,運行yarn add child_process --dev,創建start.js
          let exec=require("child_process").exec;
          //yarn start -p 9003 指定端口運行項目
          exec("yarn start", {windowsHide: true});
          1. 運行pm2 start start.js --name projectName,pm2:node進程管理工具

          十三、其他

          1. next 命令
          yarn dev --help 某個命令幫助信息
          next lint 設置 Next.js 的內置 ESLint 配置
          next lint --no-cache 清除 ESLint 緩存
          1. next內置組件
          • import Head from 'next/head'
          • import Image from 'next/image'
          • import { Inter } from 'next/font/google'
          • import { Html, Head, Main, NextScript } from 'next/document'
          • import type { AppProps } from 'next/app'
          • import type { NextApiRequest, NextApiResponse } from 'next'
          • import { useRouter } from 'next/router'
          • import Link from 'next/link'
          • import Script from 'next/script'
          • import { useAmp } from 'next/amp'
          • import type { NextRequest } from 'next/server'
          • import { Inter } from 'next/font/google'
          • import getConfig from 'next/config'
          1. next.config.js

          主站蜘蛛池模板: 人妻无码一区二区不卡无码av| 国产精品一区二区综合| 久久一区二区三区精华液使用方法| 日本一区二区三区不卡视频 | 中文字幕日本精品一区二区三区 | 久夜色精品国产一区二区三区| 日韩社区一区二区三区| 精品国产AⅤ一区二区三区4区 | 大屁股熟女一区二区三区 | 国产精品一区二区AV麻豆| 无码AV一区二区三区无码| 国产激情无码一区二区| 亚无码乱人伦一区二区| 成人一区二区免费视频| 亚洲AV香蕉一区区二区三区| 污污内射在线观看一区二区少妇| 交换国产精品视频一区| 久久免费区一区二区三波多野| 成人精品视频一区二区| 海角国精产品一区一区三区糖心 | 午夜福利国产一区二区| 无码av免费毛片一区二区| 久久国产精品一区二区| 国产在线精品一区二区不卡麻豆| 欧美一区内射最近更新| 国产AV午夜精品一区二区三区| 国产精品被窝福利一区| 无码毛片一区二区三区中文字幕| 国产乱码精品一区二区三区 | 色窝窝免费一区二区三区| 日日摸夜夜添一区| 亚洲一区二区三区乱码在线欧洲| 一区二区三区在线免费看| 久久久精品一区二区三区| 亚洲国产精品一区第二页| 2018高清国产一区二区三区 | 国产拳头交一区二区| 日韩视频在线观看一区二区| 高清国产精品人妻一区二区| 亚洲一区综合在线播放| 亚洲av成人一区二区三区观看在线|