React/redux - 传递 actionCreators 多个层次

IT技术 reactjs redux
2021-05-15 10:18:00

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

例如,按照这个关于 redux 的优秀教程,如果我像这样将动作创建者列表传递到props中

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

...

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

然后在我的 Voting 组件中,我可以访问真的很酷的 actionCreators。

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

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 组合在一起而每一步都没有大量样板的方法?我在任何教程/示例中都没有看到任何内容...

4个回答

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

对于任何复杂的应用程序,一旦传递props变得乏味,connect()下面的组件。 我在我的免费视频中介绍了这一点:请参阅提取容器组件和接下来的几个视频。

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

你可以这样做:

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

这相当于:

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

为了避免将属性从级别传递到实际使用这些props的位置,可以使用React Context将顶部 Child 与上下文(某些特定数据)包装在一起。

代码笔演示

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

A<Button>在 a 内部<Modal>,两个Modal组件在App内部,每个组件最终都应该将内部(在最深的组件中)所做的更改“广播”到实际监听更改并对其采取行动的顶级组件(App)。

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

模态是简单的东西在其中存在只代表性的目的之间。它不关心任何props或状态。除了通过 Context 直接发送给它的内容之外,Button也不关心任何事情。他通过以下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>

当然,您可以通过多种方式解决此问题。

最近,我开始跳过整个链中动作创建者函数的传递,转而直接要求 store 和我的动作创建者直接在需要的地方并从那里分派,例如

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

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

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