React 要点解析 🤔 1.J-ava-S-cript-X-ml 2. const element = 渲染元素 3. 组件和属性 4. 状态和生命周期 5. 处理🖐🏽事件 6. ✅ 条件渲染❌ 7. 列表📝和键🔑 8. 表单 9. 状态提升 10. 组合与继承🥊

2025-06-07

React 详解

要点

🤔

1. J -ava- S -cript- X -ml

2. const element =渲染元素

3. 组件和道具

4. 状态和生命周期

5. 处理🖐🏽事件

6.✅ 条件渲染❌

7. 列表📝和键🔑

8. 表格

9. 提升状态

10. 组合与继承

要点

如果您曾经花过一点时间访问过React网站,您就会读到他们的标语……

用于构建用户界面的 JavaScript 库

UI 和状态管理是 React 致力于为前端开发者解决的主要问题,这也是 React 的宗旨。

在面试前端职位或温习相关概念时,我们经常会在浏览器上打开超过 100 个标签页。我想总结一下在与同行讨论 React 时需要涵盖的要点。

以下概述旨在涵盖 React 的主要概念,理解这些概念对于高效工作非常重要。

这篇文章确实很长,但它更多的是想提供一些参考,让你更好地利用阅读时间。希望你喜欢。

让我们开始吧!🏊🏽‍♂️

在构建 JavaScript 应用程序时,我们希望处理数据。

JS 中的数据通常由原始值构成,包括:

  • 数字
  • 字符串
  • 布尔值
  • 无效的
  • 不明确的
  • 大整数
  • 符号

作为开发者,我们在应用程序的最底层使用这些值。JS 中的这些原始值是不可变的,这意味着它们无法被更改。另一方面,保存这些原始值的变量可以被重新赋值。

作为工程师,尤其是作为对网络一切事物充满好奇的爱好者,这对我们意味着什么?

🤔

我们需要一种方式来管理应用程序的数据,也就是我们收集并提供给用户的信息,从而最大程度地减少我们的麻烦。作为一名工程师,您总是在权衡各种解决方案的利弊,它们的效率是否比可读性和易用性更重要?您会发现这个问题的答案一直在不断变化。

对于以下解释,我将按照 React 自己的开发人员阐述概念的顺序进行,并在此过程中添加额外的示例和分解(🤘🏽)。

收费

永远向前,永不后退!

主要概念

  1. JSX
  2. 渲染元素
  3. 组件和道具
  4. 状态和生命周期方法
  5. 处理事件
  6. 条件渲染
  7. 列表和键
  8. 表格
  9. 提升状态
  10. 组合与继承

1. J -ava- S -cript- X -ml

我们应该始终努力理解基础知识。虽然我个人理解 JSX 的大部分 JavaScript 方面,但我很少接触 XML。所以我非常感兴趣:什么是 XML?

XML是可扩展标记语言 (Extensible Markup Language)的缩写。如果你正在想:“Kurt,XML 听起来很像 HTML”,那么你就猜对了。它们之间关系密切!

“可扩展”部分是由于 XML 允许您(作为开发人员)定义自己的标签,以满足您自己的特定需求。

这方面非常有力,构建 React 的 Facebook 开发人员也意识到了这一点。

好吧,说了这么多,但你更擅长视觉学习。我们来看一些 JSX 代码吧!🔥⋔

JSX

我们上面看到的是什么?

这里我们有所谓的功能组件,或“哑组件”,因为最佳实践是不在这些组件中包含太多逻辑。

我们所拥有的只是一个分配给常量App的匿名箭头函数,然后通过我们的export default App语句将其作为模块导出

我们将进一步研究 React 中的 App.js 文件,但现在要明白,它与位于应用程序目录顶层的 Index.js 文件一起作为主要事实来源。

在我们的匿名箭头函数中,我们返回一个 div 元素。好的,到目前为止一切都很好,我们之前都处理过 div。但是这个 div 里面到底是什么呢?

