-
-
Save on3iro/ef7ba74586e34002aae908fbb9b78f9b to your computer and use it in GitHub Desktop.
| import { applyMiddleware, createStore } from 'redux' | |
| import thunkMiddleware from 'redux-thunk' | |
| import { composeWithDevTools } from 'redux-devtools-extension' | |
| import { postReducer } from './posts/posts' | |
| import { combineReducers } from '@reduxjs/toolkit' | |
| export function configureStore() { | |
| const middlewares = [thunkMiddleware] | |
| const middlewareEnhancer = applyMiddleware(...middlewares) | |
| const enhancers = [middlewareEnhancer] | |
| const composedEnhancers = composeWithDevTools(...enhancers) | |
| const store = createStore(combineReducers({ posts: postReducer }), undefined, composedEnhancers) | |
| return store | |
| } |
| import React from 'react' | |
| import ReactDOM from 'react-dom' | |
| import './index.scss' | |
| import App from './components/App/App' | |
| import reportWebVitals from './reportWebVitals' | |
| import { configureStore } from './store/configureStore' | |
| export const store = configureStore() | |
| // Infer the `RootState` and `AppDispatch` types from the store itself | |
| export type RootState = ReturnType<typeof store.getState> | |
| // Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} | |
| export type AppDispatch = typeof store.dispatch | |
| ReactDOM.render( | |
| <React.StrictMode> | |
| <App /> | |
| </React.StrictMode>, | |
| document.getElementById('root') | |
| ) | |
| // If you want to start measuring performance in your app, pass a function | |
| // to log results (for example: reportWebVitals(console.log)) | |
| // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals | |
| reportWebVitals() |
Reducer Beispiel
const initialState = [] // empty list of Todos
const reducer = (oldState = initialState, action) => {
switch (action.type) {
case 'todos/todoAdded': {
const addedTodo = action.payload.todo
return [...oldState, addedTodo]
}
default: {
// leave state as it is
return oldState
}
}
}2. Actions und neues State
- Erstelle einen actionCreator
addPost()mit einemaction.type = 'posts/addPost' - Sorge dafür, dass der
postReducer:- auf die Action reagiert
- ein neues
PostStatezurueckgibt - das neue
PostStatesollte zum einen inbyIdden neuen Post und zum anderen
inidsdieiddes Posts beinhalten
- Dispatche manuell eine
posts/addPostaction mit Hilfe der Redux-Dev-Tools und
vergewissere dich, dass das State korrekt geupdated wurde
Beispiel ActionCreator:
const ADD_TODO = 'ADD_TODO'
const addTodo = (text: string) => {
return {
type: ADD_TODO,
payload: { text },
}
}3. Verknüpfung mit dem view via React-Redux
Hinweis nutze zum korrekten typen der Props
ConnectedPropshttps://react-redux.js.org/using-react-redux/usage-with-typescript#typing-the-connect-higher-order-component
- Installiere
react-redux:npm install react-redux - Wrappe deine App mit der
<Provider>komponente vonreact-reduxund übergib deinen Store - Zeige in der
PostListing-Komponente die posts aus dem Redux-Store an (Achtung: die posts liegen noch nicht als Liste vor!) - Füge der
PostListing-Komponente einen Button hinzu, welcher einen Standard-Post mit Hilfe der FunktioncreateBlankPostaus demPost-Model erstellt und dem Store hinzufügt - Betätige den Button mehrmals und prüfe in den Redux-Devtools, dass:
- jedes mal eine neue Action dispatched wird
- jeder neue Post auch eine eigene Id besitzt. (schau dir auch kurz die Implementierung von
createBlankPostan) - klicke eine Action an und schau dir in den Devtools auch den Diff dazu an
4. Einführung von Selektoren
- Erstelle einen primitiven Selektor, welcher das Postsstate ausliest und verwende diesen anstelle des direkten State-Zugriffs in
PostListing - Erstelle einen memoisierten Selektor, welcher den ersten Post zurückgibt.
- Verwende den memoisierten Selektor in der
PostListing-Komponente undconsole.log()ihn (er muss nicht via JSX gerendert werden)
5. Einführung von Redux-Toolkit und Vereinfachung des Stores
- Ersetze die selbstgeschriebene
configureStore()funktion, durch einen Aufruf von Redux-ToolkitsconfigureStore() - Schreibe das
Postslice so um, dass escreateSlice()von Redux-Toolkit verwendet
6. Seiteneffekthandling: Posts fetchen
-
Erstelle einen thunk action creator mit
createAsyncThunk()namensfetchPosts,
welcher analog zufetchPosts()insrc/model/Post.tsalle posts fetched. -
Handle alle Action-Types des thunks im
postReducer:
(
pending-> aktuelles State returnen,
fulfilled-> Postliste aus der response returnen,
rejected-> aktuellen State returnen (wir handlen den Fehler vorerst nicht)
) -
Erstelle ein weiteres slice
postsLoadinginsrc/store/posts/loading.tsmit folgenden Eigenschaften:type PostLoadingState = boolean const initialState: PostLoadingState = false
Hinweis: Lasse die
reducers-property des slices vorerst leer
-
Erstelle einen primitiven Selektor, welcher das PostLoadingState ausliest
-
Stelle sicher, dass der loading reducer auch im kombinierten
RootReducerverwendet wird -
Reagiere im
postsLoadingslice auf diefetchPosts()Action-types:
(
pending-> true
fulfilled-> false
rejected-> false
) -
Ersetze in
PostListing.tsxdas loading state durchpostsLoadingaus dem Redux-Store.
Ersetze außerdem das fetching durch einen Aufruf desfetchPoststhunk creators.
Sorge zudem dafür, dass der fetchuseEffectnur beim ersten Rendern der Komponente aufgerufen wird.
(Da wir die Posts jetzt im Application-State halten, muss nicht jedesmal neu gefetched werden)Hinweis: Dadurch, dass wir die Posts nun nur noch beim ersten Rendering fetchen, werden einige Funktionen
unserer App vorerst nicht korrekt funktionieren (bspw.postPost).
7. Post via Selektor auslesen
Derzeit werden in unserem Post-Listing alle Posts aus dem Store gelesen und einfach auf Item-Komponenten gemapped.
Nun soll das ganze folgendermaßen umgebaut werden:
- Lies in
PostListingnur noch die Post-Ids statt der Posts aus dem Store aus - Mappe dann in
PostListingüber die Ids - Die
PostListingItem-Komponente soll nun als Props statt einesPostdesseniderwarten - Erstelle einen primitiven Selektor
getPostById(), welcher aus dem State einen Post anhand dessenidausliest
und verwende diesen in derPostListingItem-Komponente.
8. Post löschen
- Analog zu Aufgabe 6 soll nun die
deletePost()-Funktion aussrc/model/Post.tsdurch einen Thunk ersetzt werden. - Das Post state soll erst aktualisiert werden, wenn das Request erfolgreich war.
Hinweis:
rejectedundpendingmüssen hier vorerst nicht gehandled werden
Immutable remove:
.addCase(deletePost.fulfilled, (state, action) => {
const { id } = action.payload
const {
[id]: postToRemove, // const _ = state.byId[id] <= dynamic access of element via key (id)
...byId // rest operator -> all remaining elements in object are put into new obj with the name "byId"
} = state.byId
console.log('hi, I deleted this post for you:', postToRemove)
return {
ids: state.ids.filter((postId) => postId !== id),
byId,
}
})9. Post editieren
- Sorge dafür dass der Post zum editeren nicht mehr neu gefetched werden muss
Hinweis: Gehe dabei analog zu Aufgabe 7 vor.
Dieidkommt in diesem Fall allerdings aus der Route, welche viaprops.match.params.idausgelesen werden kann.
Hier kann die Typisierung etwas knifflig sein, um die Props zu typisieren, könnt ihr folgendermaßen vorgehen:
type RouteProps = {
id: string
}
type OwnProps = {
postId: Post['id']
} & RouteComponentProps<RouteProps>
const mapStateToProps = (state: RootState, ownProps: OwnProps) => {
const postId = ownProps.match.params.id
return {
post: selectors.getPostById(state, { postId }),
}
}
const mapDispatchToProps = {
updatePost,
}
const connector = connect(mapStateToProps, mapDispatchToProps)
type PropsFromRedux = ConnectedProps<typeof connector>
type Props = PropsFromRedux & OwnProps
const PostEditorView = (props: Props) => {
...
}- Ersetze die
updatePost-Funktion aussrc/model/Postdurch einen entsprechend Thunk in
src/store/posts/posts. Ist das request erfolgreich, soll der Store mit der Antwort des Servers aktualisiert werden.
Hinweis: Das Transient-Post state wird nicht mehr benötigt.
10. Erstellen eines neuen Posts
- Ersetze nun auch die
postPost-Funktion aussrc/model/Postdurch einen entsprechenden Thunk
1. Store initialisieren + posts slice erstellen (ohne Toolkit)
Installiere dir die Redux-Devtools-Browsererweiterung (Chrome/Firefox)
Initialisiere deinen Store und integriere die dev-tools-extension
Integriere einen Posts-Reducer (in
src/store/posts/posts.ts) in deinen Store mit folgendem Typen undinitialState:initialisiert wurde (du solltest dein initiales state sehen)