Pro Yearly is on sale from $80 to $50! »

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

1362a0fe45d90c3ff4c0138a53148b0f?s=47 moqada
September 14, 2017

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

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

1362a0fe45d90c3ff4c0138a53148b0f?s=128

moqada

September 14, 2017
Tweet

Transcript

  1. ͓Εͨͪͷ εϚϗΞϓϦͱ Web൛ Real World React Native in Agri/Med/Fin Tech

  2. ࠓ೔͸ͳ͢͜ͱ • ͓Εͨͪͱ͓ΕͨͪͷεϚϗΞϓϦʹ͍ͭͯ • εϚϗΞϓϦͰϛεͬͨ͜ͱ • Web൛Ͱ΍Δઃܭ

  3. ͓Ε͸ (͓·͑͸?)

  4. moqada

  5. ͓Εͨͪ͸

  6. Kanmu

  7. ͭͬͯ͘Δ΋ͷ

  8. όϯυϧΧʔυ • ΞϓϦ͔Β1෼ͰൃߦͰ͖ΔVISAϓϦϖΠυΧʔυ • બ΂ΔόʔνϟϧΧʔυͱϦΞϧΧʔυ • https://vandle.jp

  9. όϯυϧΧʔυͷྺ࢙ • 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൛ϦϦʔε༧ఆ
  10. όϯυϧΧʔυͷٕज़ελοΫ • ϑϩϯτ͸ɺReact Native, Redux, Flow, ESLint, ESDoc, etc... •

    όοΫΤϯυ͸Golang • JSON Hyper Schema Ͱ APIϦιʔεͷܕΛڞ ༗ͯ͠ΫϥΠΞϯτΛࣗಈੜ੒
  11. Α͛͞ײͷ͋ΔελοΫ (2016౰࣌)

  12. ͚ͩͲݱ࣮͸ͭΒ͍

  13. ͓Εͨͪͷࣦഊ • ΞʔΩςΫνϟ͕݁ߏόϥόϥ • ౰࣌ͷ։ൃਞ(ideyuta, moqada)͕ٸਐ೿ͩͬͨ͜ͱ΋͋Γ࠷ద ͳΞʔΩςΫνϟΛٻΊͨ݁Ռɺ࣌ظຖʹඍົʹ࣮૷͕ҧ͏ঢ় گʹ • ͋ͱͰ౷Ұ͠Α͏ͱ͍͕ͯͨ͠ɺ͕࣌ؒͱΕͣʹϦϦʔεʹࢸΔ

    • ϦϦʔεޙɺͦͷ্ʹػೳ௥ՃΛ͍ͯ͘͜͠ͱʹͳΓͭΒ͍ίʔυ ͕Ճ଎ • ౰ॳͱΖ͏ͱࢥͬͯͨϦϑΝΫλͷ͕࣌ؒͱΕͣ
  14. ݁Ռ

  15. Ͳ͜ͰԿ͕ى͍ͬͯ͜Δͷ͔ ॲཧ͕௥͍ʹ͘͘ɺ มߋͮ͠Β͍ίʔυʹ

  16. ࠓճͷ͓୊

  17. ॲཧ͕௥͍ʹ͍͘໰୊

  18. ʮॲཧʯͱ͸ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷ૊Έ߹ΘͤʹΑΔৼΔ෣͍ •

    ྫ: Կ͕Ͳ͏૊Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
  19. ʮॲཧʯͱ͸ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷ૊Έ߹ΘͤʹΑΔৼΔ෣͍ •

    ྫ: Կ͕Ͳ͏૊Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
  20. Θ͔Γʹ͍͘ʮUIΛؚΉΞϓϦ έʔγϣϯͷಈ͖ʯ • ྫ͑͹ಛఆͷॲཧΛ଴ͬͯϖʔδભҠͤ͞Δ৔߹ • ͍ΖΜͳύλʔϯͰॻ͚Δ • ReactϥΠϑαΠΫϧϝιουύλʔϯ • ActionCreatorύλʔϯ

    • Sagaύλʔϯ
  21. ReactϥΠϑαΠΫϧϝιου ύλʔϯ • componentWillReceivePropsͰ໨తͷΦϒ δΣΫτ͕౉͖ͬͯͨΒભҠͤ͞Δύλʔϯ • ͔ͳΓ΍͹͍΍ͭ

  22. 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()); }); } } } }
  23. ActionCreatorύλʔϯ • ActionCreatorʹඇಉظॲཧΛू໿࣮ͯ͠ߦ͢Δ ύλʔϯ • redux-thunk΍redux-promiseͱ͔Λ࢖͏৔߹͕ ͋Δ • ReduxͷActionʹີ݁߹͔ͭඇಉظͳͷͰςε τ͠ʹ͍͘

  24. 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; }; }
  25. Sagaύλʔϯ • redux-sagaʹҰ࿈ͷॲཧΛू໿࣮ͯ͠ߦ͢Δ ύλʔϯ • खଓ͖తʹ͔͚ͯΘ͔Γ΍͘͢͸͋Δ • GeneratorͰͻͨ͢Βॻ͍͍ͯ͘ɺܕ͕ࢦఆ͠ ʹ͘ҹ৅

  26. 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()); } }
  27. ͬ͘͠Γ͜ͳ͍

  28. ͬ͘͠Γ͜ͳ͍ • Component͸͕͢͞ʹ·͍ͣ • Saga͸Αͦ͞͏͚ͩͲܕ͕ඍົͳ఺͕͍·ͻ ͱͭ • ͜ΕΒͷํ๏͕ࠞࡏͯͨ͠Γ͢Δͱ΋͏…

  29. ʮॲཧʯͱ͸ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷ૊Έ߹ΘͤʹΑΔৼΔ෣͍ •

    ྫ: Կ͕Ͳ͏૊Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
  30. Θ͔Γʹ͍͘ʮ૊Έ߹ΘͤʹΑ ΔৼΔ෣͍ʯ • ྫ͑͹ϢʔβͱΧʔυ৘ใ͔ΒܯࠂεςʔλεΛಋग़͢Δ৔߹ • ؾΛൈ͘ͱ͍ΖΜͳ৔ॴʹॻ͍ͪΌ͏͜ͱ͕͋Δ • Selectorύλʔϯ • Selector(connect௚ॻ͖)ύλʔϯ

    • Helperύλʔϯ • Comonent಺Ͱܭࢉύλʔϯ
  31. ͔͋Μ

  32. ͔͋Μ • ͪΌΜͱॻ͘৔ॴΛܾΊΑ͏… • ֓೦ΛϞσϧԽͯ͠ɺͦͷϝιου౳ʹ͢Δ • ౰ͨΓલͰ͕͢…

  33. ղܾͷͨΊͷΞϓϩʔν • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • Ϣʔεέʔε૚Λͭ͘Δ͜ͱͰγφϦΦΛखଓతʹهड़͢Δ • Clean Architecture ͱ͔ͷ΍ͭ •

    σʔλͷ૊Έ߹ΘͤʹΑΔৼΔ෣͍ • υϝΠϯ૚Λͭ͘Δ͜ͱͰ࢓༷ΛہॴԽ͢Δ • DDDతͳ࿩
  34. ࣮ࡍʹͲ͏΍Δ͔

  35. Ϣʔεέʔε૚

  36. Ϣʔεέʔε૚ͷ࢓૊Έ • खଓ͖͸UseCaseΫϥεʹ·ͱΊΔ • Container͔Β͸UseCase༻ͷ Command(Action)Λൃߦ͠SagaͰॲཧ͢Δ • ࢖͏ଆ͕ҙࣝ͢Δͷ͸ContainerͱUseCaseͩ ͚

  37. 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(); } }
  38. 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);
  39. 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 }; }; }
  40. UseCase࣮ߦج൫ΛSagaͰ؂ࢹ export default function *useCaseSaga( executor: UseCaseExecutor ): Generator<*, *,

    *> { const chan = createUseCaseChannel(executor); yield fork(watchUseCaseEvent, chan); yield takeEvery( useCaseActions.COMMAND_COMMANDED, handleCommand, executor ); }
  41. 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; }
  42. υϝΠϯ૚

  43. υϝΠϯ૚ͷ࢓૊Έ • ֓೦ຖʹΫϥεΛ࡞Δ • ੍໿ͷ͋ΔϞσϧ͸ͦͷ੍໿Λهड़͢Δ • ϑΥʔϜͷValidationͳͲʹ΋ྲྀ༻͢Δ • ͻͱͭͷϞσϧʹऩ·Βͳ͍΋ͷ͸DomainServiceͱ͢ Δ

    • RepositoryͳͲ͸FlowͰinterface͚ͩΛఆٛ͢Δ
  44. Ϟσϧྫ 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()}; } }
  45. શମߏ੒

  46. Α͍ͱ͜Ζ • Ϣʔβʔى఺ͷॲཧΛ௥͍΍͍͢ • جຊతʹϢʔεέʔεʹ௚ྻʹॻ͔Ε͍ͯΔ • ςετ͠΍͍͢ • UseCase͸DIͬΆ͘ͳͬͯΔͷͰɺmockͯ͠ਖ਼͘͠ݺͼग़͞ Ε͍ͯΔ͔֬ೝ͢Δ͚ͩ

    • ܕ͕ͪΌΜͱޮ͘ • UseCaseʹඞཁͳύϥϝʔλΛ࣮֬ʹ౉ͤΔ
  47. ؾʹͳΔͱ͜Ζ • ͳΜ͔΍Γ͗͢ײ͕͋Δ • ݁ߏ࣮૷͢Δͱ͖ʹߟ͑ͳ͍ͱ͍͚ͳ͍ • Ϟσϧͱ͔֓೦ͱ͔ • ਖ਼௚ࣗ෼Ͱ΋͜ΕͰ͍͍ͷ͔ࣗ৴͕ͳ͍ •

    ڵຯ͋Δਓμϝग़ͯ͠͠΄͍͠…
  48. ͱɺ͍͏ͷ͕ Web൛ͷઃܭͷ࿩

  49. εϚϗΞϓϦΛͲ͏͢Δ͔? • Web൛ͱҰ෦Λڞ௨Խ͢Δ • υϝΠϯϞσϧɺϦϙδτϦͷҰ෦ɺAPIΫϥΠΞϯτͳ ͲΛผϦϙδτϦʹ੾Γग़ͯ͠submoduleͰऔΓࠐΉ༧ ఆ • Ϣʔεέʔε΍ίϯϙʔωϯτ͸ڞ௨Խ͠ͳ͍ •

    UIΛؚΉ΋ͷ͸֤ϓϥοτϑΥʔϜͰͷ࠷దղ͕ҟͳΔ ͸ͣͳͷͰແཧʹྲྀ༻͠ͳ͍
  50. ·ͱΊ • Ϣʔεέʔε૚υϝΠϯ૚Λͭ͘Δ͜ͱͰʮॲ ཧ͕௥͍ʹ͍͘ʯΛ؇࿨Ͱ͖Δઆ • React NativeͳΒωΠςΟϒͱWebͰɺઃܭ ͚ͩ͡Όͳ͘ɺ࣮૷΋૬ޓʹϑΟʔυόοΫ ͓͋͑ͯ͠ಘ •

    ਓ(ྗ)͕ཉ͍͠
  51. ͓ΘΓ

  52. ࢀߟࢿྉ • ෳࡶͳ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/ • ଞʹ΋ͨ͘͞Μ…