【问题标题】:Designing / combining redux reducers设计/组合 redux reducer
【发布时间】:2017-07-08 12:51:18
【问题描述】:

我正在清理我的减速器设置,这从一开始就是错误的。我使用redux docothe ducks pattern 来设置它。

我想像doco suggests 一样分离/划分我的状态:

{
    domainData1 : {},
    domainData2 : {},
    appState1 : {},
    appState2 : {},
    ui : {
        uiState1 : {},
        uiState2 : {},
    }
}

就我而言,我有一些我正在尝试管理的相册、目录和照片。状态应该如下所示:

{
    //domainData
    albums : {...},
    catalogs : {...},
    photos : {...},

    //appState
    selectedAlbum : {...},

    //uiState
    ui : {
        loading : {},
        uiState2 : {},
    }
}

我已将属于相册的所有代码放在动作、动作创建者、recuder 和 API 所在的一个文件中 类似的目录和照片。 然后我将 reducer 与 combineReducers 合并到一个单独的文件中。

代码如下...

我的问题是……当我在专辑上做事时,我希望所有域数据都进入该州的专辑分支。但我不希望 ui 和 app 状态的东西进入相册分支 - 这应该进入 app 和 ui 状态分支。这在我的设置中是不可能的,例如加载专辑时,我在“专辑”减速器中设置了加载标志,这是直到 combineReducer 的馈送,结果是加载状态成为状态的专辑分支的一部分......

相册模块:

import { createRequest, responseHandler, notAuthorized } from './apiUtils';
import { List, Map, fromJS } from 'immutable';

//actions
const REQUEST_ALBUMS = 'REQUEST_ALBUMS';
const FETCH_ALBUMS_SUCCESS = 'FETCH_ALBUMS_SUCCESS';
const FETCH_ALBUM_SUCCESS = 'FETCH_ALBUM_SUCCESS';
const REQUEST_ALBUM = 'REQUEST_ALBUM';
const CREATE_ALBUM = 'CREATE_ALBUM';
const CREATE_ALBUM_SUCCESS = 'CREATE_ALBUM_SUCCESS';
const UPDATE_ALBUM = 'UPDATE_ALBUM';
const UPDATE_ALBUM_SUCCESS = 'UPDATE_ALBUM_SUCCESS';

// Reducer
var init = Map(fromJS({
  albums: [],
  album: [],
  loading: false,
}));

var newState = null;

export function reducer(state=init, action={}) {
  switch (action.type) {

    case FETCH_ALBUMS_SUCCESS: {
      newState = state
        .set('albums', fromJS(action.payload.albums))
        .set('loading', false);
      return newState;
    }

    case REQUEST_ALBUMS: {
      return state.set('loading', true);
    }

    case FETCH_ALBUM_SUCCESS: {
      newState = state
        .set('album', fromJS(action.payload.album))
        .set('loading', false);
      return newState;
    }

    case REQUEST_ALBUM: {
      return state.set('loading', true);
    }

    case CREATE_ALBUM: {
      return state.set('loading', true);
    }

    case CREATE_ALBUM_SUCCESS: {
      newState = state
        .set('album', fromJS(action.payload.album))
        .set('loading', false);
      return newState;
    }

    case UPDATE_ALBUM: {
      return state.set('loading', true);
    }

    case UPDATE_ALBUM_SUCCESS: {
      newState = state
        .set('album', fromJS(action.payload.album))
        .set('loading', false);
      return newState;
    }
  }
  return state;
}

// Action Creators
export function getAlbumsPending(response) {
  return {
    type: REQUEST_ALBUMS,
  };
}

export function getAlbumsSuccess(response) {
  return {
    type: FETCH_ALBUMS_SUCCESS,
    payload: response,
  };
}

export function getAlbumPending(response) {
  return {
    type: REQUEST_ALBUM,
  };
}

export function getAlbumSuccess(response) {
  return {
    type: FETCH_ALBUM_SUCCESS,
    payload: response,
  };
}

function createAlbumPending(response) {
  return {
    type: CREATE_ALBUM,
  };
}

function createAlbumSuccess(response) {
  return {
    type: CREATE_ALBUM_SUCCESS,
    payload: response,
  };
}

export function updateAlbumPending(response) {
  return {
    type: UPDATE_ALBUM,
  };
}

