react如何控制全局loading_React / Redux技巧:处理reducer中loading标志的更好方法

最近又重新开始做起React的网站来,在做的时候想添加骨架屏,之前添加的Skeleton骨架屏有些不够好用。因为每次请求的时候,都要设置Action并且修改reducer里面的isLoading的值,重写一个界面之后又再写一遍类似的代码。所以说很多人都说React冗余复杂,可能就是复杂在这些上面吧。

于是我搜索了一下,发现了一个很好用的方法,自己用了一下觉得真的很好用,就翻译过来。

以下是原文。

目的: 使用单独的reducer存储所有的isFetching标志,而不是每个reducer都存储isFetching而污染了reducer。

相信,每个人使用React作为前端,并将Redux用于状态管理的时候,并且每当应用都有一定复杂程度的时候,我们可能会遇到如下代码。

// todo/actionCreators.js

export const getTodos = (dispatch) => () => {

dispatch({ type: 'GET_TODOS_REQUEST' });

return fetch('/api/todos')

.then((todos) => dispatch({ type: 'GET_TODOS_SUCCESS', payload: todos })

.catch((error) => dispatch({ type: 'GET_TODOS_FAILURE', payload: error, error: true });

};

// todo/reducer.js

const initialState = { todos: [] };

export const todoReducer = (state = initialState, action) => {

switch(action.type) {

//我用的是isLoading

case 'GET_TODOS_REQUEST':

return { ...state, isFetching: true };

case 'GET_TODOS_SUCCESS':

return { ...state, isFetching: false, todos: action.payload };

case 'GET_TODOS_FAILURE':

return { ...state, isFetching: false, errorMessage: action.payload.message };

default:

return state;

}

};

当您的应用程序需要更多的API调用时,此代码才能正常工作。并且,将近一半的代码都是我们要处理的 isFetching / errorMessage 的代码。(写重复的代码难免会有些疲惫 )

加载Loading Reducer

在StashAway中,我们通过创建一个减少负载的方法来解决此问题,该方法存储所有API请求状态:

// api/loadingReducer.js

const loadingReducer = (state = {}, action) => {

const { type } = action;

const matches = /(.*)_(REQUEST|SUCCESS|FAILURE)/.exec(type);

// 如果不是 *_REQUEST / *_SUCCESS / *_FAILURE actions, 我们就将它们忽略

if (!matches) return state;

const [, requestName, requestState] = matches;

return {

...state,

// 存储当前是否正在发生请求

// 例如:当收到GET_TODOS_REQUEST的时候,isFetching为true

// 当收到GET_TODOS_SUCCESS / GET_TODOS_FAILURE的时候,isFetching为false

[requestName]: requestState === 'REQUEST',

};

};

然后我们可以通过组件调用选择器访问那些加载状态。 原作者使用lodash,下面有原生JS的做法。

// api/selectors.js

import _ from 'lodash';

export const createLoadingSelector = (actions) => (state) => {

// 仅在未加载所有action时返回true

return _(actions)

.some((action) => _.get(state, `api.loading.${action}`));

};

// components/todos/index.js

import { connect } from 'react-redux';

import Todos from './Todos';

import { createLoadingSelector } from '../../redux/api/selectors';

// GET_TODOS_REQUEST时,显示正在加载。

const loadingSelector = createLoadingSelector(['GET_TODOS']);

const mapStateToProps = (state) => ({ isFetching: loadingSelector(state) });

export default connect(mapStateToProps)(Todos);

另一种不用lodash的方法

// api/selectors.js

import _ from 'lodash';

export const createLoadingSelector = actions => state =>

actions.some(action => state.loading[action]);

//actions.some(action => state.get("loading")[action]); 如果用immutable

由于我们不需要维护任何isFetching标志,因此我们的reducer只需要关心存储数据,所以代码更加简单:

// todo/reducer.js

const initialState = { todos: [] };

export const todoReducer = (state = initialState, action) => {

switch(action.type) {

case 'GET_TODOS_SUCCESS':

return { ...state, todos: action.payload };

default:

return state;

}

};

这样基本上就好了,虽然在这个测试代码中,显得没有那么简化,但是当您的应用程序要求您向超过20个API发送请求时,这就会让您的生活变得更加轻松一些了。

我们可以合并API调用吗?

当在构建一个真实的应用程序时,我们可能需要结合API调用(例如:当getUser和getTodos请求都成功时,才显示代办事项页面)。这种方法很简单:

// components/todos/index.js

import { connect } from 'react-redux';

import Todos from './Todos';

import { createLoadingSelector } from '../../redux/api/selectors';

// 当 GET_TODOS_REQUEST, GET_USER_REQUEST 中的任意一个处于active时,显示正在加载

const loadingSelector = createLoadingSelector(['GET_TODOS', 'GET_USER']);

const mapStateToProps = (state) => ({ isFetching: loadingSelector(state) });

export default connect(mapStateToProps)(Todos);

那,错误信息处理呢?

处理API错误消息与处理加载标志类似,除了在选择要显示的消息时:

// api/errorReducer.js

export const errorReducer = (state = {}, action) => {

const { type, payload } = action;

const matches = /(.*)_(REQUEST|FAILURE)/.exec(type);

// 如果不是 *_REQUEST / *_FAILURE action, 我们就将其忽略

if (!matches) return state;

const [, requestName, requestState] = matches;

return {

...state,

// 存储 errorMessage

// 例如:当接收到 GET_TODOS_FAILURE 时存储 errorMessage

// 否则在收到 GET_TODOS_REQUEST 时清除 errorMessage

[requestName]: requestState === 'FAILURE' ? payload.message : '',

};

};

// api/selectors.js

import _ from 'lodash';

export const createErrorMessageSelector = (actions) => (state) => {

// 返回操作的第一条错误消息

// *我们假设当任何请求失败时,

// 需要多个API调用,我们显示第一个错误

return _(actions)

.map((action) => _.get(state, `api.error.${action}`))

.compact()

.first() || '';

};

当然,错误信息处理同样可以不用lodash

// api/selectors.js

export const createErrorMessageSelector = actions => (state) => {

const errors = actions.map(action => state.error[action]);

if (errors && errors[0]) {

return errors[0];

}

return '';

};

就像编程中许多事物一样,有很多方式处理React / Redux。这只是我的首选方式。

我们一直都在努力寻找改善的方法!

原文完。

注:在使用loadingReducer时,用combineReducer将loadingReducer进行管理。这里引入之后,通过React Developer Tools可以查看Redux state中的变化。

// store/reducer.js

import { combineReducers } from "redux-immutable";

const reducer = combineReducers({

loading: loadingReducer

});

export default reducer;

在加载请求的时候,执行REQUEST action后,再发请求。

// /components/todos/store/actionCreators.js

import axios from "axios";

const setTodos= (payload) => ({

type: "GET_TODOS_SUCCESS",

payload

});

const getTodosRequest = () => ({

type: "GET_TODOS_REQUEST"

});

export const getTodos = () => {

return (dispatch) => {

dispatch(getTodosRequest()); // 这里正在请求

axios.get("/api/todos").then((res)=>{

dispatch(setTodos(res.data.payload));

}).catch((e)=>{

console.log(e);

});

}

};

大概就是这些了,希望这些能够简化您的Redux代码。

你可能感兴趣的:(react如何控制全局loading_React / Redux技巧:处理reducer中loading标志的更好方法)