怎樣使用React Context API
翻譯:瘋狂的技術宅
原文: https://www.toptal.com/react/...
本文首發微信公眾號:jingchengyideng
歡迎關注,每天都給你推送新鮮的前端技術文章
React Context API 現在已經成為一個實驗性功能,但是隻有在 React 16.3.0 中才能用在生產中。本文將向你展示兩個基本的 Web 商店應用程式,一個使用了 Context API 進行構建,另一個則不用。
這個新的API解決了一個嚴重的問題 —— prop drilling 。 即使你不熟悉這個術語,如果你曾經用 React.js 做過開發,它可能就已經在你身上發生過了。 Prop drilling 是通過將資料傳遞到多箇中間 React 元件層,將資料從元件A 獲取到元件 Z 的過程。 元件將間接的接收props,而你必須確保一切正常。
我們先探討如何在沒有 React Context API 的情況下處理常見問題:
App.js
class App extends Component { state = { cars: { car001: { name: 'Honda', price: 100 }, car002: { name: 'BMW', price: 150 }, car003: { name: 'Mercedes', price: 200 } } }; incrementCarPrice = this.incrementCarPrice.bind(this); decrementCarPrice = this.decrementCarPrice.bind(this); incrementCarPrice(selectedID) { // a simple method that manipulates the state const cars = Object.assign({}, this.state.cars); cars[selectedID].price = cars[selectedID].price + 1; this.setState({ cars }); } decrementCarPrice(selectedID) { // a simple method that manipulates the state const cars = Object.assign({}, this.state.cars); cars[selectedID].price = cars[selectedID].price - 1; this.setState({ cars }); } render() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to my web store</h1> </header> {/* Pass props twice */} <ProductList cars={this.state.cars} incrementCarPrice={this.incrementCarPrice} decrementCarPrice={this.decrementCarPrice} /> </div> ); } }
ProductList .js
const ProductList = props => ( <div className="product-list"> <h2>Product list:</h2> {/* Pass props twice */} <Cars cars={props.cars} incrementCarPrice={props.incrementCarPrice} decrementCarPrice={props.decrementCarPrice} /> {/* Other potential product categories which we will skip for this demo: */} {/* <Electronics /> */} {/* <Clothes /> */} {/* <Shoes /> */} </div> ); export default ProductList;
Cars.js
const Cars = props => ( <Fragment> <h4>Cars:</h4> {/* Finally we can use data */} {Object.keys(props.cars).map(carID => ( <Car key={carID} name={props.cars[carID].name} price={props.cars[carID].price} incrementPrice={() => props.incrementCarPrice(carID)} decrementPrice={() => props.decrementCarPrice(carID)} /> ))} </Fragment> );
Car.js
const Cars = props => ( <Fragment> <p>Name: {props.name}</p> <p>Price: ${props.price}</p> <button onClick={props.incrementPrice}>↑</button> <button onClick={props.decrementPrice}>↓</button> </Fragment> );
當然,這不是處理資料的最好方式,但我希望能夠用它說明為什麼 prop drilling 很差勁 。 那麼 Context API 是如何幫我們避免這種情況呢?
介紹Context Web Store
讓我們重構程式並演示它可以做些什麼。 簡而言之,Context API 允許你擁有一個儲存資料的中央儲存(是的,就像儲存在 Redux 中一樣)。你可以把任何元件直接插入到商店應用中,也可以切斷 middleman!
重構非常簡單 —— 我們不必對元件的結構進行任何修改。但是我們確實需要建立一些新元件:Provider 和 consumer。
1.初始化 Context
首先,我們需要 建立context ,後面可以使用它來建立 Provider 和 consumer。
MyContext.js
import React from 'react'; // this is the equivalent to the createStore method of Redux // https://redux.js.org/api/createstore const MyContext = React.createContext(); export default MyContext;
2. 建立 Provider
完成後,我們可以匯入 context 並用它來建立我們的 provider,我們稱之為 MyProvider。在裡面使用一些值初始化一個狀態,你可以通過 value prop 共享我們的 provider 元件。 在例子中,我們將共享 this.state.cars
以及一些操縱狀態的方法。 將這些方法可以看作是 Redux 中的 Reducer。
MyProvider.js
import MyContext from './MyContext'; class MyProvider extends Component { state = { cars: { car001: { name: 'Honda', price: 100 }, car002: { name: 'BMW', price: 150 }, car003: { name: 'Mercedes', price: 200 } } }; render() { return ( <MyContext.Provider value={{ cars: this.state.cars, incrementPrice: selectedID => { const cars = Object.assign({}, this.state.cars); cars[selectedID].price = cars[selectedID].price + 1; this.setState({ cars }); }, decrementPrice: selectedID => { const cars = Object.assign({}, this.state.cars); cars[selectedID].price = cars[selectedID].price - 1; this.setState({ cars }); } }} > {this.props.children} </MyContext.Provider> ); } }
為了使 provider 可以訪問其他元件,我們需要用它包裝我們的應用程式(沒錯,就像 Redux 一樣)。我們可以擺脫這些狀態和方法,因為它們在 MyProvider.js 中定義。
App.js
class App extends Component { render() { return ( <MyProvider> <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Welcome to my web store</h1> </header> <ProductList /> </div> </MyProvider> ); } }
3. 建立 Consumer
我們需要再次匯入 context 並用它包裝我們的元件,它會在元件中注入context 引數。 之後,它非常直接。 你使用 context 就像用 props 一樣。 它包含我們在 MyProducer 中共享的所有值,我們所需要做的只是去使用它!
Cars.js
const Cars = () => ( <MyContext.Consumer> {context => ( <Fragment> <h4>Cars:</h4> {Object.keys(context.cars).map(carID => ( <Car key={carID} name={context.cars[carID].name} price={context.cars[carID].price} incrementPrice={() => context.incrementPrice(carID)} decrementPrice={() => context.decrementPrice(carID)} /> ))} </Fragment> )} </MyContext.Consumer> );
我們似乎忘記了什麼?是 ProductList !它使好處變得非常明顯。 我們不必傳遞任何資料或方法。這個元件被簡化,因為它只需要去渲染一些元件。
ProductList.js
const ProductList = () => ( <div className="product-list"> <h2>Product list:</h2> <Cars /> {/* Other potential product categories which we will skip for this demo: */} {/* <Electronics /> */} {/* <Clothes /> */} {/* <Shoes /> */} </div> );
在本文中,我對 Redux 和 Context API 進行了一些比較。 Redux 最大的優勢之一就是你的應用可以擁有一個可以從任何元件訪問的中央儲存。而使用新的 Context API,預設情況下你已經有了這個功能。 在巨大的宣傳攻勢下 Context API 將會使 Redux 變得過時。
對於那些只把 Redux 作為中央儲存功能的人來說,可能確實如此。 如果你只使用 Redux 的這一個功能,現在可以使用 Context API 替換它,並避免在不使用第三方庫的情況下進行 prop drilling。
本文首發微信公眾號:jingchengyideng
歡迎關注,每天都給你推送新鮮的前端技術文章