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