export function updateAlbumSuccess(response) {
  return {
    type: UPDATE_ALBUM_SUCCESS,
    payload: response,
  };
}

//API

export function fetchAlbums() {
  //API url
  const url = '/api/albums.json';

  //make the call
  return dispatch => {

    dispatch(getAlbumsPending());

    fetch(createRequest('GET', url, null))
    .then(response => responseHandler(response))
    .then(data => dispatch(getAlbumsSuccess(data)))
    .catch(error => console.log('request failed', error));
  };
}

export function fetchAlbum(id) {
  //url
  var url = '/api/albums/'.concat(id);

  return dispatch => {
    dispatch(getAlbumPending());

    fetch(createRequest('GET', url, null))
    .then(response => responseHandler(response))
    .then(data => dispatch(getAlbumSuccess({ album: data })))
    .catch(error => console.log('request failed', error));
  };
}

export function createAlbum(params) {
  //API url
  const url = '/api/albums/';

  return dispatch => {

    dispatch(createAlbumPending());

    fetch(createRequest('POST', url, params))
    .then(response => responseHandler(response))
    .then(data => dispatch(createAlbumSuccess({ album: data })))
    .catch(error => console.log('request failed', error));
  };
}

export function updateAlbum(params) {
  //API url
  var url = '/api/albums/'.concat(params.id);

  //make the call
  return dispatch => {

    dispatch(updateAlbumPending());

    fetch(createRequest('PUT', url, params))
    .then(response => responseHandler(response))
    .then(data => dispatch(updateAlbumSuccess({ album: data })))
    .catch(error => console.log('request failed', error));
  };
}

CombineRecucer:

import { combineReducers } from 'redux';
import { reducer as albumReducer } from '../actions/album';
import { reducer as catalogReducer } from '../actions/catalog';

export default combineReducers({
  albums: albumReducer,  // <- loading flag ends up here
  catalogs: catalogReducer,
  loading: // I want loading flag here 
}); 

抱歉发了这么长的帖子-没有土豆

【问题讨论】:

  • 您必须在 loading 减速器中添加 case UPDATE_ALBUM: 内容。您可以从其他鸭子文件中导入动作类型。相册 reducer 只负责更新它自己的状态切片。多个 reducer 可以监听相同的 action 类型,并对自己的 slice 进行一些更改。
  • OMG...如此简单.. 我完全错过了 redux 的天才...@HåkenLid 提出了一个答案,所以我可以相信你
  • 当然。我已经添加了答案。
  • 与你的实际问题完全无关:我不知道你认为你在用 newState 变量做什么,但你不是。一般来说,当你编写函数式代码(包括 Redux 代码)时,尽量使用const 而不是var——很多事情会变得更清晰。
  • 感谢@Malvolio 的好建议

标签: reactjs redux react-redux


【解决方案1】:

您只需将该逻辑移至负责该状态切片的 Loading reducer。

// ducks/Loading.js

import {
  REQUEST_ALBUM,
  CREATE_ALBUM,
  CREATE_ALBUM_SUCCESS
} from 'ducks/Albums'  
// remember to also export from the Albums duck.

export const reducer = (state, action) => {
  switch (action.type) {
    case REQUEST_ALBUM:
    case CREATE_ALBUM:
      return state.set('loading', true)

    case CREATE_ALBUM_SUCCESS:
      return state.set('loading', false)
  }
}

您可以从其他鸭子文件中导入动作类型。 Albums reducer 只负责更新它自己的状态切片。

多个reducer 可以监听相同的动作类型,并对自己的切片做一些改变。所以CREATE_ALBUM_SUCCESS 也可以触发 Albums reducer 的一些变化。

您也可以通过其他方式实现这一点。例如,通过在操作的 meta 字段中使用键。这避免了执行大量的导入和导出,因为 reducer 不必知道动作的类型。

// loading reducer
export const reducer = (state = false, action) => 
  (action.meta && action.meta.loading !== undefined)
    ? action.meta.loading
    : state


// action creator
export const updateAlbumPending = () => ({
  type: UPDATE_ALBUM,
  meta: {
    loading: true
  }
})

【讨论】:

    猜你喜欢
    • 2018-11-14
    • 2019-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    • 2019-07-23
    • 1970-01-01
    • 2017-09-03
    相关资源
    最近更新 更多