<PostList /> 👀
这里我们看到了一个野生的 JSX 元素

在应用文件的顶部,我们PostList从一个PostList.js文件导入组件。得益于 ES6 JS 的强大功能,我们能够使用模块导入来引入在其他地方定义的功能。太棒了!

为了获得更彻底的心理图像,让我们看看我们已经抽象出来的逻辑。

帖子列表

PostList 类组件

我们抽象出了 44 行代码!这使得我们在开发应用程序时更容易专注于重要的事情。

JSX 允许我们使用类似 XML 的标签<OurUniqueTag/>来构建我们在 React 中使用的组件和元素。

等一下,我们似乎还没有讨论过组件或元素。

让我们从元素开始,因为组件是由元素构建的!

2. const element =渲染元素

与 JavaScript 语言最低级别的原始值类似,“元素是 React 应用程序的最小构建块”。

咚!咚!咚!

DOM
在我的辩护中,当我搜索 DOM 时,GIPHY 上出现了“速度与激情”

我怎么突然开始说 DOM 了?构建块,一切都是关于构建块的。

DOM代表“DocumentObjectModel”,与图形用户界面一样,它是 HTML 和 XML 的编程接口。

它不是网页,而是网页的一种表现形式,让您可以神奇地挥动开发魔杖🧙🏽‍♂️并更改文档结构、样式和内容。

DOM 用于允许编程语言连接到页面的数据结构是节点和对象。

import ReactDOM from 'react-dom'
ReactDOM.render(
        <App/>,
    document.querySelector('#root')
)

如果您使用 React 进行开发,那么您必须<App />使用 ReactDOM 的渲染方法来包装您的。

为了向用户展示功能强大的酷炫网站,我们必须持续更新 DOM。然而,这些动态更新本身也存在一些 bug。

每次更新时,浏览器必须刷新 CSS、刷新 DOM 节点树,最终刷新正在显示的屏幕。在 React 出现之前,我们需要编写大量的 JavaScript 代码来完成这些繁重的工作,如果不小心,这些繁重的工作就会变得非常明显。

鲁保罗

我和我的朋友第一次花了一周时间编写一堆 JS,结果却发现我们的网页速度慢得要命

我们的 JSX 元素代表 DOM 元素,并在由 ReactDOM.render() 渲染后被解析为网页上的那些元素。

当 React 最初渲染元素时,它也会构建一个代表DOM 或当前树的“树” 。

协调实际上是我们设法在这里保留的一个高级 React 概念。您可以在React 文档中找到更多信息,不过我们会在这里稍微讨论一下。

当比较两棵树的差异时,React 首先比较两个根元素。具体行为取决于根元素的类型。

当 React 进行更新,需要重新渲染或刷新时,会创建第二个workInProgress树,用于表示 DOM最终的样子。处理完workInProgressDOM 的更新后,它currentTree协调所有差异。

这就是使用 React 的优势,性能优化。

您的应用程序在网络上的性能通过此过程的两个关键方面进行优化

  • 分组 DOM 更新
    • React 等待所有更新都处理完毕后才将它们放入 workInProgress 树中。
  • 选择性更新
    • React 能够应用差异算法来快速选择需要更新的数据。

现在,让我们回到组件🏃🏽‍♂️

3. 组件和道具

在我们上面的代码片段中,有一个我们导入的组件,由 JSX 元素组成。

我们看到了可以从 App 文件中抽象出来的 44 行代码。这样的组件使我们能够将用户界面拆分成可重用的构建块。

从概念上讲,组件就像 JavaScript 函数。它们接受任意输入(称为“props”),并返回描述屏幕上应显示内容的 React 元素。——React 文档

