В ежедневном процессе разработки мы используем решение react+redux для разработки и часто сталкиваемся с проблемой слишком большого количества шаблонного кода redux.redux-middleware. Вот подробные проблемы и решения. Окончательный код и примеры можно просмотреть и использовать в проекте, добро пожаловать в использование, предлагайте и помечайте звездочкой~
бросать вопросы
При разработке с Redux нам часто нужен очень сложный процесс, когда мы столкнулись с запросами, и этот процесс повторяется. Мы часто разделяем запрос на три этапа, соответствующие трем типам действия, и сотрудничать сredux-thunkПромежуточное ПО, которое разбивает асинхронное действие, соответствующее трем этапам запроса. Следующим образом:
// 请求的三个状态,开始请求,请求成功,请求失败
export const START_FETCH = 'START_FETCH'
export const FETCH_SUCCESS = 'FETCH_SUCCESS'
export const FETCH_FAILED = 'FETCH_FAILED'
const startFetch = () => ({
type: START_FETCH
})
const fetchSuccess = payload => ({
type: FETCH_SUCCESS,
payload
})
const fetchFailed = error => ({
type: FETCH_FAILED,
error
})
// 在请求的三个阶段中,dispatch不同的action
export const fetchData = (params) => (dispatch) => {
// 开始请求
dispatch(startFetch())
return fetch(`/api/getData`)
.then(res => res.json())
.then(json => {
dispatch(fetchSuccess(json))
})
.catch(error => {
dispatch(fetchFailed(error))
})
}
В то же время нам нужно добавить изменения состояния, соответствующие трем действиям в редюсере, чтобы соответствующим образом отобразить весь запрос. Например:
- Загрузка при запуске запроса требует поля загрузки
- Завершить загрузку после успешного выполнения запроса, изменить данные
- Завершить загрузку при сбое запроса, показывая ошибку
Соответственно нам нужно написать следующее:
const initialData = {
data: {},
loading: false,
error: null
}
const data = (state = initialData, action) => {
switch(action.type) {
case START_FETCH:
return {
...state,
loading: true,
error: null
}
case FETCH_SUCCESS:
return {
...state,
loading: false,
data: action.payload
}
case FETCH_FAILED:
return {
...state,
loading: false,
error: action.error
}
default:
return state
}
})
Для полного и надежного запроса нам часто нужно снова написать весь вышеприведенный код. Предположим, у нас есть N интерфейсов запроса на странице, и нам нужно написать этот примерно один и тот же код бесчисленное количество раз, что, очевидно, очень хлопотно и не является хорошей практикой, тогда как мы можем обеспечить поток кода и читабельность одновременно, чтобы сократить шаблонный код***
Исходное решение, оберните его функцией
Фактически, для такого рода повторяющегося кода первое, что приходит нам в голову, — это инкапсулировать его как функцию и использовать переменный factor в качестве параметра. Но это может быть немного сложнее, потому что для этой функции мы можем выполнить несколько шагов, которые не очень важны или не могут быть названы шагами, следует сказать, чтобы получить то, что мы не понимаем:
- Получить действие трех состояний
- В процессе запроса три действия обрабатываются отдельно, а параметры запроса, результаты запроса, обработка ошибок и т.д. могут быть гибко настроены
- Настройте initialState и автоматически сопоставьте три состояния действия в редюсере, обновите состояние
Поскольку это не окончательное решение, я выложу код, чтобы прояснить наши основные идеи:
import update from "immutability-helper";
// 根据actions来返回目的reducer, 此reducer会自动对单个过程更新state
// 并且可以增加自定义的修改
const reducerCreator = actions => (initState, otherActions) => {
const resultInitState = Object.assign({}, initState, {
isFetching: true,
isError: false,
ErrMsg: ""
});
const { START_ACTION, SUCCESS_ACTION, FAILED_ACTION } = actions;
return (state = resultInitState, action) => {
let ret;
switch (action.type) {
case START_ACTION:
ret = update(state, {
isFetching: {
$set: true
},
isError: {
$set: false
},
ErrMsg: {
$set: ""
}
});
break;
case SUCCESS_ACTION:
ret = update(state, {
isFetching: {
$set: false
}
});
break;
case FAILED_ACTION:
ret = update(state, {
isFetching: {
$set: false
},
isError: {
$set: true
}
});
break;
default:
ret = state;
}
return otherActions(ret, action);
};
};
// 1.创建三个action
// 2.执行请求函数, 在请求中我们可以任意的格式化参数等
// 3.请求过程中执行三个action
// 4.根据三个action返回我们的reducer
export default (action, fn, handleResponse, handleError) => {
const START_ACTION = Symbol(`${action}_START`);
const SUCCESS_ACTION = Symbol(`${action}_SUCCESS`);
const FAILED_ACTION = Symbol(`${action}_FAILED`);
const start = payload => ({
type: START_ACTION,
payload
});
const success = payload => ({
type: SUCCESS_ACTION,
payload
});
const failed = payload => ({
type: FAILED_ACTION,
payload
});
return {
actions: {
[`${action}_START`]: START_ACTION,
[`${action}_SUCCESS`]: SUCCESS_ACTION,
[`${action}_FAILED`]: FAILED_ACTION
},
method: (...args) => (dispatch, getState) => {
dispatch(start());
return fn(...args, getState)
.then(r => r.json())
.then(json => {
if (json.response_code === 0) {
const ret = handleResponse
? handleResponse(json, dispatch, getState)
: json;
dispatch(success(ret));
} else {
dispatch(failed(json));
}
})
.catch(err => {
const ret = handleError ? handleError(err) : err;
dispatch(failed(err));
});
},
reducerCreator: reducerCreator({
START_ACTION,
SUCCESS_ACTION,
FAILED_ACTION
})
};
};
С помощью этой функции инструмента мы можем значительно упростить весь процесс.Для запроса мы можем сделать это следующими способами:
const getDataFn = params => {
return fetch("/api/getData", {
method: "POST",
headers: {
"Content-type": "application/json; charset=UTF-8"
},
body: JSON.stringify(params)
});
};
export const {
// 三个action
actions: getDataActions,
// 创建reducer
reducerCreator: getDataReducerCreator,
// 请求,触发所有的过程
method: getData
} = reduxCreator("GET_DATA", getDataFn, res => res.data);
В редюсере мы можем напрямую использовать reducerCreator для создания редуктора и добавлять дополнительный контент.
const initialData = {
list: []
}
// 最终的reducer,包含请求和错误状态,且根据请求自动更新
const threatList = threatListReducerCreator(initialData, (state, action) => {
switch (action.type) {
case getDataActions.GET_DATA_SUCCESS:
return update(state, {
list: {
$set: action.payload.items
}
});
default:
return state;
}
})
Таким образом, мы значительно сокращаем код всего процесса и можем гибко добавлять то, что хотим в каждый процесс. с моей посылкойреактивный компонентКомпонент Box в , очень удобно реализовать Процесс запроса->загрузки->отображения контента.
Однако я всегда смутно чувствую, что этот код немного неудобен, где дискомфорт? Да, хоть это и сильно упрощает код, после использования этой функции инструмента сильно меняется структура всего редукционного кода. Весь процесс использования функции и семантика *** очень неясны, и нам трудно сразу увидеть, что мы сделали. а также,Людям, не знакомым с API, будет очень неудобно пользоваться.
Поэтому мы улучшаем приведенный выше код, чтобы выполнить наше последнее требование: элегантный
Введение
Redux заимствует механизм промежуточного программного обеспечения Koa, а также предоставляет нам хорошее промежуточное программное обеспечение для использования. Мы не будем здесь повторять конкретные принципы, давайте посмотрим, как выглядит следующий базовый мидлвар:
const logMiddleware = store => next => action => {
console.log(action)
next(action)
console.log(action, 'finish')
}
Мы увидим, что в промежуточном программном обеспечении мы можем получить хранилище и действие и автоматически выполнить следующее промежуточное программное обеспечение или действие. По сути, мы получаем весь контент, который нам нужен, мы можем напрямую передать фиксированный код в процессе запроса промежуточному программному обеспечению, чтобы сделать это!
Упростите процесс с помощью Redux-MiddleWare
Мы можем использовать процесс распределения действий автоматически, я думаю, что многие люди будут делать это, нам нужно только определить наш особый формат действий и сделать специальные процессы для этого Действия. Например, мы определяем наше действие запроса для этого:
{
url: '/api/getData',
params,
types: [ START_ACTION, SUCCESS_ACTION, FAILED_ACTION ],
handleResult,
handleError,
}
В промежуточном программном обеспечении мы можем сделать следующее:
const fetchMiddleware = store => next => action => {
// 普通action直接执行
if (!action.url || !Array.isArray(action.types)) {
return next(action)
}
// 处理我们的request action
const {
handleResult = val => val,
handleError = error => error,
types, url, params
} = action
const [ START, SUCCESS, FAILED ] = types
next({
type: START,
loading: true,
...action
})
return fetchMethod(url, params)
.then(handleResponse)
.then(ret => {
next({
type: SUCCESS,
loading: false,
payload: handleResult(ret)
})
return handleResult(ret)
})
.catch(error => {
next({
type: FAILED,
loading: false,
error: handleError(error)
})
})
}
В то же время мы предоставляем actionCreator, reducerCreator для создания соответствующих действий и редукторов. Упростите код, сохранив процесс и структуру без изменений.
Окончательный версия
- apply middleware
import createFetchMiddleware from 'redux-data-fetch-middleware'
import { applyMiddleware } from 'redux'
import thunk from 'redux-thunk'
// 设置公用的请求函数
const fetchMethods = (url, params) => fetch(url, {
method: "post",
headers: {
"Content-type": "application/json; charset=UTF-8"
},
body: JSON.stringify(params)
})
// 设置共用的处理函数,如进行统一的错误处理等
const handleResponse = res => res.json()
const reduxFetch = createFetchMiddleware(fetchMethods, handleResponse)
const middlewares = [thunk, reduxFetch]
applyMiddleware(...middlewares)
- actions
import { actionCreator } from 'redux-data-fetch-middleware'
// 创建三个action
export const actionTypes = actionCreator('GET_USER_LIST')
export const getUserList = params => ({
url: '/api/userList',
params: params,
types: actionTypes,
// handle result
handleResult: res => res.data.list,
// handle error
handleError: ...
})
// 可以直接dispatch,自动执行整个过程
dispatch(getUserList({ page: 1 }))
- reducer
import { combineReducers } from 'redux'
import { reducerCreator } from 'redux-data-fetch-middleware'
import { actionTypes } from './action'
const [ GET, GET_SUCCESS, GET_FAILED ] = actionTypes
// userList会自动变成 {
// list: [],
// loading: false,
// error: null
// }
// 并且当GET, GET_SUCCESS and GET_FAILED改变时,会自动改变loading,error的值
const fetchedUserList = reducerCreator(actionTypes)
const initialUserList = {
list: []
}
const userList = (state = initialUserList, action => {
switch(action.type) {
case GET_SUCCESS:
return {
...state,
action.payload
}
}
})
export default combineReducers({
userList: fetchedUserList(userList)
})
Суммировать
Процесс от первоначальной постановки проблемы до решения идей и постоянного улучшения — стандартный процесс решения проблем. Благодаря этой инкапсуляции мы решили проблему избыточности кода запроса Redux в ежедневном процессе разработки, а также полностью поняли механизм redux-middleware. Добро пожаловать, чтобы исправить и пометить ~