Асинхронный код в контексте redux
| const getStarredRepos = (username: string) => { |
| return fetch(`https://api.github.com/users/${username}/starred`); |
| }; |
| const getStarredRepos = (username: string) => { |
| return fetch(`https://api.github.com/users/${username}/starred`).then( |
| (data) => saveData(data) |
| ); |
| }; |
| const getStarredRepos = (username: string) => { |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => saveData(data)) |
| .catch((error) => showError(error)); |
| }; |
| const getStarredRepos = (username: string) => { |
| saveIsLoading(); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => saveData(data)) |
| .catch((error) => showError(error)); |
| }; |
| const getStarredRepos = (username: string) => { |
| dispatch({ type: "LOADING" }); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| }; |
| const reducer = (state = { isLoading: false }, action) => { |
| switch (action.type) { |
| case "LOADING": |
| return { ...state, isLoading: true }; |
| case "SUCCESS": |
| return { ...state, isLoading: false, data: action.data, error: null }; |
| case "ERROR": |
| return { ...state, isLoading: false, data: null, error: action.error }; |
| default: |
| return state; |
| } |
| }; |
| const getStarredReposForCurrentUser = () => { |
| const username = getCurrentUser(state).username; |
| |
| dispatch({ type: "LOADING" }); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| }; |
Практика
При клике на кнопку:
- отправьте событие
loading
- дождидесь получения данных и отправьте событие
success
- в случае ошибки отправьте событие
error
Thunk - это отложенное вычисление
| const think = 42 + 666; |
| |
| const thunk = () => 42 + 666; |
| |
| const think2 = thunk(); |
| |
| const think = dispatch({ type: "ADD", value: 666 }); |
| |
| const thunk = () => dispatch({ type: "ADD", value: 666 }); |
| |
| const thunk = (dispatch) => dispatch({ type: "ADD", value: 666 }); |
| |
| const thunk = (dispatch, getState) => dispatch({ type: "ADD", value: 666 }); |
| |
| const dispatch = (action) => { |
| const newState = rootReducer(store.getState(), action); |
| store.setState(newState); |
| }; |
| |
| const dispatch = (action) => { |
| if (typeof action === "function") { |
| return action(dispatch, store.getState); |
| } |
| |
| const newState = rootReducer(store.getState(), action); |
| store.setState(newState); |
| }; |
| |
| const username = getCurrentUser(state).username; |
| |
| dispatch({ type: "LOADING" }); |
| |
| fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| |
| const getStarredReposForCurrentUser = () => { |
| const username = getCurrentUser(state).username; |
| |
| dispatch({ type: "LOADING" }); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| }; |
| |
| const getStarredReposForCurrentUser = (dispatch, getState) => { |
| const username = getCurrentUser(getState()).username; |
| |
| dispatch({ type: "LOADING" }); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| }; |
Напомню, мы обычно используем не actions напрямую, а action creators
dispatch({ type: "USERS_LOADED", payload: list });
dispatch(usersLoaded(list));
const userLoaded = (payload) => ({
type: "USERS_LOADED",
payload,
});
| |
| const getStarredReposForCurrentUser = () => (dispatch, getState) => { |
| const username = getCurrentUser(getState()).username; |
| |
| dispatch({ type: "LOADING" }); |
| |
| return fetch(`https://api.github.com/users/${username}/starred`) |
| .then((data) => dispatch({ type: "SUCCESS", data })) |
| .catch((error) => dispatch({ type: "ERROR", error })); |
| }; |
| |
| dispatch(getStarredReposForCurrentUser()); |
| const middleware = (middlewareApi) => (next) => (action) => { |
| |
| }; |
| type MiddlewareApi = { |
| dispatch: Dispatch; |
| getState: () => State; |
| }; |
| |
| const middleware = (middlewareApi: MiddlewareApi) => (next) => (action) => { |
| |
| }; |
| const middleware = |
| (middlewareApi: MiddlewareApi) => |
| (next: (action: AnyAction) => AnyAction) => |
| (action) => { |
| |
| }; |
| const middleware = |
| (middlewareApi: MiddlewareApi) => |
| (next: (action: AnyAction) => AnyAction) => |
| (action: AnyAction): AnyAction => { |
| |
| }; |
| const middleware: Middleware = |
| ({ dispatch, getState }) => |
| (next) => |
| (action) => { |
| |
| const resultAction = next(action); |
| |
| return resultAction; |
| }; |
| const middleware: Middleware = |
| ({ dispatch, getState }) => |
| (next) => |
| (action) => { |
| const resultAction = next(action); |
| |
| if (action.type === "BUY_BUTTON_CLICK") { |
| const state = getState(); |
| sendAnalytics("buy", { user: state.currentUser }); |
| } |
| |
| return resultAction; |
| }; |
Задачи, для middleware:
- Логирование
- Аналитика
- Devtools
- Обработка actions
- Цепочки actions
Практика
В файле logger.ts
раскомментируйте объявленную там middleware
- Залогируйте состояние до экшена
- Пустите экшен по цепочке дальше
- Залогируйте состояние после
- Верните экшен полученный из цепочки
| |
| const dispatch = (action) => { |
| if (typeof action === "function") { |
| action(dispatch, store.getState); |
| } |
| |
| const newState = rootReducer(store.getState(), action); |
| store.setState(newState); |
| }; |
Напомню, что эта трагическая история описана в документации
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const thunk = (store) => (next) => (action) => |
| typeof action === "function" |
| ? action(store.dispatch, store.getState) |
| : next(action); |
Как миддлвары подключаются к store:
| import { createStore, applyMiddleware } from "redux"; |
| import todos from "./reducers"; |
| |
| function logger({ getState }) { |
| return (next) => (action) => { |
| console.log("will dispatch", action); |
| |
| |
| const returnValue = next(action); |
| |
| console.log("state after dispatch", getState()); |
| |
| |
| |
| return returnValue; |
| }; |
| } |
| |
| const store = createStore(todos, ["Use Redux"], applyMiddleware(logger)); |
| |
| store.dispatch({ |
| type: "ADD_TODO", |
| text: "Understand the middleware", |
| }); |
Практика
- установите пакет redux-thunk
- подключите миддлвару redux-thunk к вашему store
- превратите функцию загрузки данных в thunk-action