const Comment = (props) =>  {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

在这个组件中,我们将其props作为参数传递给数组函数。

Props,或称属性,是遍历 React 节点树的数据对象,目的是向组件提供浏览器 DOM 所需的信息repaint

但是这些 props 是从哪里来的呢?为了理解这一点,我们需要花一点时间来了解一下 state。

4. 状态和生命周期

在我们的 React 应用程序中,我们经常会在对象中设置初始状态。

// PREFERRED & COMMON WAY
state = {
  isClicked: true,
  initialGreeting: "hello, there!"
}

//OR BUILT WITH A CONSTRUCTOR

constructor(props){
  super(props)
  this.state = {
    isClicked: true,
    initialGreeting: "hello, there!"
  }
}

您的状态应该位于类组件内,通常看起来像下面的代码。

class下面是一个类 React 组件实例的示例。组件与 组件functional(其核心纯粹是一个箭头函数)之间的区别在于,React 类组件预先封装了生命周期方法。

class Clock extends React.Component {
  render() {
    return (
      <div>
        // Here's some text!
      </div>
    );
  }
}

这也是为什么开发人员可能会将类组件称为“智能组件”,而将函数组件称为“哑组件”。类组件用于传递所有逻辑,而函数组件则更像是容器或用于构建简单的模块。

但是生命周期方法是什么?

当 React 开始工作时,它首先会检查组件的状态(如果组件是类组件)。React 不会因为检查哑组件而浪费资源。

鸟

你可以为状态设置默认值来启动应用,就像我们在示例中看到的那样,或者你也可以根据需要传入 props。首选方法是使用简单的状态对象,而不是使用构造函数。虽然构造函数在创建引用或方法绑定时会很方便,但那是另一个话题了。

让我们列出当前可用的生命周期方法并附上一些简短的描述。

  • componentDidMount()
    • 初始渲染后,调用方法
    • 用于加载/设置数据
    • 确保在发送 AJAX 请求之前,确实有一个组件可以渲染它
  • shouldComponentUpdate(nextProps, nextState)
    • 仅当组件所需的 props 发生变化时才更新组件
    • 问题:不允许你的组件定期更新
  • componentDidUpdate(prevProps, prevState, snapshot)
    • 这使我们能够处理之前查看 DOM 时检查过的当前 DOM 树的已提交更改
  • componentWillUnmount
    • 根据 React 文档:“在组件被销毁时释放它们所占用的资源非常重要。”
    • 此方法主要用于清除消耗重要应用程序资源的残留行为

哎呀,说了这么多,还有其他一些有用的方法,比如getSnapshotBeforeUpdate、、和,你应该花点时间看看。但我们列表中提到的方法是构建一个优秀应用程序所需的主要方法。getDerivedStateFromErrorcomponentDidCatchgetDerivedStateFromProps

主要要点是这些生命周期方法允许我们更新应用程序数据或状态。

国家三大规则

  • 不要直接修改状态
    • this.state.comment =“nopity-nope nope”
    • this.setState({words:“更好!”})
  • 状态更新可以是异步的
    • 记住使用一种通过对象接受函数的 setState 形式。
    • this.setState((state, props) => ({words: state.words}))
    • 也可以是常规函数
  • 状态更新已合并
    • 更新后的状态将合并到当前节点树中,然后您可以在任意位置多次设置 setState({})。

记住:在 React 中,数据是向下流动的。想象一下瀑布,父组件中创建的状态通过 props 向下传递到子组件。

5. 处理🖐🏽事件

描述事件处理程序

这部分的好处在于不需要太多的脑力劳动。React 中的事件处理方式大部分与常规 JS 事件类似。

我们主要应该考虑用于描述 React 事件的语法糖。需要记住的是,它们是驼峰命名的。

  • 常规 JS 事件
    • <button onclick="rainDownMoney()">
  • React 事件处理程序
    • <button onClick={this.raindDownMoney}>

合成事件

你的事件处理程序将传递 SyntheticEvent 的实例,它是浏览器原生事件的跨浏览器包装器。它与浏览器原生事件具有相同的接口,包括 stopPropagation() 和 preventDefault(),但这些事件在所有浏览器中的工作方式相同。—— React 文档

事件池

  • 重点提示:您无法以异步方式访问合成事件
    • 由于事件池
    • 这意味着您的 SyntheticEvent 对象被重复使用以提高性能。
    • 触发回调函数后,附加到合成事件的属性将变为空。
    • event.persist()
    • 将允许您以异步方式访问事件道具。

在 React 中绑定 JS 的 THIS

在 JavaScript 中,类方法并不绑定到它们的 THIS 值。现在,很多时候,训练营都在花大量时间复习和深入研究这个概念。不过,我们先来快速了解一下。

来自MDNFunction.prototype.bind()

bind() 最简单的用法是创建一个函数,无论如何调用,都会使用特定的 this 值。JavaScript 新手常犯的一个错误是从对象中提取方法,然后调用该函数,并期望它使用原始对象作为 this(例如,在基于回调的代码中使用该方法)。然而,如果不特别注意,原始对象通常会丢失。使用原始对象从函数创建一个绑定函数,可以巧妙地解决这个问题:

绑定

上面的例子来自 MDN,我们应该从中得出的是全局“窗口”对象和范围在这里发挥作用。

我们的函数retrieveX()在全局范围内被调用,并且 this 的值是module.getXthis.x = 9文件顶部定义的值中获取的,而不是从我们模块对象内部的 x 获取的。

解决方案:retrieveX.bind(module)

绑定 this 允许我们将 THIS 值固定为我们想要的值。

This取决于在运行时绑定或关联变量、函数和数据时函数的调用方式。This始终默认为全局对象,或浏览器中的窗口。相信我,如果您忘记绑定,控制台中会清晰地显示错误。

两种绑定方式

  • 公共类字段语法(实验性)
class LoggingButton extends React.Component {
handleClick.
  // EXPERIMENTAL 
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
  • 箭头函数!
class LoggingButton extends React.Component {
  handleClick() {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={(e) => this.handleClick(e)}>
        Click me
      </button>
    );
  }
}

事件和 this 的绑定在你刚开始使用 React 时可能会导致大多数 bug,如果以后忘记绑定,后果可能更严重。我之前就把箭头函数和公共类字段的语法搞混了,所以最好选择其中一种,并在你的应用中坚持使用。

6.✅ 条件渲染❌

还记得使用组件是如何让我们减少文件中的代码混乱吗?条件渲染,或者根据应用程序的状态/属性显示元素,可以让我们编写更少的代码并使其更加清晰。

class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;

    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}

