Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Angular使いがReactでアプリ組んだらこうなった

puku0x
June 05, 2019

 Angular使いがReactでアプリ組んだらこうなった

React勉強会@福岡 vol.2

puku0x

June 05, 2019
Tweet

More Decks by puku0x

Other Decks in Technology

Transcript

  1. Full-fledged & opinionated Angular Protractor Forms PWA Augury Language Services

    Router Elements CDK Universal Karma Labs Compiler i18n Http Material Animations CLI
  2. HttpClient (inspired by Angular’s HttpClient) export abstract class HttpClient {

    abstract get<T>(url: string, options?: HttpRequestOptions) abstract post<T>(url: string, data?: unknown, options?: HttpRequestOptions) abstract put<T>(url: string, data?: unknown, options?: HttpRequestOptions) abstract delete(url: string, options?: HttpRequestOptions) }
  3. Services const fetchUser = async (id: number) => { const

    res = await httpClient.get<User>(`/users/${id}`); return res.data; }; export const UserService = { fetch: fetchUser, ... };
  4. State management • react-redux + redux-thunk + re-ducks pattern •

    Keep reducers PURE • Good practices from NgRx ◦ Good Action Hygiene ◦ Entity pattern
  5. 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
  6. Thunk Actions export interface FetchUserRequest extends Action<UserActionTypes.FETCH_REQUEST> { payload: {

    id: number }; } export function fetchUserRequest(id: number): ThunkAction<Promise<Success | Failure>, State, undefined, Actions> { return async dispatch => { dispatch<FetchUserRequest>({ type: UserActionTypes.FETCH_REQUEST, payload: { id } }); const result = await UserService.fetch(id) .then(response => fetchUserSuccess(response)) .catch(error => fetchUserFailure(error)); return dispatch(result); }; }
  7. Entity pattern interface Dictionary<T> { [id: number]: T; } export

    interface EntityState<T> { ids: number[]; entities: Dictionary<T>; }
  8. EntityAdapter (inspired by NgRx’s EntityAdapter) export const adapter: EntityAdapter<User> =

    createEntityAdapter<User>(); 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
  9. Selectors const usersStateSelector = (state: { users: UserState }) =>

    state.users; const { selectAll, selectEntities } = adapter.getSelectors(); export const usersSelector = createSelector( usersStateSelector, selectAll );
  10. 3. Separation of concerns (for components) • Business logic •

    State management • Components ◦ Page components ◦ Container components ◦ Presentational components
  11. Structure of components Page component Router params Store Container components

    Presentational components http://localhost:3000/users/:id
  12. Page components type Props = RouteComponentProps<{ id: string }>; const

    UserDetailPage: FunctionComponent<Props> = props => { const { match, location } = props; const { id } = match.params; const params = new URLSearchParams(location.search); const edit = params.get('edit') || false; return <UserDetail id={+id} edit={+edit} />; }; export default withRouter(UserDetailPage);
  13. Container components type Props = { id: number; }; export

    const UserDetail: FunctionComponent<Props> = props => { const { id } = props; const user = useSelector(userSelector); const dispatch = useDispatch(); useEffect(() => dispatch(fetchUserRequest(id)), [id]); return <UserDetailComponent user={user} />; };
  14. Presentational components type Props = RouteComponentProps & { user: User;

    }; const UserDetailComponent: FunctionComponent<Props> = props => { const { user, history } = props; const goBack = useCallback(() => history.goBack(), []); return <>...</> }; export const UserDetail = withRouter(UserDetailComponent);
  15. 4. Lazy loading • Suspense + lazy() • Route-based lazy

    loading import { lazy } from 'react'; export const UsersPage = lazy(() => import('./UsersPage'));
  16. Routing separation <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/" render={() =>

    <Redirect to="/dashboard" />} /> <Route path="/dashboard" component={DashboardPage} /> <Route path="/groups" component={GroupsPage} /> <Route path="/users" component={UsersPage} /> </Switch> </Suspense> “Separation of concerns” for routing
  17. Routing separation for child pages const UsersPage: FunctionComponent = ()

    => ( <Suspense fallback={<div>Loading...</div>}> <Switch> <Route exact path="/users" component={UserListPage} /> <Route exact path="/users/new" component={UserCreatePage} /> <Route exact path="/users/:id"component={UserDetailPage} /> <Route exact path="/users/:id/edit" component={UserEditPage} /> </Switch> </Suspense> ); export default UsersPage;
  18. 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
  19. Form validation • Formik (v1.5) ◦ v2.0.1-rc.x is not recommended

    https://github.com/jaredpalmer/formik/pull/1570 • Yup
  20. 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);
  21. State management • react-redux (v7.1) ◦ Requires adding custom @types/react-redux

    • redux-thunk ◦ Requires overloading redux’s Dispatch<Action> https://github.com/reduxjs/redux-thunk/pull/247 • reselect
  22. Summary • The experience of helped us to make app

    ◦ Using TypeScript ◦ Abstraction ◦ Separation of concerns ◦ Lazy loading ◦ Naming