前言
筆者上一篇文章:
主要寫了typescript的用法和核心知識(shí)點(diǎn)總結(jié), 這篇文章將通過一個(gè)實(shí)際的前端案例來教大家如何在項(xiàng)目中使用typescript.
你將收獲
- 如何使用umi快速搭建一個(gè)基于React antd typescript的前端項(xiàng)目
- 中后臺(tái)前端項(xiàng)目的目錄和ts文件劃分
- 在React組件中使用typescript
- 在工具庫中使用typescript
- 互聯(lián)網(wǎng)黑白墻案例分析
正文
在開始文章之前, 我們先看一下企業(yè)黑白墻項(xiàng)目的演示:
趣談前端
(注: 本文僅針對(duì)項(xiàng)目剖析和學(xué)習(xí)使用, 不做任何商業(yè)用途)
該項(xiàng)目是一個(gè)響應(yīng)式網(wǎng)站, 針對(duì)PC端和H5均做了一定的適配, 接下來我們將正對(duì)該網(wǎng)站做一次typescript剖析.
由上面的gif可以看出網(wǎng)站的信息結(jié)構(gòu)圖大致如下:
趣談前端
接下來進(jìn)入我們的正文.
1. 使用umi快速搭建一個(gè)基于React antd typescript的前端項(xiàng)目
umi是一個(gè)功能強(qiáng)大且開箱即用的企業(yè)級(jí)項(xiàng)目腳手架, 這里筆者直接采用umi來創(chuàng)建一個(gè)ts項(xiàng)目, 具體方式如下:
// 1.創(chuàng)建項(xiàng)目空目錄$ mkdir ts-react && cd ts-react// 2.創(chuàng)建項(xiàng)目yarn create @umijs/umi-app// 3.安裝項(xiàng)目依賴yarn
用umi開發(fā)只需要簡單的3個(gè)命令即可, 值得注意的是, 在執(zhí)行步驟2時(shí)會(huì)在命令行出現(xiàn)如下交互選項(xiàng):
主要是讓我們選擇創(chuàng)建的項(xiàng)目類型的, 這里我們選typescript和antd即可, 有關(guān)如何創(chuàng)建可交互的命令行工具, 在筆者的 基于react/vue生態(tài)的前端集成解決方案探索與總結(jié) 中有介紹, 感興趣的可以學(xué)習(xí)交流.
經(jīng)過以上的步驟我們就初步搭建了一個(gè)支持react typescript antd技術(shù)棧的項(xiàng)目骨架.
2. 中后臺(tái)前端項(xiàng)目的目錄和ts文件劃分
我們先看看本次研究的項(xiàng)目的目錄劃分:
ts-react ├─ src │ ├─ assets │ │ └─ yay.jpg │ ├─ components │ │ └─ PublicModal │ │ ├─ index.css │ │ ├─ index.tsx │ │ └─ type.ts │ ├─ layouts │ │ ├─ __tests__ │ │ │ └─ index.test.tsx │ │ ├─ index.css │ │ └─ index.tsx │ ├─ locales │ │ └─ en-US.ts │ ├─ models │ ├─ pages │ │ ├─ __tests__ │ │ │ ├─ __mocks__ │ │ │ │ └─ umi-plugin-locale.ts │ │ │ └─ index.test.tsx │ │ ├─ about │ │ │ ├─ components │ │ │ ├─ index.d.ts │ │ │ ├─ index.less │ │ │ └─ index.tsx │ │ ├─ index.css │ │ ├─ index.tsx │ │ ├─ innerRec.tsx │ │ └─ list.tsx │ ├─ utils │ │ ├─ tool.ts │ │ └─ type.ts │ ├─ app.ts │ └─ global.css ├─ global.d.ts ├─ package.json ├─ readme.md ├─ tsconfig.json └─ typings.d.ts
我們從外往里看, 在項(xiàng)目根目錄下有typings.d.ts和global.d.ts這兩個(gè)文件, 前者我們可以放置一些全局的導(dǎo)出模塊,比如css,less, 圖片的導(dǎo)出聲明, 這樣我們就不用一個(gè)個(gè)的在頁面代碼里再重新聲明了, 如下:
// typings.d.tsdeclare module '*.css';declare module '*.less';declare module "*.png";declare module "*.jpeg";
這樣做我們就能避免在頁面中導(dǎo)入css或者圖片文件時(shí)ts報(bào)錯(cuò)的問題了. 對(duì)于global.d.ts, 筆者建議放一些全局聲明的變量, 接口等, 比如說Jquery這種第三方庫的聲明, window下全局變量的聲明等.
其次是src目錄,我們具體介紹一下目錄的意義: assets 存放靜態(tài)資源如圖片/視頻/音頻等, 參與webpack的打包過程 layouts 存放公共布局 components 存放全局共同組件 locales 多語言配置目錄 models dva的models文件夾, 處理redux流 pages 存放頁面的目錄, 內(nèi)部可以有頁面組件components, 結(jié)構(gòu)類似于全局的components * utils 存放js工具庫, 請(qǐng)求庫等公共js文件
在了解了上面的目錄和目錄的含義之后, 我們?cè)賮砜纯慈绾我?guī)劃其中的ts文件.
對(duì)于組件庫來說, 其下面的一個(gè)子目錄對(duì)應(yīng)一個(gè)組件, 里面包含必須的樣式文件, 組件tsx文件和組件自有類型文件, 這里命名為type.ts, 專門存放該組件所需要的類型和接口聲明.
同理對(duì)于頁面文件夾來說, 也應(yīng)具有類似的結(jié)構(gòu), 就好比上面的about頁面, 包含如下結(jié)構(gòu): components 該頁面專有的組件目錄 index.tsx 關(guān)于頁面的主文件 index.less 關(guān)于頁面的樣式文件 type.ts 關(guān)于頁面的類型和接口聲明文件
還需要說明一點(diǎn)的是, 如果某個(gè)頁面有私有的類型或者接口聲明,我們可以直接在文件內(nèi)部去聲明, 沒必要全部都拿到外面去定義和聲明.
目錄規(guī)劃這塊基本完成, 實(shí)際情況還是需要根據(jù)自身項(xiàng)目結(jié)構(gòu)來做更合理的劃分, 接下來我們看看具體的typescript在業(yè)務(wù)代碼中的應(yīng)用.
3. 在React組件中使用typescript
這里筆者將會(huì)拿該項(xiàng)目的自定義上傳組件以及白名單頁面作為例子, 文件上傳組件筆者將采用SFC(即函數(shù)組件), 白名單頁面將采用類組件, 這樣可以方便大家對(duì)這兩中組件開發(fā)模式下的typescript開發(fā)有個(gè)全面的認(rèn)知.
3.1 自定義上傳組件開發(fā)
自定義上傳組件我們主要應(yīng)用在發(fā)布模塊, 基于antd進(jìn)行二次封裝以便能兼容支持antd的Form模型, 如下圖:
趣談前端
結(jié)合typescript的實(shí)現(xiàn)如下:
import React, { useState, useEffect, SFC, ReactNode } from 'react';import { Upload, message } from 'antd';import { LoadingOutlined, PlusOutlined } from '@ant-design/icons';import styles from './index.less';export interface BeforeUploadFunc { (file:File, fileList:FileList): boolean | Promise<File>;}export interface SuccessBack { (url: string): string;}export interface ChangeFunc { (value: string | Array<string>): void;}export interface IProps { action: string; listType?: string; showUploadList?: boolean; headers?: object; beforeUpload?: BeforeUploadFunc; onSuccess?: SuccessBack; withCredentials?: boolean; text?: string | ReactNode; imgUrl?: string; onChange?: ChangeFunc; value?: string;}const UploadCp:SFC<IProps> = (props:IProps) => { const { listType = 'picture-card', showUploadList = false, action = 'http://io.cc.com/api/files/free', headers, beforeUpload = handleBeforeUpload, onSuccess, withCredentials = true, text = '上傳封面', imgUrl, onChange, value } = props const [loading, setLoading] = useState(false) const [imageUrl, setImageUrl] = useState(imgUrl) const handleChange = (info:FileList):void => { // 一些操作 } function handleBeforeUpload(file:File):boolean { const isJpgOrPng = file.type === 'image/jpeg' || file.type === 'image/png'; if (!isJpgOrPng) { message.error('You can only upload JPG/PNG file!'); } const isLt2M = file.size / 1024 / 1024 < 2; if (!isLt2M) { message.error('Image must smaller than 2MB!'); } return isJpgOrPng && isLt2M; } useEffect(() => { !value && setImageUrl(imgUrl) }, [imgUrl, value]) return <Upload name="file" listType={listType} className={styles.avatarUploader} showUploadList={showUploadList} action={action} withCredentials={withCredentials} headers={headers} beforeUpload={beforeUpload} onChange={handleChange} > {(value || imageUrl) ? <img src={value || imageUrl} alt="avatar" style={{ width: '100%' }} alt={text} /> : text} </Upload>}export default UploadCp
以上代碼我們使用了React的函數(shù)組件, React提供了函數(shù)組件的類型SFC, 內(nèi)置了children所以我們不用顯示的再聲明一次. 其他的比如函數(shù)聲明, 泛型接口, 可選類型的設(shè)置等筆者在上一篇文章:
徐小夕:TS核心知識(shí)點(diǎn)總結(jié)及項(xiàng)目實(shí)戰(zhàn)案例分析
有詳細(xì)介紹.不懂的可以在評(píng)論區(qū)與我交流.
3.2 白名單頁面開發(fā)
在了解完函數(shù)式組件如何與typescript搭配使用之后, 我們?cè)賮砜纯搭惤M件. 我們那拿搜索列表頁作為例子來講解:
代碼如下:
import React from 'react';import { List, Avatar, Button, Skeleton, Tag, Modal } from 'antd';import styles from './index.less';import req from '@/utils/req';export interface IProps extends Location {}interface List { name: string; img: string; desc: string; isLoading?: boolean;}interface LoadingState { initLoading: boolean; loading: boolean;}export interface IState extends LoadingState { data: Array<List>; list: Array<List>;}class LoadMoreList extends React.Component<IProps, IState> { state:IState = { initLoading: true, loading: false, data: [], list: [], }; componentDidMount() { this.getData(); } getData = () => { req.get(`/blackwhite/get?type=${this.props.location.query.type}`).then((res:List) => { this.setState({ initLoading: false, data: res, list: res.slice(0, pageNum) }); }) }; render() { const { initLoading, loading, list, data } = this.state; return // 頁面實(shí)現(xiàn)代碼 }}export default LoadMoreList
以上代碼實(shí)現(xiàn)了class組件的typescript應(yīng)用, 對(duì)于interface類型聲明用到了繼承, 當(dāng)然也可以不用繼承直接寫類型聲明, 這里主要為了學(xué)習(xí)方便. 大家也可以把公用的頁面類型放到單獨(dú)的type.ts目錄下復(fù)用.
4. 在工具庫中使用typescript
在掌握了類組件和函數(shù)組件的typescript寫法之后, 我們來說說工具類的typescript編寫方式, 這塊比較簡單, 筆者簡單舉幾個(gè)常用工具函數(shù), 將其改造成typescript的模式. 代碼如下:
// utils/tool.ts/* * @Author: Mr Jiang.Xu * @Date: 2019-06-06 11:23:05 * @Last Modified by: Mr Jiang.Xu * @Last Modified time: 2019-06-29 22:33:52 *//** * 識(shí)別ie--淺識(shí)別 */export const isIe = ():boolean => { let explorer = window.navigator.userAgent; //判斷是否為IE瀏覽器 if (explorer.indexOf("MSIE") >= 0) { return true; }else { return false }}/** * 顏色轉(zhuǎn)換16進(jìn)制轉(zhuǎn)rgba * @param {String} hex * @param {Number} opacity */export function hex2Rgba(hex:string, opacity:number):string { if(!hex) hex = "#2c4dae"; return "rgba(" parseInt("0x" hex.slice(1, 3)) "," parseInt("0x" hex.slice(3, 5)) "," parseInt("0x" hex.slice(5, 7)) "," (opacity || "1") ")";}// 去除html標(biāo)簽export const htmlSafeStr = (str:string):string => { return str.replace(/<[^>] >/g, "")}interface params { [propertyName: string]: string | number}/* 解析url參數(shù) */export const toParams = (params:params):string => { if(params){ let query = []; for(let key in params){ query.push(`${key}=${params[key]}`) } return `${query.join('&')}` }else{ return '' }}
以上是幾個(gè)比較簡單的案例, 方便大家入門和理解, 實(shí)際工作中場(chǎng)景會(huì)更復(fù)雜, 但是掌握了基本聲明和定義模式, 基本可以解決大部分ts聲明問題. 作為一名前端工程師, typescript的意義很大,雖然它增加了編程的復(fù)雜度和學(xué)習(xí)成本, 但是長遠(yuǎn)來說, 對(duì)于團(tuán)隊(duì)的編碼規(guī)范, 問題定位, 項(xiàng)目維護(hù)和代碼管理的角度確實(shí)有不少積極作用, 所以學(xué)習(xí)typescript刻不容緩.
最后
如果想學(xué)習(xí)更多H5游戲, webpack,node,gulp,css3,javascript,nodeJS,canvas數(shù)據(jù)可視化等前端知識(shí)和實(shí)戰(zhàn),歡迎在《趣談前端》專欄學(xué)習(xí)討論,共同探索前端的邊界。
版權(quán)聲明:本文內(nèi)容由互聯(lián)網(wǎng)用戶自發(fā)貢獻(xiàn),該文觀點(diǎn)僅代表作者本人。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如發(fā)現(xiàn)本站有涉嫌抄襲侵權(quán)/違法違規(guī)的內(nèi)容, 請(qǐng)發(fā)送郵件至 舉報(bào),一經(jīng)查實(shí),本站將立刻刪除。