除了更克制地使用 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 应用设计模式。
选择用什么样的工具从来都不是决定一个开发团队成败的关键,根据业务场景选择恰当的工具,并利用工具反过来约束开发者,最终达到控制整体项目复杂度的目的,才是促进一个开发团队不断提升的核心动力。