ReactDOM.render(
  <LoginControl />,
  document.getElementById('root')
);

向 JSX 添加表达式

有几种很酷的方法可以将逻辑添加到 JSX 中

  • &&带有逻辑运算符 的内联 IF
    • IF 条件,渲染true后的元素&&
    • 如果条件false,则忽略
return (
    <div>
      <h1>Hello!</h1>
       // start of condition
      {unreadMessages.length > 0 

          &&

        <h2>
          You have {unreadMessages.length} unread messages.
        </h2>
      }
      // end of condition
    </div>
  );
  • IF-Else 三元运算符(需要 3 个操作数)
    • 条件 ? 如果为真则返回 : 如果为假则返回
return (
    <div>
    // start of condition
      {
        isLoggedIn ? 
(<LogoutButton onClick={this.handleLogoutClick} />) 
        : 
(<LoginButton onClick={this.handleLoginClick} />)
       }
   // end of condition
    </div>
  • null
    • 如果您希望在条件为假时不发生任何事情,您也可以随时交换null原始值。
    • 这不会影响生命周期方法

7. 列表📝和键🔑

关于建立列表,您应该了解两个要点。

  • 显示项目列表通常借助函数来完成map()
  • 被映射的元素需要唯一的键,但它们不需要是全局唯一的。
function NumberList(props) {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =>
    <ListItem key={number.toString()}
              value={number} />
  );
  return (
    <ul>
      // if we watned to make things look messy
      // we could also directly embed 
      // our functioninside 
      // of the brackets
      {listItems}
    </ul>
  );
}

