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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
moqada
September 14, 2017
Technology
0
710
おれたちのスマホアプリと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
SaaSに宿る21g
kanyamaguc
2
180
CloudFrontのHost Header転送設定でパケットの中身はどう変わるのか?
nagisa53
1
220
非同期・イベント駆動処理の分散トレーシングの繋げ方
ichikawaken
1
160
CREがSLOを握ると 何が変わるのか
nekomaho
0
160
AgentCoreとLINEを使った飲食店おすすめアプリを作ってみた
yakumo
2
260
「AIエージェントで変わる開発プロセス―レビューボトルネックからの脱却」
lycorptech_jp
PRO
0
160
GitHub Advanced Security × Defender for Cloudで開発とSecOpsのサイロを超える: コードとクラウドをつなぐ、開発プラットフォームのセキュリティ
yuriemori
1
110
Astro Islandsの 内部実装を 「日本で一番わかりやすく」 ざっくり解説!
knj
0
300
タスク管理も1on1も、もう「管理」じゃない - KiroとBedrock AgentCoreで変わった“判断の仕事”
yusukeshimizu
0
140
「活動」は激変する。「ベース」は変わらない ~ 4つの軸で捉える_AI時代ソフトウェア開発マネジメント
sentokun
0
110
契約書からの情報抽出を行うLLMのスループットを、バッチ処理を用いて最大40%改善した話
sansantech
PRO
3
300
Laravelで学ぶOAuthとOpenID Connectの基礎と実装
kyoshidaxx
4
1.9k
Featured
See All Featured
16th Malabo Montpellier Forum Presentation
akademiya2063
PRO
0
82
Become a Pro
speakerdeck
PRO
31
5.9k
GraphQLとの向き合い方2022年版
quramy
50
14k
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.2k
Principles of Awesome APIs and How to Build Them.
keavy
128
17k
Documentation Writing (for coders)
carmenintech
77
5.3k
How GitHub (no longer) Works
holman
316
150k
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
160
A Modern Web Designer's Workflow
chriscoyier
698
190k
WENDY [Excerpt]
tessaabrams
9
37k
Leo the Paperboy
mayatellez
4
1.6k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
210
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/ • ଞʹͨ͘͞Μ…