React文档(十一)提升state
经常有些组件需要映射同一个改变的数据。我们建议将共用的state提升至最近的同一个祖先元素。我们来看看这是怎样运作的。
在这一节中,我们会创建一个温度计算器来计算提供的水温是否足够沸腾。
我们先创建一个叫BoilingVerdict的组件。它接受摄氏度温度为prop,并且打印水温是否足够沸腾:
function BoilingVerdict(props) {
if (props.celsius >= 100) {
return <p>The water would boil.</p>;
}
return <p>The water would not boil.</p>;
}
下一步,我们创建一个Calculator组件。它渲染一个<input>让你输入温度,然后将温度保存在this.state.value里。
另外,它通过当前输入的温度值渲染BoilingVerdict组件。
class Calculator extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
} handleChange(e) {
this.setState({value: e.target.value});
} render() {
const value = this.state.value;
return (
<fieldset>
<legend>Enter temperature in Celsius:</legend>
<input
value={value}
onChange={this.handleChange} />
<BoilingVerdict
celsius={parseFloat(value)} />
</fieldset>
);
}
}
添加第二个输入框
我们新的要求是除了摄氏度的输入之外,我们还提供一个华氏度输入框,并且它们保持同步。
我们可以先从Calculator组件里提取出TemperatureInput组件。并且添加一个新的scale prop给它,可以是“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 = {value: ''};
} handleChange(e) {
this.setState({value: e.target.value});
} render() {
const value = this.state.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
现在来修改Calculator组件来渲染两种单独的温度输入:
class Calculator extends React.Component {
render() {
return (
<div>
<TemperatureInput scale="c" />
<TemperatureInput scale="f" />
</div>
);
}
}
现在我们有两种输入了,但是当你在其中一个输入温度后,另外一个却不会更新。这和我们的要求矛盾了:我们想让它们同步。
我们也不能在Calculator里显示BoilingVerdict。Calculator不知道当前的温度因为温度被隐藏在了TemperatureInput里。
写转换函数
首先,我们会写两个函数去将摄氏度和华氏度互相转换:
function toCelsius(fahrenheit) {
return (fahrenheit - 32) * 5 / 9;
} function toFahrenheit(celsius) {
return (celsius * 9 / 5) + 32;
}
这两个函数转换数字。我们会写另外一个函数,它接收一个字符串值和一个转换函数做为参数并且返回一个字符串。我们用这个函数来根据一个输入来计算另一个输入。
如果最终结果无效它会返回一个空字符串,并且它会将结果四舍五入保存到小数点后第三位:
function tryConvert(value, convert) {
const input = parseFloat(value);
if (Number.isNaN(input)) {
return '';
}
const output = convert(input);
const rounded = Math.round(output * 1000) / 1000;
return rounded.toString();
}
举个例子,tryConvert('abc', toCelsius)返回一个空字符串,tryConvert('10.22', toFahrenheit)返回'50.396'。
提升state
现在,两个TemperatureInput组件都独立地将它们的值保存在本地state里:
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {value: ''};
} handleChange(e) {
this.setState({value: e.target.value});
} render() {
const value = this.state.value;
然而,我们希望这两个输入彼此之间可以同步。当我们更新了摄氏度输入,华氏度输入应该反映出转换后的温度,反之亦然。
在React里,共用的state通过把它传递给组件里最近的共同的祖先来完成。这一步骤叫做“提升state”。我们会从Temperature移除本地state并且把它移到Calculator里。
如果Calcutor拥有了共用的state,它变成了当前两个温度输入的“真正数据源”。它可以指示它们两个都拥有值并且彼此一致。自从两个TemperatureInput组件的props都来自于同一个父亲Calculator组件,那么这两个输入会一直保持同步。
让我们一步一步来看看这是如何运作的。
首先,在temperatureInput组件里,我们会用this.props.value替换this.state.value。目前,让我们假装this.props.value已经存在了,虽然我们在后面将需要把它从Calculator里传递过来:
render() {
// Before: const value = this.state.value;
const value = this.props.value;
我们知道props是只读的。当value在state里,TemperatureInput能够调用this.setState()来改变它。然而,现在value是从父组件传递来并且作为一个prop,TemperatureInput组件无法控制它。
在React里,这样的情况经常通过使组件“受控”来解决。就像<input>DOM元素同时接受一个value和一个onChange属性,因此自定义的TemperatureInput也能从它的父组件Calculator那里同时接受value和onChange。
现在,当TemperatureInput想要更新温度,它就会调用this.props.onChange:
handleChange(e) {
// Before: this.setState({value: e.target.value});
this.props.onChange(e.target.value);
注意自定义组件里的value和onChange属性都没有特殊的含义。虽然这是一个共同的约定,但是我们可以给它们取任意名字。
onChange属性和value属性会一起通过父组件Calculator提供。它将会处理修改它自己的本地state的变化,从而使用新的值重新渲染两个输入。我们将很快看到Calculator的完成。
在深入Calculator组件里的变化之前,让我们先简要重述一下Temperature组件的变化。我们从本地state里移除了它,并且现在读取this.props.value而不是this.state.value。当我们要改变的时候,我们不调用this.setState(),而是调用Calsulator组件提供的this.props.onChange():
class TemperatureInput extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
} handleChange(e) {
this.props.onChange(e.target.value);
} render() {
const value = this.props.value;
const scale = this.props.scale;
return (
<fieldset>
<legend>Enter temperature in {scaleNames[scale]}:</legend>
<input value={value}
onChange={this.handleChange} />
</fieldset>
);
}
}
现在让我们看一看Calculator组件。
我们将要保存当前输入的value和scale在它的state里。这是我们从输入那里提升的state,并且它会像真正源一样为两种输入服务。这是所有我们需要了解的为了渲染两个输入的数据的最小限度的表示。
举个例子,如果我们输入37到摄氏度输入框里,Calculator组件的state会像这样:
{
value: '37',
scale: 'c'
}
{
value: '212',
scale: 'f'
}
我们本应该保存两个输入但是结果发现这不重要了。保存最近改变的输入和它代表的比例单位已经足够了。然后我们可以根据当前的value和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 = {value: '', scale: 'c'};
} handleCelsiusChange(value) {
this.setState({scale: 'c', value});
} handleFahrenheitChange(value) {
this.setState({scale: 'f', value});
} render() {
const scale = this.state.scale;
const value = this.state.value;
const celsius = scale === 'f' ? tryConvert(value, toCelsius) : value;
const fahrenheit = scale === 'c' ? tryConvert(value, toFahrenheit) : value; return (
<div>
<TemperatureInput
scale="c"
value={celsius}
onChange={this.handleCelsiusChange} />
<TemperatureInput
scale="f"
value={fahrenheit}
onChange={this.handleFahrenheitChange} />
<BoilingVerdict
celsius={parseFloat(celsius)} />
</div>
);
}
}
现在,不论你编辑哪一个输入框,Calculator组件里的this.state.value和this.state.scale都会更新。其中获得value的输入保持现状,因此所有用户的输入都被保存,然后另外一个输入的值由已输入的值计算出。
我们简述当编辑一个输入框的时候会发生什么:
- React在DOM元素<input>上调用指定的onChange函数。在我们这个例子下,调用的是TemperatureInput组件的handleChange方法。
- TemperatureInput组件的handleChange方法调用this.props.onChange()带着新的value。它的属性,包括onChange都是父组件Calculator提供的。
- 当它先前渲染的时候,Calculator组件指定摄氏度TemperatureInput组件的onChange就是Calculator组件的handleCelsiusChange方法,并且华氏度TemperatureInput组件的onChange就是Calculator的handleFahrehnheitChange方法。因此这两个Calculator方法被调用取决于哪一个输入框我们编辑了。
- 在这些方法内部,Calculator组件会要求React去重新渲染它自己通过调用this.setState(),传递的值是新输入的温度和当前的比例单位。
- React调用Calculator组件的render方法得知UI应该是什么样子。两个输入框的值都依据当前温度和比例单位重新计算。温度转换就在这里完成。
- React调用单独的TemperatureInput的render方法,传递Calculator指定的新props。它会得知UI的样子。
- React DOM更新UI来匹配输入的值。我们编辑的值会保持原状,另外一个值会通过计算得出,每一个更新都经过同样的步骤所以输入会彼此同步。
学习总结
在一个React应用里应该有一个“单一数据源”来应对任何改变的数据。通常,state是首先被添加到组件里需要它来处理渲染。接着,如果其他组件也需要state,你可以把它提升到最近的共同的祖先组件。想要在不同的组件之间同步state,你需要依赖从上到下的数据流。
将state提升会牵扯到写更多的“引用”代码,相比双向绑定要多得多。但是作为一个好处就是,这样子可以更快的找到和隔离程序中的bug。因为哪个组件保有状态数据,也只有它自己能够操作这些数据,发生bug的范围就被大大地减小了。此外,你可以完成任何自定义的逻辑去丢弃或者转变用户输入。
如果有东西可以既从props也可以从state里获取到,那么它可能不应该出现在state里。举个例子,我们只保存下最后一次编辑的value和scale,而不是去保存celsiusValue和fahrenheitValue的值。另一个输入框的值可以在render()方法里通过计算算出。这样我们可以清除或者运用四舍五入为其他输入域而不丢失精确度。
当你看到UI里有某些东西发生错误了,你可以使用React开发者工具去检查props并且顺着组件树寻找直到你找到更新state的组件。这样你可以跟踪bug到它们的源头。
React文档(十一)提升state的更多相关文章
- React文档(十三)思考React
在我们的看来,React是使用js创建大型快速网站应用的首要方法.它在Facebook和Instagram的使用已经为我们展现了它自己. React的一个很好的地方就在于当你创建应用的时候它使你思考如 ...
- React文档(二十四)高阶组件
高阶组件(HOC)是React里的高级技术为了应对重用组件的逻辑.HOCs本质上不是React API的一部分.它是从React的组合性质中显露出来的模式. 具体来说,一个高阶组件就是一个获取一个组件 ...
- react文档demo实现输入展示搜索结果列表
文档页面地址:https://doc.react-china.org/docs/thinking-in-react.html 该文档只给了具体实现思路,下面是我实现的代码. 初学react,如果有写的 ...
- React文档(十六)refs和DOM
Refs 提供了一种方式,用于访问在 render 方法中创建的 DOM 节点或 React 元素. 在标准的React数据流中,props是使得父组件和子组件之间交互的唯一方式.你通过props重新 ...
- React文档(一)安装
React是一个灵活的可以用于各种不同项目的框架,你可以用它来写新应用,你也可以逐步将它引进已有的代码库而不用重写整个项目. 试用React 如果你想玩一玩React,那么就去CodePen上试一试. ...
- React文档总结
元素渲染 更新元素渲染 计时器例子 function tick(){ const element = ( <div> <h1>Hello, World!</h1> ...
- React文档(七)处理事件
React元素处理事件和DOM元素处理事件很类似.下面是一些语法的不同之处: React事件的命名是用驼峰命名,而不是小写字母. 利用JSX你传递一个函数作为事件处理器,而不是一个字符串. 举个例子, ...
- createDocumentFragment 文档碎片提升dom增删的性能
原理: 操作dom会使得页面进行重新渲染,如果 经常性的对dom就行操作或者一次性操作dom较多,每一次操作都会使页面进行重新渲染,降低页面加载性能. 针对IE9以下,可以使用文档碎片(documen ...
- elasticsearch 父子文档(十一)
说明 需求 一个产品多个区域销售 每个区域有自己的价格, 方式1冗余行,a 产品分别在 area1 area2 area3区域销售 a产品就会生成3条产品数据 搜索id去重就行了,但是问题就是 聚合 ...
随机推荐
- MAVEN_day01 下载与安装及环境变量的配置
一.MAVEN简介 MAVEN是apache组织下的一个开源项目,是使用纯java编写的,之用于管理java工程. 二.MAVEN下载与安装 下载地址:http://maven.apache.org/ ...
- shiro 角色与权限的解读
1.为什么 shiro 有了<角色>后,还要设置<角色权限>呢?(问题) 思考:设置好角色了,那么就代表什么操作都可以执行了吗? 理解:如果上边回答是的话,那么只是<角色 ...
- Mysql导入表信息[Err] 1067 - Invalid default value for '字段名'
修改mysql配置文件 vi /etc/my.cnf //添加以下配置 sql_mode=ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,ERROR_FOR_DIVISI ...
- 《More Accurate Question Answering on Freebase》文献笔记
bast-2015-CIKM CIKM全称是International Conference on Information and Knowledge Management 这篇文章主要采用采用lea ...
- 编译器将"+"转换成了StringBuilder类
MapReduce map100% Reduce 66% 卡死 如果你碰到map100%,reduce 66% 然后程序就貌似停止在这里了,可能是由于在Reduce类里使用了String造成的 根据一 ...
- Java基础(basis)-----关键字final和static的作用
1.关键字final final修饰类:这个类就不能被继承 final修饰方法:不能被重写 final修饰属性:此属性就是一个常量,一旦初始化后,不可再被赋值.习惯 ...
- centos7.5 修改网卡名称
1.修改网卡配置文件中名称信息 vim /etc/sysconfig/network-scripts/ifcfg-ens33 将其中的名称为ens33的改为eth0 ,并将uuid删除以便后面克隆 2 ...
- CSS中一个冒号和两个冒号有什么区别
一个冒号是伪类,两个冒号是伪元素 伪类可以独立于文档的元素来分配样式,且可以分配给任何元素,逻辑上和功能上类类似,但是其是预定义的.不存在于文档树中且表达方式也不同,所以叫伪类.伪元素所控制的内容和一 ...
- Mysql 书写语句时避免出现关键字导致报错 关键字大全
ADD ALL ALTER ANALYZE AND AS ASC ASENSITIVE BEFORE BETWEEN BIGINT BINARY BLOB BOTH BY CALL CASCADE C ...
- Linux tshark抓包
使用tshark进行抓包 注:需要安装wireshar抓包工具 安装:yum -y install wireshark # 可以抓的包 命令:tshark # 抓取mysql查询 命令:tshark ...