Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Type-Safe Flux Using Flowtype

joe_re
December 15, 2016

Type-Safe Flux Using Flowtype

joe_re

December 15, 2016
Tweet

More Decks by joe_re

Other Decks in Technology

Transcript

  1. Benefits of static type checking • Improve code readability •

    Explicitly declaring interface makes it easier to understand the intentions behind code • By realtime code check, get fast feedback cycle of developing JavaScript
  2. What is Flowtype? • Type checking tool created by Facebook

    • Can introduce static type checking in JavaScript • Made with OCaml
  3. Features • Strong type inference • Fast check in development

    cycle
 (monitor changes, and since only the ones that have changed are checked for the second time and thereafter) • Not AltJS
 (It provides only type annotation)
  4. Strong type inference // @flow function foo(x) { return x

    * 10; } foo('Hello, world!'); qPX qPXKT GPP )FMMP XPSME  ????????????????????GVODUJPODBMM SFUVSOY  ?TUSJOH5IJTUZQFJTJODPNQBUJCMFXJUI SFUVSOY  ??????OVNCFS
  5. feel like OCaml let foo x = x * 10;;

    # val foo : int -> int = <fun>
  6. Interface in Flux ActionCreators notify Action to Store(via Diapatcher) ReactViews

    listen change events of Store ReactViews call ActionCreator
  7. Demo app use flux-utils (but I guess that it can

    be applied in other frameworks with the same way of thinking.)
  8. Definition of Action Creator // @flow import Dispatcher from './Dispatcher';

    type CREATE = { type: 'create', comment: string }; type DELETE = { type: 'delete', id: number }; export type ActionTypes = CREATE | DELETE; function dispatch(params: ActionTypes) { Dispatcher.dispatch(params); } export default { create(comment: string) { dispatch({ type: 'create', comment }); }, delete(id: number) { dispatch({ type: 'delete', id }); } }; define actions and UnionTypes dispatch function receives UnionTypes dispatch’s args is always fulfill either definition of UnionTypes
  9. ReactViews -> ActionCreator route is Type-Safe class CommentContainer extends React.Component

    { … handleCreateComment(comment: string) { CommentActions.create(1); } } $ flow scripts/CommentContainer.jsx:27 27: CommentActions.create(1); ^ number. This type is incompatible with the expected param type of 14: create(comment: string) { ^^^^^^ string. See: scripts/CommentActions.js:14 Found 1 error create method is expected to receive string
  10. Definition of Store(1) // @flow import { ReduceStore } from

    'flux/utils'; import type { ActionTypes } from './CommentActions'; import Dispatcher from './Dispatcher'; export type Comment = {id: number, comment: string }; export type State = Comment[]; let count = 0; class CommentStore extends ReduceStore<State> { getInitialState(): State { return []; } reduce(state: State, action: ActionTypes): State { switch (action.type) { case 'create': return state.concat({ id: ++count, comment: action.comment }); case 'delete': const deleteId = action.id; return state.filter((v) => v.id !== deleteId); default: return state; } } } const instance = new CommentStore(Dispatcher); export default instance; import UnionTypes of Actions set action’s type as UnionTypes enable to narrow down UnionTypes can access only narrowed type’s property
  11. ActionCreators -> Store route is Type-Safe // @flow class CommentStore

    extends ReduceStore<State> { //… reduce(state: State, action: ActionTypes): State { switch (action.type) { case 'create': return state.concat({ id: ++count, comment: action.id }); case 'delete': const deleteId = action.id; return state.filter((v) => v.id !== deleteId); default: return state; } } $ flow scripts/CommentStore.js:20 20: return state.concat({ id: ++count, comment: action.id }); ^^ property `id`. Property not found in 20: return state.concat({ id: ++count, comment: action.id }); ^^^^^^ object type create action doesn’t have id property
  12. Definition of Store(2) // @flow //… export type Comment =

    {id: number, comment: string }; export type State = Comment[]; //… class CommentStore extends ReduceStore<State> { //… } define state of Store give State as generics declare module 'flux/utils' { declare class ReduceStore<T> { getState(): T; getDispatchToken(): string; } declare class Container { static create(): any; } //… } use generics for public API
  13. Store -> ReactViews route is Type-Safe // @flow import type

    { Comment } from './CommentStore'; type State = { comments: Comment[] }; class CommentContainer extends React.Component { state: State; //… static calculateState(_prevState: State): State { const comments = CommentStore.getState(); return { comments }; } //… } can get typed state of Store
  14. The following routes got type-safe • ReactViews call ActionCreator
 (ReactViews

    -> ActionCreator) • ActionCreators notify Action to Store
 (ActionCreators -> Store) • ReactViews listen change events of Store
 (Store -> ReactViews)
  15. We need WebAPI Utils layer • Action Creator === User

    Interaction • Some WebAPI may be called from multiple ActionCreators • Sometimes, WebAPI are called by Store • DRY…
  16. Definition of WebAPI Util // @flow import request from './request';

    import type { Foo } from 'types/foo'; function toCreateParans(foo: Foo) { /* some logic */ } type GetFoos = { foos: Array<Foo> }; const getFoos = (): Promise<Foo> => { return request.get({ url: '/api/v1/foo' }); }; type CreateFooParams = { foo: Foo };
 type CreateFooResponse = { foo: Foo, bar: Bar }; const createFoo = (params: CreateFooParams): Promise< CreateFooResponse> => { const { foo } = params; return request.post({ url: '/api/p/v2/expense_applications', parameters: toRequestObject(foo) }); }; //... export default { getFoos, createFoo, /* ... */ }; import ajax lib
 (fetch or bluebird or super agent or…) set return value's type as Promise
 (wrap APIResponse type) define ApiUtil’s interface
  17. ActionCreators <-> WebAPI is Type-Safe // @flow //.. create(params: {

    foo: Foo }) { const { Foo } = params; FooAPI.createFoo({ foo }) .then((res) => { dispatch({ type: 'foo/success_create', foo: res.foo, bar: res.bar }); }).catch((data) => { //... });; }, became type-safe interface (Request and Response)