简体   繁体   English

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

[英]Typescript type error while destructuring on empty object with default values?

I'm writing a web application in ReactJS + Typescript. 我正在用ReactJS + Typescript编写一个Web应用程序。 I've a functional component defined like below. 我有一个如下定义的功能组件。

My problem is the following: in the props, for the property exercise , the parent component is passing an object, either initialized empty or of a certain type that I specify, Exercise . 我的问题是:在道具中,属性exercise ,父组件传递对象,无论是初始化空的,或者我指定,某种类型的Exercise Then Typescript raises the following errors: 然后Typescript引发以下错误:

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

How could I refactor it so that if the object is indeed empty, it will use the default values, and otherwise, use the values passed? 我如何重构它,以便如果对象确实为空,它将使用默认值,否则使用传递的值?

EDIT: Added the other props that I use 编辑:添加了我使用的其他道具

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

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

const Exercises = ({
  exercises,
  category,
  onSelect,
  exercise: {
    description = 'Please select an exercise',
    title = 'Welcome!'
  }
}: Props) => (
   <Grid container>
     <Grid item sm>
       {/* Some stuff going on with exercises, category and onSelect */ }
     </Grid>
     <Grid item sm>
       <Paper>
         <Typography variant="h4">{title}</Typography>
         <Typography variant="subtitle1">{description}</Typography>
       </Paper>
     </Grid>
   </Grid>
)

I think something similar to this should work 我认为类似的东西应该起作用

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>
    )
}

edit: align code 编辑:对齐代码

So overall I don't think your API design is correct for this component. 因此,总的来说,我认为您的API设计不适用于此组件。 You're basically misusing exercise entity as some default "Welcome message stuff", which is rather miss leading to consumers of this component. 您基本上是在将练习实体误用为某些默认的“欢迎消息”,这很容易导致该组件的使用者使用。

What I would do, is to provide these intro defaults when there is no exercise present, but would definitely not use exercise prop to assign those defaults. 我要做的是在没有练习时提供这些介绍性默认值,但绝对不会使用练习道具来分配这些默认值。

Next thing, don't use {} , that's not empty object (you can define empty object like following https://github.com/Hotell/rex-tils/blob/master/src/guards/types.ts#L39 ) . 接下来,不要使用{} ,它不是空对象(您可以像下面的https://github.com/Hotell/rex-tils/blob/master/src/guards/types.ts#L39那样定义空对象) 。 It used to be a bottom type prior to TS 3.0 ( now unknown is bottom type ). 在TS 3.0之前,它曾经是底部类型(现在unknown是底部类型)。 What does it mean? 这是什么意思? {} can be anything except null/undefined: {}可以是null / undefined以外的任何值:

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

Also if you really wanna support passing "empty" non primitive data types to components, prefer null : 另外,如果您真的想支持将“空”非原始数据类型传递给组件,则最好使用null

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

Anyways if your really wanna keep your API as is. 无论如何,如果您确实想保持API原样。 You have following option: 您有以下选择:

  1. Extract defaults to constant which needs to be cast to Exercise 提取默认为常量,需要将其强制转换为练习
const defaultExercise = {
  description: 'Please select an exercise',
  title: 'Welcome!',
} as Exercise
  1. you need to type narrow exercise prop outside function default parameter, as that's not possible within function params 您需要在函数默认参数之外键入狭窄的运动道具,因为在函数参数中这是不可能的
const Exercises = ({ exercises, category, onSelect, exercise }: Props) => {
  // $ExpectType Exercise
  const { title, description } = exercise ? exercise : defaultExercise

  return <>your markup</>
}

Now while this works it gives you false assumptions. 现在,尽管这样做有效,但给您错误的假设。 As your exercise might be a partial one (if defaults are used), which may lead to runtime errors. 因为您的exercise可能只是部分exercise (如果使用默认设置),则可能会导致运行时错误。 You'll need additional type narrowing via guards ( if, ternary ). 您将需要通过警卫(如果是三元)来进一步缩小类型。

You can improve this situation on type level, by some type mappings: 您可以通过一些类型映射在类型级别上改善这种情况:

// $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'>>

With that type if you would use id or muscles within your component, you'll get proper types as they might be undefined, which mirrors correctly our ternary 如果您要在组件中使用idmuscles ,则使用该类型,您将获得正确的类型,因为它们可能是未定义的,从而正确地反映了我们的三元

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM