在学习React组件的过程中,发现state的运用很广泛,但对于它的使用及运行机制还是比较模凌两可的,故找了一些资料学习一下。
React中的组件类型被分为了两类:函数组件,又被称为无状态组件;类组件,又被称为有状态组件。状态(state)即数据。函数组件没有自己的状态,自负责数据展示。类组件有自己的状态,负责更新UI。React中想要实现该功能,就要使用类组件(有状态组件)。随着React的发展,在React 16.8之后,引入了Hooks,函数组件也可以开始使用状态,如下例子:
// 函数式组件中使用 useState Hook
import React, { useState } from 'react';
function ExampleComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
而对于有状态组件(类组件),我们通常在constructor中定义state,通过this.setState()去修改,react会自动帮我们合并state中未修改的部分。
import React from "react";
class Clock extends React.Component {
constructor(props) {
super(props);
this.timer = null;
this.state = {
currentTime: new Date()
};
}
componentDidMount() {
this.timer = setInterval(() => {
this.setState({
currentTime: new Date()
});
}, 5000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
render() {
const { currentTime } = this.state;
return (
<div>
<p>currentTime: {currentTime.toLocaleTimeString()}</p>
</div>
);
}
}
export default Clock;
注意! 当我们调用this.setState(),组件会调用render方法重新渲染页面而直接去修改state,而直接修改不会重新渲染组件,在官方文档描述里也不推荐这种做法。因为它可能导致 React 无法正确地进行状态追踪和组件的重新渲染。
另一方面,state的更新可能会被合并,当你连续多次调用this.setState()的时候,出于性能考虑,react会将多次合并为一次更新,这导致了state的更新可能是异步的,所以当我们修改state的时候不应该依赖旧的state(为了解决这个问题,setState也支持传入一个函数接受最新的props和state)。
// 错误姿势
this.setState({
counter: this.state.counter + this.props.increment,
});
// 正确姿势
this.setState((state, props) => ({
counter: state.counter + props.increment
}));
这里主要罗列了React社区提出了一些最佳实践方式:
状态提升(Lifting State Up): 当多个组件共享状态时,将状态提升到这些组件的最近共同父组件是一种良好的实践。这样可以避免状态的不一致性和复杂性。
例如一个简单的购物车应用。我们将创建两个组件:ProductList 和 ShoppingCart。ProductList 显示可供购买的商品列表,而 ShoppingCart 显示已选商品和计算总价。我们将使用状态提升来管理选购商品的状态。那么我们可以考虑如下代码:
在这个例子中,ProductList 组件管理商品列表和已选商品的状态。当用户点击 “Add to Cart” 按钮时,通过回调函数 handleProductSelect 更新状态,并将选购的商品传递给 ShoppingCart 组件。通过这种方式,ShoppingCart 组件通过 props 获取所需的状态,并显示已选商品和计算总价。这样两个组件通过 state 提升的方式共享了选购商品的状态。
// 商品列表组件
class ProductList extends React.Component {
state = {
products: [
{ id: 1, name: 'Product A', price: 20 },
{ id: 2, name: 'Product B', price: 30 },
{ id: 3, name: 'Product C', price: 40 }
],
selectedProducts: []
};
handleProductSelect = (productId) => {
const selectedProduct = this.state.products.find(product => product.id === productId);
this.setState(prevState => ({
selectedProducts: [...prevState.selectedProducts, selectedProduct]
}));
}
render() {
return (
<div>
<h2>Product List</h2>
<ul>
{this.state.products.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
<button onClick={() => this.handleProductSelect(product.id)}>Add to Cart</button>
</li>
))}
</ul>
<ShoppingCart selectedProducts={this.state.selectedProducts} />
</div>
);
}
}
// 购物车组件
class ShoppingCart extends React.Component {
calculateTotalPrice = () => {
return this.props.selectedProducts.reduce((total, product) => total + product.price, 0);
}
render() {
return (
<div>
<h2>Shopping Cart</h2>
<ul>
{this.props.selectedProducts.map(product => (
<li key={product.id}>
{product.name} - ${product.price}
</li>
))}
</ul>
<p>Total Price: ${this.calculateTotalPrice()}</p>
</div>
);
}
}
// 应用根组件
class App extends React.Component {
render() {
return (
<div>
<ProductList />
</div>
);
}
}
用状态容器库: 对于大型应用,可以考虑使用状态容器库(如Redux、MobX)来集中管理应用的状态。这样可以更好地组织和分离关注点,使得应用的状态变得更加可维护。
关于状态容器库,其实就像Java里封装好的函数方法或者jar包。在前端开发中,状态容器通常用于管理全局状态,使得多个组件能够方便地访问和共享相同的状态数据。以Redux为例子,它通过单一的不可变状态树来管理整个应用的状态,并使用纯函数来执行状态的变更。