状态提升:使用react经常遇到组件共用状态数据的情况,将这部分共享状态提升到他们最近的父组件当中进行管理。
创建一个温度计算器来计算水是否会在给定的温度下烧开。
首先创建一个名为BoilingVerdict 的组件。他会接受 celsius 这个温度变量最为它的props属性,最后根据温度判断返回内容:
function BoilingVerdict(props){
if(props.celsius >=100){
return <p>水会烧开</p>;
}
return <p>水不会烧开</p>;
}
接下来我们写一个名为Calculator 的组件。它会渲染一个<input>来接受用户输入,然后将输入的温度保存在this.state.temperature 当中。
之后,根据输入的值渲染出BoilingVerdict 组件。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {temperature: ''};
}
handleChange(e) {
this.setState({temperature: e.target.value});
}
render() {
const temperature = this.state.temperature;
return (
<fieldset>
<legend>输入一个摄氏温度</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
添加第二个输入框:
在提供摄氏度的基础上,再提供一个华氏温度输入。
我们从Calculator 组件中抽离一个TemperatureInput组件,加一个值为c或者f的表示温度当我scale属性
const scaleNames = {
c:'Celsius',
f:'Fahrenheit'
};
class TemperatureInput extends React.Component{
constructor(props){
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {Temperature:''};
}
handleChange(e){
this.setState({Temperature:e.target.value});
}
render(){
const Temperature = this.state.Temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>enter Temperature in {scaleNames[scale]}</legend>
<input value={Temperature} onChange = {this.handleChange}/>
</fieldset>
);
}
}
然后,我那对Calculator 稍作修改,来渲染两个不同的温度输入框
class Calculator extends React.component{
render(){
return (
<div>
<Temperature scale = 'c'/>
<Temperature scale = 'f'/>
</div>
)
}
}
现在有了两个输入框,但是其中一个输入时,另一个并不会更新,另外我们此时不能从Calculator 组件中展示BoilingVerdict 的渲染结果。因为现在表示温度的状态数据只存在于TemperatureInput组件当中。
写出转换函数、
首先,我们写两个可以将摄氏度和华氏度相互转换的函数。
function toCelsius(fahrenheit){
return (fahrenheit-32)*5/9;
}
function toFahrenheit(celsius){
return (celsius*9/5)+32
}
我们还需要一个转换函数(字符串):
取到输出小数点后三位,当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();
}
状态提升
到这一步为止,两个TemperatureInput组件都在自己的state里面单独保存数据。
但是我们想要这两个输入框保持同步。
在react中 ,状态分析是通过将state数据提升至离需要这些数据的组件的父组件来完成的。这就是所谓的状态表提升。
我们会将TemperatureInput 组件自身保存的state 移到Calculator中,
如果Claculator 组件拥有了提升上来共享的状态数据,那他就会成为两个温度输入组件的“数据源”,它会传递给下面温度输入组件一致的数据。由于两个TemperatureInput 温度组件的props属性都是来源于共同的父组件Calculator,
他们的数据也会保持同步。
首先,我们在TemperatureInput 组件中将this.state.temperature 替换为this.props.temperature.从现在开始,我们假定this.props.temperature属性已经存在了,不过之后仍然需要将数据从Calculator组件中传进去
render(){
//之前的代码 const Temperature = this.state.Temperature;
const Temperature = this.props.Temperature;
props 是只读的,所以现在temperature 变量作为props从父组件传递下来的,TemperatureInput 组件失去了控制权。
在react中,这个问题通常让组件“受控”来解决的。自定义组件 TemperatureInput
也能接受来自 Calculator
父组件的 temperature
变量和 onTemperatureChange
方法作为props属性值。
调用 this.props.onTemperatureChange
方法就可以更新temperature的值。
handleChange(e){
//之前的代码: this.setState({Temperature:e.target.value});
this.props.onTemperatureChange(e.target.value);
}
当我们想要响应数据改变时,使用父组件提供的 this.props.onTemperatureChange()
而不是this.setState()
方法:
代码:
//用于 显示
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
//tryConvert 的 convert(input)
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
//为了取三位小数 然后转换字符串
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();
}
//BoilingVerdict组件
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
//TemperatureInput 组件
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
handleChange(e) {
//关键点 props调用父组件 e.target.value 修改值
this.props.onTemperatureChange(e.target.value);
}
//渲染值,当输入框的值改变时 首先调用的是这里的onChange
render() {
const temperature = this.props.temperature;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={temperature}
onChange={this.handleChange} />
</fieldset>
);
}
}
//父组件 Calculator
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
this.state = {temperature: '', scale: 'c'};
}
handleCelsiusChange(temperature) {
this.setState({scale: 'c', temperature});
}
handleFahrenheitChange(temperature) {
this.setState({scale: 'f', temperature});
}
render() {
const scale = this.state.scale;
const temperature = this.state.temperature;
const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;
return (
<div>
<TemperatureInput
scale="c"
temperature={celsius}
onTemperatureChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
temperature={fahrenheit}
onTemperatureChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
基本调用过程:
1、React会检测到DOM元素的onchange事件,然后进行响应,先找到了TemperatureInput的handleChange 函数
2、handleChange函数调用了this.props.onTemperatureChange,由于我们进行bind绑定,React就找到了父组件Calculator
3、执行this.props.onTemperatureChange函数时,改变了父组件的state状态,即调用了this.setState()
4、React检测到父组件Calculator的状态变化,然后调用它的render函数,然后render函数又调用了TemperatureInput,更新了视图
技术点学习:
1.请看Calculator中的render()中有个tryConvert(temperature, toCelsius) toCelsius函数的名称,也就是toCelsius当做对象传入
2.请看Calculator中handleCelsiusChange(temperature)设置值 this.setState({scale: 'c', temperature}); 中temperature本应该是键值对的形式,直接更新temperature值
3.观察temperature值的传递从
a.TemperatureInput 中handleChange()中 this.props.onTemperatureChange(e.target.value);
b.Calculator 中render()中<TemperatureInput scale="c" temperature={celsius} onTemperatureChange={this.handleCelsiusChange} />
c.Calculator 中handleCelsiusChange(temperature) this.setState({scale: 'c', temperature});
d.temperature转换成celsius或fahrenheit值
e.TemperatureInput 中render()中 const temperature = this.props.temperature; <input value={temperature} onChange={this.handleChange} />
4. Number.isNaN(input)判断是不是数字
5. ===表示 类型 和 值都相等
6. Math.round(output * 1000) / 1000;保留三个小数
7. parseFloat(celsius)将值转换为float的值
小小的温度案例,知识点还是挺多的,写久了JavaScript1,react的思想还是要慢慢的培养。
2018.10.29 用友产业园