直接上需求
假设你的团队都需要使用一个你写出来的utils,就比如转化一些元素元素为元素数组,这个方法称之为convertToArray
如果写js 我们当然可以洋洋洒洒这样就完事
js
function convertToArray(...input) {
return [...input]
}
const output = convertToArray('hello', 'world')
const output2 = convertToArray(2)
const output3 = convertToArray({ name: 'herry' })
如果代码在ts文件中,显然是会报警告的
如果要应付一下这个报错当然也很简单,把prettier的ts any直接ignore也行
也可以直接添加类型
ts
const convertToArray = (...input: (string | number | object)[]): (string | number | object)[] => [
...input,
]
但是是硬编码不够灵活,这个时候就比较适合上泛型。
泛型出场
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
在函数名之后 参数之前加上一对尖括号<Type1, Type2, ...>,一般简写为T,当然这都只是一个参数名称而已
ts
function convertToArray<T>(...input : T[]): T[] {
return [...input]
}
const output = convertToArray('hello' ,'world')
const output2 = convertToArray(2, 1, 3, 7)
const output3 = convertToArray({ name: 'herry' })
或者写成箭头函数,在等号之后 参数之前加尖括号<Type1, Type2, ...>
ts
const convertToArray = <T>(...input: T[]): T[] => [...input]
添加类型
你当然可以显式给参数添加类型
ts
const output = convertToArray<string>('hello', 'world')
一般情况下ts可以直接隐式推断出参数类型,就没有必要再添加
但是如果传递的参数类型不同也会报错
加深巩固
趁热再来个需求,返回数组元素的下标(不考虑边界情况)
- 一般来说数组中的元素都一样,所以我们可以共用一个类型参数T
- 数组下标肯定是number类型,返回值可以写死,当然ts其实会做隐式推断
js
function getIndexOfItem<T>(array: T[], arrayItem: T): number {
return array.findIndex(item => item === arrayItem)
}
const getIndexOfItem = <T>(array: T[], arrayItem: T) => array.findIndex(item => item === arrayItem)
多个类型参数
多个类型参数只需要在<>中添,和函数参数类似
ts
const createArrayPair = <T, S>(input: T, input2: S): [T, S] => {
return [input, input2]
}
const output = createArrayPair('hello', 1024)
在tsx中实操
TODO:假设一个简单需求, 实现一个主题选择器
tsx
import { useState } from 'react'
import './theme.css'
export default function Theme() {
const [selectedTheme, setTheme] = useState('light')
const themesOptions = ['light', 'dark', 'system']
return (
<div className="container">
<h2>主题设置</h2>
<ul>
{themesOptions.map(theme => (
<li
key={theme}
onClick={() => setTheme(theme)}
className={theme === selectedTheme ? 'selected' : ''}
>
{theme}
</li>
))}
</ul>
<div>
我选择主题的是 <strong>{selectedTheme}</strong>
</div>
</div>
)
}
代码很简单就不啰嗦了,虽然没有一点类型注解没有一点ts的痕迹但是在tsx运行良好,其原因是ts会隐式推断
子组件进阶
TODO:你需要将ul
用子组件包裹(也就是上面的第9行到19行)
将第9行到19行替换为
tsx
<ThemeOptions themesOptions={themesOptions} selectedTheme={selectedTheme} setTheme={setTheme}/>
ThemeOptions就定义为
tsx
function ThemeOptions({
selectedTheme,
themesOptions,
setTheme,
}: {
selectedTheme: string
themesOptions: string[]
setTheme: (theme: string) => void
}) {
return (
<ul>
{themesOptions.map(theme => (
<li
key={theme}
onClick={() => setTheme(theme)}
className={theme === selectedTheme ? 'selected' : ''}
>
{theme}
</li>
))}
</ul>
)
}
其实就写了硬参数类型,不具备通用型
泛型改造
tsx
type ThemeOptionsProps<T> = {
selectedTheme: T
themesOptions: T[]
setTheme: (theme: T) => void
}
function ThemeOptions<T>({ selectedTheme, setTheme, themesOptions }: ThemeOptionsProps<T>) {
return {}
}
显示报错,根据提示加上类型约束就可以了ThemeOptions<T extends React.ReactNode>
表示ThemeOptions组件的参数必须是React.ReactNode类型的 当然,如果图简便直接将theme转化成字符串{String(theme)}
也没问题, 这样可以确保theme是ReactNode的一种有效形式。但ReactNode
是什么类型呢
ReactNode类型
通过查看react声明文件,得出 ReactNode 的类型如下:
js
type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined;
type ReactChild = ReactElement | ReactText;
interface ReactNodeArray extends Array<ReactNode> {}
type ReactFragment = {} | ReactNodeArray;
type ReactText = string | number;
感觉像是在套娃。。 分析一下
-
联合类型ReactNode包括6个,其中ReactChild也是联合类型,包含ReactElement 和 ReactText
-
而页面上的JSX.Element 又通过执行 React.createElement 或是转译 JSX 而来
- JSX.Element 是一个 ReactElement, react类型定义如下
jsxdeclare global { namespace JSX { interface Element extends React.ReactElement<any, any> { } } }
- 分析大半天居然是any。。但这个过程还是要有的
-
而从
type ReactText = string | number;
可以看出string
是符合ReactText
类型当然也就符合ReactNode
类型
所以,图简便直接将theme转化成字符串{String(theme)}
也没问题
extends类型约束
那么接下来我们来研究一下 ThemeOptions<T extends React.ReactNode>
<T extends React.ReactNode>
将ThemeOptions的参数类型限定为React.ReactNode
,传参时ts将theme推导为string
类型 而string符合ReactNode类型
关于extends用法网上有很多文章,这里就不再赘述辽