如何使用 react-redux 将 redux 存储中封装的 ui 状态映射到道具?

How to map encapsulated ui state in a redux store to props with react-redux?

我无法弄清楚如何组合 ui 小部件缩减器并串联反应渲染树,以便我稍后可以将缩减器生成的 redux 存储映射到渲染树中的道具使用反应减少。

假设这样的设置。我正在尝试制作一个我可以在任何应用程序中的任何地方使用的 NameWidget:

NameView.jsx:
...
render() {
  return <div> Name: {this.props.name} </div>;
}
...

====

NameAction.js:
...
export const CHANGE_NAME = 'CHANGE_NAME';
...
export function changeNameAction(newName) {
  return {
    type: CHANGE_NAME,
    payload: newName,
  };
}

====

NameReducer.js:

import { CHANGE_NAME } from './NameAction';

function ui(state = {name: ''}, action) {
  switch(action.type) {
    case(CHANGE_NAME):
      return Object.assign({}, state, {name: action.payload});
      break;
    default:
      return state;
  }
}

export default ui;

====

NameWidget.js:

import { connect } from 'react-redux';
import { NameView } from './NameView';

const mapStateToProps = (state) => {
  return {
    name: state.name,
  };
};

const NameWidget = connect(
  mapStateToProps
)(NameView);

export default NameWidget;

如果我直接使用 NameWidget 就没有问题:

NameApp.jsx:

import { createStore } from 'redux';
import app from './NameReducers';
let store = createStore(app);
...
function render() {
  return (
    <Provider store={store}>
      <NameWidget/>
    </Provider>
  );
}

但是,我不知道如何制作这个模块。我实际上无法将此 Widget 放入其他任何东西。假设我想这样做:

EncapsulatingView.jsx:
...
render() {
  return (
    <div>
      <NameWidget/>
      <SomeOtherReduxWidget/>
    </div>
  );
}
...

====

EncapsulatingReducer.js:
import { combineReducers } from 'redux'
import nameWidget from '../name/NameReducer';
import someOtherWidget from '../someOther/SomeOtherReduxWidgetReducer';

export default combineReducers({nameWidget, someOtherWidget});

(我希望剩下的代码,包括用于封装*的 connect() 和 createStore() 是显而易见的。)

几乎 有效,只要所有封装视图中的所有操作都具有唯一类型。但是问题是NameWidget的mapStateToProps;它需要从上面进行更改以使用新路径到其商店部分:

...
const mapStateToProps = (state) => {
  return {
    name: state.nameWidget.name,
  };
};
...

等等 - 根据祖先如何 combineReducers() 来定义他们更强大的超级小部件和应用程序,后代必须以某种方式改变他们 mapStateToProps() 的方式来补偿。这破坏了代码的封装和可重用性,我不知道如何在 react-redux 中绕过它。我如何通过组合 combineReducers() 来定义小部件,然后以一次写入随处使用的方式将生成的存储映射到这些小部件的道具?

(毫无意义,重复的 combineReducers() 创建一个自下而上的形状似乎很奇怪,但是 mapStateToProps() 似乎假设采用自上而下的 "absolute path" 方法来查找该形状.)

有趣的问题。我想知道要求父级生成 guid 并通过 props 向下传递是否符合您的目的,然后您只需将其用作该小部件实例的状态对象的索引。因此,每当您要创建此小部件的新实例时,您需要在父级中为它创建一个 ID,然后将其传递到 props 中,然后它就可供 connect 方法使用mapStateToProps 和 mapDispatchToProps。我想理论上你甚至可以自己创建这个父组件,它在使用时可能是透明的,只需使用 componentDidMount 生成一个新的 guid 来存储组件状态并传递给子组件。

我把这个问题归功于 John,因为他基本上为答案提供了正确的魔法,通过 mapStateToProps() 的 ownProps 函数 arg 传递存储 "routing" 逻辑。但我更喜欢我自己的观点。本质上,每当你在 EncapsulatingReducer.js 中使用 combineReducers() 时,你必须记住在 EncapsulatingView.js:

中传递一个 uiStorePath prop
New NameWidget.js:

const _ = require('lodash');
import { connect } from 'react-redux';
import { NameView } from './NameView';

const mapStateToProps = (state, props) => {
  const uiStore = (
    (ownProps && _.has(ownProps, 'uiStorePath') && ownProps.uiStorePath &&
        ownProps.uiStorePath.length) ? 
      _.get(state, ownProps.uiStorePath) :  // deep get...old versions of lodash would _.deepGet()
      state
  );

  return {
    name: uiStore.name,
  };
};

const NameWidget = connect(
  mapStateToProps
)(NameView);

export default NameWidget;

====

EncapsulatingView.js:

...
render() {
  const uiStorePathBase = ((this.props.uiStorePath &&         
      this.props.uiStorePath.length) ?
    this.props.uiStorePath + '.' :
    ''
  );
  return (
    <div> 
      <NameWidget uiStorePath={uiStorePathBase+"nameWidget"}/>
      <SomeOtherWidget uiStorePath={uiStorePathBase+"someOtherWidget"/>
    </div>
  );
}
...