網(wǎng)站首頁 編程語言 正文
前言:
本文主要記錄我如何在React項目中優(yōu)雅的使用TypeScript,來提高開發(fā)效率及項目的健壯性。
項目目錄及ts文件劃分
由于我在實際項目中大部分是使用umi
來進(jìn)行開發(fā)項目,所以使用umi
生成的目錄來做案例。
. ├── README.md ├── global.d.ts ├── mock ├── package.json ├── src │?? ├── assets │?? ├── components │?? │?? └── PublicComA │?? │?? ├── index.d.ts │?? │?? ├── index.less │?? │?? └── index.tsx │?? ├── layouts │?? ├── models │?? ├── pages │?? │?? ├── PageA │?? │?? │?? ├── index.d.ts │?? │?? │?? ├── index.less │?? │?? │?? └── index.tsx │?? │?? ├── index.less │?? │?? └── index.tsx │?? └── utils ├── tsconfig.json ├── typings.d.ts └── yarn.lock
在項目根目錄下有typings.d.ts和global.d.ts這兩個文件, 前者我們可以放置一些全局的導(dǎo)出模塊,比如css,less, 圖片的導(dǎo)出聲明;后者可以放一些全局聲明的變量, 接口等, 比如說window下全局變量的聲明等。
如下:
// typings.d.ts declare module '*.css'; declare module '*.less'; declare module "*.png"; declare module "*.jpeg"; declare module '*.svg' { export function ReactComponent(props: React.SVGProps<SVGSVGElement>): React.ReactElement const url: string export default url }
// global.d.ts interface Window { helloWorld: () => void; }
接下來介紹一下src目錄:
- assets 存放靜態(tài)資源如圖片/視頻/音頻等, 參與webpack的打包過程
- layouts 存放公共布局
- components 存放全局公共組件
- models dva的models文件夾
- pages 存放頁面的目錄, 內(nèi)部可以有頁面組件components, 結(jié)構(gòu)類似于全局的components
- utils 存放js工具庫, 請求庫等公共js文件
在pages和components中有存放當(dāng)前組件/頁面所需要的類型和接口聲明的index.d.ts。另外如models中的文件由于是每個model私有類型和接口聲明,所以可以直接在文件內(nèi)部去聲明。 具體的目錄規(guī)劃如上,可以根據(jù)實際項目來做更合理的劃分。
在項目中使用TypeScript具體實踐
組件聲明
- 函數(shù)組件 推薦使用
React.FC<P={}>
來表示函數(shù)類型,當(dāng)使用該類型定義組件時,props中會默認(rèn)帶有children屬性。
interface IProps { count: number } const App: React.FC<IProps> = (props) => { const {count} = props; return ( <div className="App"> <span>count: {count}</span> </div> ); }
- 類組件 類組件接受兩個參數(shù),第一個是props的定義,第二個是state的定義,如果使用
React.PureComponent<P, S={} SS={}>
定義組件,則還有第三個參數(shù),表示getSnapshotBeforeUpdate
的返回值。
interface IProps { name: string; } interface IState { count: number; } class App extends React.Component<IProps, IState> { state = { count: 0 }; render() { return ( <div> {this.state.count} {this.props.name} </div> ); } }
React Hooks使用
useState
聲明定義:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>]; // convenience overload when first argument is omitted /** * Returns a stateful value, and a function to update it. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usestate */ function useState<S = undefined>(): [S | undefined, Dispatch<SetStateAction<S | undefined>>]; /** * An alternative to `useState`. * * `useReducer` is usually preferable to `useState` when you have complex state logic that involves * multiple sub-values. It also lets you optimize performance for components that trigger deep * updates because you can pass `dispatch` down instead of callbacks. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usereducer */
如果初始值能夠體現(xiàn)出類型,那么可以不用手動聲明類型,TS會自動推斷出類型。如果初始值為null或者undefined則需要通過泛型顯示聲明類型。
如下:
const [count, setCount] = useState(1); const [user, setUser] = useState<IUser | null>(null);
useRef
聲明定義:
function useRef<T>(initialValue: T): MutableRefObject<T>; // convenience overload for refs given as a ref prop as they typically start with a null value /** * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument * (`initialValue`). The returned object will persist for the full lifetime of the component. * * Note that `useRef()` is useful for more than the `ref` attribute. It's handy for keeping any mutable * value around similar to how you'd use instance fields in classes. * * Usage note: if you need the result of useRef to be directly mutable, include `| null` in the type * of the generic argument. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useref */
使用該Hook時,要根據(jù)使用場景來判斷傳入泛型類型,如果是獲取DOM節(jié)點(diǎn),則傳入對應(yīng)DOM類型即可;如果需要的是一個可變對象,則需要在泛型參數(shù)中包含'| null'。
如下:
// 不可變DOM節(jié)點(diǎn),只讀 const inputRef = useRef<HTMLInputElement>(null); // 可變,可重新復(fù)制 const idRef = useRef<string | null>(null); idRef.current = "abc";
useCallback
聲明定義:
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T; /** * `useMemo` will only recompute the memoized value when one of the `deps` has changed. * * Usage note: if calling `useMemo` with a referentially stable function, also give it as the input in * the second argument. * * ```ts * function expensive () { ... } * * function Component () { * const expensiveResult = useMemo(expensive, [expensive]) * return ... * } * ``` * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usememo */
useCallback會根據(jù)返回值自動推斷出類型,如果傳入的參數(shù)不指定類型,則會默認(rèn)為any
,所以為了嚴(yán)謹(jǐn)和可維護(hù)性,一定要指定入?yún)⒌念愋汀R部梢允謩觽魅敕盒椭付ê瘮?shù)類型。
如下:
// 會自動推導(dǎo)出類型: (a: number, b: number) => number; const add = useCallback((a: number, b: number) => a + b, [a, b]) // 傳入泛型,則指定函數(shù)類型 const toggle = useCallback<(a: number) => number>((a: number) => a * 2, [a])
useMemo
聲明定義:
function useMemo<T>(factory: () => T, deps: DependencyList | undefined): T; /** * `useDebugValue` can be used to display a label for custom hooks in React DevTools. * * NOTE: We don't recommend adding debug values to every custom hook. * It's most valuable for custom hooks that are part of shared libraries. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usedebugvalue */
useMemo和useCallback類似,只是定義類型為具體返回值的類型,而不是函數(shù)的類型。
如下:
// 會自動推導(dǎo)出類型: number; const add = useCallback((a: number, b: number) => a + b, [a, b]) // 傳入泛型,則指定函數(shù)類型 const toggle = useCallback<number>((a: number) => a * 2, [a])
useContext
聲明定義:
function useContext<T>(context: Context<T>/*, (not public API) observedBits?: number|boolean */): T; /** * Returns a stateful value, and a function to update it. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#usestate */
useContext會根據(jù)傳入的上下文對象自動推導(dǎo)出context的類型,當(dāng)然也可以使用泛型來設(shè)置context的類型,
如下:
interface ITheme { color: string; } const ThemeContext = React.createContext<ITheme>({ color: "red" }); // 自動推導(dǎo)出類型為ITheme const theme = useContext(ThemeContext); // 等同于const theme = useContext<ITheme>(ThemeContext);
useReducer
聲明定義:
function useReducer<R extends Reducer<any, any>>( reducer: R, initialState: ReducerState<R>, initializer?: undefined ): [ReducerState<R>, Dispatch<ReducerAction<R>>]; /** * `useRef` returns a mutable ref object whose `.current` property is initialized to the passed argument * (`initialValue`). The returned object will persist for the full lifetime of the component. * * Note that `useRef()` is useful for more than the `ref` attribute. It's handy for keeping any mutable * value around similar to how you'd use instance fields in classes. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useref */
上面只列出了一種類型定義,我在項目中也是使用這種定義去指定useReducer
的類型。普通的案例如下:
type StateType = { name: string; age: number; } type Actions = { type: 'Change_Name'; payload: string; } | { type: 'Change_Age'; payload: number; } const initialState = { name: '小明', age: 18 } const reducerAction: Reducer<StateType, Actions> = ( state, action, ) => { switch (action.type) { case 'Change_Name': return { ...state, name: action.payload }; case 'Change_Age': return { ...state, age: action.payload }; default: return state; } }; function Index() { const [state, dispatch] = useReducer(reducerAction, initialState); return ( <div> <div>姓名:{state.name}</div> <div>年齡:{state.age}</div> </div> ); }
可以看到,這樣能夠得到正確的類型推斷,但是略微繁瑣。
案例如下:
// 定義一個生成Action類型的泛型 type ActionMap<M extends Record<string, any>> = { [Key in keyof M]: M[Key] extends undefined ? { type: Key } : { type: Key payload: M[Key] } } type StateType = { name: string; age: number; } // 定義具體的Action類型 type PayloadType = { Change_Name: string; Change_Age: number; } /** ActionMap<PayloadType>會生成類型 { Change_Name: { type: Types.Name; payload: string; }; Change_Age: { type: Types.Age; payload: number; }; } 而keyof ActionMap<PayloadType>則會生成 'Change_Name' | 'Change_Age'的類型。 所以Action最終的類型便為: type Actions = { type: Types.Name; payload: string; } | { type: Types.Age; payload: number; } */ type Actions = ActionMap<PayloadType>[keyof ActionMap<PayloadType>] const initialState = { name: '小明', age: 18 } const reducerAction: Reducer<StateType, Actions> = ( state, action, ) => { switch (action.type) { case Types.Name: return { ...state, name: action.payload }; case Types.Age: return { ...state, age: action.payload }; default: return state; } };
我們定義了一個ActionMap
泛型,該泛型會將傳入的類型{key: value}
生成為新的{key: {type: key, payload: value }
類型。然后我們利用keyof
關(guān)鍵字獲取到所有的key,就可以得到我們所需要的{type: key1, payload: value1} | {type: key2, payload: value2}
的類型了。只要我們定義好PayloadType
類型,則可以自動推導(dǎo)出我們需要的Actions
類型。
useImperativeHandle
聲明定義:
function useImperativeHandle<T, R extends T>(ref: Ref<T>|undefined, init: () => R, deps?: DependencyList): void; // NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref<T> /** * `useImperativeHandle` customizes the instance value that is exposed to parent components when using * `ref`. As always, imperative code using refs should be avoided in most cases. * * `useImperativeHandle` should be used with `React.forwardRef`. * * @version 16.8.0 * @see https://reactjs.org/docs/hooks-reference.html#useimperativehandle */
useImperativeHandle
可以讓自定義組件通過ref
屬性,將內(nèi)部屬性暴露給父組件進(jìn)行訪問。因為是函數(shù)式組件,所以需要結(jié)合forwardRef
一起使用。
案例如下:
interface FancyProps {} interface FancyRef { focus: () => void; } const FancyInput = forwardRef<FancyRef, FancyProps>((props, ref) => { const inputRef = useRef<HTMLInputElement>(null); useImperativeHandle(ref, () => ({ focus: () => { inputRef.current?.focus(); } })); return ( <input ref={inputRef} {...props} /> ); }) const Parent = () => { // 定義子組件ref const inputRef = useRef<FancyRef>(null); return ( <div> <FancyInput ref={inputRef} /> <button onClick={() => { // 調(diào)用子組件方法 inputRef.current?.focus(); }} >聚焦</button> </div> ) }
Axios請求/響應(yīng)定義封裝
axios
是很流行的http庫,他的ts封裝已經(jīng)很完美了,我們只做簡單的二次封裝,返回通用的數(shù)據(jù)響應(yīng)格式。 首先在utils/request.ts
中創(chuàng)建一個構(gòu)造axios實例的生成器:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; // 攔截器定義 export interface RequestInterceptors { // 請求攔截 requestInterceptors?: (config: AxiosRequestConfig) => AxiosRequestConfig requestInterceptorsCatch?: (err: any) => any // 響應(yīng)攔截 responseInterceptors?: (config: AxiosResponse) => AxiosResponse responseInterceptorsCatch?: (err: any) => any } // 生成axios實例的參數(shù),實例可以單獨(dú)傳入攔截器 export interface RequestConfig extends AxiosRequestConfig { interceptorsObj?: RequestInterceptors } // loading請求數(shù)量 let loadingCount: number = 0; // 打開loading const showLoading = () => { loadingCount ++; if(loadingCount > 0) { // 顯示loading // Loading.show() } } // 關(guān)閉loading const hideLoading = () => { loadingCount --; if(loadingCount <= 0) { // 隱藏loading // Loading.hide(); } } function RequestBuilder(config: RequestConfig) { const { interceptorsObj, ...res } = config; const instance: AxiosInstance = axios.create(res); // 全局請求攔截器 instance.interceptors.request.use( (request: AxiosRequestConfig) => { // 顯示loading showLoading(); console.log('全局請求攔截器'); // TODO:全局的請求頭操作等等 return request; }, (err: any) => err, ) /** * 實例請求攔截器 * 要注意 axios請求攔截器為倒序執(zhí)行,所以要將實例請求攔截器注冊在全局請求攔截器后面 */ instance.interceptors.request.use( interceptorsObj?.requestInterceptors, interceptorsObj?.requestInterceptorsCatch, ) /** * 實例響應(yīng)攔截器 * axios響應(yīng)攔截器為正序執(zhí)行,所以要將實例響應(yīng)攔截器注冊在全局響應(yīng)攔截器前面 */ instance.interceptors.response.use( interceptorsObj?.responseInterceptors, interceptorsObj?.responseInterceptorsCatch, ) // 全局響應(yīng)攔截器 instance.interceptors.response.use( (response: AxiosResponse) => { console.log('全局響應(yīng)攔截器'); // 關(guān)閉loading hideLoading(); // TODO: 通用的全局響應(yīng)處理,token過期重定向登錄等等 // 返回值為res.data,即后端接口返回的數(shù)據(jù),減少解構(gòu)的層級,以及統(tǒng)一響應(yīng)數(shù)據(jù)格式。 return response.data }, (err: any) => { // 關(guān)閉loading hideLoading(); // TODO: 錯誤提示等 return err; }, ) return instance; } export const http = RequestBuilder({baseURL: '/api'});
該生成器可以實現(xiàn)每個實例有單獨(dú)的攔截器處理邏輯,并且實現(xiàn)全局的loading加載效果,全局?jǐn)r截器的具體實現(xiàn)可以根據(jù)項目實際需求進(jìn)行填充。生成器已經(jīng)完成,但是還沒法定制我們的通用響應(yīng)數(shù)據(jù),接下來我們在typings.d.ts
中重新定義axios模塊:
import * as axios from 'axios'; declare module 'axios' { // 定制業(yè)務(wù)相關(guān)的網(wǎng)絡(luò)請求響應(yīng)格式, T 是具體的接口返回類型數(shù)據(jù) export interface CustomSuccessData<T> { code: number; msg?: string; message?: string; data: T; [keys: string]: any; } export interface AxiosInstance { // <T = any>(config: AxiosRequestConfig): Promise<CustomSuccessData<T>>; request<T = any, R = CustomSuccessData<T>, D = any>(config: AxiosRequestConfig<D>): Promise<R>; get<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>; delete<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>; head<T = any, R = CustomSuccessData<T>, D = any>(url: string, config?: AxiosRequestConfig<D>): Promise<R>; post<T = any, R = CustomSuccessData<T>, D = any>( url: string, data?: D, config?: AxiosRequestConfig<D>, ): Promise<R>; put<T = any, R = CustomSuccessData<T>, D = any>( url: string, data?: D, config?: AxiosRequestConfig<D>, ): Promise<R>; patch<T = any, R = CustomSuccessData<T>, D = any>( url: string, data?: D, config?: AxiosRequestConfig<D>, ): Promise<R>; } }
完成以上操作后,我們在業(yè)務(wù)代碼中具體使用:
import { http } from '@/utils/request'; interface Req { userId: string; } interface Res { userName: string; userId: string; } // 獲取用戶信息接口 const getUserInfo = async (params: Req) => { return http.get<Res>('/getUserInfo', {params}) }
這個時候getUserInfo
返回的就是CustomSuccessData<Res>
類型的數(shù)據(jù)了。至此我們對axios
簡單的封裝也就完成了。
原文鏈接:https://juejin.cn/post/7144966323984924680
相關(guān)推薦
- 2021-11-09 Android如何實現(xiàn)時間線效果(下)_Android
- 2022-04-17 aspx頁面報“XPathResult未定義”的解決方法
- 2022-04-03 C++?OpenCV實戰(zhàn)之車道檢測_C 語言
- 2023-05-13 Python?readline()和readlines()函數(shù)實現(xiàn)按行讀取文件_python
- 2022-06-28 C++實現(xiàn)String與UF8互轉(zhuǎn)_C 語言
- 2023-01-05 Qt操作SQLite數(shù)據(jù)庫的教程詳解_C 語言
- 2022-11-05 Android實現(xiàn)雙曲線折線圖_Android
- 2022-11-03 APAP?ALV進(jìn)階寫法及優(yōu)化詳解_其它綜合
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支