Slide 1

Slide 1 text

Angular使いが Reactでアプリ組んだらこうなった React勉強会@福岡 vol.2

Slide 2

Slide 2 text

Noriyuki Shinpuku ng-fukuoka organizer VEGA corporation Co., Ltd. @puku0x

Slide 3

Slide 3 text

Angular

Slide 4

Slide 4 text

Supported by Google

Slide 5

Slide 5 text

Full-fledged & opinionated Angular Protractor Forms PWA Augury Language Services Router Elements CDK Universal Karma Labs Compiler i18n Http Material Animations CLI

Slide 6

Slide 6 text

React

Slide 7

Slide 7 text

Supported by Facebook

Slide 8

Slide 8 text

React is a library

Slide 9

Slide 9 text

Scalable apps with React?

Slide 10

Slide 10 text

1. Use TypeScript $ npm i -D @types/{react,react-dom}

Slide 11

Slide 11 text

2. Abstraction ● Keep components SIMPLE ● Better testability Component Service HttpClient Axios

Slide 12

Slide 12 text

HttpClient (inspired by Angular’s HttpClient) export abstract class HttpClient { abstract get(url: string, options?: HttpRequestOptions) abstract post(url: string, data?: unknown, options?: HttpRequestOptions) abstract put(url: string, data?: unknown, options?: HttpRequestOptions) abstract delete(url: string, options?: HttpRequestOptions) }

Slide 13

Slide 13 text

Services const fetchUser = async (id: number) => { const res = await httpClient.get(`/users/${id}`); return res.data; }; export const UserService = { fetch: fetchUser, ... };

Slide 14

Slide 14 text

3. Separation of concerns ● Business logic ● State management ● Components

Slide 15

Slide 15 text

State management ● react-redux + redux-thunk + re-ducks pattern ● Keep reducers PURE ● Good practices from NgRx ○ Good Action Hygiene ○ Entity pattern

Slide 16

Slide 16 text

Good Action Hygiene export enum UserActionTypes { // ACTION_NAME = '[Source] Event', FETCH_REQUEST = '[User/Page] Fetch Request', FETCH_SUCCESS = '[User/API] Fetch Success', FETCH_FAILURE = '[User/API] Fetch Failure', ... } Good Action Hygiene with NgRx Mike Ryan https://www.youtube.com/watch?v=JmnsEvoy-gY

Slide 17

Slide 17 text

Thunk Actions export interface FetchUserRequest extends Action { payload: { id: number }; } export function fetchUserRequest(id: number): ThunkAction, State, undefined, Actions> { return async dispatch => { dispatch({ type: UserActionTypes.FETCH_REQUEST, payload: { id } }); const result = await UserService.fetch(id) .then(response => fetchUserSuccess(response)) .catch(error => fetchUserFailure(error)); return dispatch(result); }; }

Slide 18

Slide 18 text

Entity pattern interface Dictionary { [id: number]: T; } export interface EntityState { ids: number[]; entities: Dictionary; }

Slide 19

Slide 19 text

EntityAdapter (inspired by NgRx’s EntityAdapter) export const adapter: EntityAdapter = createEntityAdapter(); export function reducer(state = initialState, action: Action): State { switch (action.type) { ... case UserActionTypes.UPDATE_SUCCESS: { const { user } = action.payload; return adapter.update(user, { ...state, isFetching: false }); } ... } } Immutable operation with less boilerplates

Slide 20

Slide 20 text

Selectors const usersStateSelector = (state: { users: UserState }) => state.users; const { selectAll, selectEntities } = adapter.getSelectors(); export const usersSelector = createSelector( usersStateSelector, selectAll );

Slide 21

Slide 21 text

3. Separation of concerns (for components) ● Business logic ● State management ● Components ○ Page components ○ Container components ○ Presentational components

Slide 22

Slide 22 text

Structure of components Page component Router params Store Container components Presentational components http://localhost:3000/users/:id

Slide 23

Slide 23 text

Page components type Props = RouteComponentProps<{ id: string }>; const UserDetailPage: FunctionComponent = props => { const { match, location } = props; const { id } = match.params; const params = new URLSearchParams(location.search); const edit = params.get('edit') || false; return ; }; export default withRouter(UserDetailPage);

Slide 24

Slide 24 text

Container components type Props = { id: number; }; export const UserDetail: FunctionComponent = props => { const { id } = props; const user = useSelector(userSelector); const dispatch = useDispatch(); useEffect(() => dispatch(fetchUserRequest(id)), [id]); return ; };

Slide 25

Slide 25 text

Presentational components type Props = RouteComponentProps & { user: User; }; const UserDetailComponent: FunctionComponent = props => { const { user, history } = props; const goBack = useCallback(() => history.goBack(), []); return <>...> }; export const UserDetail = withRouter(UserDetailComponent);

Slide 26

Slide 26 text

4. Lazy loading ● Suspense + lazy() ● Route-based lazy loading import { lazy } from 'react'; export const UsersPage = lazy(() => import('./UsersPage'));

Slide 27

Slide 27 text

Routing separation Loading...}> } /> “Separation of concerns” for routing

Slide 28

Slide 28 text

Routing separation for child pages const UsersPage: FunctionComponent = () => ( Loading...}> ); export default UsersPage;

Slide 29

Slide 29 text

src/ models/ user.model.ts index.ts pages/ UsersPage/ UserDetailPage/ components/ containers/ UserDetailPage.tsx index.ts UsersPage.tsx index.ts MainPage/ index.ts 5. Naming & structuring like way shared/ components/ Button/ Button.tsx Button.test.tsx Button.stories.tsx index.ts helpers/ hooks/ index.ts : App.tsx AuthenticatedRoute.tsx index.tsx index.html services/ user/ user.service.ts user.service.test.ts index.ts store/ user/ actions/ user.action.ts user.action.test.ts index.ts reducers/ selectors/ states/ index.ts https://angular.io/guide/styleguide

Slide 30

Slide 30 text

Other libraries?

Slide 31

Slide 31 text

Styling ● emotion ● classnames

Slide 32

Slide 32 text

Form validation ● Formik (v1.5) ○ v2.0.1-rc.x is not recommended https://github.com/jaredpalmer/formik/pull/1570 ● Yup

Slide 33

Slide 33 text

Routing ● react-router (v5.0) ● connected-react-router const middleware = process.env.NODE_ENV !== 'production' ? [require('connected-react-router/immutable').routerMiddleware(history), thunk] : [require('connected-react-router').routerMiddleware(history), thunk]; const middlewares = applyMiddleware(...middleware);

Slide 34

Slide 34 text

State management ● react-redux (v7.1) ○ Requires adding custom @types/react-redux ● redux-thunk ○ Requires overloading redux’s Dispatch https://github.com/reduxjs/redux-thunk/pull/247 ● reselect

Slide 35

Slide 35 text

Testing ● jest ● enzyme ● react-test-renderer ● redux-mock-store

Slide 36

Slide 36 text

Follow good practices

Slide 37

Slide 37 text

One more option

Slide 38

Slide 38 text

We don’t use create-react-app Because it doesn’t allow us to use path alias.

Slide 39

Slide 39 text

How was it?

Slide 40

Slide 40 text

Awesome!

Slide 41

Slide 41 text

● Type safe ● Simple ● Easy to refactor ● Performant ● Scalable

Slide 42

Slide 42 text

Summary ● The experience of helped us to make app ○ Using TypeScript ○ Abstraction ○ Separation of concerns ○ Lazy loading ○ Naming

Slide 43

Slide 43 text

Always keep an open mind

Slide 44

Slide 44 text

Thank you! @puku0x Noriyuki Shinpuku