使用 React 上下文 API

已发表: 2022-03-11

React Context API 作为一个实验性特性已经存在了一段时间,但只有在 React 的 16.3.0 版本中,它才可以安全地在生产环境中使用。 下面的文章将向您展示两个基本的网络商店应用程序,一个使用 Context API 构建,一个没有它。

这个新的 API 解决了一个主要问题——支柱钻孔。 即使你不熟悉这个术语,如果你曾经开发过 React.js 应用程序,它也可能发生在你身上。 Prop 钻取是通过多层中间 React 组件将数据从组件 A 获取到组件 Z 的处理。 组件将间接接收道具,而,React 开发人员将必须确保一切正常。

让我们探讨一下在没有 React Context API 的情况下如何处理常见问题,

应用程序.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> ); } }

产品列表 .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;

汽车.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> );

汽车.js

 const Cars = props => ( <Fragment> <p>Name: {props.name}</p> <p>Price: ${props.price}</p> <button onClick={props.incrementPrice}>&uarr;</button> <button onClick={props.decrementPrice}>&darr;</button> </Fragment> );

诚然,这不是处理数据的最佳方式,但我希望它能说明为什么螺旋钻很烂。 那么 Context API 如何帮助我们避免这种情况呢?

介绍 Context Web Store

让我们重构应用程序并演示它可以做什么。 简而言之,Context API 允许您拥有一个存储数据的中央存储(是的,就像在 Redux 中一样)。 商店可以直接插入到任何组件中。 你可以砍掉中间人!

两种状态流的示例:一种使用 React Context API,一种没有

重构非常容易——我们不必对组件的结构进行任何更改。 然而,我们确实需要创建一些新组件——提供者和消费者。

1.初始化上下文

首先,我们需要创建上下文,稍后我们可以使用它来创建提供者和消费者。

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.创建提供者

完成后,我们可以导入上下文并使用它来创建我们的提供者,我们称之为 MyProvider。 在其中,我们使用一些值初始化一个状态,您可以通过value prop 共享我们的提供程序组件。 在我们的示例中,我们共享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> ); } }

为了让其他组件可以访问提供程序,我们需要用它来包装我们的应用程序(是的,就像在 Redux 中一样)。 当我们这样做时,我们可以摆脱状态和方法,因为它们现在在 MyProvider.js 中定义。

应用程序.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. 创建消费者

我们需要再次导入上下文并用它包装我们的组件,这会在组件中注入上下文参数。 之后,它非常简单。 你使用context ,就像你使用props一样。 它包含我们在 MyProducer 中共享的所有值,我们只需要使用它!

汽车.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> );

我们忘记了什么? 产品清单! 这就是好处变得显而易见的地方。 我们不传递任何数据或方法。 组件被简化,因为它只需要渲染几个组件。

产品列表.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 用于其中央存储功能的人来说,这可能是正确的。 如果这是您使用它的唯一功能,您现在可以用 Context API 替换它,并避免在不使用第三方库的情况下进行道具钻探。

如果您对测量和优化 React 应用程序的性能感兴趣,请阅读 Toptaler William Wang 的A Guide to Optimizing React Performance