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

Posted

技术标签:

【中文标题】如何使用react-redux将redux商店中封装的ui状态映射到道具?【英文标题】:How to map encapsulated ui state in a redux store to props with react-redux? 【发布时间】:2016-12-03 15:41:03 【问题描述】:

我无法弄清楚如何组合 ui 小部件减速器和串联反应渲染树,这样我以后可以使用 react-redux 将减速器的结果 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>
  );

但是,我不知道如何制作这个模块化。我实际上不能将这个小部件放入其他任何东西。假设我想这样做:

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,
  ;
;
...

等等 - 取决于祖先如何结合Reducers() 来定义他们更大的超级小部件和应用程序,后代必须以某种方式改变他们的 mapStateToProps() 来补偿。这破坏了代码的封装和可重用性,我看不出如何在 react-redux 中解决它。如何通过组合 combineReducers() 来定义小部件,然后将生成的 store 映射到这些小部件的 props,以一种写一次就用的方式?

(毫无疑问,重复的 combineReducers() 创建一个自下而上的形状似乎很奇怪,但 mapStateToProps() 似乎假设采用自上而下的“绝对路径”方法来查找该形状。)

【问题讨论】:

您打算将此代码导出到另一个项目,还是打算在同一项目的另一个设置中重新使用该小部件? 短期是特定于项目的,但如果有一个好的模式/解决方案,希望它会支持两者。 你有没有看过 redux-form 他们使用定义的名称(表单)作为他们自己的 root-reducer。这样,组件将查看 state.form 的所有值。 【参考方案1】:

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

【讨论】:

谢谢约翰!我曾想过类似的事情,但我不知道如何“在道具中传递” - 你能详细说明吗?在我看来,connect() 本质上是在我导入它返回的类型时调用的,而不是在我使用该类型时在 render() 中调用的。我想我可以将 connect() 包装在一个函数中,然后在 render() 期间动态调用它,将您提到的 ID 传递给该函数;但我不确定每次调用渲染时动态生成的类型的含义...... 所以我是说你会有一个父组件,并且在 componentDidMount(实际上 componentWillMount 可能是更好的地方)你基本上会调用this.setState(childId: newGuid()),然后在你渲染的渲染方法中带有&lt;ChildContainer id=this.state.childId/&gt; 之类的子容器组件。容器组件是指一个“仅限redux”组件,它是使用您的实际子组件调用connect 的结果。 然后在你的连接函数中,当你传入参数是函数mapStateToProps和mapDispatchToProps时,在那些函数中你使用它们的第二个参数(这是组件的道具),使用 id。例如,您的连接函数可能如下所示:jsbin.com/joyodaxazi/1/edit?js【参考方案2】:

我将这个问题归功于 John,因为他基本上为答案提供了正确的魔法,通过 mapStateToProps() 的 ownProps 函数 arg 传递存储“路由”逻辑。但我更喜欢我自己的想法。本质上,每当你在 EncapsulatingReducer.js 中 combineReducers() 时,你必须记住在 EncapsulatingView.js 中传递一个 uiStorePath 属性:

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>
  );

...

【讨论】:

以上是关于如何使用react-redux将redux商店中封装的ui状态映射到道具?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 React-Router 和 React-Redux 将道具发送到子路由组件?

React-Redux/Jest 不变违规:找不到“商店”

如何在类组件中使用 react-redux useSelector?

如何在 react-redux 中调用页面刷新的函数?

具有关系数据存储的列表和项目之间的关系(使用 react-redux)

React Hooks 与 React-redux