Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
おれたちのスマホアプリとWeb版 / Real World React Native
Search
moqada
September 14, 2017
Technology
0
610
おれたちのスマホアプリとWeb版 / Real World React Native
Real World React Native in Agri/Med/Fin Tech の発表資料です。
https://connpass.com/event/62895/
moqada
September 14, 2017
Tweet
Share
Other Decks in Technology
See All in Technology
TypeScript開発にモジュラーモノリスを持ち込む
sansantech
PRO
2
640
あの日俺達が夢見たサーバレスアーキテクチャ/the-serverless-architecture-we-dreamed-of
tomoki10
0
500
【re:Invent 2024 アプデ】 Prompt Routing の紹介
champ
0
160
スタートアップで取り組んでいるAzureとMicrosoft 365のセキュリティ対策/How to Improve Azure and Microsoft 365 Security at Startup
yuj1osm
0
240
[JAWS-UG新潟#20] re:Invent2024 -CloudOperationsアップデートについて-
shintaro_fukatsu
0
120
React Routerで実現する型安全なSPAルーティング
sansantech
PRO
2
280
いまからでも遅くないコンテナ座学
nomu
0
130
Working as a Server-side Engineer at LY Corporation
lycorp_recruit_jp
0
370
日本版とグローバル版のモバイルアプリ統合の開発の裏側と今後の展望
miichan
1
140
LINEスキマニにおけるフロントエンド開発
lycorptech_jp
PRO
0
340
APIとはなにか
mikanichinose
0
110
LINE Developersプロダクト(LIFF/LINE Login)におけるフロントエンド開発
lycorptech_jp
PRO
0
150
Featured
See All Featured
What's in a price? How to price your products and services
michaelherold
244
12k
How STYLIGHT went responsive
nonsquared
96
5.2k
Music & Morning Musume
bryan
46
6.2k
The Invisible Side of Design
smashingmag
298
50k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
47
5.1k
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
132
33k
It's Worth the Effort
3n
183
28k
Unsuck your backbone
ammeep
669
57k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Reflections from 52 weeks, 52 projects
jeffersonlam
347
20k
Transcript
͓Εͨͪͷ εϚϗΞϓϦͱ Web൛ Real World React Native in Agri/Med/Fin Tech
ࠓͳ͢͜ͱ • ͓Εͨͪͱ͓ΕͨͪͷεϚϗΞϓϦʹ͍ͭͯ • εϚϗΞϓϦͰϛεͬͨ͜ͱ • Web൛ͰΔઃܭ
͓Ε (͓·͑?)
moqada
͓Εͨͪ
Kanmu
ͭͬͯ͘Δͷ
όϯυϧΧʔυ • ΞϓϦ͔Β1ͰൃߦͰ͖ΔVISAϓϦϖΠυΧʔυ • બΔόʔνϟϧΧʔυͱϦΞϧΧʔυ • https://vandle.jp
όϯυϧΧʔυͷྺ࢙ • 201512݄ ։ൃ։࢝ • ωΠςΟϒܦݧऀͷ͍ͳ͍νʔϜͰૉૣͦ͘ΕͳΓͷΫΦϦςΟͰϦϦʔε͢ΔͨΊʹ React Native Λબ (ॳ20165݄ϦϦʔε༧ఆ)
• 3ਓͷ։ൃνʔϜͰɺFintechࣄۀΛͲ͏࡞Δʁ࠷খߏͷνʔϜͰΉɺͦͷઓུͱ (https:// seleck.cc/850) • React Native ͰiOS൛ͷΈ࣮͢Δ༧ఆͩͬͨ • 20169݄ iOS൛ϦϦʔε • 201612݄ Android൛ϦϦʔε • ҙ֎ͱAndroid͙͍͚ͨ͢ • 20179݄ Web൛ϦϦʔε༧ఆ
όϯυϧΧʔυͷٕज़ελοΫ • ϑϩϯτɺReact Native, Redux, Flow, ESLint, ESDoc, etc... •
όοΫΤϯυGolang • JSON Hyper Schema Ͱ APIϦιʔεͷܕΛڞ ༗ͯ͠ΫϥΠΞϯτΛࣗಈੜ
Α͛͞ײͷ͋ΔελοΫ (2016࣌)
͚ͩͲݱ࣮ͭΒ͍
͓Εͨͪͷࣦഊ • ΞʔΩςΫνϟ͕݁ߏόϥόϥ • ࣌ͷ։ൃਞ(ideyuta, moqada)͕ٸਐͩͬͨ͜ͱ͋Γ࠷ద ͳΞʔΩςΫνϟΛٻΊͨ݁Ռɺ࣌ظຖʹඍົʹ࣮͕ҧ͏ঢ় گʹ • ͋ͱͰ౷Ұ͠Α͏ͱ͍͕ͯͨ͠ɺ͕࣌ؒͱΕͣʹϦϦʔεʹࢸΔ
• ϦϦʔεޙɺͦͷ্ʹػೳՃΛ͍ͯ͘͜͠ͱʹͳΓͭΒ͍ίʔυ ͕Ճ • ॳͱΖ͏ͱࢥͬͯͨϦϑΝΫλͷ͕࣌ؒͱΕͣ
݁Ռ
Ͳ͜ͰԿ͕ى͍ͬͯ͜Δͷ͔ ॲཧ͕͍ʹ͘͘ɺ มߋͮ͠Β͍ίʔυʹ
ࠓճͷ͓
ॲཧ͕͍ʹ͍͘
ʮॲཧʯͱ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷΈ߹ΘͤʹΑΔৼΔ͍ •
ྫ: Կ͕Ͳ͏Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
ʮॲཧʯͱ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷΈ߹ΘͤʹΑΔৼΔ͍ •
ྫ: Կ͕Ͳ͏Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
Θ͔Γʹ͍͘ʮUIΛؚΉΞϓϦ έʔγϣϯͷಈ͖ʯ • ྫ͑ಛఆͷॲཧΛͬͯϖʔδભҠͤ͞Δ߹ • ͍ΖΜͳύλʔϯͰॻ͚Δ • ReactϥΠϑαΠΫϧϝιουύλʔϯ • ActionCreatorύλʔϯ
• Sagaύλʔϯ
ReactϥΠϑαΠΫϧϝιου ύλʔϯ • componentWillReceivePropsͰతͷΦϒ δΣΫτ͕͖ͬͯͨΒભҠͤ͞Δύλʔϯ • ͔ͳΓ͍ͭ
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()); }); } } } }
ActionCreatorύλʔϯ • ActionCreatorʹඇಉظॲཧΛू࣮ͯ͠ߦ͢Δ ύλʔϯ • redux-thunkredux-promiseͱ͔Λ͏߹͕ ͋Δ • ReduxͷActionʹີ݁߹͔ͭඇಉظͳͷͰςε τ͠ʹ͍͘
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; }; }
Sagaύλʔϯ • redux-sagaʹҰ࿈ͷॲཧΛू࣮ͯ͠ߦ͢Δ ύλʔϯ • खଓ͖తʹ͔͚ͯΘ͔Γ͋͘͢Δ • GeneratorͰͻͨ͢Βॻ͍͍ͯ͘ɺܕ͕ࢦఆ͠ ʹ͘ҹ
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()); } }
ͬ͘͠Γ͜ͳ͍
ͬ͘͠Γ͜ͳ͍ • Component͕͢͞ʹ·͍ͣ • SagaΑͦ͞͏͚ͩͲܕ͕ඍົͳ͕͍·ͻ ͱͭ • ͜ΕΒͷํ๏͕ࠞࡏͯͨ͠Γ͢Δͱ͏…
ʮॲཧʯͱ • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ྫ: ϩάΠϯϘλϯԡͨ͠ΒͲ͏ͳΔͷɺͲͷ ϖʔδʹભҠ͢Δͷ? • σʔλͷΈ߹ΘͤʹΑΔৼΔ͍ •
ྫ: Կ͕Ͳ͏Έ߹ΘͤͬͨΒܯࠂঢ়ଶʹͳΔͷ?
Θ͔Γʹ͍͘ʮΈ߹ΘͤʹΑ ΔৼΔ͍ʯ • ྫ͑ϢʔβͱΧʔυใ͔ΒܯࠂεςʔλεΛಋग़͢Δ߹ • ؾΛൈ͘ͱ͍ΖΜͳॴʹॻ͍ͪΌ͏͜ͱ͕͋Δ • Selectorύλʔϯ • Selector(connectॻ͖)ύλʔϯ
• Helperύλʔϯ • ComonentͰܭࢉύλʔϯ
͔͋Μ
͔͋Μ • ͪΌΜͱॻ͘ॴΛܾΊΑ͏… • ֓೦ΛϞσϧԽͯ͠ɺͦͷϝιουʹ͢Δ • ͨΓલͰ͕͢…
ղܾͷͨΊͷΞϓϩʔν • UIΛؚΉΞϓϦέʔγϣϯͷಈ͖ • ϢʔεέʔεΛͭ͘Δ͜ͱͰγφϦΦΛखଓతʹهड़͢Δ • Clean Architecture ͱ͔ͷͭ •
σʔλͷΈ߹ΘͤʹΑΔৼΔ͍ • υϝΠϯΛͭ͘Δ͜ͱͰ༷ΛہॴԽ͢Δ • DDDతͳ
࣮ࡍʹͲ͏Δ͔
Ϣʔεέʔε
ϢʔεέʔεͷΈ • खଓ͖UseCaseΫϥεʹ·ͱΊΔ • Container͔ΒUseCase༻ͷ Command(Action)Λൃߦ͠SagaͰॲཧ͢Δ • ͏ଆ͕ҙࣝ͢ΔͷContainerͱUseCaseͩ ͚
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(); } }
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);
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 }; }; }
UseCase࣮ߦج൫ΛSagaͰࢹ export default function *useCaseSaga( executor: UseCaseExecutor ): Generator<*, *,
*> { const chan = createUseCaseChannel(executor); yield fork(watchUseCaseEvent, chan); yield takeEvery( useCaseActions.COMMAND_COMMANDED, handleCommand, executor ); }
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; }
υϝΠϯ
υϝΠϯͷΈ • ֓೦ຖʹΫϥεΛ࡞Δ • ੍ͷ͋ΔϞσϧͦͷ੍Λهड़͢Δ • ϑΥʔϜͷValidationͳͲʹྲྀ༻͢Δ • ͻͱͭͷϞσϧʹऩ·Βͳ͍ͷDomainServiceͱ͢ Δ
• RepositoryͳͲFlowͰinterface͚ͩΛఆٛ͢Δ
Ϟσϧྫ 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()}; } }
શମߏ
Α͍ͱ͜Ζ • ϢʔβʔىͷॲཧΛ͍͍͢ • جຊతʹϢʔεέʔεʹྻʹॻ͔Ε͍ͯΔ • ςετ͍͢͠ • UseCaseDIͬΆ͘ͳͬͯΔͷͰɺmockͯ͠ਖ਼͘͠ݺͼग़͞ Ε͍ͯΔ͔֬ೝ͢Δ͚ͩ
• ܕ͕ͪΌΜͱޮ͘ • UseCaseʹඞཁͳύϥϝʔλΛ࣮֬ʹͤΔ
ؾʹͳΔͱ͜Ζ • ͳΜ͔Γ͗͢ײ͕͋Δ • ݁ߏ࣮͢Δͱ͖ʹߟ͑ͳ͍ͱ͍͚ͳ͍ • Ϟσϧͱ͔֓೦ͱ͔ • ਖ਼ࣗͰ͜ΕͰ͍͍ͷ͔ࣗ৴͕ͳ͍ •
ڵຯ͋Δਓμϝग़ͯ͠͠΄͍͠…
ͱɺ͍͏ͷ͕ Web൛ͷઃܭͷ
εϚϗΞϓϦΛͲ͏͢Δ͔? • Web൛ͱҰ෦Λڞ௨Խ͢Δ • υϝΠϯϞσϧɺϦϙδτϦͷҰ෦ɺAPIΫϥΠΞϯτͳ ͲΛผϦϙδτϦʹΓग़ͯ͠submoduleͰऔΓࠐΉ༧ ఆ • Ϣʔεέʔείϯϙʔωϯτڞ௨Խ͠ͳ͍ •
UIΛؚΉͷ֤ϓϥοτϑΥʔϜͰͷ࠷దղ͕ҟͳΔ ͣͳͷͰແཧʹྲྀ༻͠ͳ͍
·ͱΊ • ϢʔεέʔευϝΠϯΛͭ͘Δ͜ͱͰʮॲ ཧ͕͍ʹ͍͘ʯΛ؇Ͱ͖Δઆ • React NativeͳΒωΠςΟϒͱWebͰɺઃܭ ͚ͩ͡Όͳ͘ɺ࣮૬ޓʹϑΟʔυόοΫ ͓͋͑ͯ͠ಘ •
ਓ(ྗ)͕ཉ͍͠
͓ΘΓ
ࢀߟࢿྉ • ෳࡶͳ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/ • ଞʹͨ͘͞Μ…