Press → to see first slide Press ESC to see all slides

Jakub Sowiński

jakubsowinski.com | mail[at]jakubsowinski.com

3 lata życia aplikacji napisanej w React i TypeScript: lekcje i wnioski

3 lata życia
aplikacji napisanej
w React i TypeScript:
lekcje i wnioski

🗺️

  • 👉 Założenia 📌

  • 👉 Implementacja

  • 👉 Życie na produkcji

🙋?

Problemy
oryginalnej aplikacji:

  • 🔥 nie działa

  • ⚠️ niejasny data flow

Problemy
oryginalnej aplikacji:

  • 🔥 nie działa

  • ⚠️ niejasny data flow

  • ⚠️ słaby performance

Problemy
oryginalnej aplikacji:

  • 🔥 nie działa

  • ⚠️ niejasny data flow

  • ⚠️ słaby performance

  • ⚠️ brak możliwości rozbudowy

Założenia rozwiązania

  • 👌 separacja kodu na moduły

Założenia rozwiązania

  • 👌 separacja kodu na moduły

  • 👌 deterministyczny data flow

Założenia rozwiązania

  • 👌 separacja kodu na moduły

  • 👌 deterministyczny data flow

  • 👌 otwartość na modyfikacje

  • 👌 lepszy performance

🗺️

  • 👉 Założenia

  • 👉 Implementacja 📌

  • 👉 Życie na produkcji

React

/src
  /components
    /PersonalDetailsEdit
    /PersonalDetailsPreview
    ...
  /containers
    /App
    /PersonalDetails
    ...
   

src/containers/App/index.tsx

export class App extends React.Component {
  render() {
    return(
      <PageLayout loading={this.props.loading}>
        <PersonalDetails />
        <MediaQuery maxWidth={this.props.screenMdMax}>
          <ProfileCompleteness />
          <MatchMyCv />
        </MediaQuery>
        <Skills />
        <Languages />
        <WorkExperience />
        <Education />
        <WorkPreferences />
        {this.props.profileVisibilityEnabled
          && <ProfileVisibility />}
        <Toast />
        <ErrorHandler />
      </PageLayout>
    );
  }
}
   

src/containers/PersonalDetails/index.tsx

export class PersonalDetails extends React.Component {
  render() {
    return(
      <SectionWrapper sectionName={this.props.sectionName} >
        <SectionBody>
        {this.props.editMode
          ? <PersonalDetailsEdit
            data={this.props.data}
          />
          : <PersonalDetailsPreview
            data={this.props.data}
          />
        }
        </SectionBody>
      </SectionWrapper>
    );
  }
}
   

Redux

/src
  /components
    /PersonalDetailsEdit
    /PersonalDetailsPreview
    ...
  /containers
    /App
    /PersonalDetails
    ...
  /store
    /personalDetails
    ...
   

src/store/personalDetails/reducer.tsx

import { PERSONAL_DETAILS_SAVE } from 'store/actionTypes';

export const initialState = { loading: false };

export const appReducer = (state = initialState, action) => {
  switch (action.type) {
    case PERSONAL_DETAILS_SAVE:
      return {
        loading: true,
        ...state,
      }

    default:
      return { ...state };
  }
};
   

src/containers/PersonalDetails/index.tsx

import { connect } from 'react-redux';
import { onPersonalDetailsSave } from './actions'

export class PersonalDetails extends React.Component { /* ... */ }

const mapStateToProps = (state) => ({
  data: state.personalDetails.data,
  editMode: state.personalDetails.editMode,
});

const mapDispatchToProps = (dispatch) => ({
  onSave: () => dispatch(onPersonalDetailsSave),
})

export const PersonalDetailsContainer = connect(
  mapStateToProps,
  mapDispatchToProps
)(PersonalDetails);
   

Globalny stan

Redux Dev Tools

Redux-Saga

src/containers/PersonalDetails/saga.ts

import { put, call } from 'redux-saga/effects';
import { takeLatest } from 'redux-saga';
import { PERSONAL_DETAILS_SAVE } from 'store/actionTypes';
import {
  updatePersonalDetailsRequestSuccess,
  updatePersonalDetailsRequestFailure,
} from './actions';

function* updatePersonalDetails(action) {
  const response = yield call(
    fetch,
    '/public-api/v1/profile/personal-details',
    { body: JSON.stringify(action.data) },
  );

  yield put(response.err
    ? updatePersonalDetailsRequestFailure(response)
    : updatePersonalDetailsRequestSuccess(response));
}

export function* watchPersonalDetails() {
  yield takeLatest(PERSONAL_DETAILS_SAVE, updatePersonalDetails);
}
   

Redux Form

TypeScript

💀 Szkielet aplikacji

🗺️

  • 👉 Założenia

  • 👉 Implementacja

  • 👉 Życie na produkcji 📌

😱

🐛 Wykrywanie błędów

Property based tests

import { sum } from '../';
import fc from 'fast-check';

describe('sum function', () => {

  test('checks adding two random numbers', () => {
    fc.assert(
      fc.property(fc.float(), fc.float(), (a, b) => {
        expect(sum(a, b)).toBe(a + b);
      }),
    );
  });

});
   

🚒 Rozszerzanie aplikacji

src/containers/App/index.tsx

export class App extends React.Component {
  render() {
    return(
      <PageLayout loading={this.props.loading}>
        <NewSection />
        /* ...  */
      </PageLayout>
    );
  }
}
   

webpack.config.js

entry: {
  app: 'src/containers/App/index.tsx',
  altApp: 'src/containers/AltApp/index.tsx',
},
   

Założenia rozwiązania

  • ✔️ separacja kodu na moduły

  • ✔️ deterministyczny data flow

  • ✔️ otwartość na modyfikacje

  • ❌ lepszy performance

💡

Thank you

jakubsowinski.com | mail[at]jakubsowinski.com

Press ← to see last slide Press ESC to see all slides