状态提升
状态提升(state lifting)是指将多个组件中相同的状态提升到它们最近的共同父组件中,以便这些组件共享这个状态。这种技术可以减少重复代码,并使组件之间的通信更加容易和可预测。
当我们需要在页面上展示货币的汇率转化时,就可以考虑使用状态提升来解决。
假设我们有两个组件:CurrencyInput和ExchangeRateDisplay,其中CurrencyInput用于输入金额,ExchangeRateDisplay用于显示汇率和计算出的转换后的金额。
我们可以将输入金额和当前汇率这两个状态抽象出来,放到共同的父组件中进行管理。具体代码如下:
import React, { useState } from 'react';
function CurrencyInput(props) {
const { currency, amount, onAmountChange } = props;
return (
<div>
<label>{currency}: </label>
<input type="number" value={amount} onChange={onAmountChange} />
</div>
);
}
function ExchangeRateDisplay(props) {
const { fromCurrency, toCurrency, exchangeRate, amount } = props;
const convertedAmount = amount * exchangeRate;
return (
<div>
<p>Exchange rate: {fromCurrency} to {toCurrency} is {exchangeRate}</p>
<p>{amount} {fromCurrency} is equivalent to {convertedAmount} {toCurrency}</p>
</div>
);
}
function CurrencyConverter() {
const [fromAmount, setFromAmount] = useState(0);
const [toAmount, setToAmount] = useState(0);
const [exchangeRate, setExchangeRate] = useState(1.2); // 默认汇率是 1.2
function handleFromAmountChange(event) {
setFromAmount(event.target.value);
}
function handleToAmountChange(event) {
setToAmount(event.target.value);
}
return (
<div>
<h2>Currency Converter</h2>
<CurrencyInput
currency="USD"
amount={fromAmount}
onAmountChange={handleFromAmountChange}
/>
<CurrencyInput
currency="EUR"
amount={toAmount}
onAmountChange={handleToAmountChange}
/>
<ExchangeRateDisplay
fromCurrency="USD"
toCurrency="EUR"
exchangeRate={exchangeRate}
amount={fromAmount}
/>
</div>
);
}
在上面的例子中,我们将fromAmount和toAmount这两个状态提升到了CurrencyConverter组件中,然后通过handleFromAmountChange和handleToAmountChange两个回调函数来更新这两个状态。ExchangeRateDisplay组件也从父组件中接收了fromAmount和exchangeRate两个属性,并且在组件内部进行了计算和展示。
这样做的好处是,我们可以在父组件中对这两个状态进行控制和管理,而不需要将它们分别传递到两个子组件中去。另外,我们也可以方便地通过父组件来控制ExchangeRateDisplay的渲染逻辑,比如在网络请求获取汇率数据时显示一个 Loading 动画。
Vue中的实现
Vue也有类似状态提升的概念,通常称为“组件通信”。这里就不详细介绍了,简单列一下常用的组件通信方式:
- 父子组件通信:父组件可以通过
props将数据传递给子组件,子组件则可以通过emit事件向父组件发送消息。 - 兄弟组件通信:可以使用一个共享的父组件来实现兄弟组件之间的通信,将需要共享的数据放在父组件的
data中,然后通过props传递给两个兄弟组件。 - 跨级组件通信:可以使用Vue提供的
$emit和$on来实现跨级组件的通信,$emit用于触发事件,$on用于监听事件。
组合 & 继承
React 有十分强大的组合模式。我们推荐使用组合而非继承来实现组件间的代码重用
组合
组合是指一种通过将多个组件组合成一个更大的组件的方式来构建应用程序的方法. 从官方文档可知, 组合关系有包含关系和特例关系, 我们分别学习一下
包含关系
一个组件作为另一个组件的子组件来使用,组件之间的包含关系可以使用 props 以及 children 属性来实现。通过 props 传递一个组件到另一个组件中,然后在另一个组件中使用它。在这种情况下,传递的组件可以在父组件的 render 方法中被渲染,从而实现组合的效果。
例如,一个包含了多个按钮的组件可以通过包含多个 Button 组件来实现:
function Button(props) {
return <button>{props.label}</button>;
}
function ButtonPanel(props) {
return (
<div>
<Button label="Button 1" />
<Button label="Button 2" />
<!-- More button... -->
</div>
);
}
在这个示例中,ButtonPanel 组件包含了多个 Button 组件,因此可以将 Button 视为 ButtonPanel 的子组件。在 ButtonPanel 的 render 方法中,多个 Button 组件被渲染在一个 div 元素中,从而实现了组合的效果。
特例关系
特例关系是一种常见的组合模式,其中一个组件被设计为只能接受一个特定的子组件,并且通过该子组件来控制其行为。
还是以具体事物来举个例子:
function ExchangeRateCalculator(props) {
const [currency, setCurrency] = useState('USD');
const [amount, setAmount] = useState(0);
function handleCurrencyChange(e) {
setCurrency(e.target.value);
}
function handleAmountChange(e) {
setAmount(e.target.value);
}
let result = 0;
if (currency === 'USD') {
result = amount * 7.75; // 假设汇率是 1 美元 = 7.75 人民币
} else if (currency === 'JPY') {
result = amount * 0.072; // 假设汇率是 1 日元 = 0.072 人民币
} else if (currency === 'EUR') {
result = amount * 8.82; // 假设汇率是 1 欧元 = 8.82 人民币
}
return (
<div>
<select value={currency} onChange={handleCurrencyChange}>
<option value="USD">美元</option>
<option value="JPY">日元</option>
<option value="EUR">欧元</option>
</select>
<input type="number" value={amount} onChange={handleAmountChange} />
<p>结果:{result.toFixed(2)}</p>
{props.children}
</div>
);
}
function ThemeSelector(props) {
return (
<div>
<h2>请选择主题</h2>
<button onClick={() => props.onThemeChange('light')}>浅色主题</button>
<button onClick={() => props.onThemeChange('dark')}>深色主题</button>
</div>
);
}
function LightThemeCalculator() {
return (
<ExchangeRateCalculator>
<ThemeSelector onThemeChange={console.log} />
<p>这是浅色主题的货币兑换计算器</p>
</ExchangeRateCalculator>
);
}
function DarkThemeCalculator() {
return (
<ExchangeRateCalculator>
<ThemeSelector onThemeChange={console.log} />
<p>这是深色主题的货币兑换计算器</p>
</ExchangeRateCalculator>
);
}
在这个例子中,ExchangeRateCalculator 组件是一个特例组件,它包含了一个 ThemeSelector 组件和一个描述当前主题的文本(在这里用 p 标签表示)作为其子组件。这个例子中,我们在 LightThemeCalculator 和 DarkThemeCalculator 中使用了 ExchangeRateCalculator 组件,它们都具有相同的 ExchangeRateCalculator 和 ThemeSelector 子组件,但它们可以根据需求渲染不同的货币兑换计算器主题,因此我们可以使用组合来复用这些组件。
继承
在 Facebook,我们在成百上千个组件中使用 React。我们并没有发现需要使用继承来构建组件层次的情况。
Props 和组合为你提供了清晰而安全地定制组件外观和行为的灵活方式。注意:组件可以接受任意 props,包括基本数据类型,React 元素以及函数。
如果你想要在组件间复用非 UI 的功能,我们建议将其提取为一个单独的 JavaScript 模块,如函数、对象或者类。组件可以直接引入(import)而无需通过 extend 继承它们。
说人话, 就是可以用, 但不推荐
总结
自此, React核心概念就了解完了, 接下来开始更新React高级特性