React Context API作为实验功能已经存在了一段时间, 但是只有在React的16.3.0版本中, 它才可以在生产中安全使用。下面的文章将向你展示两个基本的网络商店应用程序, 一个使用Context API构建, 另一个不使用Context API。
这个新的API解决了一个主要问题-道具钻探。即使你不熟悉该术语, 但是如果你使用过React.js应用程序, 它也可能发生在你身上。道具钻探是通过使数据经过多层中间React组件层将数据从组件A传递到组件Z的过程。组件将间接接收道具, 而你, React Developer必须确保一切正常。
让我们探讨一下在没有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>
);
}
}
产品清单.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>
);
诚然, 这不是处理数据的最佳方法, 但我希望它能说明为什么钻探钻头很烂。那么, Context API如何帮助我们避免这种情况?
Context Web Store简介
让我们重构应用程序并演示其功能。简而言之, Context API允许你拥有一个数据存储所在的中央存储(是的, 就像Redux中一样)。商店可以直接插入任何组件中。你可以砍掉中间人!
重构非常容易-我们不必对组件的结构进行任何更改。但是, 我们确实需要创建一些新组件-提供者和使用者。
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。在其中, 我们使用一些值初始化状态, 你可以通过值将其共享给提供者组件。在我们的示例中, 我们共享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中定义的。
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.创建消费者
我们将需要再次导入上下文, 并用它包装我们的组件, 这会将context参数注入到组件中。之后, 这很简单。你使用上下文, 就像使用道具一样。它包含了我们在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.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应用程序的性能感兴趣, 请阅读srcminier William Wang同事撰写的《优化React性能指南》。