首页 > 解决方案 > Redux:选择器模式的另一种实现

问题描述

以下示例显示了选择器模式的通常实现。然后我将讨论这个实现的问题。之后,我将建议另一种可能有用的实现。

通常的实现:

下面是 root reducer 和公共选择器的样子:

// reducers/index.js
import { combineReducers } from 'redux';
import * as Items from './items';
import * as Login from './login';

export default combineReducers({
  login: Login.reducer,
  items: Items.reducer
  // more slices ...
});


// PUBLIC SELECTORS
export const getToken = state => Login.getToken(state.login);
export const getLoginError = state => Login.getError(state.login);
export const isLoginLoading = state => Login.isLoading(state.login);

export const getItems = state => Items.getToken(state.items);
export const getCurrentItem = state => Items.getError(state.items);
export const isItemsLoading = state => Items.isLoading(state.items);

// Selectors for data from other slices
// ...

// Selectors for derived data from multiple slices
export const getOwnedItems = (state) => {
  // ...
};

以下是切片缩减器及其私有选择器的外观:

// reducers/login.js
import {
  // actions ...
} from '../actions';

const defaultState = {
  // ...
};

export const reducer = (state = defaultState, action = {}) => {
  // Reducer ...
};


// PRIVATE SELECTORS
export const getToken = state => state.token;
export const getError = state => state.error;
export const isLoading = state => state.loading;

另一个切片缩减器及其私有选择器:

// reducers/items.js
import {
  // actions ...
} from '../actions';

const defaultState = {
  // ...
};

export const reducer = (state = defaultState, action = {}) => {
  // Reducer ...
};


// PRIVATE SELECTORS
export const getItems = state => state.items;
export const getCurrentItem = state => state.currentItem;
export const isItemsLoading = state => state.isLoading;

这个实现的用法是这样的:

import { getLoginError, isLoginLoading } from '../reducers';

const mapStateToProps = state => {
  return {
    error: getLoginError(state),
    loading: isLoginLoading(state)
  };
};

问题:

之前的选择器实现要求所有公共选择器都定义在reducers/index.js. 请注意,公共选择器接收由定义的根 reducer 管理的完整状态reducers/index.js

公共选择器可以分为两种类型。提取选择器仅用于从状态中提取数据。以及从状态计算派生信息的派生信息选择器。对于用户来说,所有选择器都是相同的,它们的目的是将客户端代码与状态的形状分开。

先前实现的第一个问题是所有提取选择器都被写入了两次。一次作为公共选择器,另一个作为私有选择器,公共选择器调用私有选择器。

第二个问题是特定切片的所有私有选择器只能接收该切片,但它被传递了很多次,一次用于该切片的私有选择器的每个使用实例,这似乎是重构的好案例。

以下是可能证明有用的选择器的另一种实现:

另一种实现

根 reducer 文件将仅提供一个select()获取完整状态的函数,并提供公共接口,客户端代码可以从该接口中检索状态中的数据。

接口可能由函数或按名称分组的函数集合组成。这种结构允许我们提供一个接口,除了能够提供更多定制的公共选择器之外,该接口将使提取选择器的实现变得微不足道。

请不要将选择器的结构与状态的形状混淆。两者之间没有耦合。即使状态的形状发生了变化,公共选择器仍然可以为应用程序实现相同的接口。

这是select(state)函数中实现的公共选择器:

// reducers/index.js
import { combineReducers } from 'redux';
import * as Items from './items';
import * as Login from './login';

export default combineReducers({
  login: Login.reducer,
  items: Items.reducer
  // more slices ...
});


// PUBLIC SELECTORS
export const select = (state) => {
  return {
    login: Login.select(state.login),
    items: Items.select(state.items),

    // Selectors for drived data from multiple slices
    getOwnedItems: () => {
      // ...
    }
  };
};

这是以相同方式实现的私有选择器:

// reducers/login.js
import {
  // actions ...
} from '../actions';

const defaultState = {
  // ...
};

export const reducer = (state = defaultState, action = {}) => {
  // Reducer ...
};


// PRIVATE SELECTORS
export const select = (state) => {
  return {
    getToken: () => state.token,
    getError: () => state.error,
    isLoading: () => state.loading
  };
};

再次为另一个切片:

// reducers/items.js
import {
  // actions ...
} from '../actions';

const defaultState = {
  // ...
};

export const reducer = (state = defaultState, action = {}) => {
  // Reducer ...
};


// PRIVATE SELECTORS
export const select = (state) => {
  return {
    getItems: () => state.items,
    getCurrentItem: () => state.currentItem,
    isLoading: () => state.loading
  };
};

此实现的用法如下所示:

import { select } from '../reducers';

const mapStateToProps = state => {
  return {
    error: select(state).login.getError(),
    loading: select(state).login.isLoading()
  };
};

问题一:

这种实现的缺点是什么?

问题2:

是否有其他方法可以解决上述问题?

谢谢

标签: javascriptreduxreact-redux

解决方案


推荐阅读