本文内容摘自:三千字讲清TypeScript与React的实战技巧
vscode报错:[ts] 对象可能“未定义”的解决方法
- 加**
!**,它的作用就是告诉编译器这里不是undefined,从而避免报错- 使用三目运算符做一个简单的判断
- 使用TypeScript的高级类型,编译器自己推导出这里的类型不是undefined
默认属性
React中有时候会运用很多默认属性,尤其是在我们编写通用组件的时候,之前我们介绍过一个关于默认属性的小技巧,就是利用class来同时声明类型和创建初始值。
再回到我们这个项目中,假设我们需要通过props来给input组件传递属性,而且需要初始值,我们这个时候完全可以通过class来进行代码简化。
// props.type.tsinterface InputSetting {placeholder?: stringmaxlength?: number}export class TodoInputProps {public handleSubmit: (value: string) => voidpublic inputSetting?: InputSetting = {maxlength: 20,placeholder: '请输入todo',}}
再回到TodoInput组件中,我们直接用class作为类型传入组件,同时实例化类,作为默认属性。

用class作为props类型以及生产默认属性实例有以下好处:
- 代码量少:一次编写,既可以作为类型也可以实例化作为值使用
- 避免错误:分开编写一旦有一方造成书写错误不易察觉
这种方法虽然不错,但是之后我们会发现问题了,虽然我们已经声明了默认属性,但是在使用的时候,依然显示inputSetting可能未定义。

在这种情况下有一种最快速的解决办法,就是加**!**,它的作用就是告诉编译器这里不是undefined,从而避免报错。

如果你觉得这个方法过于粗暴,那么可以选择三目运算符做一个简单的判断:

如果你还觉得这个方法有点繁琐,因为如果这种情况过多,我们需要额外写非常多的条件判断,而更重要的是,我们明明已经声明了值,就不应该再做条件判断了,应该有一种方法让编译器自己推导出这里的类型不是undefined,这就涉及到一些高级类型了。
利用高级类型解决默认属性报错
我们现在需要先声明defaultProps的值:
const todoInputDefaultProps = {
inputSetting: {
maxlength: 20,
placeholder: '请输入todo',
}
}
接着定义组件的props类型
type Props = {
handleSubmit: (value: string) => void
children: React.ReactNode
} & Partial<typeof todoInputDefaultProps>
Partial的作用就是将类型的属性全部变成可选的,也就是下面这种情况:
{
inputSetting?: {
maxlength: number;
placeholder: string;
} | undefined;
}
那么现在我们使用Props是不是就没有问题了?
export class TodoInput extends React.Component<Props, State> {
public static defaultProps = todoInputDefaultProps
...
public render() {
const { itemText } = this.state
const { updateValue, handleSubmit } = this
const { inputSetting } = this.props
return (
<form onSubmit={handleSubmit} >
<input maxLength={inputSetting.maxlength} type='text' value={itemText} onChange={updateValue} />
<button type='submit' >添加todo</button>
</form>
)
}
...
}
我们看到依旧会报错:

其实这个时候我们需要一个函数,将defaultProps中已经声明值的属性从『可选类型』转化为『非可选类型』。
我们先看这么一个函数:
const createPropsGetter = <DP extends object>(defaultProps: DP) => {
return <P extends Partial<DP>>(props: P) => {
type PropsExcludingDefaults = Omit<P, keyof DP>
type RecomposedProps = DP & PropsExcludingDefaults
return (props as any) as RecomposedProps
}
}
这个函数接受一个defaultProps对象,<DP extends object>这里是泛型约束,代表DP这个泛型是个对象,然后返回一个匿名函数。
再看这个匿名函数,此函数也有一个泛型P,这个泛型P也被约束过,即<P extends Partial<DP>>,意思就是这个泛型必须包含可选的DP类型(实际上这个泛型P就是组件传入的Props类型)。
接着我们看类型别名PropsExcludingDefaults,看这个名字你也能猜出来,它的作用其实是剔除Props类型中关于defaultProps的部分,很多人可能不清楚Omit这个高级类型的用法,其实就是一个语法糖:
type Omit<P, keyof DP> = Pick<P, Exclude<keyof P, keyof DP>>
而类型别名RecomposedProps则是将默认属性的类型DP与剔除了默认属性的Props类型结合在一起。
其实这个函数只做了一件事,把可选的defaultProps的类型剔除后,加入必选的defaultProps的类型,从而形成一个新的Props类型,这个Props类型中的defaultProps相关属性就变成了必选的。
这个函数可能对于初学者理解上有一定难度,涉及到TypeScript文档中的高级类型,这算是一次综合应用。
完整代码如下:
import * as React from 'react'
interface State {
itemText: string
}
type Props = {
handleSubmit: (value: string) => void
children: React.ReactNode
} & Partial<typeof todoInputDefaultProps>
const todoInputDefaultProps = {
inputSetting: {
maxlength: 20,
placeholder: '请输入todo',
}
}
export const createPropsGetter = <DP extends object>(defaultProps: DP) => {
return <P extends Partial<DP>>(props: P) => {
type PropsExcludingDefaults = Omit<P, keyof DP>
type RecomposedProps = DP & PropsExcludingDefaults
return (props as any) as RecomposedProps
}
}
const getProps = createPropsGetter(todoInputDefaultProps)
export class TodoInput extends React.Component<Props, State> {
public static defaultProps = todoInputDefaultProps
constructor(props: Props) {
super(props)
this.state = {
itemText: ''
}
}
public render() {
const { itemText } = this.state
const { updateValue, handleSubmit } = this
const { inputSetting } = getProps(this.props)
return (
<form onSubmit={handleSubmit} >
<input maxLength={inputSetting.maxlength} type='text' value={itemText} onChange={updateValue} />
<button type='submit' >添加todo</button>
</form>
)
}
private updateValue(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({ itemText: e.target.value })
}
private handleSubmit(e: React.FormEvent<HTMLFormElement>) {
e.preventDefault()
if (!this.state.itemText.trim()) {
return
}
this.props.handleSubmit(this.state.itemText)
this.setState({itemText: ''})
}
}
