· 表述兩個學生的姓名,年齡,學號 --- 學習
? 直接定義變量形式
? 弊端
? 過于分散
? 所有變量暴漏外界, 不安全
? 易被修改或產生沖突
? 表述含義的變量名無法統一
? 使用對象封裝優化
· 同一操作,作用于不同的對象,會產生不同的解釋和行為。
· 例如: toString()
? 不同的對象, 調用相同的方法, 產生不同的結果
· // 弱類型: 不同類型之間運算, 會存在隱式轉換
· // 強類型: 不同類型之間運算, 需要顯示的轉換為同一個類型進行運算
· 都是綁定在使用構造函數創建出來的對象p上; 最終使用的時候也是使用對象p來進行訪問;
· 案例
? function Person(name, age, doFunc) {
? this.name=name;
? this.age=age;
? this.doFunc=doFunc;
? }
· 綁定在函數身上的屬性和方法
· 注意:
? 函數本質也是一個對象, 既然是個對象, 那么就可以動態的添加屬性和方法
? 只要函數存在, 那么綁定在它身上的屬性和方法, 也會一直存在
· 應用場景
? 記錄總共創建了多少個人對象
? 方案1
? 全局變量
? 方案2
? 實例屬性
? ?
? 方案3
? 靜態屬性/靜態方法
· 實例化
? 通過構造函數, 構造出對象這個過程
· 實例
? 被構造出來的對象
· {}
· [1,2,3]
· p.constructor.name
· constructor
? 對象的構造器
? 類似于, 一個產品上, 關于廠家的標識
· var p=new Person();
· var p=new Person
? 針對無參數的情況
· var p=Person();
? this會變
· tmp();
· ?
· this則代表這個函數的調用者
· this, 代表構造出來的對象
· 針對于每個對象, 都會產生一個新的方法對象
· 造成資源的浪費
· 抽取函數對象到全局
· 構造函數內部直接賦值
· 1. 每當我們聲明了一個函數(本質對象); 那么這個函數上就會被附加很多屬性來描述這個函數
· 2. 其中有個屬性 叫 prototype , 是一個引用類型, 指向著的對象, 就被成為原型對象
· 3. 原型對象中, 有一個屬性 叫 constructor , 指向著關聯的函數
· 每次我們通過一個構造函數創建一個對象的時候, 被創建的對象, 就會自動添加一個屬性 __proto__ 來指向構造函數的原型對象
· 這個指向有什么用?
? 當我們調用一個對象的屬性,或者方法時, 會先到對象內部查找, 如果查找到了, 就直接調用
? 如果查找不到, 則,根據這條線, 到原型對象上面去查找
? 原型對象中如果存在該屬性或方法,那么就直接使用,如果不存在指定的屬性則返回undefined,如果不存在指定的方法則報錯
· 原型對象, 可以說是各個對象公共的區域
· 舉例
? 數組
· 代碼
? Person.prototype.run=function () {
? console.log(this.name, '跑吧, 熊孩子');
? }
· 01-給原型對象增加一個方法
? 所有的對象, 都可以根據關聯的線查找到這個方法
· 02- 內部的this, 是調用者
· 就近原則
· 通過原型對象來修改
· 不要通過對象來修改
? ?
· 構造函數內部, 綁定的屬性和方法
· 構造函數自身, 綁定的屬性和方法
· 原型對象, 綁定的屬性和方法
· 如果大批量的添加, 冗余代碼比較多, 看起來比較亂
· 原型對象本身就是一個對象
· 它與函數, 之間, 是通過函數的prototype屬性進行關聯
· 所以可以直接創建一個對象,當做是原型對象, 然后修改函數的prototype指針指向
· 注意替換原型對象,和創建對象的先后順序
? 替換在前
? 創建在后
· 替換總結
? ① 當替換構造函數的原型對象的時候,已經使用構造函數創建出來的對象指向的原型對象不會發生改變
? ② 如果是替換了構造函數的原型對象,那么構造函數的新的原型對象和舊的原型對象之間沒有任何關系
· 如果該屬性在對象中已經存在,則修改該屬性的值
· 如果該屬性在對象中尚未存在,則新增該屬性
· 錯
· 一定要獲取到原型對象來設置
· 函數名.prototype
· 如果原型對象的屬性, 是一個引用類型的
· 那么通過對象也可以修改(操作對象內部屬性)
? 影響所有對象
· 拿到原型對象
篇文章是為ReactJs小白準備的,希望他們快速抓住ReactJs的要點并能在實踐中隨機應變。
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、和移動端
一、介紹
Next.js,這是一個 React 的同構應用開發框架。
二、構建項目
yarn create next-app “文件名” --typescript
yarn dev
三、調整項目
import type { AppProps } from "next/app";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
import {NextPage} from "next";
const Home: NextPage=(props)=> {
return <div>dsada</div>
};
export default Home
三、靜態資源assets
yarn add sass
<i class="iconfont icon-usename"></i>
//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字體大小
"paths": {
...
"@css/": [
"./src/assets/css/"
],
"@img/": [
"./src/assets/img/"
],
...
}
import type { AppProps } from "next/app";
import "@css/iframe.scss";
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
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
:root:root {
--adm-color-primary: #ff4d4f;
}
/* 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;
}
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>
}
yarn add postcss-px-to-viewport-8-plugin --dev
//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
yarn add redux react-redux redux-saga
yarn add @types/react-redux @types/redux-saga next-redux-wrapper redux-devtools-extension --dev
"paths": {
...
"@reducers/*": [
"./src/redux/store/reducers/*"
],
"@sagas/*": [
"./src/redux/store/sagas/*"
],
"@store/*": [
"./src/redux/store/*"
],
...
}
/**
* @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;
/**
* @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;
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;
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)
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)
{
"extends": "next/core-web-vitals",
"rules": {
"react/display-name": "off"
}
}
//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>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;
//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;
}
@import "./index";
import Button from "@/components/antd/button";
<Button type="primary">antd</Button>
yarn add redux-persist
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)
五、頁面配置
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>
</>
}
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>
)
}
export default function Custom_404(){
return <>404頁面</>
}
六、圖片引用
import idCard from '@img/idCard.png';
<img src={idCard.src}/>
//建議用div包括起來,不單獨使用,單獨使用會自動生成很多自帶的樣式;Image會自適應div大小
import idCard from '@img/idCard.png';
import Image from 'next/image';
<div><Image src={idCard} alt=""/></div>
const nextConfig={
reactStrictMode: true,
swcMinify: true,
images:{
unoptimized:true
}
}
module.exports=nextConfig
yarn add sharp
七、動態路由
//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";
import {NextRouter, useRouter} from "next/router";
const router:NextRouter=useRouter();
<Button onClick={()=> router.push('/demo')}>goDemo</Button>
const nextConfig={
...
//自定義路由,通常不需要自定義路由,適用于SS
exportPathMap: async ()=>{
return {
'/':{
page:'/index'
}
}
},
...
}
module.exports=nextConfig
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
<Button onClick={()=> router.push('/demo/1')}>goDemo</Button>
八、接口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,然后靜態化輸出。由于是完全靜態化輸出,當數據變化時,必須重新靜態化才能更新頁面
yarn add 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;
"paths": {
...
"@common/*": [
"./src/common/*"
],
"@api/*": [
"./src/pages/api/*"
],
...
}
// 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)
}
}
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)
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start",
"lint": "next lint"
},
const nextConfig={
reactStrictMode: true,
swcMinify: true,
basePath: process.env.NODE_ENV=="development"? '':'/app'
images:{
unoptimized:true
}
}
module.exports=nextConfig
"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
十一、動態生成項目目錄
npm install cross-env -g
"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
},
十二、多環境開發配置
yarn add cross-env --dev
//.env.test
TEST=test //只有服務端可以獲取到
NEXT_PUBLIC_HOST=http://127.0.0.1:3000/ //變量暴漏給瀏覽器端,加NEXT_PUBLIC_
"scripts": {
"dev:test": "cross-env NODE_ENV=test next dev",
},
頁面打印:
console.log(process.env.TEST);
console.log(process.env.NEXT_PUBLIC_HOST);
十二、項目部署
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/;
}
}
{
"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"
}
let exec=require("child_process").exec;
//yarn start -p 9003 指定端口運行項目
exec("yarn start", {windowsHide: true});
十三、其他
yarn dev --help 某個命令幫助信息
next lint 設置 Next.js 的內置 ESLint 配置
next lint --no-cache 清除 ESLint 緩存
*請認真填寫需求信息,我們會在24小時內與您取得聯系。