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
670
おれたちのスマホアプリと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
アウトプットから始めるOSSコントリビューション 〜eslint-plugin-vueの場合〜 #vuefes
bengo4com
3
1.8k
OPENLOGI Company Profile for engineer
hr01
1
46k
From Natural Language to K8s Operations: The MCP Architecture and Practice of kubectl-ai
appleboy
0
330
東京大学「Agile-X」のFPGA AIデザインハッカソンを制したソニーのAI最適化
sony
0
150
ソースを読む時の思考プロセスの例-MkDocs
sat
PRO
1
320
CREが作る自己解決サイクルSlackワークフローに組み込んだAIによる社内ヘルプデスク改革 #cre_meetup
bengo4com
0
360
QA業務を変える(!?)AIを併用した不具合分析の実践
ma2ri
0
160
激動の時代を爆速リチーミングで乗り越えろ
sansantech
PRO
1
170
コンパウンド組織のCRE #cre_meetup
layerx
PRO
1
290
クラウドとリアルの融合により、製造業はどう変わるのか?〜クラスメソッドの製造業への取組と共に〜
hamadakoji
0
450
JSConf JPのwebsiteをGatsbyからNext.jsに移行した話 - Next.jsの多言語静的サイトと課題
leko
2
190
ViteとTypeScriptのProject Referencesで 大規模モノレポのUIカタログのリリースサイクルを高速化する
shuta13
3
220
Featured
See All Featured
Visualization
eitanlees
150
16k
Typedesign – Prime Four
hannesfritz
42
2.8k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
3.7k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
1.7k
GitHub's CSS Performance
jonrohan
1032
470k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
35
3.2k
How To Stay Up To Date on Web Technology
chriscoyier
791
250k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
132
19k
Navigating Team Friction
lara
190
15k
Why Our Code Smells
bkeepers
PRO
340
57k
Intergalactic Javascript Robots from Outer Space
tanoku
272
27k
Learning to Love Humans: Emotional Interface Design
aarron
274
41k
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/ • ଞʹͨ͘͞Μ…