Фронтендер в Контур.Гособлако
Люблю ходить в горы
В прошлом бэкендер
о
case 'SEARCH_PAGE_UPDATE_RESLUTS'
action.payload.qeury
chengeQueryText()
// ловим ошибки в рантайме
// или пишем много тупых тестов
// какие параметры принимает onSearch?
onSearch(query)
onSearch({query})
onSearch(() => {...})
// что прийдёт в «options»?
const onSearch = (options) => {
// options.query?
...
}
import Input from 'ui/Input';
<Input
size="large"
width={600}
value={props.query}
onUpdate={props.onUpdate}
...
/>
// поменялось имя свойства onUpdate → onChange
Действия
Состояние
Компоненты
Events
State
Actions
Есть один нюанс
const SearchPage = (props) => (
<SearchBar
// что такое props.onSearch?
// какие параметры принимает
onSearch={props.onSearch}
/>
...
);
const reducer = (
previousState, // ← что здесь?
action // ← а здесь?
) => {
...
};
const reducer = (
previousState,
action
) => {
switch (action.type) {
сase 'SEARCH_PAGE_UPDATE_RESULTS':
return {
...state,
// а точно ли query?
query: action.query
}
...
const reducer = (
previousState,
action
) => {
switch (action.type) {
// какие значения
// может принимать action.type?
сase 'SEARCH_PAGE_UPDATE_RESULTS':
сase Constants.SEARCH_PAGE_CHANGE_QUERY:
...
}
};
const rootReducer = combineReducers({
searchPageReducer,
authReducer,
...
});
// из чего состоит state приложения
// из каких редьюсеров
let isDone: boolean;
let query: string;
let onSearch: (query: string) => void;
let list: any[] = [1, true, 'free'];
type fn = () => void;
interface Action {
type: string;
payload: any;
}
// пример
const anyAction: Action = {
type: 'ANY_ACTION',
payload: 0
}
interface Action<T> {
type: string;
payload: T;
}
// пример
const numberAction: Action<number> = {
type: 'NUMBER_ACTION',
payload: 0
}
interface Action<T = any> {
type: T;
}
type Reducer<
S = any,
A extends Action = AnyAction
> = (state: S | undefined, action: A) => S;
...
redux/index.d.ts
interface Props {
query: string;
onSearch: (options: SearchParams) => void;
}
const SearchPage: React.SFC<Props> = props => (
<SearchBar
query={props.query}
onSearch={props.onSearch}
/>
)
interface Actions {
onSearch: (options: SearchParams) => void;
}
const searchPageActions: Actions = {
onSearch: searchRequest
};
const SearchContainer = connect(
searchPageSelector,
searchPageActions
)(SearchPage);
// connect - связывает React и Redux
Action
interface Action<TPayload, Type> {
readonly type: Type;
payload: TPayload;
//error: boolean;
//meta: any;
}
type
payload
payload
const SEARCH_PAGE_REQUEST = 'SEARCH_PAGE_REQUEST';
const searchRequest = (options) => ({
type: SEARCH_PAGE_REQUEST,
payload: {
...options
}
});
interface SearchRequestPayload {
query: string;
}
type SearchRequestAction = Action<
SearchRequestPayload,
'SEARCH_PAGE_REQUEST'
>;
const SEARCH_PAGE_REQUEST = 'SEARCH_PAGE_REQUEST';
const searchRequest = (
options: SearchRequestPayload
): SearchRequestAction => ({
type: SEARCH_PAGE_REQUEST,
payload: {
...options
}
});
function actionCreator<
T extends Action<any, string>
>(Ctor: { new (payload: T['payload']): T }) {
return (payload: T['payload']): T
=> new Ctor(payload);
}
// Возвращает Action через конструктор Ctor
function actionType<Payload, Type>(type: Type) {
return class implements Action<Payload, Type> {
static readonly Type = type;
public readonly type = type;
constructor(public payload: Payload) {
return {
payload,
type
};
}
};
}
// Возвращает Action из конструктора
// Хранит константу action.type в Type
class SearchRequest extends actionType<
{ query: string },
'SEARCH_PAGE_REQUEST'
>('SEARCH_PAGE_REQUEST') {}
const searchRequest = actionCreator(SearchRequest);
const rootReducer = combineReducers<
AppState,
AppActions
>({ searchPageReducer, authReducer });
interface AppState {
searchPageReducer: SearchPageState;
authReducer: AuthState;
}
type AppActions = SearchPageActions | AuthActions;
interface State {
readonly query: string;
readonly searchResult: SearchResult;
}
const searchPageReducer = (
previousState: State,
action
): State => {
...
};
const getState = (
state: State,
ownProps: OwnProps
): State => ({
...state,
...ownProps
});
const searchPageReducer = (
previousState: State,
action: AppActions
): State => {
switch (action.type) {
...
}
};
const searchPageReducer = (
previousState: State,
action: AppActions
): State => {
switch (action.type) {
case 'SEARCH_PAGE_UPDATE_RESULTS':
return {
...state,
searchResult: action.payload
// (property) payload: SearchResult
};
}
};
// IDE — по типу action выводит тип payload
Сколько стоит?
function actionCreator<
T extends Action<any, string>
>(Ctor: { new (payload: T['payload']): T }) {
return (payload: T['payload']): T => new Ctor(payload);
}
interface Action<TPayload, Type> {
readonly type: Type;
payload: TPayload;
meta: any;
error: boolean;
}
Нужно писать контракты
Требуется время на компиляцию
Создатель: Turbo Pascal, Delphi, C#, Typescript
Web API
CLIENT
models.d.ts
Автогенерация контрактов с backend
Model.cs → interface Model { ... }
public class AddressFullObjectM
{
public AddressM address { get; set; }
public string postalCode { get; set; }
public HouseM house { get; set; }
public string building { get; set; }
public string room { get; set; }
}
interface AddressFullObjectM {
address: server.AddressM;
building: string;
house: server.HouseM;
postalCode: string;
room: string;
}
AddressFullObjectM.cs
AddressFullObjectM.d.ts
shatikhin@skbkontur.ru
2018
Михаил Шатихин
Пример типизации: