web前端高级React - React从入门到进阶之组件的状态提升

系列文章目录

第一章:React从入门到进阶之初识React
第一章:React从入门到进阶之JSX简介
第三章:React从入门到进阶之元素渲染
第四章:React从入门到进阶之JSX虚拟DOM渲染为真实DOM的原理和步骤
第五章:React从入门到进阶之组件化开发及Props属性传值
第六章:React从入门到进阶之state及组件的生命周期
第七章:React从入门到进阶之React事件处理
第八章:React从入门到进阶之React条件渲染
第九章:React从入门到进阶之React中的列表与key
第十章:React从入门到进阶之表单及受控组件和非受控组件
第十一章:React从入门到进阶之组件的状态提升

什么是组件的状态提升

  • 什么是组件的状态提升呢,为什么需要状态提升?我们先来思考一个问题:
    • 在前面的学习中,我们知道如果一个父组件想要给子组件传递一些值,可以通过props属性的方式进行传递
    • 而如果而子组件想要给父组件传值的话可以通过调用父组件中的方法的方式进行传递(父组件通过props把方法传给子组件,然后子组件在拿到方法后执行该方法并传递参数)
    • 父传子和子传父都有办法实现了,那么如果说兄弟组件之间也想互相传值(或者说共享数据)该如果实现呢,通过props显然是无法实现的。这个时候状态提升就派上用场了
  • 通过上面的分析我们知道了,原来状态提升可以实现兄弟组件间的数据共享。那么到底什么是状态提升,或者说如何实现状态提升,它是如何能让兄弟组件间的数据共享呢
    • 其实很简单,所谓的状态提升就是:在React中将多个组件中需要共享的state向上移动到它们的最近的共同父组件中,便可以实现共享state。
    • 举个简单的例子,比如现在有A、B、C三个组件,其中A是B和C共同的父组件,在B组件中有自己的state管理着B的数据,同样在C组件中也有C自己的state管理着C的数据,那么现在问题是:如果想要B和C之间实现数据共享,那么这个时候就需要把需要共享的state提升至它们共同的父组件A中,然后再通过props分别传递给B和C这样就简单的实现了兄弟组件间的数据共享了。也就是所谓的状态提升
  • 下面我们以一个烧水的案例来展示一下状态提升的运作过程
  • 首先我们创建一个类组件BoilingWater,这个组件里有控制着水温temperture的state,还有一个用于用户输入温度的文本框来模拟温度的变化
  • 然后再添加一个函数组件ShowTemperture,它接收temperture温度作为一个props用于显示当前水温(摄氏度)。

下面我们来完成以下这个小案例的代码

import React from 'react';

function ShowTemperture(props){
    
    
	if(props.temperture >= 100){
    
    
		return <p>水烧开了,请享用!<p>
	}
	return <p>正在加热中,当前水温是:{
    
    props.temperture}℃,请稍后片刻!<p>
}

class BoilingWater extends React.Component{
    
    
	constructor(props){
    
    
		super(props);
		this.state = {
    
    
			temperture:0
		}
	}
	changeTemperture = (e)=>{
    
    
		this.setState({
    
    
			temperture: e.target.value
		});
	}
	render(){
    
    
		return <>
			<ShowTemperture temperture={
    
    this.state.temperture}></ShowTemperture>
			<input type='text' value={
    
    this.state.temperture} onChange={
    
    this.changeTemperture } />
		</>
	}
}

以上代码就实现了一个简单的烧水功能。但是目前只能是控制和显示摄氏度,现在我们想要再加一个华氏度输入框,并且还想让两个输入框的数据能够同步共享。

  • 接着上面的例子,我们先从BoilingWater组件中抽离出TempertureInput组件,然后为其添加一个type 的prop,type的值为c或f
const typeNames = {
    
    
  c: '摄氏度',
  f: '华氏度'
};

class TempertureInput extends React.Component {
    
    
  constructor(props) {
    
    
    super(props);
    this.state = {
    
    temperature: ''};
  }

  changeTemperture = (e)=>{
    
    
		this.setState({
    
    
			temperture: e.target.value
		});
	}

  render() {
    
    
    const temperture = this.state.temperture;
    const scale = this.props.type;
    return (<>
     	{
    
    typeNames[scale]}:
        <input value={
    
    temperture}
               onChange={
    
    this.changeTemperture} />
      </>
    );
  }
}

然后我们现在可以修改BoilingWater组件,让它渲染两个独立的温度输入框组件:


class BoilingWater extends React.Component{
    
    	
	render(){
    
    
		return <>
			<ShowTemperture temperture={
    
    this.state.temperture}></ShowTemperture>
			<TempertureInput type='c' />
			<TempertureInput type='f' />
		</>
	}
}

上面代码中我们已经添加了两个输入框,但是当我们运行时发现,在一个框中输入温度时,另一个并不会跟着变化,这显然没有实现我们想要的效果,并且我们也不能通过ShowTemperture组件看到实时温度结果,因为ShowTemperture并不知道隐藏在TempertureInput组件内部的当前温度是多少。
接下来我们就用上面提到的状态提升来继续完善我们的小案例

