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

Jakub Sowiński
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
Press ← to see last slide Press ESC to see all slides