Saya ingin menentukan apakah suatu fungsi harus berisi argumen melalui antarmuka. Pustaka yang saya kembangkan memanggil banyak metode berbeda yang akan dihasilkan, dan hardcoding metode tersebut akan membutuhkan terlalu banyak pemeliharaan; jadi saya pikir tipe akan menjadi tempat yang baik untuk mendefinisikan hal-hal seperti itu.

Mungkin ini paling baik dijelaskan dengan kode. Inilah perpustakaan yang mengabstraksi beberapa API lainnya:

interface RequestInterface {
  endpoint: string
  body?: unknown
}

interface GetPosts extends RequestInterface {
  endpoint: '/posts'
  body: never
}

interface CreatePost extends RequestInterface {
  endpoint: '/posts'
  body: string
}

function Factory<R extends RequestInterface> (endpoint: R['endpoint']) {

  return (body?: R['body']): void => {

    console.log(`Hitting ${endpoint} with ${body}`)

  }
}

const myLibrary = {

  getPosts: Factory<GetPosts>('/posts'),
  createPosts: Factory<CreatePost>('/posts'),

}

myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999)      // => Correctly errors
myLibrary.createPosts()         // => I want this to error

Di atas, saya mendefinisikan endpoint dan body dari jenis permintaan tertentu di antarmuka saya. Meskipun kompiler TypeScript dengan benar menjaga saya agar tidak meneruskan tipe argumen salah, itu tidak melindungi saya dari tidak memberikan nilai saat diperlukan.

Saya mengerti mengapa TypeScript tidak error (karena metode yang didefinisikan di pabrik dapat tidak ditentukan menurut pengetikan saya), tetapi saya pikir kode di atas adalah cara yang baik untuk menggambarkan apa yang ingin saya capai: pustaka metode deklaratif cepat yang memenuhi tipe tertentu.

Solusi yang Mungkin

Jika saya ingin memperluas antarmuka saya dari dua antarmuka terpisah (satu atau yang lain) maka saya dapat mencapai sesuatu yang mendekati apa yang saya inginkan menggunakan Buat Tanda Tangan:

interface RequestInterface {
  endpoint: string
  call: () => void
}

interface RequestInterfaceWithBody {
  endpoint: string
  call: {
    (body: any): void
  }
}

interface GetPosts extends RequestInterface {
  endpoint: '/posts'
}

interface CreatePost extends RequestInterfaceWithBody {
  endpoint: '/posts'
  call: {
    (body: string): void
  }
}

function Factory<R extends RequestInterface|RequestInterfaceWithBody> (endpoint: R['endpoint']): R['call'] {

  return (body): void => {

    console.log(`Hitting ${endpoint} with ${body}`)

  }
}

const myLibrary = {

  getPosts: Factory<GetPosts>('/posts'),
  createPosts: Factory<CreatePost>('/posts'),

}

myLibrary.getPosts()            // => Correctly passes
myLibrary.getPosts('something') // => Correctly errors
myLibrary.createPosts(999)      // => Correctly errors
myLibrary.createPosts()         // => Correctly errors
myLibrary.createPosts('hi')     // => Correctly passes

Selain fakta bahwa saya harus memilih di antara dua tipe "super" sebelum memperluas apa pun, masalah utama dengan ini adalah bahwa argumen Construct Signature tidak terlalu mudah diakses.

Meskipun tidak ditunjukkan dalam contoh, tipe yang saya buat juga digunakan di tempat lain dalam basis kode saya, dan body dapat diakses (yaitu GetPosts['body']). Dengan hal di atas, tidak mudah untuk mengakses dan saya mungkin perlu membuat definisi tipe terpisah yang dapat digunakan kembali untuk mencapai hal yang sama.

0
shennan 27 November 2021, 12:06
Ada alasan khusus untuk downvote?
 – 
shennan
27 November 2021, 12:19
Apakah Anda mengaktifkan mode ketat TypeScript? Anda dapat melakukannya dengan mengatur opsi "ketat" menjadi true di tsconfig.ts.
 – 
Jesse Schokker
27 November 2021, 12:19

1 menjawab

Jawaban Terbaik

Anda hampir mencapai tempat dengan tipe awal Anda. Diperlukan dua perubahan:

  1. Jadikan body dari GetPosts dengan tipe void
  2. Buat body dari fungsi yang dikembalikan diperlukan
interface RequestInterface {
  endpoint: string;
  body?: unknown;
}

interface GetPosts extends RequestInterface {
  endpoint: "/posts";
  body: void;
}

interface CreatePost extends RequestInterface {
  endpoint: "/posts";
  body: string;
}

function Factory<R extends RequestInterface>(endpoint: R["endpoint"]) {
  return (body: R["body"]): void => {
    console.log(`Hitting ${endpoint} with ${body}`);
  };
}

const myLibrary = {
  getPosts: Factory<GetPosts>("/posts"),
  createPosts: Factory<CreatePost>("/posts"),
};

// @ts-expect-error
myLibrary.getPosts("something");
// @ts-expect-error
myLibrary.createPosts(999);
// @ts-expect-error
myLibrary.createPosts();

myLibrary.getPosts();
myLibrary.createPosts("Hello, StackOverflow!");

Taman Bermain TS

Penjelasan

Tipe never memberi tahu kompiler bahwa ini tidak boleh terjadi. Jadi, jika seseorang mencoba menggunakan GetPosts, ini adalah kesalahan, karena seharusnya tidak pernah terjadi. void (undefined dalam hal ini harus juga baik-baik saja) memberi tahu bahwa nilai tidak boleh ada.

Membuat body diperlukan dalam fungsi yang dikembalikan membuatnya diperlukan. Tetapi karena void untuk GetPosts, Anda dapat menyebutnya seperti myLibrary.getPosts(undefined) atau cukup myLibrary.getPosts() yang setara

0
Mr. Hedgehog 27 November 2021, 13:17
Terima kasih! Saya terus direndahkan oleh pengetahuan orang tentang TypeScript dan cakupan bahasa itu sendiri.
 – 
shennan
27 November 2021, 13:18
Sejujurnya, saya tidak tahu TypeScript dengan baik. Saya menyalin kode Anda ke taman bermain dan mengutak-atiknya sampai berhasil. Baru kemudian saya mengerti mengapa itu berhasil dan apa yang salah. Semua yang tersisa - salin kode Anda sekali lagi dan terapkan bagian yang relevan dengannya, sehingga tidak akan jauh berbeda.
 – 
Mr. Hedgehog
27 November 2021, 13:23
Maka izinkan saya setidaknya memberi selamat kepada Anda atas kejujuran dan keuletan Anda!
 – 
shennan
27 November 2021, 13:30