状态提升

  • 到目前为止,两个 TempertureInput 组件均在各自内部的 state 中相互独立地保存着各自的数据。
  • 然而,我们希望两个输入框内的数值彼此能够同步。当我们更新摄氏度输入框内的数值时,华氏度输入框内应当显示转换后的华氏温度,反之亦然。
  • 下面我们将TempertureInput 组件中的state向上移动到它的父组件BoilingWater中去,这样如果BoilingWater组件中拥有了共享的state,它将成为两个温度输入框中当前温度的数据源。它就能够使得两个温度输入框的数值彼此保持一致。由于两个 TempertureInput 组件的 props 均来自共同的父组件 BoilingWater,因此两个输入框中的内容将始终保持一致
  • 继续修改我们的代码
    • 首先我们将 TempertureInput 组件中的 this.state.temperture 替换为 this.props.temperture。现在,我们先假定 this.props.temperature 已经存在,后面我们需要通过 BoilingWater组件将其以props的方式传入
    • 但是我们知道props是只读的不能直接修改。在我们修改代码前temperture 存在于 TempertureInput 组件的 state 中的,这样组件通过调用this.setState() 便可修改它。然而,现在的temperture 是由父组件传入的 prop,TempertureInput 组件便失去了对它的控制权。
    • 这时,我们就需要让自定义的TempertureInput 组件接收 temperture 和 onTempertureChange 这两个来自父组件 BoilingWater的 props,前者(temperture)是用于接收父组件传过来的温度值,后者(onTempertureChange)则是通过父组件通过props传进来的函数进而来修改父组件中的温度值
    • 现在,当 TempertureInput 组件想更新温度时,需调用 this.props.onTempertureChange 来更新它
  • 完善后的代码如下:
const typeNames = {
    
    
  c: '摄氏度',
  f: '华氏度'
};

class TempertureInput extends React.Component {
    
    
  constructor(props) {
    
    
    super(props);
    this.state = {
    
    temperature: ''};
  }

  changeTemperture = (e)=>{
    
    
		this.props.onTempertureChange(e.target.value)
	}

  render() {
    
    
    const temperture = this.props.temperture;
    const scale = this.props.type;
    return (<>
     	{
    
    typeNames[scale]}:
        <input value={
    
    temperture}
               onChange={
    
    this.changeTemperture} />
      </>
    );
  }
}

接下来,我们继续完善我们的父组件BoilingWater

  • 在父组件中我们hui会把当前输入的temperture和type保存在组件内部的state中。这个 state 就是从两个输入框组件中“提升”而来的,并且它将用作两个输入框组件的共同“数据源”。这是我们为了渲染两个输入框所需要的所有数据的最小表示。
  • 现在当我们在摄氏度输入框中输入100时,BoilingWater中的state将会是{temperture: 100, type:‘c’},如果我们在华氏度中输入212时,BoilingWater中的state将会是{temperture: 212, type:‘f’}
  • 然后我们就可以根据当前的温度类型和当前的温度值换算出另一个类型的温度了。
  • 由于两个输入框中的数值都是由同一个 state 计算而来,因此它们始终保持同步,这样就实现了兄弟组件间的数据共享了。

BoilingWater组件完整代码如下:

class BoilingWaterextends React.Component {
    
    
  constructor(props) {
    
    
    super(props);   
    this.state = {
    
    temperture: '', type: 'c'};
  }

  handleCelsiusChange = (temperture) => {
    
    
    this.setState({
    
    type: 'c', temperture});
  }

  handleFahrenheitChange = (temperture) => {
    
    
    this.setState({
    
    type: 'f', temperture});
  }

  render() {
    
    
    const type = this.state.type;
    const temperture = this.state.temperture;
    const celsius = type === 'f' ? (temperture - 32) * 5 / 9 : temperture;
    const fahrenheit = type === 'c' ? (temperture * 9 / 5) + 32 : temperture;

    return (
      <div>
      	<ShowTemperture temperture={
    
    celsius}></ShowTemperture>
		<TempertureInput type='c' temperture={
    
    celsius} onTempertureChange={
    
    this.handleCelsiusChange} />
		<TempertureInput type='f' temperture={
    
    fahrenheit} onTempertureChange={
    
    this.handleFahrenheitChange}/>           
      </div>
    );
  }
}

到此,我们就完成了我们的小案例,现在无论我们编辑哪个输入框中的内容,BoilingWater组件中的 this.state.temperture 和 this.state.type均会被更新。其中一个输入框保留用户的输入并取值,另一个输入框始终基于这个值显示转换后的结果。
让我们来重新梳理一下当你对输入框内容进行编辑时会发生些什么:

  • React 会调用 DOM 中 < input> 的 onChange 方法。在本实例中,它是 TempertureInput 组件的 changeTemperture方法
  • TempertureInput 组件中的 changeTemperture方法会调用 this.props.onTemperatureChange(),并传入新输入的值作为参数。其 props 诸如 onTempertureChange 之类,均由父组件 BoilingWater提供
  • 起初渲染时,用于摄氏度输入的子组件 TempertureInput 中的 onTemperatureChange 方法与 BoilingWater组件中的 handleCelsiusChange 方法相同,而,用于华氏度输入的子组件 TempertureInput 中的 onTemperatureChange 方法与 BoilingWater组件中的 handleFahrenheitChange 方法相同。因此,无论哪个输入框被编辑都会调用 BoilingWater组件中对应的方法。
  • 在这些方法内部,BoilingWater组件通过使用新的输入值与当前输入框对应的温度计量单位来调用 this.setState() 进而请求 React 重新渲染自己本身。
  • React 调用 BoilingWater组件的 render 方法得到组件的 UI 呈现。温度转换在这时进行,两个输入框中的数值通过当前输入温度和其计量单位来重新计算获得。
  • React 使用 BoilingWater组件提供的新 props 分别调用两个 TempertureInput 子组件的 render 方法来获取子组件的 UI 呈现。
  • React 调用 ShowTemperture组件,并将摄氏温度值以组件 props 方式传入。
  • React DOM 根据输入值匹配水是否沸腾,并将结果更新至 DOM。我们刚刚编辑的输入框接收其当前值,另一个输入框内容更新为转换后的温度值。

本章内容就介绍到这里
下一章节中我们将给大家继续讲解React中的组合

猜你喜欢

转载自blog.csdn.net/lixiaosenlin/article/details/112684837