具有内部隔离 Redux 存储的可重用组件

Resuable component with internal, isolated Redux store

我正在考虑在 React 中构建一个非常复杂的图表组件,我希望它可以在许多不同的项目中使用。不用说,像这样的组件有多种状态需要以某种方式进行管理。

Redux 似乎非常适合这个,但是如果我只是将顶级容器包装在带有自定义存储的 Provider 中...如果组件的内部 redux 存储不会干扰全局应用程序状态包含在更大的 React/Redux 应用程序中?

import React, { Component } from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import internalReducer from './reducer';

export default class MyReduxComponent extends Component {

    render() {
        const store = createStore(internalReducer, this.props.initialState);
        return <Provider store={store}>
            <Chart />
        </Provider>
    }

}

你的组件不应该有自己的内部 redux store,而是有自己的 reducer + reducer 知道的动作/动作创建者,这样它就可以很容易地与现有的 redux 应用程序集成:

import { chartReducer, chartActionCreators } from 'your-chart'

combineReducers({
  ...global.stuff,
  chartReducer
})

store.dispatch(chartActionCreators.action())

--

import Chart from 'your-chart'

export default () =>
  <div>
    <Chart />
  </div>

您的库的用户可能不需要显式使用任何操作,只需包含 reducer 就足够了,因此可以在应用程序的任何位置访问图表状态。

此外,如果您需要以某种方式增强您的操作调用,您可以编写一些中间件。

http://redux.js.org/docs/advanced/Middleware.html

你有没有仅仅使用 React 组件状态的原因? Redux 旨在用于需要由整个应用程序中的多个组件访问的状态。如果您管理的状态仅供特定组件的后代使用,那么您也可以只使用组件状态——在这种情况下不需要 Redux。这样您的组件将更加可重用,并且不会依赖其他依赖项。

我想我找到了一个很好的解决问题的方法,在顶部组件中使用 childContextTypessetState。这允许任何深度的子组件仅 "import" 他们需要的上下文操作。 (这消除了通过多个嵌套组件将回调作为 props 传递下去的需要)。

Example on JSBin

顶级组件可能如下所示

export default class ReusableComponent extends Component {

  // Define the public API interface to child components
  static childContextTypes = {
    // expose component state store
    store: PropTypes.object,
    // actions
    setFoo: PropTypes.func,
    setBar: PropTypes.func
  };

  // Set initial state, can be overriden by props
  constructor(props) {
    super(props)
    this.state = {
      foo: 'My Foo',
      ...props
    }
  }

  // Define methods for public API 
  getChildContext() {
    return {
      store: this.state,
      setFoo: this.setFoo.bind(this),
      setBar: this.setBar.bind(this)
    };
  }

  // Reducer for action setFoo
  setFoo(foo) {
    this.setState({ foo })
  }

  // Just render components, no need for passing props
  render() {
    return <div>
      <UpdateFooComponent />
      </div>
  }

}

还有一个子组件

class UpdateFooComponent extends Component {

  // 'import' the store and actions you need from top component
  static contextTypes = {
    store: PropTypes.object,
    setFoo: PropTypes.func
  };

  clickHandler(e) {
    this.context.setFoo('Hello from subcomponent');
  }

  render() {
    const { foo } = this.context.store;
    return <div>
      <button onClick={::this.clickHandler}>Update foo</button>
      <p><strong>foo:</strong> {foo}</p>
    </div>
  }

}