Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
おれたちのスマホアプリとWeb版 / Real World React Native
Search
moqada
September 14, 2017
Technology
740
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
おれたちのスマホアプリとWeb版 / Real World React Native
Real World React Native in Agri/Med/Fin Tech の発表資料です。
https://connpass.com/event/62895/
moqada
September 14, 2017
Other Decks in Technology
See All in Technology
実践! SRE × AI - ABEMA における AIOps -
tetsuya28
8
3.1k
AIカンパニーへの転換を支えた、エンジニアの仕事の再定義
kworkdev
PRO
2
1.2k
AI導入は済んだ。次は統制を作り替える番だ_AI×開発組織 Summit 2026
techtekt
PRO
0
220
How to Design Configuration for Rails Applications
willnet
1
790
"優秀な誰か"の下位互換にならないために
yut0naga1_fa
1
1.8k
分析の民主化を支えるメタデータ整備の実践
kakehashi
PRO
4
1.4k
10年後、Engineering 行為を剥奪されるとして、わたしたちは、どう、何を創ればいいだろうか。 / If Engineering Is Taken Away in 10 Years, How and What Should We Create?
bitkey
PRO
1
190
hackers_champloo_2026_tidb
makocchi
0
160
20261017福岡devIO 「AIは"良いダッシュボード"を作れるか?
jtakeda
0
150
Persisting Massive Data with Rails ~Designing for 60 billion rows, and fighting the bottlenecks~ Railsで挑む大量データ永続化 〜600億件を永続化するための設計とボトルネックとの戦い〜
occhi
0
730
Local CI × GitHub Actions
gogutan
2
190
Claude Code本は、どのように執筆チームで書かれたか
oikon48
1
380
Featured
See All Featured
Design in an AI World
tapps
1
350
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
659
62k
We Are The Robots
honzajavorek
0
400
Designing for Performance
lara
612
70k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
How to Grow Your eCommerce with AI & Automation
katarinadahlin
PRO
2
330
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
68
58k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
254
22k
4 Signs Your Business is Dying
shpigford
187
23k
Bridging the Design Gap: How Collaborative Modelling removes blockers to flow between stakeholders and teams @FastFlow conf
baasie
0
730
WCS-LA-2024
lcolladotor
0
890
Are puppies a ranking factor?
jonoalderson
2
4.1k
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/ • ଞʹͨ͘͞Μ…