1. 程式人生 > 實用技巧 >typescript實戰總結之實現一個網際網路黑白牆

typescript實戰總結之實現一個網際網路黑白牆

前言

筆者上一篇文章TS核心知識點總結及專案實戰案例分析主要寫了typescript的用法和核心知識點總結, 這篇文章將通過一個實際的前端案例來教大家如何在專案中使用typescript.

你將收穫

  • 如何使用umi快速搭建一個基於React+antd+typescript的前端專案
  • 中後臺前端專案的目錄和ts檔案劃分
  • 在React元件中使用typescript
  • 在工具庫中使用typescript
  • 網際網路黑白牆案例分析

正文

在開始文章之前, 我們先看一下企業黑白牆專案的演示:(注: 本文僅針對專案剖析和學習使用, 不做任何商業用途)

該專案是一個響應式網站, 針對PC端和H5均做了一定的適配, 接下來我們將正對該網站做一次typescript剖析.

由上面的gif可以看出網站的資訊結構圖大致如下:接下來進入我們的正文.

1. 使用umi快速搭建一個基於React+antd+typescript的前端專案

umi是一個功能強大且開箱即用的企業級專案腳手架, 這裡筆者直接採用umi來建立一個ts專案, 具體方式如下:


   
   
     
 
    
    // 1.建立專案空目錄
$ mkdir ts-react && cd ts-react

// 2.建立專案
yarn create @umijs/umi-app

// 3.安裝專案依賴
yarn
複製程式碼

用umi開發只需要簡單的3個命令即可, 值得注意的是, 在執行步驟2時會在命令列出現如下互動選項:

主要是讓我們選擇建立的專案型別的, 這裡我們選typescript和antd即可, 有關如何建立可互動的命令列工具, 在筆者的基於react/vue生態的前端整合解決方案探索與總結中有介紹, 感興趣的可以學習交流.

經過以上的步驟我們就初步搭建了一個支援react + typescript + antd技術棧的專案骨架.

2. 中後臺前端專案的目錄和ts檔案劃分

我們先看看本次研究的專案的目錄劃分:


   
   
     
 
    
    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
複製程式碼

我們從外往裡看, 在專案根目錄下有typings.d.ts和global.d.ts這兩個檔案, 前者我們可以放置一些全域性的匯出模組,比如css,less, 圖片的匯出宣告, 這樣我們就不用一個個的在頁面程式碼裡再重新聲明瞭, 如下:


   
   
     
 
    
    // typings.d.ts
declare module '*.css';
declare module '*.less';
declare module "*.png";
declare module "*.jpeg";
複製程式碼

這樣做我們就能避免在頁面中匯入css或者圖片檔案時ts報錯的問題了. 對於global.d.ts, 筆者建議放一些全域性宣告的變數, 介面等, 比如說Jquery這種第三方庫的宣告, window下全域性變數的宣告等.

其次是src目錄,我們具體介紹一下目錄的意義:

  • assets存放靜態資源如圖片/視訊/音訊等, 參與webpack的打包過程
  • layouts存放公共佈局
  • components存放全域性共同元件
  • locales多語言配置目錄
  • modelsdva的models資料夾, 處理redux流
  • pages存放頁面的目錄, 內部可以有頁面元件components, 結構類似於全域性的components
  • utils存放js工具庫, 請求庫等公共js檔案

在瞭解了上面的目錄和目錄的含義之後, 我們再來看看如何規劃其中的ts檔案.對於元件庫來說, 其下面的一個子目錄對應一個元件, 裡面包含必須的樣式檔案, 元件tsx檔案和元件自有型別檔案, 這裡命名為type.ts, 專門存放該元件所需要的型別和介面宣告.同理對於頁面資料夾來說, 也應具有類似的結構, 就好比上面的about頁面, 包含如下結構:

  • components該頁面專有的元件目錄
  • index.tsx關於頁面的主檔案
  • index.less關於頁面的樣式檔案
  • type.ts關於頁面的型別和介面宣告檔案

還需要說明一點的是, 如果某個頁面有私有的型別或者介面宣告,我們可以直接在檔案內部去宣告, 沒必要全部都拿到外面去定義和宣告.目錄規劃這塊基本完成, 實際情況還是需要根據自身專案結構來做更合理的劃分, 接下來我們看看具體的typescript在業務程式碼中的應用.

3. 在React元件中使用typescript

這裡筆者將會拿該專案的自定義上傳元件以及白名單頁面作為例子, 檔案上傳元件筆者將採用SFC(即函式元件), 白名單頁面將採用類元件, 這樣可以方便大家對這兩中元件開發模式下的typescript開發有個全面的認知.

3.1 自定義上傳元件開發

自定義上傳元件我們主要應用在釋出模組, 基於antd進行二次封裝以便能相容支援antd的Form模型, 如下圖:結合typescript的實現如下:


   
   
     
 
    
    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的函式元件, React提供了函式元件的型別SFC, 內建了children所以我們不用顯示的再宣告一次. 其他的比如函式宣告, 泛型介面, 可選型別的設定等筆者在上一篇文章TS核心知識點總結及專案實戰案例分析有詳細介紹.不懂的可以在評論區與我交流.

3.2 白名單頁面開發

在瞭解完函式式元件如何與typescript搭配使用之後, 我們再來看看類元件. 我們那拿搜尋列表頁作為例子來講解:

程式碼如下:


   
   
     
 
    
    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 // 頁面實現程式碼
}
}

export default LoadMoreList

以上程式碼實現了class元件的typescript應用, 對於interface型別宣告用到了繼承, 當然也可以不用繼承直接寫型別宣告, 這裡主要為了學習方便. 大家也可以把公用的頁面型別放到單獨的type.ts目錄下複用.

4. 在工具庫中使用typescript

在掌握了類元件和函式元件的typescript寫法之後, 我們來說說工具類的typescript編寫方式, 這塊比較簡單, 筆者簡單舉幾個常用工具函式, 將其改造成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
*/

/**
* 識別ie--淺識別
*/
export const isIe = ():boolean => {
let explorer = window.navigator.userAgent;
//判斷是否為IE瀏覽器
if (explorer.indexOf("MSIE") >= 0) {
return true;
}else {
return false
}
}

/**
* 顏色轉換16進位制轉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標籤
export const htmlSafeStr = (str:string):string => {
return str.replace(/<[^>]+>/g, "")
}


interface params {
[propertyName: string]: string | number
}
/* 解析url引數 */
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 ''
}
}

以上是幾個比較簡單的案例, 方便大家入門和理解, 實際工作中場景會更復雜, 但是掌握了基本宣告和定義模式, 基本可以解決大部分ts宣告問題. 作為一名前端工程師,typescript的意義很大,雖然它增加了程式設計的複雜度和學習成本, 但是長遠來說, 對於團隊的編碼規範, 問題定位, 專案維護和程式碼管理的角度確實有不少積極作用, 所以學習typescript刻不容緩.

更多內容請訪問:諾頓教育