在即將發佈的 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 應用設計模式。
選擇用什麼樣的工具歷來都不是決定一個開發團隊成敗的關鍵,根據業務場景選擇恰當的工具,並利用工具反過來約束開發者,最終達到控制總體項目複雜度的目的,纔是促進一個開發團隊不斷提高的核心動力。