怎樣使用React Context API [每日前端夜話0x26]
京程一燈 前端先鋒
每日前端夜話0x26
每日前端夜話,陪你聊前端。
每天晚上18:00準時推送。
正文共:5730 字 1 圖
預計閱讀時間: 15 分鐘
翻譯:瘋狂的技術宅
原文:https://www.toptal.com/react/react-context-api
React Context API 【https://reactjs.org/docs/context.html】現在已經成爲一個實驗性功能,但是隻有在 React 16.3.0
【https://reactjs.org/blog/2018/03/29/react-v-16-3.html】中才能用在生產中。本文將向你展示兩個基本的 Web 商店應用程序,一個使用了 Context API 進行構建,另一個則不用。
這個新的API解決了一個嚴重的問題 ——prop drilling。 即使你不熟悉這個術語,如果你曾經用 React.js 做過開發,它可能就已經在你身上發生過了。 Prop drilling 是通過將數據傳遞到多箇中間 React 組件層,將數據從組件A 獲取到組件 Z 的過程。 組件將間接的接收props,而你必須確保一切正常。
我們先探討如何在沒有 React Context API 的情況下處理常見問題:
App.js
1class App extends Component {
2 state = {
3 cars: {
4 car001: { name: 'Honda', price: 100 },
5 car002: { name: 'BMW', price: 150 },
6 car003: { name: 'Mercedes', price: 200 }
7 }
8 };
9 incrementCarPrice = this.incrementCarPrice.bind(this);
10 decrementCarPrice = this.decrementCarPrice.bind(this);
11
12 incrementCarPrice(selectedID) {
13 // a simple method that manipulates the state
14 const cars = Object.assign({}, this.state.cars);
15 cars[selectedID].price = cars[selectedID].price + 1;
16 this.setState({
17 cars
18 });
19 }
20
21 decrementCarPrice(selectedID) {
22 // a simple method that manipulates the state
23 const cars = Object.assign({}, this.state.cars);
24 cars[selectedID].price = cars[selectedID].price - 1;
25 this.setState({
26 cars
27 });
28 }
29
30 render() {
31 return (
32 <div className="App">
33 <header className="App-header">
34 <img src={logo} className="App-logo" alt="logo" />
35 <h1 className="App-title">Welcome to my web store</h1>
36 </header>
37 {/* Pass props twice */}
38 <ProductList
39 cars={this.state.cars}
40 incrementCarPrice={this.incrementCarPrice}
41 decrementCarPrice={this.decrementCarPrice}
42 />
43 </div>
44 );
45 }
46}
ProductList .js
1const ProductList = props => (
2 <div className="product-list">
3 <h2>Product list:</h2>
4 {/* Pass props twice */}
5 <Cars
6 cars={props.cars}
7 incrementCarPrice={props.incrementCarPrice}
8 decrementCarPrice={props.decrementCarPrice}
9 />
10 {/* Other potential product categories which we will skip for this demo: */}
11 {/* <Electronics /> */}
12 {/* <Clothes /> */}
13 {/* <Shoes /> */}
14 </div>
15);
16
17export default ProductList;
18
Cars.js
1const Cars = props => (
2 <Fragment>
3 <h4>Cars:</h4>
4 {/* Finally we can use data */}
5 {Object.keys(props.cars).map(carID => (
6 <Car
7 key={carID}
8 name={props.cars[carID].name}
9 price={props.cars[carID].price}
10 incrementPrice={() => props.incrementCarPrice(carID)}
11 decrementPrice={() => props.decrementCarPrice(carID)}
12 />
13 ))}
14 </Fragment>
15);
16
Car.js
1const Cars = props => (
2 <Fragment>
3 <p>Name: {props.name}</p>
4 <p>Price: ${props.price}</p>
5 <button onClick={props.incrementPrice}>↑</button>
6 <button onClick={props.decrementPrice}>↓</button>
7 </Fragment>
8);
當然,這不是處理數據的最好方式,但我希望能夠用它說明爲什麼 prop drilling 很差勁。 那麼 Context API 是如何幫我們避免這種情況呢?
介紹Context Web Store
讓我們重構程序並演示它可以做些什麼。 簡而言之,Context API 允許你擁有一個存儲數據的中央存儲(是的,就像存儲在 Redux 中一樣)。你可以把任何組件直接插入到商店應用中,也可以切斷 middleman!
兩個狀態流的示例:一個使用React Context API,另一個不用
重構非常簡單 —— 我們不必對組件的結構進行任何修改。但是我們確實需要創建一些新組件:Provider 和 consumer。
1.初始化 Context
首先,我們需要創建context【https://reactjs.org/docs/context.html#reactcreatecontext】,後面可以使用它來創建 Provider 和 consumer。
MyContext.js
1import React from 'react';
2
3// this is the equivalent to the createStore method of Redux
4// https://redux.js.org/api/createstore
5
6const MyContext = React.createContext();
7
8export default MyContext;
2. 創建 Provider
完成後,我們可以導入 context 並用它來創建我們的 provider,我們稱之爲 MyProvider。在裏面使用一些值初始化一個狀態,你可以通過 value prop 共享我們的 provider 組件。 在例子中,我們將共享 this.state.cars 以及一些操縱狀態的方法。 將這些方法可以看作是 Redux 中的 Reducer。
MyProvider.js
1import MyContext from './MyContext';
2
3class MyProvider extends Component {
4 state = {
5 cars: {
6 car001: { name: 'Honda', price: 100 },
7 car002: { name: 'BMW', price: 150 },
8 car003: { name: 'Mercedes', price: 200 }
9 }
10 };
11
12 render() {
13 return (
14 <MyContext.Provider
15 value={{
16 cars: this.state.cars,
17 incrementPrice: selectedID => {
18 const cars = Object.assign({}, this.state.cars);
19 cars[selectedID].price = cars[selectedID].price + 1;
20 this.setState({
21 cars
22 });
23 },
24 decrementPrice: selectedID => {
25 const cars = Object.assign({}, this.state.cars);
26 cars[selectedID].price = cars[selectedID].price - 1;
27 this.setState({
28 cars
29 });
30 }
31 }}
32 >
33 {this.props.children}
34 </MyContext.Provider>
35 );
36 }
37}
38
爲了使 provider 可以訪問其他組件,我們需要用它包裝我們的應用程序(沒錯,就像 Redux 一樣)。我們可以擺脫這些狀態和方法,因爲它們在 MyProvider.js 中定義。
App.js
1class App extends Component {
2 render() {
3 return (
4 <MyProvider>
5 <div className="App">
6 <header className="App-header">
7 <img src={logo} className="App-logo" alt="logo" />
8 <h1 className="App-title">Welcome to my web store</h1>
9 </header>
10 <ProductList />
11 </div>
12 </MyProvider>
13 );
14 }
15}
16
3. 創建 Consumer
我們需要再次導入 context 並用它包裝我們的組件,它會在組件中注入context 參數。 之後,它非常直接。 你使用 context 就像用 props 一樣。 它包含我們在 MyProducer 中共享的所有值,我們所需要做的只是去使用它!
Cars.js
1const Cars = () => (
2 <MyContext.Consumer>
3 {context => (
4 <Fragment>
5 <h4>Cars:</h4>
6 {Object.keys(context.cars).map(carID => (
7 <Car
8 key={carID}
9 name={context.cars[carID].name}
10 price={context.cars[carID].price}
11 incrementPrice={() => context.incrementPrice(carID)}
12 decrementPrice={() => context.decrementPrice(carID)}
13 />
14 ))}
15 </Fragment>
16 )}
17 </MyContext.Consumer>
18);
我們似乎忘記了什麼?是 ProductList !它使好處變得非常明顯。 我們不必傳遞任何數據或方法。這個組件被簡化,因爲它只需要去渲染一些組件。
ProductList.js
1const ProductList = () => (
2 <div className="product-list">
3 <h2>Product list:</h2>
4 <Cars />
5 {/* Other potential product categories which we will skip for this demo: */}
6 {/* <Electronics /> */}
7 {/* <Clothes /> */}
8 {/* <Shoes /> */}
9 </div>
10);
在本文中,我對 Redux 和 Context API 進行了一些比較。 Redux 最大的優勢之一就是你的應用可以擁有一個可以從任何組件訪問的中央存儲。而使用新的 Context API,默認情況下你已經有了這個功能。 在巨大的宣傳攻勢下 Context API 將會使 Redux 變得過時。
對於那些只把 Redux 作爲中央存儲功能的人來說,可能確實如此。 如果你只使用 Redux 的這一個功能,現在可以使用 Context API 替換它,並避免在不使用第三方庫的情況下進行 prop drilling。