在使用默认值对空对象进行解构时出现打字稿类型错误?

时间:2019-02-06 17:02:49

标签: javascript reactjs typescript ecmascript-6

我正在用ReactJS + Typescript编写一个Web应用程序。我有一个如下定义的功能组件。

我的问题如下:在道具中,对于属性exercise,父组件正在传递一个对象,该对象要么初始化为空,要么为我指定的某种类型,{{1 }}。然后Typescript引发以下错误:

Exercise
[ts] Property 'description' does not exist on type '{} | Exercise'

如何重构它,以便如果对象确实为空,它将使用默认值,否则,使用传递的值?

编辑:添加了我使用的其他道具

[ts] Property 'title' does not exist on type '{} | Exercise'

2 个答案:

答案 0 :(得分:1)

我认为类似的事情应该起作用

type Exercise = {
  description: string
  id: string
  muscles: string
  title: string
}

type Props = {
  exercise: Partial<Exercise>
}

const Exercises = (props: Props) => {
    const exercice = {
      description:'Please select an exercise',
      title: 'Welcome!', 
      ...props.exercise
    }

    return (
        <Grid container>
          <Grid item sm>
            <Paper>
              <Typography variant="h4">{exercice.title}</Typography>
              <Typography variant="subtitle1">{exercice.description}</Typography>
            </Paper>
          </Grid>
        </Grid>
    )
}

编辑:对齐代码

答案 1 :(得分:1)

所以总的来说,我认为您的API设计不适用于此组件。您基本上是在将运动实体误用为某些默认的“欢迎消息”,这很容易导致该组件的使用者使用。

我要做的是在没有锻炼的情况下提供这些介绍性默认值,但是绝对不会使用运动道具来分配这些默认值。

接下来,不要使用{},它不是空对象(您可以像在https://github.com/Hotell/rex-tils/blob/master/src/guards/types.ts#L39之后那样定义空对象)。在TS 3.0之前,它曾经是底部类型(现在unknown是底部类型)。这是什么意思? {}可以是除null / undefined以外的任何值:

// all of this is valid !
let foo: {} = 1231
foo = true
foo = { baz: 'bar' }
foo = [1,2,3]

如果您真的想支持将“空”非原始数据类型传递给组件,请使用null

type Props = {
  category: string
  children?: never
  // Maybe type
  exercise: null | Exercise
  exercises: [string, Exercise[]][]
  onSelect: (id: string) => void
}

无论如何,如果您真的想保持API原样。您有以下选择:

  1. “提取”默认为常量,需要将其强制转换为“锻炼”
const defaultExercise = {
  description: 'Please select an exercise',
  title: 'Welcome!',
} as Exercise
  1. 您需要在函数默认参数之外键入狭窄的运动道具,因为在函数参数内无法实现
const Exercises = ({ exercises, category, onSelect, exercise }: Props) => {
  // $ExpectType Exercise
  const { title, description } = exercise ? exercise : defaultExercise

  return <>your markup</>
}

现在,尽管这样做有效,但给您错误的假设。由于您的exercise可能是不完整的(如果使用默认值),可能会导致运行时错误。您将需要通过警卫(如果是三元的话)来进一步缩小类型。

您可以通过一些类型映射在类型级别上改善这种情况:

// $ExpectType  { description: string, title: string, id?: string, muscles?: string }
const defaultExercise = {
  description: 'Please select an exercise',
  title: 'Welcome!',
} as Partial<Exercise> & Required<Pick<Exercise, 'description' | 'title'>>

如果您在组件中使用idmuscles,则使用该类型,您将获得正确的类型,因为它们可能是未定义的,从而正确地反映了我们的三元组

const { 
  title, //$ExpectType string 
  description, //$ExpectType string
  id, //$ExpectType string | undefined  
} = exercise ? exercise : defaultExercise