Given this code as starter:
export interface DataEntity {
id: string
}
export interface Movie extends DataEntity {
director: string
}
export interface Song extends DataEntity {
singer: string
}
export type DataEntityMap = {
movie: Movie
song: Song
}
export class DataStore {}
This DataEntityMap
object should drive a lot of what happens to DataStore
.
Ultimately, DataStore
should end up with methods like
const ds = new DataStore()
ds.addSong({ id: "song-123", singer: "The Flaming Lips" })
ds.addMovie({
id: "movie-456",
director: "Stephen Spielberg",
})
ds.getSong("song-123") // returns the song
ds.getMovie("movie-456") // returns the movie
ds.getAllSongs() // array of all songs
ds.getAllMovies() // array of all movies
ds.clearSongs() // clears all songs
ds.clearMovies() // clears all movies
It’s ok to define these explicitly in the DataStore
class, but they should be type-checked against the DataEntityMap
type in some way.
Requirements
- If you mis-name a method on the class (e.g.,
getSongs
instead ofgetAllSongs
), you should get some sort of type error that alerts you that you’ve broken the established pattern - If you add a new entity like
Comic
(shown below) and make no other changes to your solution, you should get some sort of type error that alerts you to the absence of aclearComics
,getAllComics
andgetAllSongs
method.
- There should be no externally-visible properties on an instance of
DataStore
beyond the required methods - Your code, and the test suite should type-check
.
.
.
.
.
.
.
.
Answer
export interface DataEntity {
id: string
}
export interface Movie extends DataEntity {
director: string
}
export interface Song extends DataEntity {
singer: string
}
export interface Comic extends DataEntity {
issueNumber: number
}
export type DataEntityMap = {
movie: Movie
song: Song
}
/**
* if you change DataEntityMap from type to interface
* Then you can have the DataEntityMap defined in multi files and TS will combine those automaticlly
// model/song.model.ts
export interface DataEntityMap {
song: Song;
}
// model/movie.model.ts
export interface DataEntityMap {
movie: Movie;
}
*/
export type DataStoreMethods = {
[K in keyof DataEntityMap as `getAll${Capitalize<K>}s`]: () => DataEntityMap[K][]
} & {
[K in keyof DataEntityMap as `get${Capitalize<K>}`]: (id: string) => DataEntityMap[K]
} & {
[K in keyof DataEntityMap as `clear${Capitalize<K>}s`]: () => void
}
& {
[K in keyof DataEntityMap as `add${Capitalize<K>}`]: (item: DataEntityMap[K]) => DataEntityMap[K]
}
function isDefined<T>(x: T | undefined): x is T {
return typeof x !== "undefined";
}
export class DataStore implements DataStoreMethods {
#data: {[K in keyof DataEntityMap]: Record<string, DataEntityMap[K]>} = {
movie: {},
song: {}
}
getSong(id: string): Song {
const song = this.#data.song[id];
if (!song) {
throw new Error(`Could not find a song with id ${id}`);
}
return song;
}
getAllSongs(): Song[] {
return Object.keys(this.#data.song).map(id => this.#data.song[id]).filter(isDefined)
}
clearSongs(): void {
this.#data.song = {}
}
addSong(song: Song) {
this.#data.song[song.id] = song;
return song;
}
getMovie(id: string): Movie {
const movie = this.#data.movie[id];
if (!movie) {
throw new Error(`Could not find a movie with id ${id}`);
}
return movie;
}
getAllMovies(): Movie[] {
return Object.keys(this.#data.movie).map(id => this.#data.movie[id]).filter(isDefined)
}
clearMovies(): void {
this.#data.movie = {}
}
addMoive(movie: Movie) {
this.#data.movie[movie.id] = movie;
return movie;
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。