8. 表格

  • 受控组件
    • 在常规 HTML 表单中
    • 元素(例如 input、textArea、select)保持自己的状态
    • 反应方式
    • 可变状态保存在 state prop 中,由setState()
  • 问题
    • React 应该负责处理singl source of truth数据。上图中我们看到两种不同的数据结构在相互竞争。让我们借助受控组件将它们结合起来。

处理程序函数

即使你给函数取了其他名字,也不会对函数本身造成影响,但通常的做法是根据函数的功能来命名,例如handleSubmit()。组件之所以能够被控制,是因为我们用构造函数设置了初始状态,并用我们自己的 onChange 事件处理程序对其进行了修改,该处理程序会setState()根据我们定义的条件触发我们定义的函数。这样,​​我们就拥有了控制权。

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

    this.handleChange = this.handleChange.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleChange(event) {
    this.setState({value: event.target.value});
  }

  handleSubmit(event) {
    alert('A name was submitted: ' + this.state.value);
    event.preventDefault();
  }

  render() {
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}

9. 提升状态

这是另一个学习曲线陡峭、攀登艰难的领域。但最终一切都会逐渐明朗,尤其是在你花了大量时间阅读文档之后。

以下是将状态从子组件提升到其直属父组件时要遵循的基本步骤。

  1. 在父组件中定义一个函数
  2. 将其作为函数传递给你的子组件
  3. 将子组件中改变的状态传递给 prop,其中包含父组件的函数,这样数据就会遍历你的节点树,一直回到你的唯一真实来源

10. 组合与继承

React 团队并没有说哪个更好,所以为了澄清起见,我们也不会这么说。但是 React 的开发团队建议在大多数情况下使用组合,在少数情况下使用继承。这些是架构方法,与我们的父组件和子组件相关。

  • 继承(从父类扩展属性)
    • 在面向对象语言中,这是指子类从其父类获取属性的时候。
  • COMPOSITION(引用其他类实例中的对象)
    • 描述引用另一个类的对象作为实例的类。
  • 重点是什么?
    • 代码重用

让我们来看看Mosh Hamedani的一些示例,他是一位很棒的 React 开发者和博主。我强烈推荐你多看看他的作品。

//PARENT
export default class Heading extends React.Component {
  render () {
    return (
       <div>
         <h1>{this.props.message}</h1>
       </div>
    )
  }
}
Heading.propTypes = {
  message: PropTypes.string
}
Heading.defaultProps = {
  message: 'Heading One'
}

//CHILD #1
export default class ScreenOne extends React.Component {
  render () {
    return (
     <div>
          <Heading message={'Custom Heading for Screen One'}/>
      </div>
    )
  }
}

// CHILD #2
export default class ScreenTwo extends React.Component {
  render () {
    return (
     <div>
          <Heading message={'Custom Heading for Screen Two'}/>
      </div>
    )
  }
}

我们在这里看到的是,我们定义了一个父组件,它依赖于传入的 props 进行更新。这是一个可自定义的值,可以根据显示它的子组件进行更改。如果 props 发生变化,显示的消息也会随之改变。

下面是一个继承的例子,不用太详细,继承就是从父组件扩展 props。但事情可能会变得很复杂。

class CreateUserName extends UserNameForm {
   render() {
      const parent = super.render();
      return (
         <div>
            {parent}
            <button>Create</button>
         </div>
      )
   }
}

坚持创作方法,你会没事的。

返回索引

检查站

恭喜!我们成功了!

太棒了,我们终于读完了!还有其他一些令人兴奋的概念,比如 Context,HigherOrderComponents以及更多内容,Hooks我会在其他文章中介绍。但这并不意味着它们就不那么重要了。我希望这篇文章能够解答你在使用 React 时遇到的许多 bug。

文章来源:https://dev.to/krtb/react-explained-5gk
PREV
明智地选择开源许可证
NEXT
打造出色投资组合的技巧