简体   繁体   中英

Generic typescript with optional extends

How to make generic ApiResBook type work with optional props using extends for input check?

Sandbox .

I have this main types, same as database fields:

// Main types as in database - should't be changed
type Book = {
  id: string
  title: string
  visible: boolean
  author: string
}

type Author = {
  id: string
  name: string
}

For api fetch response i need generic type that will shape object based on requested fields

// Inhereted from types from main
type BookFields = keyof Book
type AuthorFields = keyof Author

// type for generating expected fetch response from API
type ApiResBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | undefined = undefined,
> = {
  book: Pick<Book, PickedBookFields> & {
    author?: PickedAuthorFields extends AuthorFields ? Pick<Author, PickedAuthorFields> : undefined
  }
}

// fetch example of usage
async function fn() {

  const fetchAPI = <ExpectedData = any>(
    apiAction: string,
    body: any
  ): Promise<{ data: ExpectedData } | { error: true }> => {
    return new Promise((resolve) => {
      fetch(`api`, body)
        .then((raw) => raw.json())
        .then((parsed: { data: ExpectedData } | { error: true }) => resolve(parsed))
        .catch((err) => {
          console.log(err)
        })
    })
  }

  // response type is { error: true  } | {data: { book: { id: string } } }
  const response = await fetchAPI<ApiResBook<'id'>>('smth', {}) 
}

Problem is with generic ApiResBook type, i don't know how to make some generic types optional. Test examples included:

//tests
type BookOnly = ApiResBook<'id'>
type BookWithAuthor = ApiResBook<'id', 'name'>

// should be ok
const bookOnly: BookOnly = { book: { id: '1' } }
const bookWithAuthor: BookWithAuthor = { book: { id: '1', author: { name: 'Max' } } }

// should be error
type BookOnly2 = ApiResBook<'propFoesntExist'>
const bookOnlyError: BookOnly = { book: { id: '1', author: {name: 'Max'} } } 
const bookWithoutAuthorError: BookWithAuthor = {book: {id: '1'}} 

Resolved this myself :/ Seems to work fine.

type ApiResBook<
  PickedBookFields extends BookFields,
  PickedAuthorFields extends AuthorFields | undefined = undefined,
> = {
  book: Pick<Book, PickedBookFields> & {
    author: PickedAuthorFields extends AuthorFields ? Pick<Author, PickedAuthorFields> : undefined
  }
}

Sandbox .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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