在上一篇的文章中,学习了 TypeScript 的基本知识,以及 TS 在 React中的基本使用方法。在本文中,我们深入了解 TS在 React 中的实践。本文将采用 ant-design 作为基础的 UI 框架。
一、用 TS 创建 React 的 SFC(无状态组件)
在本次实践中,我们基于 antd 创建一个统一的 Input Form组件。该组件可以同时支持基本 Input、InputNumber和 Input.Text,我们将定义 elementTypeEnum来判断该使用哪个输入组件。
1、引入 antd 的 Form, Input, InputNumber组件(HXInputItem)
1 2 3 4
| import { Form, Input, InputNumber } from "antd";
|
2、定义输入类型的枚举
1 2 3 4 5 6
| enum elementTypeEnum { normal = "normal", number = "number", text = "text" }
|
3、创建 HXInputItem 的属性声明
1 2 3 4 5 6 7 8 9 10 11
| interface HXInputProps { label: string; name: string; getFieldDecorator: (name, options) => any; fieldDecoratorOptions: object; formLayout?: object; inputProps?: object; formItemProps?: object; elementType?: elementTypeEnum; }
|
4、创建 HXInputItem的 SFC
这一步非常重要,跟普通 React创建 SFC 有点区别。具体用法如下:
1 2 3
| const HXInputItem: React.SFC<HXInputProps> = props => {}
|
可以看到在 TS 创建 SFC 需要使用 React.SFC属性,否则无法创建成功。
5、填充我们的 HXInputItem SFC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const { label, name, getFieldDecorator, formLayout, fieldDecoratorOptions, inputProps, formItemProps, elementType } = props; return ( <FormItem {...formLayout} label={<span className="form-label-text">{label}</span>} colon={false} hasFeedback {...formItemProps} > {getFieldDecorator(name, fieldDecoratorOptions)( elementType === "normal" ? ( <Input {...inputProps} /> ) : elementType === "number" ? ( <InputNumber {...inputProps} /> ) : ( <Input.TextArea rows={5} key={name} {...inputProps} /> ) )} </FormItem> );
|
通过elementType的值,调用不同的antd 中的输入组件,在项目中只引入当前SFC即可,让开发更加便利,快速和统一。若在以后的开发过程,存在特殊的需要,稍微兼容改造一下该组件即可,减少代码量。
6、声明该 SFC 的默认属性
1 2 3 4 5
| HXInputItem.defaultProps = { elementType: elementTypeEnum.normal };
|
7、导出该组件
1 2
| export default HXInputItem;
|
二、用TS 加载远程数据(fetch Api)
1、定义 http Response 的 Interface
1 2 3 4 5 6 7 8 9 10 11 12 13
| export default interface IRestRes { message?: string | null; code?: number; filename?: string | null; data?: any; loginAccount?: string; name?: string; roles?: Array<string>; token?: string; type?: string | null; total?: number; }
|
2、定义统一的获取 JSON 数据的 fetch 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
export const initialFetch = (url:string, options:Object): Promise<IRestRes> => { return new Promise((resolve, reject) => { const finalOpts = { headers: new Headers({ Authorization: myToken, }), ...options }; fetch(url, finalOpts) .then(res => { console.log(res); return res.text(); }) .then(text => { const resJson = text ? JSON.parse(text) : {}; resolve(resJson as IRestRes); }) .catch(e => { reject(e); }); }); };
|
3、使用 fetch 的 get方法,请求JSON 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
export const fetchGetRequest = (api:string, params?:Object): Promise<IRestRes> => { return new Promise((resolve, reject) => { let url = new URL(api); url.search = new URLSearchParams(params); initialFetch(url, { method: "GET" }) .then(response => { console.log(response, 766); resolve(response); }) .catch(e => { reject(e); }); }); };
|
3、使用 fetch 的 post,提交表单(FormData)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
|
export const fetchPostForm = (url:string, values:object={}): Promise<IRestRes> => { return new Promise((resolve, reject) => { const formData = new FormData(); Object.keys(values).forEach(key => { const vl = values[key]; if (vl) { if (vl.constructor !== Array) { formData.append(key, vl); } else { const fileList = vl; for (let i = 0; i < fileList.length; i++) { const item = fileList[i]; formData.append(key, item); } } } }); initialFetch( url, { method: "POST", body: formData }) .then(response => { console.log(response, 766); resolve(response); }) .catch(e => { reject(e); }); }); };
|
4、使用 fetch 获取文件流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
export const fetchGetFile = (api:string, params?:obejct): Promise<IRestRes> => { let url = new URL(api); return new Promise((resolve, reject) => { const finalOps = { headers: new Headers({ Authorization: myToken, "Access-Control-Allow-Origin": BASE_URL, }), ...params }; fetch(url, finalOps) .then(async res => { const contentDisposition = res.headers.get("Content-Disposition"); const myBlob = await res.blob(); const Ires: IRestRes = { data: myBlob, type: res.headers.get("Content-Type"), filename: decodeURI( String(contentDisposition).replace("attachment;filename=", "") || "" ) }; resolve(Ires); }) .catch(e => { reject(e); }); }); };
|
三、 使用 TS创建普通的 React 组件
先上完整的组件代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| import * as React from "react"; import { observer, inject } from "mobx-react"; import IStore from "../../interface/IStore"; import SearchType from "../../enum/SearchType";
interface SearchBoxProps { store?: IStore; placeholder?: string; } interface SearchBoxState { focus: boolean; focusLock: boolean; searchField: SearchType; words: string; searching: boolean; } @inject("store") @observer export default class SearchBox extends React.Component<SearchBoxProps, SearchBoxState> { constructor(props) { super(props); this.state = { focus: false, focusLock: false, searchField: SearchType.SEARCH_FIELD_ALL, words: "", searching: false }; }
static defaultProps : { placeholder: '输入关键词并搜索' } onFocus = () => { this.setState({ focus: true }); }; onBlur = () => { if (this.state.focusLock) { return; } this.setState({ focus: false }); }; onMouseEnter = () => { this.setState({ focusLock: true }); }; onMouseLeave = () => { this.setState({ focusLock: false }); }; render() { return ( <div onFocus={this.onFocus} onBlur={this.onBlur} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} /> ); } }
|
有看出与普通 JS 创建React 组件的区别了吗?
(1)、使用泛型定义组件的 props 与 state;
(2)、创建默认的 state 值,必须在SearchBoxState已经定义,否则出错。
(3)、 defaultProps 需要使用 static 修饰
四、创建 d.ts 文件
在项目根目录创建 typings 的文件夹,在 typings 中创建 index.d.ts
文件中输入以下内容
1 2 3 4
| interface Promise<T> { finally: (callback) => Promise<T>; }
|
由于我们在使用 fetch 请求数据时,返回的数据使用 Promise<IRestRes>返回。如果不创建上门的 d.ts,编辑器在编译时可能无法Promise<IRestRes>。为什么呢?
因为:
typings的存在是为了方便TypeScript识别、编译、智能提示TypeScript无法识别的JS库的特性和语法。
五、总结
TS在 React 的使用远不止这些,本文只是抛砖引玉,让我们对 TS 的了解更加深入,而且更是感叹 TS 的强大。