Skip to content

Instantly share code, notes, and snippets.

@aprilrd
Created May 20, 2019 07:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aprilrd/2d4e59eae3d63190f50595411e844832 to your computer and use it in GitHub Desktop.
Save aprilrd/2d4e59eae3d63190f50595411e844832 to your computer and use it in GitHub Desktop.
Typescript+Redux Best Practice at Vingle
interface Connect {
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
): InferableComponentEnhancerWithProps<
TStateProps & TDispatchProps & TOwnProps,
TOwnProps
>;
}
interface Connect {
<TStateProps = {}, no_dispatch = {}, TOwnProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
): InferableComponentEnhancerWithProps<
TStateProps & DispatchProp<any> & TOwnProps,
TOwnProps
>;
}
export interface DispatchProp<S> {
dispatch?: Dispatch<S>;
}
export interface InferableComponentEnhancerWithProps<
TInjectedProps,
TNeedsProps
> {
<P extends TInjectedProps>(component: Component<P>): ComponentClass<
Omit<P, keyof TInjectedProps> & TNeedsProps
> & { WrappedComponent: Component<P> };
}
export type InferableComponentEnhancer<
TInjectedProps
> = InferableComponentEnhancerWithProps<TInjectedProps, {}>;
export interface Connect {
(): InferableComponentEnhancer<DispatchProp<any>>;
...
// this is an all-encompassing definition
// TStateProps is a type for props generated by mapStateToProps
// TDispatchProps is a type for props generated by mapDispatchToProps
// TOwnProps is a type for props from the container component's parent.
// TMergedProps is a type for props generated by merge.
// State is a type for Redux store.
<TStateProps = {}, TDispatchProps = {}, TOwnProps = {}, TMergedProps = {}, State = {}>(
mapStateToProps: MapStateToPropsParam<TStateProps, TOwnProps, State>,
mapDispatchToProps: MapDispatchToPropsParam<TDispatchProps, TOwnProps>,
mergeProps: MergeProps<TStateProps, TDispatchProps, TOwnProps, TMergedProps>,
options: Options<State, TStateProps, TOwnProps, TMergedProps>
): InferableComponentEnhancerWithProps<TMergedProps, TOwnProps>;
}
<P extends (TStateProps & DispatchProp<any> & TOwnProps)>(component: Component<P>): ComponentClass<Omit<P, keyof (TStateProps & DispatchProp<any> & TOwnProps)> & TOwnProps> & {WrappedComponent: Component<P>}
interface InjectedIntlProps {
intl: InjectedIntl;
}
function injectIntl<P>(
component: ComponentConstructor<P & InjectedIntlProps>,
options?: InjectIntlConfig,
): React.ComponentClass<P> & {
WrappedComponent: ComponentConstructor<P & InjectedIntlProps>;
};
// actual usage
interface IProps {
flag: boolean;
}
class Toast extends React.PureComponent<IProps & InjectedIntlProps> {
...
}
export default injectIntl<IProps>(Toast);
(initialState: State, ownProps: TOwnProps) => (
state: State,
ownProps: TOwnProps,
) => TStateProps;
type SearchData = { query: string };
type AppState = {
searchData: SearchData;
};
type Props = { query: string; data: any; dispatch: Dispatch<any> };
function mapStateToProps(state: AppState) {
return {
query: state.searchData.query,
};
}
const Container = (_props: Props) => {
// render something and do something useful
return <div />;
};
const A = connect(mapStateToProps)(Container);
<A data />; // this is valid
<A data dispatch={store.dispatch} />; // this isn't valid
const Container = (props: { data: any; dispatch: Dispatch<any> }) => {
// render something and do something useful
return <div />;
};
export default connect()(Container);
type TStateProps = ReturnType<typeof mapStateToProps>;
type TOwnProps = Omit<Props, keyof TStateProps | keyof DispatchProp<any>>; // this results in { data: any }. But this isn't necessary and you can use {} without a problem.
const B = connect<TStateProps, {}, TOwnProps, AppState>(mapStateToProps)(
Container,
);
<B data />; // this is valid
<B data dispatch={store.dispatch} />; // this isn't valid
enum ActionTypes {
FETCH_USER = "FETCH_USER",
}
interface IFetchUserAction {
type: ActionTypes.FETCH_USER;
payload: { userId: string }
}
interface IOtherAction {
type: "____________________";
}
type Actions = IFetchUserAction | IOtherAction;
function fetchUser(userId: string): IFetchUserAction {
return {
type: ActionTypes.FETCH_USER,
payload: {
userId,
}
};
}
function reducer(
state = INITIAL_STATE,
action: Actions,
): IState {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// in this closure, Typescript knows that action is of interface IFetchUserAction, thanks to enum ActionTypes.
return {
...state,
userId: action.payload.userId,
};
}
default: {
return state
}
}
import { ActionCreatorsMapObject } from "redux";
// interface ActionCreatorsMapObject {
// [key: string]: ActionCreator<any>;
// }
type ActionUnion<T extends ActionCreatorsMapObject> = ReturnType<
T[keyof T]
>;
enum ActionTypes {
FETCH_USER = "FETCH_USER",
}
function createAction<T extends { type: ActionTypes }>(d: T): T {
return d;
}
export const ActionCreators = {
fetchUser(payload: {userId: string}) =>
createAction({type: ActionTypes.FETCH_USER, payload}),
}
type Actions = ActionUnion<typeof ActionCreators>;
function reducer(
state = INITIAL_STATE,
action: Actions,
): IState {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// in this closure, Typescript knows that action is of ActionCreators.fetchUser's ReturnType.
return {
...state,
userId: action.payload.userId,
};
}
default: {
return state
}
}
const Container = (props: { data: any; dispatch: Dispatch<any> }) => {
// render something and do something useful
return <div />;
};
const ConnectedContainer = connect()(Container);
describe("", () => {
let wrapper: ReactWrapper;
beforeEach(() => {
const store = mockStore(state);
wrapper = mount(<ConnectedContainer data dispatch={store.dispatch} />, { store });
});
...
});
function reducer(state = INITIAL_STATE, action: Redux.Action) {
switch (action.type) {
case ActionTypes.FETCH_USER: {
// simple case
return {
...state,
userId: (action.payload as any).userId,
};
}
default: {
return state;
}
}
}
React.Component<P, S>
React.StatelessComponent<P>
React.ReactElement = instantiated React Component
React.ReactNode = React.ReactElement + Renderable primitive types (object is not valid). `children` has this type
React.CSSProperties
React.ReactEventHandler
React.<Input>Event
React.HTMLProps<ElementType> = Used to extend your component props. Ex) TOwnProps & React.HTMLProps<HTMLDivElment>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment