在即将发布的 React v16.3.0 中,React 引入了新的声明式的,可透传 props 的 Context API,对于新版 Context API 还不太了解朋友能够看一下笔者以前的一个回答。javascript
受益于此次改动,React 开发者终于拥有了一个官方提供的安全稳定的 global store,子组件跨层级获取父组件数据及后续的更新都再也不成为一个问题。这让咱们不由开始思考,相较于 Redux 等其余的第三方数据(状态)管理工具,使用 Context API 这种 vanilla React 支持的方式是否是一个更好的选择呢?前端
在 react + redux 已经成为了开始一个 React 项目标配的今天,咱们彷佛忘记了其实 react 自己是可使用 state 和 props 来管理数据的,甚至对于目前市面上大部分的应用来讲,对 redux 的不正确使用实际上增长了应用总体的复杂度及代码量。java
import React from "react";
import { render } from "react-dom";
const initialState = {
theme: "dark",
color: "blue"
};
const GlobalStoreContext = React.createContext({
...initialState
});
class GlobalStoreContextProvider extends React.Component {
// initialState
state = {
...initialState
};
// reducer
handleContextChange = action => {
switch (action.type) {
case "UPDATE_THEME":
return this.setState({
theme: action.theme
});
case "UPDATE_COLOR":
return this.setState({
color: action.color
});
case "UPDATE_THEME_THEN_COLOR":
return new Promise(resolve => {
resolve(action.theme);
})
.then(theme => {
this.setState({
theme
});
return action.color;
})
.then(color => {
this.setState({
color
});
});
default:
return;
}
};
render() {
return (
<GlobalStoreContext.Provider
value={{
dispatch: this.handleContextChange,
theme: this.state.theme,
color: this.state.color
}}
>
{this.props.children}
</GlobalStoreContext.Provider>
);
}
}
const SubComponent = props => (
<div>
{/* action */}
<button
onClick={() =>
props.dispatch({
type: "UPDATE_THEME",
theme: "light"
})
}
>
change theme
</button>
<div>{props.theme}</div>
{/* action */}
<button
onClick={() =>
props.dispatch({
type: "UPDATE_COLOR",
color: "red"
})
}
>
change color
</button>
<div>{props.color}</div>
{/* action */}
<button
onClick={() =>
props.dispatch({
type: "UPDATE_THEME_THEN_COLOR",
theme: "monokai",
color: "purple"
})
}
>
change theme then color
</button>
</div>
);
class App extends React.Component {
render() {
return (
<GlobalStoreContextProvider>
<GlobalStoreContext.Consumer>
{context => (
<SubComponent
theme={context.theme}
color={context.color}
dispatch={context.dispatch}
/>
)}
</GlobalStoreContext.Consumer>
</GlobalStoreContextProvider>
);
}
}
render(<App />, document.getElementById("root"));
复制代码
在上面的例子中,咱们使用 Context API 实现了一个简单的 redux + react-redux,这证实了在新版 Context API 的支持下,原先 react-redux 帮咱们作的一些工做如今咱们能够本身来作了。另外一方面,对于已经厌倦了成天都在写 action 和 reducer 的朋友们来讲,在上面的例子中忽略掉 dispatch,action 等这些 Redux 中的概念,直接调用 React 中常见的 handleXXX 方法来 setState 也是彻底没有问题的,能够有效地缓解 Redux 模板代码过多的问题。而对于 React 的初学者来讲,更是省去了学习 Redux 及函数式编程相关概念与用法的过程。react
从上面 Context 版本的 Redux 中能够看出,若是咱们只须要 Redux 来作全局数据源并配合 props 透传使用的话,新版的 Context 多是一个能够考虑的更简单的替代方案。另外一方面,原生版本 Redux 的核心竞争力其实也并不在于此,而是其中间件机制以及社区中一系列很是成熟的中间件。git
在 Context 版本中,用户行为(click)会直接调用 reducer 去更新数据。而在原生版本的 Redux 中,由于整个 action dispatch cycle 的存在,开发者能够在 dispatch action 先后,中心化地利用中间件机制去更好地跟踪/管理整个过程,如经常使用的 action logger,time travel 等中间件都受益于此。github
时间回到 2015 年,那时 React 刚刚发布了 0.13 版本,Redux 也尚未成为 React 应用的标配,前端开发界讨论的主题是 React 组件的最佳设计模式,后来你们得出的结论是将全部组件分为 Presentational(展现型) 及 Container(容器型)两类能够极大地提高组件的可复用性。编程
但后来 Redux 的普遍流行逐渐掩盖了这个很是有价值的结论,开发者们开始习惯性地将全部组件都 connect 到 redux store 上,以方便地获取所须要的数据。redux
组件与组件之间的层级结构渐渐地只存在于 DOM 层面,大量展现型的组件被 connect 到了 redux store 上,以致于在其余页面想要复用这个组件时,开发者们更倾向于复制粘贴部分代码。最终致使了 redux store 愈来愈臃肿,应用的数据流并无由于引入 Redux 而变得清晰,可复用的展现型组件愈来愈少,应用与应用之间愈来愈独立,没有人再愿意去思考应用层面的抽象与复用,项目越作越多,收获的却愈来愈少。设计模式
当全部的组件都与数据耦合在一块儿,视图层与数据层之间的界限也变得愈来愈模糊,这不只完全打破了 React 自己的分形结构,更是形成应用复杂度陡增的罪魁祸首。安全
除了更克制地使用 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 应用设计模式。
选择用什么样的工具历来都不是决定一个开发团队成败的关键,根据业务场景选择恰当的工具,并利用工具反过来约束开发者,最终达到控制总体项目复杂度的目的,才是促进一个开发团队不断提高的核心动力。