Context + Redux = 更好的 React 应用设计模式

202 阅读2分钟

除了更克制地使用 connect,区分展示型与容器型组件之外,受制于现在 Context API,开发者通常也会将主题,语言文件等数据挂在 redux store 的某个分支上。对于这类不常更新,却需要随时可以注入到任意组件的数据,使用新的 Context API 来实现依赖注入显然是一个更好的选择。

import React from "react";
import { render } from "react-dom";
import { createStore } from "redux";
import { Provider, connect } from "react-redux";

const ThemeContext = React.createContext("light");
class ThemeProvider extends React.Component {
  state = {
    theme: "light"
  };

  render() {
    return (
      <ThemeContext.Provider value={this.state.theme}>
        {this.props.children}
      </ThemeContext.Provider>
    );
  }
}
const LanguageContext = React.createContext("en");
class LanguageProvider extends React.Component {
  state = {
    laguage: "en"
  };

  render() {
    return (
      <LanguageContext.Provider value={this.state.laguage}>
        {this.props.children}
      </LanguageContext.Provider>
    );
  }
}
const initialState = {
  todos: []
};
const todos = (state, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return {
        todos: state.todos.concat([action.text])
      };
    default:
      return state;
  }
};
function AppProviders({ children }) {
  const store = createStore(todos, initialState);
  return (
    <Provider store={store}>
      <LanguageProvider>
        <ThemeProvider>{children}</ThemeProvider>
      </LanguageProvider>
    </Provider>
  );
}
function ThemeAndLanguageConsumer({ children }) {
  return (
    <LanguageContext.Consumer>
      {language => (
        <ThemeContext.Consumer>
          {theme => children({ language, theme })}
        </ThemeContext.Consumer>
      )}
    </LanguageContext.Consumer>
  );
}

const TodoList = props => (
  <div>
    <div>
      {props.theme} and {props.language}
    </div>
    {props.todos.map((todo, idx) => <div key={idx}>{todo}</div>)}
    <button onClick={props.handleClick}>add todo</button>
  </div>
);

const mapStateToProps = state => ({
  todos: state.todos
});

const mapDispatchToProps = {
  handleClick: () => ({
    type: "ADD_TODO",
    text: "Awesome"
  })
};

const ToDoListContainer = connect(mapStateToProps, mapDispatchToProps)(
  TodoList
);

class App extends React.Component {
  render() {
    return (
      <AppProviders>
        <ThemeAndLanguageConsumer>
          {({ theme, language }) => (
            <ToDoListContainer theme={theme} language={language} />
          )}
        </ThemeAndLanguageConsumer>
      </AppProviders>
    );
  }
}

render(<App />, document.getElementById("root"));

在上面的这个完整的例子中,通过组合多个 Context Provider,我们最终得到了一个组合后的 Context Consumer:

<ThemeAndLanguageConsumer>
  {({ theme, language }) => (
    <ToDoListContainer theme={theme} language={language} />
  )}
</ThemeAndLanguageConsumer>

另一方面,通过分离展示型组件和容器型组件,我们得到了一个纯净的 TodoList 组件:

const TodoList = props => (
  <div>
    <div>
      {props.theme} and {props.language}
    </div>
    {props.todos.map((todo, idx) => <div key={idx}>{todo}</div>)}
    <button onClick={props.handleClick}>add todo</button>
  </div>
);

在 React v16.3.0 正式发布后,用 Context 来做依赖注入(theme,intl,buildConfig),用 Redux 来管理数据流,渐进式地根据业务场景选择 redux-thunk,redux-saga 或 redux-observable 来处理复杂异步情况,可能会是一种更好的 React 应用设计模式。

选择用什么样的工具从来都不是决定一个开发团队成败的关键,根据业务场景选择恰当的工具,并利用工具反过来约束开发者,最终达到控制整体项目复杂度的目的,才是促进一个开发团队不断提升的核心动力。