vite + react + ts 手摸手做項目系列二 (實戰篇)


 

傳送門

前言

  • 這篇實戰篇文章,我改了很多遍,本來加了很多復雜的封裝,但是對於初學 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> ); }; 復制代碼
  • 國際化頁面

image.png

路由

  • react 路由看這個

  • react 路由系統和 vue 大有不同,沒有路由導航前鈎子,配置登陸鑒權就要自己配置下,結合 token,

  • 我們項目中路由的目的就是支持動態路由,路由權限,配置抽離,目前就是最簡單的,裸的

image.png

公共組件封裝

  • 我們如何封裝一個公共組件?

    1, 項目中需要多處使用的組件

    2, 不和業務耦合的組件,業務耦合的公共組件

    3, 所有狀態都可以在外部控制,通過傳入的props來控制其行為而不是暴露其內部結構。

    封裝良好的組件隱藏其內部結構,並提供一組屬性來控制其行為。
    
    隱藏內部結構是必要的。其他組件沒必要知道或也不依賴組件的內部  結構或實現細節
    復制代碼
  • 我們的項目中統一目錄,主要為了看起來舒服

  • 目錄:image.png

    • index.tsx為主入口文件

    • index.md為組件使用樣例,必要的代碼注釋,要清楚的告訴別人怎么使用這個公共組件

    • image.png

如何使用iconfont的字體圖標

  • 封裝 icon,主要配合 antd createFromIconfontCN 直接引入 iconfont 中的字體圖標,非常方便
  • 如下圖所示直接登陸到iconfont網站生成對應js文件,在項目中直接用就好,很簡單

  // 簡單來說

  // 這里可以根據各屬性動態添加,如果屬性值為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公共組件,此篇不做講解)

image.png

結語

  • vite + react + ts 手摸手做項目系列一 (項目配置篇)
  • github地址(github的更新速度比文檔要快,文檔要追加大量注釋)github地址
  • 有問題可以提issue 或者加博主:lisawhy0706
  • 下一篇會主要講解一些復雜組件
    • 完善項目中的功能
    • 公共配置的處理
    • 自定義hooks的封裝
    • 如何設計項目的權限包括路由權限,按鈕級別權限


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM