-
-
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
PostState
zurueckgibt - das neue
PostState
sollte zum einen inbyId
den neuen Post und zum anderen
inids
dieid
des Posts beinhalten
- Dispatche manuell eine
posts/addPost
action 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
ConnectedProps
https://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-redux
und ü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 FunktioncreateBlankPost
aus 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
createBlankPost
an) - 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
Post
slice 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.ts
alle 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
postsLoading
insrc/store/posts/loading.ts
mit 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
RootReducer
verwendet wird -
Reagiere im
postsLoading
slice auf diefetchPosts()
Action-types:
(
pending
-> true
fulfilled
-> false
rejected
-> false
) -
Ersetze in
PostListing.tsx
das loading state durchpostsLoading
aus dem Redux-Store.
Ersetze außerdem das fetching durch einen Aufruf desfetchPosts
thunk creators.
Sorge zudem dafür, dass der fetchuseEffect
nur 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
PostListing
nur 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 einesPost
dessenid
erwarten - Erstelle einen primitiven Selektor
getPostById()
, welcher aus dem State einen Post anhand dessenid
ausliest
und verwende diesen in derPostListingItem
-Komponente.
8. Post löschen
- Analog zu Aufgabe 6 soll nun die
deletePost()
-Funktion aussrc/model/Post.ts
durch einen Thunk ersetzt werden. - Das Post state soll erst aktualisiert werden, wenn das Request erfolgreich war.
Hinweis:
rejected
undpending
mü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.
Dieid
kommt in diesem Fall allerdings aus der Route, welche viaprops.match.params.id
ausgelesen 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/Post
durch 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/Post
durch 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)