react组件

本文详细阐述了React组件开发中的关键概念,包括组件设计原则、props与state的区别与使用、数据流向的单向性、子组件与父组件的交互、生命周期函数的执行顺序,以及高级应用如状态重置和组件纯粹原则。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一,组件使用的核心理念

定义组件时,应尽量保持组件功能的单一性,也就是一个组件只做一件事。

二,组件的两种模型数据

2.1 props

作用(传递数据):props的主要用作是父组件向子组件传递数据,来定制子组件的展示

实现:通过向子组件设置props属性={属性值} 实现;子组件在入参处接收属性,同时可以设置默认值(eg:function Avatar({ size = 100 }) {...})

注意:props子组件接受属性的默认值,仅在缺少props和设置 props属性={undefined}时才生效,如果设置 props属性={null}、props属性={0}这种假值不会生效

扩展:接收的参数可以是展开语法...props传递所有的props数据(eg:<Avatar {...props} />),但是不建议过度使用。

2.2 state

作用(修改数据):state的主要用作是在内存中保存信息,用来后续追踪与更新数据

实现:通过内置的HOOK函数useState 实现

注意

(1)state的使用需要尽量保持他不自我重复(例如一个数组列表,他即用于A处展示,也用于B处操作修改,尽量定义一次,不要copy多个)

(2)每一次渲染state的值都是固定的,修改后的state值下一次渲染才改变(react会等到事件处理函数中的所有代码都都运行完毕再处理你的state更新。例如:

export default function Counter() {
  const [number, setNumber] = useState(0); // number初始值为0

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1); // 修改下一次渲染number的值,为0 + 1
        alert(number); // 弹出0,下一次渲染number才会变成1
      }}>+3</button>
    </>
  )
}

扩展:如果想在下次渲染前多次更新同一个state(不常用),可以像setNumber(n => n + 1) 这样传入一个state的更新函数(react会将该函数添加到队列中,以便在事件处理函数中的所有其他代码运行后进行处理),而不是像 setNumber(number + 1)这样传入下一个state 值

三,state的反向数据流

由于react是单向数据流,通常数据是右上到下传入,但子组件修改state数据后,需要告知父组件更新state。他的实现方式与vue的$emit异曲同工,在子组件的引用上处自定义事件(onXXXChange)事件中可以调用useState的set方法来修改值;子组件通过onChange事件来触发自定义事件的调用

示例:

// 父组件
function FilterableProductTable({ products }) {
    const [filterText, setFilterText] = useState(''); // 定义state
    // 定义子组件透传的自定义change事件onFilterTextChange
    return (
        <div>
          <SearchBar
            filterText={filterText}
            onFilterTextChange={setFilterText} />
        </div>
    )
}

// 子组件
function SearchBar ({filterText}) {
    return (<input
          type="text"
          value={filterText}
          placeholder="Search..."
          onChange={(e) => onFilterTextChange(e.target.value)} />
    )
}

四,props传递子组件

有时候可能用组件嵌套组件,例如vue中的slot功能。react中依然用props,与普通属性不同的是:

(1)定义被嵌套的组件时,props入参接收一个children,并将其包裹在div中渲染(可与vue的slot对比理解,react中自己封装被slot的组件)

// 在Card组件中传入一个子组件,则接收参数children
function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}

(2)被嵌套的组件引用处,直接包裹要嵌套的目标组件(可与vue的slot对比理解,react中少了一个slot参数)

export default function Profile() {
 // 要把Avatar组件嵌套入Card组件中
  return (
    <Card>
      <Avatar
        size={100}
        person={{ 
          name: 'Katsuko Saruhashi',
          imageId: 'YfeOqp2'
        }}
      />
    </Card>
  );
}

五,组件纯粹原则

开发组件要尽量保证组件的纯粹原则,这样可以安全的缓存他们也可以避免以后迭代带来的各种Bug。即保证组件像一个数学公式,只要输入一样,输出就永远一致。类似于以下这种示例,就是不纯粹的组件:

function Cup() {
  guest = guest + 1;
  return <h2>Tea cup for guest #{guest}</h2>;
}

六,类(class)组件的生命周期

概念:组件实例从被创建到被销毁的过程称为组件的生命周期。这里大概了解,详细了解与图解可参考React 生命周期详解 - 掘金

class类组件中,每一次由状态改变导致页面视图的改变,都经过了两个阶段:render阶段和commit阶段。由class类组件创建的实例拥有的生命周期,在render阶段执行他的render函数,和DOM节点的diff算法,找出需要改变的DOM操作在commit阶段将要改变的DOM操作提交至视图中

首次渲染页面时,会调用mount相关的生命周期钩子

之后的页面渲染中,会调用Update相关的生命周期钩子;

因此mount相关的生命周期钩子只调用一次。

render阶段(初始化阶段)

  • constructor:构造函数(mount相关,只执行一次)。在初始化实例的时候调用,返回一个组件实例。

                          应用场景:通常用来初始化组件的state

  • getDerivedStateFromProps:(不常用)(mount、update)从更新后的props中获取State,它让组件在 props发生改变时更新它自身的内部state。
  • shouldComponentUpdate:(update)判断一个组件是否应该更新。在组件准备更新之前调用。它接收两个参数,nextProps和nextState,即下一次更新的props和state。

                          应用场景:通常用来做性能优化

  • render组件中唯一必须实现的方法(mount、update)。它的返回值将作为页面渲染的视图。返回类型有:

                                        (1)JSX语法的React 元素

                                        (2)数组返回的多个元素

                                        (3)字符串、数值文本节点

                                        (4)boolean类型或者null什么都不渲染

                                        (5)Portals:渲染子节点到不同的子树中

