写在前边
虽然是官网上的例子,但整体认真读一遍上手练一遍,还是收获满满,在学习过程还是多实践一些比较重要,加油呀,少年们!!
状态提升
遇到几个组件需要共用状态数据的情况,最好将这部分共享的状态提升至他们最近的父组件当中进行管理。
以一个实际例子展开。
需求是:开始呢我们会创建一个名为BoilingVerdict
的组件,他会接受celsius
这个温度变量作为他的props属性判断谁会不会开,接下来我们写一个名为 Calculator
的组件。它会渲染一个 <input>
来接受用户输入,然后将输入的温度值保存在 this.state.temperature
当中。
之后呢,它会根据输入的值渲染出 BoilingVerdict
组件。
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
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>Enter temperature in Celsius:</legend>
<input
value={temperature}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(temperature)} />
</fieldset>
);
}
}
ReactDOM.render(
<Calculator />,
document.getElementById('root')
);
运行结果如图所示
添加第二个输入框,在输入的摄氏温度的基础上,添加一个华氏温度输入,并保持他们的同步
- 通过从
calculator
组件中抽离一个TemperatureInput组件出来给他添加一个值为c或f表示温度单位的属性,如下:
<!--这里-->
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>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
但是此时一个输入框输入另一个并不会更新,而且现在不能从
Calculator
组件中展示BoilingVerdict
的渲染效果
写出转换函数
- 首先摄氏温度和华氏温度互相转换的函数
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
}
function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
- 上边只是单纯转换数字,
tryConvert
函数接受两个参数,一个是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中独立保存数据
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;
// ...
但是我们想要的是这两个输入能够保持同步,当我们输入摄氏温度时华氏度这个框应该显示更新后的温度,反之亦然,如何做? 状态提升
- 首先我们在
TemperatureInput
组件中将this.state.temperature
替换为this.props.temperature
。现在,先假定this.props.temperature
值已经存在,将来我们需要从Calculator
组件中传入。
render() {
// 之前的代码: const temperature = this.state.temperature;
const temperature = this.props.temperature;
// ...
props是只读的,而之前temperature
变量是保存在自身的state中的,TemperatureInput组件只需要调用this.setState()就可以改变它的值,但是现在temperature是作为prop从父组件传递下来的,也就是说TemperatureInput组件是没有控制权的
- 之后,如果TemperatureInput组件希望更新温度时,就会调用this.props.onTemperatureChange组件
handleChange(e) {
// 之前的代码: this.setState({temperature: e.target.value});
this.props.onTemperatureChange(e.target.value);
// ...
- 转向Calculator组件,将会保存当前输入的temperature和scale值到局部的state中 这样就可以保存最新输入的温度和单位,计算出另一个框中的值,如下所示:
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>
);
}
}
现在无论你编辑那一个输入框,另一个框总是基于这个值显示计算结果。
重新梳理整个过程
- React在DOM的
<input>
上调用被指定为onChange
的函数。在本例中,是TemperatureInput
组件上的handleChange
函数。 TemperatureInput
组件的handleChange
函数会用新输入值调用this.props.onTemperatureChange()
函数。输入框组件的属性,包括onTemperatureChange
都是由父组件Calculator
提供的。- 当最开始渲染时,
Calculator
组件的handleCelsiusChange
方法被指定给摄氏温度输入组件TemperatureInput
的onTemperatureChange
方法,同时把handleFahrenheitChange
方法指定给华氏输入组件TemperatureInput
的onTemperatureChange
方法。所以Calculator
组件的两个方法都会在相应输入框被编辑时被调用。 - 在这些方法内部,
Calculator
组件会使用编辑输入的新值和当前输入框的温度单位来让React调用this.setState()
方法来重渲染自身。 - React调用
Calculator
组件的render
方法来识别UI界面的样子。基于当前值和激活的温度单位,两个输入框的值会被重新计算。温度转换就是在这里被执行的。 - React使用由
Calculator
指定的新props来调用各个TemperatureInput
组件的render
方法,React也会识别出子组件的UI界面。 - React调用
BoilingVerdict
组件的render
方法,传递摄氏温度作为它的属性。 - React DOM 会用沸腾裁决更新DOM来匹配渴望的输入值。我们编辑的输入框获取新值,而另一个输入框则用经过转换的温度值进行更新。
完整代码如下:
const scaleNames = {
c: 'Celsius',
f: 'Fahrenheit'
};
function toCelsius(fahre){
return (fahre - 32) * 5 / 9;
}
function toFahrenheit(celsius){
return (celsius * 9 / 5) + 32;
console.log("kkk")
}
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 BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>水会烧开</p>;
}
return <p>水不会烧开</p>;
}
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});
this.props.onTemperatureChange(e.target.value);
}
render() {
// const temperature = this.state.temperature;
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>
);
}
}
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')
);
可以使用react开发工具检查props属性,并且可以查看组件树,追踪代码源头