post<T = any, R = AxiosResponse<T>>(url: string, data?: any, config?: AxiosRequestConfig): Promise<R>;
Оба дженерика имеют типы по умолчанию, а значит если их не указать именно они и будут использоватьсяaxios.post<ResponseApi>(/* ... */)
мы явно указываем, что T
- это тип ResponseApi
, а R берется по умолчанию, то есть AxiosResponse<T>
, что в нашем случае соответствует AxiosResponse<ResponseApi>
, а если и дальше развернуть, то выходит, что R
- это{
data: ResponseApi;
status: number;
statusText: string;
headers: any;
config: AxiosRequestConfig;
request?: any;
}
он и попадает в возвращаемый тип, обернутый в Promise, но TS знает про поведение await и деструктуризации, а следовательно без проблем вычисляет тип для data
- ResponseApi
export class MyHttpClient {
constructor(private readonly http: HttpClient) {}
put(params: Params<'body', 'arraybuffer'>): Observable<ArrayBuffer>;
put(params: Params<'body', 'blob'>): Observable<Blob>;
put(params: Params<'body', 'text'>): Observable<string>;
put(params: Params<'events', 'arraybuffer'>): Observable<HttpEvent<ArrayBuffer>>;
put(params: Params<'events', 'blob'>): Observable<HttpEvent<Blob>>;
put(params: Params<'events', 'text'>): Observable<HttpEvent<string>>;
put(params: Params<'events', 'json'>): Observable<HttpEvent<Object>>;
put<T>(params: Params<'events', 'json'>): Observable<HttpEvent<T>>;
put(params: Params<'response', 'arraybuffer'>): Observable<HttpResponse<ArrayBuffer>>;
put(params: Params<'response', 'blob'>): Observable<HttpResponse<Blob>>;
put(params: Params<'response', 'text'>): Observable<HttpResponse<string>>;
put(params: Params<'response', 'json'>): Observable<HttpResponse<Object>>;
put<T>(params: Params<'response', 'json'>): Observable<HttpResponse<T>>;
put(params: Params<'body', 'json'>): Observable<Object>;
put(params: Params<Observe, ResponseType>): Observable<unknown> {
return this.http.put(params.url, params.body, params.options as Params<'body', 'json'>['options']);
}
}
type ResponseTypeMap = {
arraybuffer: ArrayBuffer;
blob: Blob;
text: string;
json: Object;
};
type ResponseTypes = keyof ResponseTypeMap;
type ObserveWrapperMap<T extends ResponseTypeMap[ResponseTypes]> = {
body: Observable<T>;
events: Observable<HttpEvent<T>>;
response: Observable<HttpResponse<T>>;
};
type ObserveWrappers = keyof ObserveWrapperMap<ResponseTypeMap[ResponseTypes]>;
type Result<O extends ObserveWrappers, R extends ResponseTypes> = ObserveWrapperMap<ResponseTypeMap[R]>[O];
type Params<O extends ObserveWrappers, R extends ResponseTypes> = {
url: string;
body: any;
options?: {
headers?: HttpHeaders | Record<string, string | string[]>;
observe?: O;
params?: HttpParams | Record<string, string | string[]>;
reportProgress?: boolean;
responseType: R;
withCredentials?: boolean;
};
}
export class MyHttpClient {
constructor(private readonly http: HttpClient) {}
put<O extends ObserveWrappers = 'body', R extends ResponseTypes = 'json'>(params: Params<O, R>): Result<O, R> {
const {url, body, options} = params as Params<'body', 'json'>;
return this.http.put(url, body, options) as Result<O, R>;
}
}
особенно если помимо put будут и другие методы function filterObjectProps<O extends Record<string, unknown>, P extends keyof O>(obj: O, props: readonly P[]) {
const entries: [P, O[P]][] = [];
for(const prop of props) {
if(prop in obj) {
entries.push([prop, obj[prop]]);
}
}
return Object.fromEntries(entries) as {
[K in P]: O[K];
};
}
// ...
updateItem(id: string, updateItemDto: UpdateItemDto): Item {
const item = this.getItemById(id);
return {
...item,
...filterObjectProps(updateItemDto, ['name', 'lastname', 'prop1', 'prop2'])
};
}
type Animal = {
[key: string]: string;
};
type Animal = {
[<любое имя>: <тип ключей>]: <тип значений>;
};
Имя должно быть валидным идентификатором, зачем оно нужно TS - я хз, но без него не естstring | number | symbol
, притом с symbol есть траблы, там только юнион из конкретных символовtype Animal = Record<string, string>;
const useSendMessage = (): (id: number, message: Message) => void => {
const dispatch = useDispatch();
return useCallback(
(id: number, message: Message) => {
dispatch(Operation.sendMessage(id, message));
},
[dispatch]
);
};
declare namespace XXX {
// по сути просто поле XXX.a
let a: number;
// то же поле XXX.b но уже const
// современный ts позволяет делать поля readonly, но раньше так было нельзя
const b: number;
// функция, по сути метод XXX.c()
function c(): void;
// а вот вложенный тип через тип объекта не объявить, а в namespace можно
type T = number | string;
}
declare module '*.png' {
const url: string;
export default url;
}
declare module '*.css' {
const classNames: Record<string, string>;
export default classNames;
}
Можно ли обойтись обычным js?Можно. TypeScript лишь добавляет к JavaScript статическую типизацию в компайл-тайм. После компиляции будет все тот же JS.
Какие будут плюсы?Главный плюс, ИМХО, - скорость разработки за счет подсказок IDE и автодополнения, больше авторефакторингов. Ну и возможность ограничить использование функций/методов от нежелательного использования тоже плюс. А если еще и проектировать доменную модель на типах, то можно сразу видеть, если что-то не сходится, еще до написания логики.
Не будет ли много лишней писанины по сравнению с чистым js?Большинство типов TypeScript способен вывести. Далеко не Хиндли-Милнер конечно, но тоже хорошо. Я пишу больше в ФП стиле, с редкой примесью структурно-процедурного при описании эффектов, так у меня явные указания типов присутствуют только в сигнатурах функций. В самой логике код неотличим от обычного JS, но с хорошей проверкой типов.
Будет ли сложно хранить скомпилированный js?Как и любые другие артефакты сборки, скомпилированный JS хранить не нужно. Компилируйте непосредственно перед выкладыванием на продакшен, а в git храните лишь TS код + настройки компилятора. А в dev среде вообще можно запускаться через модуль ts-node.
type Validator<U extends string, A extends ReadonlyArray<string>> =
(U extends A[number] ? true : false) extends true ? A : never;
type U = 'foo' | 'bar';
const arr0 = ['foo'] as const;
const arr1 = ['bar'] as const;
const arr2 = ['foo', 'bar'] as const;
type A0 = Validator<U, typeof arr0>; // never
type A1 = Validator<U, typeof arr1>; // never
type A2 = Validator<U, typeof arr2>; // readonly ['foo', 'bar']
const _check0: A0 = arr0; // error
const _check1: A1 = arr1; // error
const _check2: A2 = arr2;
const elementSettings = {
welcome: {
placeholder: 'Type welcome message...',
question: '',
isDescription: false,
description: '',
startButtonText: 'Start',
},
checkbox: {
placeholder: 'Type question or statement...',
question: '',
isDescription: false,
description: '',
options: [] as string[],
multiple: false,
},
dropdown: {
placeholder: 'Type question here...',
question: '',
isDescription: false,
description: '',
options: [] as string[],
},
rating: {
placeholder: 'Type question...',
question: '',
isDescription: false,
description: '',
steps: 10,
defaultRate: 0,
shape: 'stars',
},
text: {
placeholder: 'Type question...',
question: '',
isDescription: false,
description: '',
maxLength: 99999,
},
slider: {
placeholder: 'Type question...',
question: '',
isDescription: false,
description: '',
steps: 10,
},
thanks: {
placeholder: 'Type message...',
question: '',
isDescription: false,
description: '',
}
} as const;
type Editable<T> = { -readonly [P in keyof T]: T[P]; };
type ElementSettings = typeof elementSettings;
export function getElementSettings<T extends keyof ElementSettings>(type: T): Editable<ElementSettings[T]> {
if(type in elementSettings) {
const settings = {...elementSettings[type]};
if('options' in settings) {
(settings as {options: string[]}).options = (settings as {options: string[]}).options.slice();
}
return settings;
}
throw new Error("element type doesn't match!");
}
mport React from 'react';
import { render } from 'react-dom';
type ITableColumn<D extends Record<string, any>> = {
property: string;
format?: (value: any, item: D) => React.ReactNode | string | number;
header?: any;
footer?: any;
};
type ITable<T extends Record<string, any>, D extends Record<string, any>> = {
isLoading?: boolean;
columns: ITableColumn<D>[];
rows: Array<T>;
};
type IContext<D extends Record<string, any>> = {
rows: any;
columns: ITableColumn<D>[];
};
const getColumns = <D extends Record<string, any>>(): ITableColumn<D>[] => [
{
property: 'name',
format: (value, item) => {
const { lastName } = item;
return `${value} ${lastName}`;
},
header: 'heade',
footer: 'footer'
},
{
property: 'count',
header: 'heade',
footer: 'footer'
}
]
interface AppProps { }
interface AppState {
name: string;
}
const Table:React.FC<ITable> = ({ isLoading, columns, rows }) => {
return <h1>table</h1>
}
const App = () => (
<div>
<Table
columns={getColumns()}
rows={[
{ name: 'userName 0', lastName: 'userLastName 0', count: 10 },
{ name: 'userName 1', lastName: 'userLastName 1', count: 20 },
{ name: 'userName 2', lastName: 'userLastName 2', count: 30 },
]}
/>
<p>Start editing to see some magic happen :)</p>
</div>
);
render(<App />, document.getElementById('root'));
import type {ComponentType} from 'react';
import {Button} from 'react-native';
type InferProps<Component> = Component extends ComponentType<infer Props> ? Props : never;
const MyButton = (props: InferProps<typeof Button>) => {
return <Button title={props.title} onPress={props.onPress} style={{color:'#fff'}} />
}
routes.map((route: IPrivateRoute | IRoute) => route.access // Здесь ошибка, так как в IRoute нет поля access, а значит нет и в юнионе IPrivateRoute | IRoute
? <PrivateRoute key={route.path} {...route}/> // а здесь, так как route.access не является тайп-гвардом, и не смотря на условие, тип по прежнему IPrivateRoute | IRoute