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
640
おれたちのスマホアプリと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
GitHub Copilot の概要
tomokusaba
1
140
フィンテック養成勉強会#54
finengine
0
200
AWS テクニカルサポートとエンドカスタマーの中間地点から見えるより良いサポートの活用方法
kazzpapa3
2
570
Witchcraft for Memory
pocke
1
640
使いたいMCPサーバーはWeb APIをラップして自分で作る #QiitaBash
bengo4com
0
1k
AIとともに進化するエンジニアリング / Engineering-Evolving-with-AI_final.pdf
lycorptech_jp
PRO
0
140
Oracle Audit Vault and Database Firewall 20 概要
oracle4engineer
PRO
3
1.7k
作曲家がボカロを使うようにPdMはAIを使え
itotaxi
0
350
AWS Summit Japan 2025 Community Stage - App workflow automation by AWS Step Functions
matsuihidetoshi
1
300
BrainPadプログラミングコンテスト記念LT会2025_社内イベント&問題解説
brainpadpr
1
170
ネットワーク保護はどう変わるのか?re:Inforce 2025最新アップデート解説
tokushun
0
140
CI/CD/IaC 久々に0から環境を作ったらこうなりました
kaz29
1
200
Featured
See All Featured
Facilitating Awesome Meetings
lara
54
6.4k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
231
53k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
The Invisible Side of Design
smashingmag
300
51k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.5k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
10
940
The Power of CSS Pseudo Elements
geoffreycrofte
77
5.8k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
357
30k
Building Better People: How to give real-time feedback that sticks.
wjessup
367
19k
A Modern Web Designer's Workflow
chriscoyier
694
190k
StorybookのUI Testing Handbookを読んだ
zakiyama
30
5.8k
Done Done
chrislema
184
16k
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/ • ଞʹͨ͘͞Μ…