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

おれたちのスマホアプリとWeb版 / Real World React Native

moqada
September 14, 2017

おれたちのスマホアプリとWeb版 / Real World React Native

Real World React Native in Agri/Med/Fin Tech の発表資料です。
https://connpass.com/event/62895/

moqada

September 14, 2017
Tweet

Other Decks in Technology

Transcript

  1. όϯυϧΧʔυͷྺ࢙ • 2015೥12݄ ։ൃ։࢝ • ωΠςΟϒܦݧऀͷ͍ͳ͍νʔϜͰૉૣͦ͘ΕͳΓͷΫΦϦςΟͰϦϦʔε͢ΔͨΊʹ React Native Λબ୒ (౰ॳ2016೥5݄ϦϦʔε༧ఆ)

    • 3ਓͷ։ൃνʔϜͰɺFintechࣄۀΛͲ͏࡞Δʁ࠷খߏ੒ͷνʔϜͰ௅Ήɺͦͷઓུͱ͸ (https:// seleck.cc/850) • React Native Ͱ͸iOS൛ͷΈ࣮૷͢Δ༧ఆͩͬͨ • 2016೥9݄ iOS൛ϦϦʔε • 2016೥12݄ Android൛ϦϦʔε • ҙ֎ͱAndroid΋͙͍͚ͨ͢ • 2017೥9݄ Web൛ϦϦʔε༧ఆ
  2. όϯυϧΧʔυͷٕज़ελοΫ • ϑϩϯτ͸ɺReact Native, Redux, Flow, ESLint, ESDoc, etc... •

    όοΫΤϯυ͸Golang • JSON Hyper Schema Ͱ APIϦιʔεͷܕΛڞ ༗ͯ͠ΫϥΠΞϯτΛࣗಈੜ੒
  3. ReactϥΠϑαΠΫϧϝιουύ λʔϯ class HogeAuthRequestScene extends React.Component { componentWillReceiveProps(nextProps: Props) {

    // ಡΈࠐΈதͷ৔߹ͷΈ if (this.state.isLoading) { // ʮrequestͷঢ়ଶ͕มΘ͍ͬͯΔ == API͔Β݁ՌΛड͚औͬͨʯͱղऍ͢Δ if (!isEqual(props.request, this.props.request)) { // Πϯδέʔλ͕มԽ͠ऴΘΔͷΛ଴ͬͯNavigationΛભҠ͢Δ this.changeIndicator(indicatorType, () => { this.props.navigator.props.push(getFooRoute()); }); } } } }
  4. ActionCreatorύλʔϯ export function fooRequest(foo: string, bar: string): AsyncAction { return

    async (dispatch, getState) => { // ΠϯδέʔλΛදࣔ dispatch(indicatorShow()); const state = getState(); // APIϦΫΤετΛ౤͛Δ const action = await dispatch(apiActions.request({bar, foo})); // ΠδέʔλΛඇදࣔ dispatch(indicatorHide()); await saveRequest(action.payload); // φϏήʔγϣϯͰτοϓʹ໭Δ dispatch(navigationActions.popToTop(NAVIGATOR.app)); return action; }; }
  5. Sagaύλʔϯ export function *fooSaga(): Generator<any, any, any> { yield fork(function

    *saga() { // ϦΫΤετΠϕϯτΛ଴ͭ yield* takeLatest(types.FOO_REQUEST, receiveRequest); }); } function *receiveRequest(action: RequestAction) { const state = yield select(s => s.navigation); // Πϯδέʔλදࣔ yield fork(showIndicator); yield put(requestActions.foo(action.payload)); const receiveAction = yield take(types.FOO_REQUEST_RECEIVE); // Πϯδέʔλඇදࣔ yield take(hideIndicator); if (state) { // φϏήʔγϣϯΛ໭Δ yield put(navigationActions.pop()); } }
  6. UseCaseΫϥε type Prams = {username: string, password: string}; class LogInUseCase

    extends UseCase<Params> { static create() { const authServiceProvider = AuthServiceProvider.create(); return new LogInUseCase({ authService: new AuthService({provider: authServiceProvider}), navigation: Navigation.create() }); } constructor(props: Props) { this.authService = props.authService; this.navigation = props.navigation; } async execute(params: Params) { await this.authService.login(params); this.navigation.pop(); } }
  7. UseCaseʹ઀ଓͨ͠Container export default compose( // useCaseΛѻ͑ΔΑ͏ʹ͢Δ connectUseCase, connect( (state: State):

    StateProps => ({ submitDisabled: authSelectors.signInForm.isSubmitDisabled(state), values: authSelectors.signInForm.getValues(state) }), null, (stateProps: StateProps, dispatchProps: {dispatch: Dispatch}, ownProps: OwnProps): Props => { const {values, ...p} = stateProps; return { ...p, handleSubmit: () => { // useCase propsʹ౉ͬͯ͘Δؔ਺ͰUseCaseΛCommand(Action)ʹ࣮ͯ͠ߦ ownProps.useCase.commander(LogInUseCase)({...values}); } }; } ) )(SignInView);
  8. UseCaseίϚϯυͱίϚϯμʔ export type UseCaseCommand<U: UseCase<*>> = { UseCase: Class<U>, id:

    string, params: any }; export function createCommander<P, U: IUseCase<P>>( useCase: Class<U> ): UseCaseCommander<P, U> { return (params: P): UseCaseCommand<U> => { return { UseCase: useCase, id: createCommandId(), params }; }; }
  9. UseCase࣮ߦج൫ΛSagaͰ؂ࢹ export default function *useCaseSaga( executor: UseCaseExecutor ): Generator<*, *,

    *> { const chan = createUseCaseChannel(executor); yield fork(watchUseCaseEvent, chan); yield takeEvery( useCaseActions.COMMAND_COMMANDED, handleCommand, executor ); }
  10. ReducerʹUseCase࣮ߦঢ়ଶΛ ετΞ const actionHandlers = { ..., [USECASE_EVENT_EMITTED]: (state: UseCaseState,

    action: UseCaseActionUseCaseEventEmitted) => { const {payload: {event}} = action; const useCaseId = getUseCaseId(event.command.UseCase); const processing = [ USECASE_EVENT_TYPES.COMMAND_CANCELED, USECASE_EVENT_TYPES.USECASE_FAILED, USECASE_EVENT_TYPES.USECASE_SUCCEEDED ].indexOf(event.type) < 0; return { ...state, summary: {...state.summary, [useCaseId]: {processing}} }; } }; export default function useCaseReducer(state: UseCaseState = initialState, action: UseCaseAction) { const handler = actionHandlers[action.type]; return handler ? handler(state, action) : state; }
  11. Ϟσϧྫ export default class Username extends ValueObject<string> { static validators

    = [ maxLength(MAX_LENGTH), format(FORMAT), notNumbersOnly() ]; } export default class Password extends ValueObject<string> { static validators = [ maxLength(MAX_LENGTH), minLength(MIN_LENGTH), format(FORMAT), multipleKindChars({ kinds: [REGEX_NUMBER, REGEX_ALPHABET, REGEX_SYMBOL], min: 2 }) ]; } export default class AuthService { provider: IAuthServiceProvider; accessTokenRepository: IAccessTokenRepository; async login(params: {password: Password, username: Username}): Promise<{accessTokenId: Token}> { const token = await this.provider.login(params); await this.accessTokenRepository.save(token); return {accessTokenId: token.getId()}; } }
  12. ࢀߟࢿྉ • ෳࡶͳJavaScriptΞϓϦέʔγϣϯΛߟ͑ͳ͕Β࡞Δ࿩ • https://azu.github.io/slide/2016/react-meetup/large-scale-javascript.html • Redux΁ͷਖ਼͍͠ղऍͷ࿩ • https://medium.com/@axross/undertanding-truthful-redux-with-usecases-179eefd9fd55 •

    DDD + Clean Architecture + UCDOM Full൛ • https://speakerdeck.com/yoskhdia/ddd-plus-clean-architecture-plus-ucdom-fullban • όϯυϧΧʔυ͕Ͱ͖Δ·Ͱ • http://ideyuta.com/vandlecard/ • ଞʹ΋ͨ͘͞Μ…