commit阶段(运行中阶段)

  • componentDidMount:(mount相关,只执行一次)将组件对应的DOM插入DOM 树中之后、浏览器更新视图之前调用

                  应用场景:通常用做依赖DOM的初始化操作、发送网络请求、订阅。

  • getSnapshotBeforeUpdate:(update)在最近一次渲染提交至DOM树之前执行。

                  应用场景:此时DOM树还未改变,可以获取DOM改变前的信息

  • componentDidUpdate:(update)在组件更新后立即调用。首次渲染不会调用该方法,但是执行时机与componentDidMount一致。他接收三个参数,分别是 prevProps、prevState、snapshot,即前一个状态的props,前一个状态的state、getSnapshotBeforeUpdate的返回值。

                 应用场景:可以对DOM进行操作,也可以进行网络请求。

  • componentWillUnmount:(Unmount)在组件卸载以及销毁之前调用。

                 应用场景:通常用来执行组件的清理操作。例如:清除 timer、取消网络请求、清除订阅等。

注意

  • shouldComponentUpdate钩子如果返回false,则render 阶段后续生命周期钩子(render方法)不会执行
  • getDerivedStateFromProps钩子是静态方法,因此不能使用this获取到组件实例

七,父子组件的生命周期执行顺序

7.1 首次渲染时的执行顺序

示例组件:父组件A、子组件B、子组件C,首次渲染

(1)依次执行父组件A的render阶段的mount相关的钩子constructor、getDerivedStateFromPro

ps、render

(2)依次执行子组件B的render阶段的mount相关的钩子(...同上)

(3)依次执行子组件C的render阶段的mount相关的钩子(...同上)

(4)执行子组件B的commit阶段update相关的钩子componentDidMount

(4)执行子组件C的commit阶段update相关的钩子componentDidMount

(4)执行父组件A的commit阶段update相关的钩子componentDidMount

7.2 子组件状态改变的执行顺序

示例组件:父组件A、子组件B、子组件C。  改变B子组件的某个状态

(1)执行B子组件的钩子:getDerivedStateFromProps

(2)执行B子组件的update相关的钩子:shouldComponentUpdate

(3)执行B子组件的钩子:render

(4)执行B子组件的update相关的钩子:getSnapshotBeforeUpdate

(5)执行B子组件的update相关的钩子:componentDidUpdate

说明:子组件的状态改变,只会执行当前子组件的生命周期函数

7.3 父组件状态改变的执行顺序

示例组件:父组件A、子组件B、子组件C。  改变A父组件的某个状态

(1)执行父组件render阶段的生命周期钩子:getDerivedStateFromProps、shouldComponentUp

date、render

(2)执行B子组件render阶段的生命周期钩子:getDerivedStateFromProps、shouldComponent

Update、render

(3)执行C子组件render阶段的生命周期钩子:getDerivedStateFromProps、shouldComponent

Update、render

(4)执行B子组件的getSnapshotBeforeUpdate

(5)执行C子组件的getSnapshotBeforeUpdate

(6)执行A父组件的getSnapshotBeforeUpdate

(7)执行B子组件的componentDidUpdate

(8)执行C子组件的componentDidUpdate

(9)执行A父组件的componentDidUpdate

说明:父组件某状态的改变,会将父组件和其所有子组件的部分生命周期钩子都触发执行一遍

八,state的高级应用

  • state保留:在同一个UI位置渲染相同的组件,react会保留这个组件的state值

示例:组件Counter渲染后UI的位置是一样的,只是切换了props属性person的值,因此react会一直保留组件Counter中的state score的值。

// 在同一个UI视图位置通过判断引用两次Counter组件,传入不同的值
export default function Scoreboard() {
  const [isPlayerA, setIsPlayerA] = useState(true);
  return (
    <div>
      {isPlayerA ? (
        <Counter person="Taylor" />
      ) : (
        <Counter person="Sarah" />
      )}
      <button onClick={() => {
        setIsPlayerA(!isPlayerA);
      }}>
        下一位玩家!
      </button>
    </div>
  );
}

// 定义Counter组件,保留state score的值
function Counter({ person }) {
  const [score, setScore] = useState(0);

  return (
    <div>
      <h1>{person} 的分数:{score}</h1>
      <button onClick={() => setScore(score + 1)}>
        加一
      </button>
    </div>
  );
}

  • state重置:综上,如果在相同UI位置渲染不同的组件,react会重置组件的state。包其含所在的所有子组件。例如:
// 相同位置,但是组件不同,一个是div,一个是section
// div或者section重置同样会重置他们的所有子组件Counter
return (
    <div>
      {isFancy ? (
        <div>
          <Counter isFancy={true} /> 
        </div>
      ) : (
        <section>
          <Counter isFancy={false} />
        </section>
      )}
    </div>
)

  • 在相同的位置重置state方法:

(1)通过多个if判断显示在不同位置定义多个组件。可以理解为:isPlayerA为true时,第二个位置是空的,他为false时,第一个位置是空的,因此虽然最终的UI看起来位置一样,但是可以理解为这是两个不同的位置。

// 非if else形式,而是改成了两个if的形式,这样被看做两个组件
return (
    <div>
      {isPlayerA &&
        <Counter person="Taylor" />
      }
      {!isPlayerA &&
        <Counter person="Sarah" />
      }
    </div>
)

(2)通过key属性key不止可以标识列表,他可以标识react中所有的组件!通过key来标识组件在他的父组件中的唯一性,这样即使多个组件渲染在UI的同一个位置,也可以实现state重置。

说明key不是全局唯一的,只是在他父组件的顺序中是唯一的

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妍思码匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值