傳送門
- vite + react + ts 手摸手做項目系列一 (項目配置篇)
- github地址(github的更新速度比文檔要快,文檔要追加大量注釋)github地址
前言
-
這篇實戰篇文章,我改了很多遍,本來加了很多復雜的封裝,但是對於初學 react+ts 的同學很不友好,因為不好看懂 所以我刪刪減減,盡量用寫的大家都能看的懂,盡量用簡潔的語言表達出我們想做什么功能,先易后難
-
復雜功能的封裝都會再后面的系列文章中詳細講解,一步一步深入學習, 項目中絕大部分的代碼都有詳細的注釋,希望大家看的不懵逼,因為我自己在初學的時候再 github 上找開源項目,很多 項目封裝的很好,但是對小白就很懵了,得看好久,所以我這個教程,就會盡力的講詳細,按照文檔來,項目能跑通,注釋寫的詳盡,再配合直播,講明白說清楚。
-
ts 大家不要覺得很難,去官網看一看基礎用法,就能干了,碰到不懂的語法直接查就好,用幾次就會了。
-
react 同理,切勿眼高手低,要大量代碼實踐。不懂就問,不會就查,一來二去自然會了。
-
這一篇主要寫的是
-
假數據 json-server 的使用
-
請求接口的簡單使用
-
路由的簡單使用
-
一些簡單的全局配置數據
-
國際化的使用
-
簡單公共組件的封裝
-
json-server mock 數據
-
yarn add json-server -D
-
在終端
mkdir mock cd mock touch db.json 復制代碼
-
在 package.json 中的 scripts 中添加
"mock": "json-server mock/db.json --port 3008" 復制代碼
-
然后運行命令 yarn mock 就可以在控制台成功訪問到我們在 db.json 中配置的接口數據了
請求封裝
注意事項:process.env 要替換成 import.meta.env
- 全局的公共配置文件都會放在根目錄下的 config.ts 文件中,目前項目剛開始只有少量配置信息
/** * 當前環境變量 */ // process.env 在vite中不能用 // export const whyEnv = import.meta.env.VITE_REACT_URL || ""; /** * 接口地址 * @description env 可為主要環境或自定義地址 */ export const apiAddress = "http://localhost:3008/"; /** * 開發代理前綴 */ export const proxyApi = "/api"; /** * 接口前綴 * 判斷環境,是否需要使用前綴 * 生產環境不需要代理,同時本地配置的代理在生產環境也是不能用的 */ export const urlPrefix = process.env.NODE_ENV === "development" ? proxyApi : ""; 復制代碼
- 項目中用的 umi-request 這個庫,目前我給配置的很少的東西,錯誤處理,中間件處理等等我的給刪減了,剛開始不搞這么復雜
// utils/request.ts /** * request 網絡請求工具 * 更詳細的 api 文檔: https://github.com/umijs/umi-request */ import umiRequest, { extend } from "umi-request"; import { urlPrefix } from "../config"; // 使用前綴,配合本地代理 export const whyRequest = extend({ prefix: `${urlPrefix}`, }); export default umiRequest; 復制代碼
- 定義接口:要提前和后段溝通好入參數,出參數的格式,結合 ts 的類型提示,在其他地方調用的時候就可以直接看到接口定義的屬性了,非常方便
/**
* 登陸請求數據類型
*/
export interface ILogin {
userName: string;
pwd: string;
}
/**
* 返回數據類型
* 要提前和后段定義好類型,等接口寫完直接替換地址就好了
*
*/
export interface ILoginData {
code: number;
message: string;
token: string;
}
/**
* 登陸接口
* @param params
*/
export const loginApp = (params: ILogin): Promise<ILoginData> => {
return whyRequest.get("/login", params);
};
復制代碼
- 使用就很簡單,直接調用,之后我們會配合,ahooks 中的 useRequest()使用
loginApp({ userName: "why", pwd: "123" }).then((res) => {
if (res.code === 200) {
history.push("/home");
} else {
message.error("用戶名或密碼錯誤!");
}
});
復制代碼
國際化配置
- yarn add react-intl -D
- 國際化我們使用 react-intl 同時也要兼容 antd,的之類插件的中英文,我們在切換語言的時候插件庫也要直接進行切換到對應的語言,配置起來也很方便,
- 我們直接上代碼
// 引入創建語言,國際化容器,暫時我們只需要用這兩個就可以實現的我們目前的功能
import { createIntl, IntlProvider } from "react-intl";
// 我們需要引入antd 的國際化的配置
import antdEnUS from "antd/lib/locale/en_US";
import antdZhCN from "antd/lib/locale/zh_CN";
// 這是我們項目中中英文的配置,
import enLn from "./components/ln-en";
import zhLn from "./components/ln-zh-cn";
···核心代碼
/**
* 包裹了默認 locale 的 Provider
* LocaleProvider 需要在App.tx使用,包裝整個項目
* @param props
* @returns
*/
export const LocaleProvider: React.FC = (props) => {
return <IntlProvider locale={getLocale()}>{props.children}</IntlProvider>;
};
/**
* 獲取當前的 intl 對象,可以在 node 中使用
* @param locale 需要切換的語言類型
* @param changeIntl 是否不使用 g_intl
* @returns IntlShape
*/
const getIntl = (locale?: string, changeIntl?: boolean) => {
// 如果全局的 g_intl 存在,且不是 setIntl 調用
if (gIntl && !changeIntl && !locale) {
return gIntl;
}
// 如果存在於 localeInfo 中
if (locale && localeInfo[locale]) {
return createIntl(localeInfo[locale]);
}
// 使用默認語言
if (localeInfo[defaultLanguage])
return createIntl(localeInfo[defaultLanguage]);
// 使用 zh-CN
if (localeInfo["zh-cn"]) return createIntl(localeInfo["zh-cn"]);
// 拋錯
if (!locale || !!localeInfo[locale]) {
throw new Error(
"The current popular language does not exist, please check the locales folder!"
);
}
// 如果還沒有,返回一個空的
return createIntl({
locale: "zh-cn",
messages: {},
});
};
/**
* 語言轉換
* @param descriptor
* @param values
*/
export const formatMessage = (
descriptor: MessageDescriptor,
values?: Record<string, any>
) => {
if (!gIntl) {
setIntl(getLocale());
}
return gIntl.formatMessage(descriptor, values);
};
復制代碼
-
頁面中使用
1,我們要在對應的 ts 文件中配置中英文對照
// 在locale 文件下配置中文對照 export default { frontEnd: "Work hard on the front end", switchLan: "Chinese-English shift", switchToEn: "switch to chinese", switchToCh: " switch to english", localLan: "The internationalization of this project is based on", }; // 配置英文對照 export default { frontEnd: "前端要努力", switchLan: "中英文切換", switchToEn: "切換到中文", switchToCh: "切換到英文", localLan: "本項目國際化基於", }; 復制代碼
2,在頁面中我們直接調用 formatMessage() 這個方法就好了
/** * 國際化頁面 * @constructor */ const LocalePage: React.FC = () => { // 這使用的是useState,其實這里是完全不需要的 const [value, setValue] = React.useState( localStorage.getItem("why__locale") || "zh-cn" ); // 切換多語言 const onChange = (e: RadioChangeEvent) => { setValue(e.target.value); //在這里是沒有作用的代碼 setLocale(e.target.value); // 調用切換多語言方法,然后刷新頁面 }; return ( <Card title={formatMessage({ id: "switchLan" })} style={{ width: "500px" }}> <Radio.Group onChange={onChange} value={value}> <Radio value={"zh-cn"}>{formatMessage({ id: "switchToEn" })}</Radio> <Radio value={"en"}>{formatMessage({ id: "switchToCh" })}</Radio> </Radio.Group> <div className={styles.localLan}> {formatMessage({ id: "localLan" })}react-intl </div> </Card> ); }; 復制代碼
- 國際化頁面
路由
-
react 路由系統和 vue 大有不同,沒有路由導航前鈎子,配置登陸鑒權就要自己配置下,結合 token,
-
我們項目中路由的目的就是支持動態路由,路由權限,配置抽離,目前就是最簡單的,裸的
公共組件封裝
-
我們如何封裝一個公共組件?
1, 項目中需要多處使用的組件
2, 不和業務耦合的組件,業務耦合的公共組件
3, 所有狀態都可以在外部控制,通過傳入的props來控制其行為而不是暴露其內部結構。
封裝良好的組件隱藏其內部結構,並提供一組屬性來控制其行為。 隱藏內部結構是必要的。其他組件沒必要知道或也不依賴組件的內部 結構或實現細節 復制代碼
-
我們的項目中統一目錄,主要為了看起來舒服
-
目錄:
-
index.tsx為主入口文件
-
index.md為組件使用樣例,必要的代碼注釋,要清楚的告訴別人怎么使用這個公共組件
-
-
如何使用iconfont的字體圖標
- 封裝 icon,主要配合 antd createFromIconfontCN 直接引入 iconfont 中的字體圖標,非常方便
- 如下圖所示直接登陸到iconfont網站生成對應js文件,在項目中直接用就好,很簡單
- classNames 的使用 npm 介紹
// 簡單來說
// 這里可以根據各屬性動態添加,如果屬性值為true則為其添加該類名,
// 如果值為false,則不添加。這樣達到了動態添加class的目的
<FontIcon
className={classNames(
{
[styles.large]: size === "large", // 返回為true使用css .large,下方同理
[styles.normal]: size === "normal",
[styles.small]: size === "small",
[styles.disabled]: disabled,
},
className
)}
{...restProps}
/>
復制代碼
- React.FC<>的使用 1.React.FC 是函數式組件,是在 TypeScript 使用的一個泛型,FC 就是 FunctionComponent 的縮寫,事實上 React.FC 可以寫成 React.FunctionComponent:
const App: React.FunctionComponent<{ message: string }> = ({ message }) => (
<div>{message}</div>
);
復制代碼
2.React.FC 包含了 PropsWithChildren 的泛型,不用顯式的聲明 props.children 的類型。React.FC<> 對於返回類型是顯式的,而普通函數版本是隱式的(否則需要附加注釋)。
3.React.FC 提供了類型檢查和自動完成的靜態屬性:displayName,propTypes 和 defaultProps(注意:defaultProps 與 React.FC 結合使用會存在一些問題)。
4.我們使用 React.FC 來寫 React 組件的時候,是不能用 setState 的,取而代之的是 useState()、useEffect 等 Hook API。
封裝icon公共組件
// IconType繼承React.HTMLAttributes的屬性,然后IconType,就擁有了其可被外界訪問的屬性
export interface IconType extends React.HTMLAttributes<any> {
// type 必有屬性,如果使用的時候沒有靜態檢查是,會提示錯誤,類型不匹配,使用ts的好處,靜態類型檢查非常nice
// 報錯如下:TS2741: Property 'type' is missing in type '{}' but required in type 'IconType'. index.tsx(7, 3): 'type' is declared here.
type: string;
// 圖標尺寸,默認 normal
size?: "small" | "normal" | "large" | null; // 可選屬性,size后面加上?
// 是否禁用
disabled?: boolean;
}
// createFromIconfontCN 返回一個組件
const FontIcon = createFromIconfontCN({
// 請給新圖標一個合適的駝峰命名,並保證單詞正確
scriptUrl: "//at.alicdn.com/t/font_955172_ymhvgyhjk.js",
});
const Icon: React.FC<IconType> = ({
className,
size = "normal",
disabled,
...restProps
}) => {
// 我們使用classNames 這個插件動態渲染icon的狀態,size,disabled等等
return (
<FontIcon
className={classNames(
{
[styles.large]: size === "large",
[styles.normal]: size === "normal",
[styles.small]: size === "small",
[styles.disabled]: disabled,
},
className
)}
{...restProps}
/>
);
};
// 思考題:這個地方需要用,react.memo嗎?
export default React.memo(Icon);
復制代碼
- 使用(截圖中有iconSelect公共組件,此篇不做講解)
結語
- vite + react + ts 手摸手做項目系列一 (項目配置篇)
- github地址(github的更新速度比文檔要快,文檔要追加大量注釋)github地址
- 有問題可以提issue 或者加博主:lisawhy0706
- 下一篇會主要講解一些復雜組件
- 完善項目中的功能
- 公共配置的處理
- 自定義hooks的封裝
- 如何設計項目的權限包括路由權限,按鈕級別權限