新聞中心
大家好,我是 CUGGZ。

公司主營(yíng)業(yè)務(wù):網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計(jì)、移動(dòng)網(wǎng)站開(kāi)發(fā)等業(yè)務(wù)。幫助企業(yè)客戶(hù)真正實(shí)現(xiàn)互聯(lián)網(wǎng)宣傳,提高企業(yè)的競(jìng)爭(zhēng)能力。創(chuàng)新互聯(lián)是一支青春激揚(yáng)、勤奮敬業(yè)、活力青春激揚(yáng)、勤奮敬業(yè)、活力澎湃、和諧高效的團(tuán)隊(duì)。公司秉承以“開(kāi)放、自由、嚴(yán)謹(jǐn)、自律”為核心的企業(yè)文化,感謝他們對(duì)我們的高要求,感謝他們從不同領(lǐng)域給我們帶來(lái)的挑戰(zhàn),讓我們激情的團(tuán)隊(duì)有機(jī)會(huì)用頭腦與智慧不斷的給客戶(hù)帶來(lái)驚喜。創(chuàng)新互聯(lián)推出麗水免費(fèi)做網(wǎng)站回饋大家。
在單獨(dú)使用 TypeScript 時(shí)沒(méi)有太多坑,不過(guò)和 React 結(jié)合之后就會(huì)復(fù)雜很多。下面就來(lái)看一看如何在 React 項(xiàng)目中優(yōu)雅的使用 TypeScript!
一、組件聲明
在React中,組件的聲明方式有兩種:函數(shù)組件和類(lèi)組件, 來(lái)看看這兩種類(lèi)型的組件聲明時(shí)是如何定義TS類(lèi)型的。
1. 類(lèi)組件
類(lèi)組件的定義形式有兩種:React.Component
和 React.PureComponent
,它們都是泛型接口,接收兩個(gè)參數(shù),第一個(gè)是props類(lèi)型的定義,第二個(gè)是state類(lèi)型的定義,這兩個(gè)參數(shù)都不是必須的,沒(méi)有時(shí)可以省略:
interface IProps {
name: string;
}
interface IState {
count: number;
}
class App extends React.Component {
state = {
count: 0
};
render() {
return (
{this.state.count}
{this.props.name}
);
}
}
export default App; React.PureComponent
也是差不多的:
class App extends React.PureComponent{}
React.PureComponent是有第三個(gè)參數(shù)的,它表示getSnapshotBeforeUpdate的返回值。
那PureComponent和Component 的區(qū)別是什么呢?它們的主要區(qū)別是PureComponent中的shouldComponentUpdate 是由自身進(jìn)行處理的,不需要我們自己處理,所以PureComponent可以在一定程度上提升性能。
有時(shí)候可能會(huì)見(jiàn)到這種寫(xiě)法,實(shí)際上和上面的效果是一樣的:
import React, {PureComponent, Component} from "react";
class App extends PureComponent {}
class App extends Component {} 那如果定義時(shí)候我們不知道組件的props的類(lèi)型,只有在調(diào)用時(shí)才知道組件類(lèi)型,該怎么辦呢?這時(shí)泛型就發(fā)揮作用了:
// 定義組件
class MyComponentextends React.Component
{
internalProp: P;
constructor(props: P) {
super(props);
this.internalProp = props;
}
render() {
return (
hello world
);
}
}
// 使用組件
type IProps = { name: string; age: number; };name="React" age={18} />; // Success name="TypeScript" age="hello" />; // Error
2. 函數(shù)組件
通常情況下,函數(shù)組件我是這樣寫(xiě)的:
interface IProps {
name: string
}
const App = (props: IProps) => {
const {name} = props;
return (
hello world
{name}
);
}
export default App;除此之外,函數(shù)類(lèi)型還可以使用React.FunctionComponent
來(lái)定義,也可以使用其簡(jiǎn)寫(xiě)React.FC
,兩者效果是一樣的。它是一個(gè)泛型接口,可以接收一個(gè)參數(shù),參數(shù)表示props的類(lèi)型,這個(gè)參數(shù)不是必須的。它們就相當(dāng)于這樣:
type React.FC= React.FunctionComponent
最終的定義形式如下:
interface IProps {
name: string
}
const App: React.FC = (props) => {
const {name} = props;
return (
hello world
{name}
);
}
export default App; 當(dāng)使用這種形式來(lái)定義函數(shù)組件時(shí),props中默認(rèn)會(huì)帶有children屬性,它表示該組件在調(diào)用時(shí),其內(nèi)部的元素,來(lái)看一個(gè)例子,首先定義一個(gè)組件,組件中引入了Child1和Child2組件:
import Child1 from "./child1";
import Child2 from "./child2";
interface IProps {
name: string;
}
const App: React.FC= (props) => {
const { name } = props;
return (
TypeScript
);
};
export default App;
Child1組件結(jié)構(gòu)如下:
interface IProps {
name: string;
}
const Child1: React.FC = (props) => {
const { name, children } = props;
console.log(children);
return (
hello child1
{name}
);
};
export default Child1; 我們?cè)贑hild1組件中打印了children屬性,它的值是一個(gè)數(shù)組,包含Child2對(duì)象和后面的文本:
使用 React.FC 聲明函數(shù)組件和普通聲明的區(qū)別如下:
- React.FC 顯式地定義了返回類(lèi)型,其他方式是隱式推導(dǎo)的;
- React.FC 對(duì)靜態(tài)屬性:displayName、propTypes、defaultProps 提供了類(lèi)型檢查和自動(dòng)補(bǔ)全;
- React.FC 為 children 提供了隱式的類(lèi)型(ReactElement | null)。
那如果我們?cè)诙x組件時(shí)不知道props的類(lèi)型,只有調(diào)用時(shí)才知道,那就還是用泛型來(lái)定義props的類(lèi)型。對(duì)于使用function定義的函數(shù)組件:
// 定義組件
function MyComponent(props: P) {
return (
{props}
);
}
// 使用組件
type IProps = { name: string; age: number; };name="React" age={18} />; // Success name="TypeScript" age="hello" />; // Error
如果使用箭頭函數(shù)定義的函數(shù)組件,直接這樣調(diào)用時(shí)錯(cuò)誤的:
const MyComponent =(props: P) {
return (
{props}
);
}
必須使用extends關(guān)鍵字來(lái)定義泛型參數(shù)才能被成功解析:
const MyComponent =(props: P) {
return (
{props}
);
}
二、React內(nèi)置類(lèi)型
1. JSX.Element
先來(lái)看看JSX.Element類(lèi)型的聲明:
declare global {
namespace JSX {
interface Element extends React.ReactElement { }
}
} 可以看到,JSX.Element是ReactElement的子類(lèi)型,它沒(méi)有增加屬性,兩者是等價(jià)的。也就是說(shuō)兩種類(lèi)型的變量可以相互賦值。
JSX.Element 可以通過(guò)執(zhí)行 React.createElement 或是轉(zhuǎn)譯 JSX 獲得:
const jsx =hello
const ele = React.createElement("div", null, "hello");
2. React.ReactElement
React 的類(lèi)型聲明文件中提供了 React.ReactElement<T>,它可以讓我們通過(guò)傳入<T/>來(lái)注解類(lèi)組件的實(shí)例化,它在聲明文件中的定義如下:
interface ReactElement= string | JSXElementConstructor
> {
type: T;
props: P;
key: Key | null;
}
ReactElement是一個(gè)接口,包含type,props,key三個(gè)屬性值。該類(lèi)型的變量值只能是兩種:null 和 ReactElement實(shí)例。
通常情況下,函數(shù)組件返回ReactElement(JXS.Element)的值。
3. React.ReactNode
ReactNode類(lèi)型的聲明如下:
type ReactText = string | number;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array{}
type ReactFragment = {} | ReactNodeArray;
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
可以看到,ReactNode是一個(gè)聯(lián)合類(lèi)型,它可以是string、number、ReactElement、null、boolean、ReactNodeArray。由此可知。ReactElement類(lèi)型的變量可以直接賦值給ReactNode類(lèi)型的變量,但反過(guò)來(lái)是不行的。
類(lèi)組件的 render 成員函數(shù)會(huì)返回 ReactNode 類(lèi)型的值:
class MyComponent extends React.Component {
render() {
return hello world
}
}
// 正確
const component: React.ReactNode = ;
// 錯(cuò)誤
const component: React.ReactNode = ; 上面的代碼中,給component變量設(shè)置了類(lèi)型是Mycomponent類(lèi)型的react實(shí)例,這時(shí)只能給其賦值其為MyComponent的實(shí)例組件。
通常情況下,類(lèi)組件通過(guò) render() 返回 ReactNode的值。
4. CSSProperties
先來(lái)看看React的聲明文件中對(duì)CSSProperties 的定義:
export interface CSSProperties extends CSS.Properties{
/**
* The index signature was removed to enable closed typing for style
* using CSSType. You're able to use type assertion or module augmentation
* to add properties or an index signature of your own.
*
* For examples and more information, visit:
* https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
*/
}
React.CSSProperties是React基于TypeScript定義的CSS屬性類(lèi)型,可以將一個(gè)方法的返回值設(shè)置為該類(lèi)型:
import * as React from "react";
const classNames = require("./sidebar.css");
interface Props {
isVisible: boolean;
}
const divStyle = (props: Props): React.CSSProperties => ({
width: props.isVisible ? "23rem" : "0rem"
});
export const SidebarComponent: React.StatelessComponent= props => (
);
這里divStyle組件的返回值就是React.CSSProperties類(lèi)型。
我們還可以定義一個(gè)CSSProperties類(lèi)型的變量:
const divStyle: React.CSSProperties = {
width: "11rem",
height: "7rem",
backgroundColor: `rgb(${props.color.red},${props.color.green}, ${props.color.blue})`
};這個(gè)變量可以在HTML標(biāo)簽的style屬性上使用:
在React的類(lèi)型聲明文件中,style屬性的類(lèi)型如下:
style?: CSSProperties | undefined;
三、React Hooks
1. useState
默認(rèn)情況下,React會(huì)為根據(jù)設(shè)置的state的初始值來(lái)自動(dòng)推導(dǎo)state以及更新函數(shù)的類(lèi)型:
如果已知state 的類(lèi)型,可以通過(guò)以下形式來(lái)自定義state的類(lèi)型:
const [count, setCount] = useState(1)
如果初始值為null,需要顯式地聲明 state 的類(lèi)型:
const [count, setCount] = useState(null);
如果state是一個(gè)對(duì)象,想要初始化一個(gè)空對(duì)象,可以使用斷言來(lái)處理:
const [user, setUser] = React.useState({} as IUser);
實(shí)際上,這里將空對(duì)象{}斷言為IUser接口就是欺騙了TypeScript的編譯器,由于后面的代碼可能會(huì)依賴(lài)這個(gè)對(duì)象,所以應(yīng)該在使用前及時(shí)初始化 user 的值,否則就會(huì)報(bào)錯(cuò)。
下面是聲明文件中 useState 的定義:
function useState(initialState: S | (() => S)): [S, Dispatch>];
// 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, Dispatch>];
/**
* 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
*/
可以看到,這里定義兩種形式,分別是有初始值和沒(méi)有初始值的形式。
2. useEffect
useEffect的主要作用就是處理副作用,它的第一個(gè)參數(shù)是一個(gè)函數(shù),表示要清除副作用的操作,第二個(gè)參數(shù)是一組值,當(dāng)這組值改變時(shí),第一個(gè)參數(shù)的函數(shù)才會(huì)執(zhí)行,這讓我們可以控制何時(shí)運(yùn)行函數(shù)來(lái)處理副作用:
useEffect(
() => {
const subscription = props.source.subscribe();
return () => {
subscription.unsubscribe();
};
},
[props.source]
);
當(dāng)函數(shù)的返回值不是函數(shù)或者effect函數(shù)中未定義的內(nèi)容時(shí),如下:
useEffect(
() => {
subscribe();
return null;
}
);
TypeScript就會(huì)報(bào)錯(cuò):
來(lái)看看useEffect在類(lèi)型聲明文件中的定義:
// Destructors are only allowed to return void.
type Destructor = () => void | { [UNDEFINED_VOID_ONLY]: never };
// NOTE: callbacks are _only_ allowed to return either void, or a destructor.
type EffectCallback = () => (void | Destructor);
// TODO (TypeScript 3.0): ReadonlyArray
type DependencyList = ReadonlyArray;
function useEffect(effect: EffectCallback, deps?: DependencyList): void;
// NOTE: this does not accept strings, but this will have to be fixed by removing strings from type Ref
/**
* `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
*/
可以看到,useEffect的第一個(gè)參數(shù)只允許返回一個(gè)函數(shù)。
3. useRef
當(dāng)使用 useRef 時(shí),我們可以訪(fǎng)問(wèn)一個(gè)可變的引用對(duì)象??梢詫⒊跏贾祩鬟f給 useRef,它用于初始化可變 ref 對(duì)象公開(kāi)的當(dāng)前屬性。當(dāng)我們使用useRef時(shí),需要給其指定類(lèi)型:
const nameInput = React.useRef(null)
這里給實(shí)例的類(lèi)型指定為了input輸入框類(lèi)型。
當(dāng)useRef的初始值為null時(shí),有兩種創(chuàng)建的形式,第一種:
const nameInput = React.useRef(null)
nameInput.current.innerText = "hello world";
這種形式下,ref1.current是只讀的(read-only),所以當(dāng)我們將它的innerText屬性重新賦值時(shí)會(huì)報(bào)以下錯(cuò)誤:
Cannot assign to 'current' because it is a read-only property.
那該怎么將current屬性變?yōu)閯?dòng)態(tài)可變得的,先來(lái)看看類(lèi)型聲明文件中 useRef 是如何定義的:
function useRef(initialValue: T): MutableRefObject ;
// 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
*/
這段代碼的第十行的告訴我們,如果需要useRef的直接可變,就需要在泛型參數(shù)中包含'| null',所以這就是當(dāng)初始值為null的第二種定義形式:
const nameInput = React.useRef(null);
這種形式下,nameInput.current就是可寫(xiě)的。不過(guò)兩種類(lèi)型在使用時(shí)都需要做類(lèi)型檢查:
nameInput.current?.innerText = "hello world";
那么問(wèn)題來(lái)了,為什么第一種寫(xiě)法在沒(méi)有操作current時(shí)沒(méi)有報(bào)錯(cuò)呢?因?yàn)閡seRef在類(lèi)型定義式具有多個(gè)重載聲明,第一種方式就是執(zhí)行的以下函數(shù)重載:
function useRef(initialValue: T|null): RefObject ;
// convenience overload for potentially undefined initialValue / call with 0 arguments
// has a default to stop it from defaulting to {} instead
/**
* `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
*/
從上useRef的聲明中可以看到,function useRef的返回值類(lèi)型化是MutableRefObject,這里面的T就是參數(shù)的類(lèi)型T,所以最終nameInput 的類(lèi)型就是React.MutableRefObject。
注意,上面用到了HTMLInputElement類(lèi)型,這是一個(gè)標(biāo)簽類(lèi)型,這個(gè)操作就是用來(lái)訪(fǎng)問(wèn)DOM元素的。
4. useCallback
先來(lái)看看類(lèi)型聲明文件中對(duì)useCallback的定義:
function useCallbackany>(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接收一個(gè)回調(diào)函數(shù)和一個(gè)依賴(lài)數(shù)組,只有當(dāng)依賴(lài)數(shù)組中的值發(fā)生變化時(shí)才會(huì)重新執(zhí)行回調(diào)函數(shù)。來(lái)看一個(gè)例子:
const add = (a: number, b: number) => a + b;
const memoizedCallback = useCallback(
(a) => {
add(a, b);
},
[b]
);
這里我們沒(méi)有給回調(diào)函數(shù)中的參數(shù)a定義類(lèi)型,所以下面的調(diào)用方式都不會(huì)報(bào)錯(cuò):
memoizedCallback("hello");
memoizedCallback(5)盡管add方法的兩個(gè)參數(shù)都是number類(lèi)型,但是上述調(diào)用都能夠用執(zhí)行。所以為了更加嚴(yán)謹(jǐn),我們需要給回調(diào)函數(shù)定義具體的類(lèi)型:
const memoizedCallback = useCallback(
(a: number) => {
add(a, b);
},
[b]
);
這時(shí)候如果再給回調(diào)函數(shù)傳入字符串就會(huì)報(bào)錯(cuò)了:
所有,需要注意,在使用useCallback時(shí)需要給回調(diào)函數(shù)的參數(shù)指定類(lèi)型。
5. useMemo
先來(lái)看看類(lèi)型聲明文件中對(duì)useMemo的定義:
function useMemo(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是非常類(lèi)似的,但是它返回的是一個(gè)值,而不是函數(shù)。所以在定義useMemo時(shí)需要定義返回值的類(lèi)型:
let a = 1;
setTimeout(() => {
a += 1 標(biāo)題名稱(chēng):使用TypeScript編寫(xiě)React的優(yōu)秀實(shí)踐!
文章路徑:http://www.dlmjj.cn/article/dhpsjso.html


咨詢(xún)
建站咨詢(xún)
