Tìm kiếm trang web

typescript - Làm thế nào để suy ra các đối số dựa trên đối số đã được cung cấp cho hàm đầu tiên?


Tôi đang cố gắng tạo một hàm tiện ích sẽ lấy một value duy nhất từ một enum và sẽ suy ra các tham số của hàm trả về dựa trên một interface với một trường type khớp với value.

Tôi đang gặp khó khăn trong việc ánh xạ value và tìm giao diện với một trường type phù hợp với value

enum ActionTypes {
  One = 'One',
  Two = 'Two',
  Three = 'Three'
}

interface Action1 {
  type: ActionTypes.One
}

interface Action2 {
  type: ActionTypes.Two
  meta: {
    fieldName: '2'
  }
}

interface Action3 {
  type: ActionTypes.Three
  payload: number[]
  meta: {
    fieldName: '3'
  }
}

type AllActions =
  | Action1
  | Action2
  | Action3

const createAction = <T>(type: T) => (
  payload?: //infer payload from AllActions.type === T,
  meta?: //infer meta from AllActions.type === T,
  ) => ({
  type,
  ...payload && { payload },
  ...meta && { meta }
})

const action1 = createAction(ActionTypes.One)
action1()

const action2 = createAction(ActionTypes.Two)
action2(undefined, {
  meta: {
    fieldName: '2'
  }
})

const action3 = createAction(ActionTypes.Three)
action3([1, 2, 3], {
  meta: {
    fieldName: '3'
  }
})

Sân chơi TS

Câu trả lời #1

Bạn có thể nhập hàm, nhưng có một số điều chúng ta cần xem xét:

  1. T nên mở rộng AllActions['type'] để nắm bắt kiểu ký tự chuỗi của giá trị được truyền vào.

  2. Để trích xuất hành động liên quan đến type: T chúng ta cần sử dụng kiểu điều kiện đã định nghĩa sẵn Extract<AllActions, { type: T }>

  3. Vì theo T, hàm bên trong có thể có số lượng tham số thay đổi, chúng ta sẽ cần sử dụng tuple trong tham số còn lại để trải số lượng tham số thay đổi vào hàm kết quả.

  4. Sử dụng tuple trong tham số rest với các tuple khai báo thủ công sẽ dẫn đến việc mất tên tham số, vì vậy thay vì xây dựng một tuple như [P, M] và trải nó ra, chúng ta sẽ xây dựng một chữ ký hàm, trích xuất các tham số dưới dạng tuple, và trải tuple đó trở lại vào một tham số rest ( function fn(...a: Parameters<(x: number) => void>) {} sẽ có một tham số số duy nhất tên là x, trong khi function fn(...a:[number]) {} sẽ có một tham số số duy nhất tên là a_0, điều này không lý tưởng)

  5. Chúng ta có thể sử dụng một kiểu điều kiện tùy chỉnh để xây dựng chữ ký mong muốn dựa trên sự tồn tại của payload và meta.

  6. Vì các loại điều kiện chứa các tham số kiểu chưa được giải quyết khá tệ khi viết phần triển khai, chúng ta sẽ sử dụng một chữ ký triển khai riêng cho hàm bên trong.

Khi kết hợp tất cả những điều này lại, chúng ta có:

type Payload<T> = T extends { payload: infer P } ? P : undefined;
type Metadata<T> = T extends { meta: infer M } ? { meta: M } : undefined;
type CreateActionParameteres<A> =  /*4*/ Parameters<
  /*5*/ A extends { payload: infer P, meta: infer M } ? /*4*/ (payload: P, meta: { meta: M }) => void :
  /*5*/ A extends { payload: infer P } ? /*4*/ (payload: P) => void :
  /*5*/ A extends { meta: infer M } ? /*4*/ (payload: undefined, meta: { meta: M }) => void :
  () => void>

const createAction = <T extends AllActions['type'] /*1*/ >(type: T) =>
{
  function inner(...a: CreateActionParameteres</*2*/ Extract<AllActions, { type: T }>>): /*2*/ Extract<AllActions, { type: T }>
  function inner(payload?: Payload<AllActions>, meta?: Metadata<AllActions>) { /*6*/
    return {
      type,
      ...payload && { payload },
      ...meta && { meta }
    };
  }
  return inner;
}

Liên kết Sân chơi