在React开发中,父子组件的通讯是常用的需求场景,在 React 中可以通过几种方式实现,包括状态提升、上下文以及状态管理。本文主要讨论状态提升和上下文的用法以及应用场景。
"状态提升"(Lifting State Up)
状态提升指的是将局部状态从子组件移动到更高级别的父组件的做法,这样做可以让多个子组件共享并访问相同的状态数据,从而在应用程序中更好的管理数据流和数据同步。如下代码创建了子组件和父组件,子组件中 Input 的 onChange 事件调用 handleChange 方法,handleChange 方法再调用父组件传入的onTemperatureChange 进行状态更新。
## 子组件
function TemperatureInput({ temperature, scale, onTemperatureChange }) {
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function handleChange(e) {
onTemperatureChange(e.target.value);
}
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input type="text" value={temperature} onChange={handleChange} />
</fieldset>
);
}
## 父组件
import React, { useState, useCallback } from 'react';
function Calculator() {
const [temperature, setTemperature] = useState('');
const [scale, setScale] = useState('c');
const handleCelsiusChange = useCallback((temperature) => {
setScale('c');
setTemperature(temperature);
}, []);
const handleFahrenheitChange = useCallback((temperature) => {
setScale('f');
setTemperature(temperature);
}, []);
function tryConvert(temperature, convert) {
const input = parseFloat(temperature);
if (Number.isNaN(input)) return '';
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={handleCelsiusChange}
/>
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={handleFahrenheitChange}
/>
</div>
);
}
export default Calculator;
"上下文"(Context)
上下文允许你在组件树的各个层级之间共享状态,而无需手动传递props。这对于全局数据非常有用,如主题、用户认证状态或应用程序范围的设置。上下文可以在应用程序的高层定义,并且任何组件都可以根据需要访问它,无论它在组件层次结构中的深度如何。
父组件中创建 Context,并通过 Context.Provider 传递到自组件中,子组件中通过 useContext 获取 Context 中传入的值,代码中{ authStatus, setAuthStatus }是传递的值。可以看到在整个组件结构中,如果应用了 Context,子组件使用 useContext Hooks 直接就可以获取到传递的值,而不用层层传递。
## 父组件
import React, { useState, createContext, useContext } from 'react';
const AuthContext = createContext(null);
function App() {
const [authStatus, setAuthStatus] = useState(false);
return (
<AuthContext.Provider value={{ authStatus, setAuthStatus }}>
<div>
<ComponentA />
<ComponentB />
</div>
</AuthContext.Provider>
);
}
## 子组件
function ComponentA() {
const { authStatus, setAuthStatus } = useContext(AuthContext);
return (
<button onClick={() => setAuthStatus(!authStatus)}>
{authStatus ? 'Log Out' : 'Log In'}
</button>
);
}
function ComponentB() {
const { authStatus } = useContext(AuthContext);
return <div>User is {authStatus ? 'logged in' : 'logged out'}</div>;
}
总结
在项目的开发中,何时使用状态提升,何时使用上下文,我们需要根据具体需求,以及组件的复杂性。针对状态状态提升,以下几种情况,我们可以考虑使用:
- 当只有少数几个组件需要访问状态,并且这些组件关系密切(如父子或兄弟组件)时使用。
- 保持数据流简单和明确,使得追踪和调试数据流通过组件的方式更加容易。
状态提升的好处是简单、方便,但是需要层层传递,如果结构复杂就显得比较混乱。
对应使用上下文,可以应用在以下几种场景
- 当状态需要被应用内不同层级的许多组件访问时。
- 用于可以被视为"全局"的数据,如主题、用户设置、身份验证数据等。
- 组件可重用性:通过减少深层组件树的属性传递需求,增强组件的可重用性。这也是 Hooks 精髓。
上下文的优点是减少了属性的传递,使得组件树更清晰、更易于维护。并且,由于组件可以通过上下文而非属性访问所需数据,因此增强了组件的可重用性。缺点是增加了一些开销。