React/redux - 将 actionCreators 传递到多个层次

React/redux - passing actionCreators many levels deep

我想知道其他人如何处理将 redux 动作创建者从智能顶级组件向下传递到许多较低级别的哑组件而不会使他们的道具定义膨胀。

例如,在 this excellent tutorial on redux 之后,如果我像这样将动作创建者列表传递到道具中

import Voting from './Voting';
import * as actionCreators from '../action_creators';

...

export const VotingContainer = connect(
    mapStateToProps,
    actionCreators
)(Voting);

然后在我的投票组件中,我可以访问非常酷的 actionCreators。

但是如果我说 20 个用于投票及其所有子组件的 actionCreator,例如

Voting -> VotingContainer -> VotingDetail -> VotingFoo -> VotingBar

然后我最终得到如下所示的渲染函数

class Voting extends React.Component {
    render(){
        <VotingContainer
            actionCreator1={this.props.actionCreator1}
            .
            .
            .
            actionCreator15={this.props.actionCreator15} />
    }
}

class VotingContainer extends React.Component {
    render(){
        <VotingDetail
            actionCreator1={this.props.actionCreator1}
            .
            .
            .
            actionCreator12={this.props.actionCreator12} />
    }
}

.
.
.

class VotingFoo extends React.Component {
    render(){
        <VotingBar
            actionCreator1={this.props.actionCreator1}
            .
            .
            .
            actionCreator6={this.props.actionCreator6} />
    }
}

是否有针对这种情况的最佳实践,一种以某种方式将 actionCreators 组合在一起而无需在每一步都使用大量样板的方法?我在任何 tutorials/examples...

中都没有看到任何内容

我发现在大多数情况下,我在核心 ui 组件周围有很多愚蠢的包装器,大多数嵌套组件都需要来自顶部容器的大部分道具。 正因为如此,ES6 ...语法有很大帮助。

你可以这样做:

<VotingBar {...this.props} />

equi与此等价:

<VotingBar
    actionCreator1={this.props.actionCreator1}
    .
    .
    .
    actionCreator6={this.props.actionCreator6} />

只需将树下的组件也连接到 Redux。
我们在示例中过分强调“顶部的容器”。
当我们谈论非常简单的应用程序时,这是有道理的。

对于任何复杂的应用程序,一旦传递道具变得乏味,connect() 下面的组件。 我在我的免费视频中对此进行了介绍:请参阅 Extracting Container Components 和接下来的几个视频。

当然有多种方法可以解决这个问题。

最近,我开始跳过动作创建者功能在链中的整个传递,转而支持只需要商店和我的动作创建者直接在需要的地方和从那里发送,例如

var store = require('../store');
var actions = require('../actions');

// Somewhere inside your component...
store.dispatch(actions.someAction(data));

只要确保您的操作创建者的结果(即新状态)通过您的顶级组件向下传递。这使您的数据流保持单向且易于理解。

为了避免从一个级别到另一个级别向下传递属性到实际使用这些道具的地方,React Context 可以用来用上下文(一些特定数据)包装顶部的 Child。

Codepen Demo

这是一个简单的用例示例,其中定义了多个 reducer,每个 reducer 负责其自己的状态(本例中为计数器)

一个<Button><Modal>里面,两个Modal组件在App里,每一个都应该最终 "broadcast" 内部(在最深的组件中)对顶级组件 (App) 所做的更改实际上正在监听更改并对其采取行动。

只有 App 关心 Button 的变化,但是因为可以有很多 Button 组件, App 必须知道哪个深层组件做了什么动作并将正确的数据发送回 Redux.

Modal 只是介于两者之间的东西,仅用于表示目的。它不关心任何道具或状态。 Button 除了通过 Context 直接发送给它的内容外,也不关心任何其他内容。他通过 Consumer 方法从 React.createContext

监听变化

const { Provider:ContextProvider, Consumer:ContextConsumer } = React.createContext({});
const {connect, Provider } = ReactRedux; 
const {createStore, compose, combineReducers} = Redux; 
const {Component} = React; 

//// REDUX STORE STUFF /////////////////////////////////////
function reducer_foo(state = {value:1}, action){
  if( action.type == 'FOO__SET_VALUE') // type should be unique
    return { ...state, value:action.value }
  else return state
}

function reducer_bar(state = {value:1}, action){
  if( action.type == 'BAR__SET_VALUE') // type should be unique
    return { ...state, value:action.value }
  else return state
}

const rootReducer = combineReducers({
  foo: reducer_foo,
  bar: reducer_bar
});

//// REACT STUFF /////////////////////////////////////

// 2nd depth-level
// This component's "job" is to simply take a value and return a manipulated value to
// whoever called it. This is a very simplifed component, but think of a datepicker instead.
const Button = () =>
  <ContextConsumer>
      {v => <button onClick={()=> v.action(v.value + 1, v.name)}>Click to INC: {v.value}</button>}
  </ContextConsumer>



// 1st depth-level (in reality this level will be more complex)
const Modal = () => <p><Button /></p>



// top depth-level component
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {};
  }
  
  // The deepest component will pass the value and the name for which to dispatch to
  updateValue = ( value, name ) => {
    this.props.dispatch({type:`${name.toUpperCase()}__SET_VALUE`, value})
  }
  
  render(){
    const {foo, bar} = this.props;

    return (
      <React.Fragment>
        <ContextProvider value={{value:foo.value, action:this.updateValue, name:'foo'}}>
          <Modal />
        </ContextProvider>
        
        <ContextProvider value={{value:bar.value, action:this.updateValue, name:'bar'}}>
          <Modal />
        </ContextProvider>
      </React.Fragment>
    )
  }
}



function mapStateToProps(state){
  return state // in this example let's just pass the whole state for simplicity
}

const ConnectedApp = connect(mapStateToProps)(App)
const store = createStore(rootReducer);

ReactDOM.render(
  <Provider store={store}>
    <ConnectedApp />
  </Provider>,
  document.getElementById('root')
)
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.0/redux.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.7/react-redux.min.js"></script>

<div id="root"></div>