细读 React | 元素、组件、实例

简介: 细读 React | 元素、组件、实例

前言


元素是构成 React 应用最小的砖块,它描述了你在页面上想看到的内容。


const element = <h1>Hello World</h1>


与浏览器的 DOM 元素不同,React 元素是创建开销极小的普通对象。React DOM 会负责更新 DOM 来与 React 元素保持一致。

以上 JSX 语法编写的代码,最终会被 Babel (在线 Babel 编译器)转换为:

const element = React.createElement('h1', null, 'Hello World')

React.createElement() 最后会返回一个普通的 JavaScript 对象(该对象就是对 React 元素的描述):

const element = {
  type: 'h1',
  props: {
    children: 'Hello World'
  },
  // ...
}


可以在控制台打印一下 element 元素:

12.webp.jpg


需要注意的是,React 元素描述的是 Virtual DOM 的结构,而非真实 DOM。真实 DOM 由 React DOM 根据 Virtual DOM 负责更新。


正文


React 元素


上面提到,React 元素本身上就是一个 JavaScript 对象,而且是不可变对象(Immutable Object)。


由于 React 元素是不可变对象,若对其 props 等属性进行修改操作,是会抛出错误的。


React 元素表示了某个时刻的 UI,而更新 UI 的唯一方式是创建一个全新的元素。React DOM 会根据 Diff 算法只更新它需要更新的部分。


React 组件


刚开始接触 React 时,人们很容易将元素组件的概念混淆。元素是 React 应用最小的单元,而组件则是由一个或多个元素构成的。

组件是 React 中很重要的思想。一个复杂庞大的 React 应用,是由许多结构简单、清晰的组件组合而成的。


组件,从概念上类似于 JavaScript 函数,它接受任意的入参(即 props),并返回 React 元素。


在 React 中,组件分为函数组件class 组件。下面声明了两种不同类型的组件:

// 函数组件
function Comp1() {
  return <h1>Functional Component.</h1>
}
// class 组件
class Comp2 extends React.Component {
  // render 是类组件唯一必需实现的方法
  render() {
    return <h1>Class Component.</h1>
  }
}


我们知道 ReactDOM.render(element, container[, callback]) 方法,第一个参数 element 接收的是 React 元素。而我们声明的组件本质上是一个 JavaScript 函数,因此,我们应该要这样使用自定义组件:

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


React 元素分类


从以上可知,我们遇到的 React 元素,可以是 DOM 标签:

const element = <h1>Hello World</h1>


也可以是用户自定义的组件:

const element = <Comp1 />


因此,我们可以将 React 元素分为两类:DOM 类型元素、组件类型元素。

前者是指使用类似 divh1pspan 等 DOM 标签创建的 React 元素,而后者是指使用 React 组件(如 Class Components、Functional Components)创建的 React 元素。


对于 DOM 标签,React 可以分辨出来,而自定义的 React 组件呢?若不约定规则,那么它无法辨认啊。所以 React 要求以 JSX 语法编写 React 组件时,其组件名称必须以大写字母开头


例如,MyComponent 是“合法”的 React 组件,而 myComponent 是“不合法”的。否则 React 会发出如下警告 ⚠️:


Warning: The tag <myComponent> is unrecognized in this browser. If you meant to render a React component, start its name with an uppercase letter.

上面打了双引号,其实在声明一个 React 组件时,是可以以小写字母开头的,但在使用时必须以大写字母开头。即我们可以将 React 组件赋值给一个以大写字母开头的变量,然后正常使用。但这种“非主流”的写法,不被推荐。再者,声明一个构造函数,它的名称应以大写字母开头,这是一种约定俗成的写法。


同样的,DOM 类型元素只能以小写字母的形式,例如 <div />。如果使用 <Div /> 会被 React 认为是自定义组件,但由于我们又没有声明,因此可能会抛出引用错误:ReferenceError: Div is not defined


想了解更多关于此规范的原因,请看深入 JSX


React 元素、组件使用误区


一些容易混淆、出错的写法:

const Element = <h1>React Element.</h1>
const rootEl = document.getElementById('root')
// 正确示例 ✅
ReactDOM.render(Element, rootEl)
ReactDOM.render(<Comp1 />, rootEl)
// 错误示例 ❌
ReactDOM.render(<Element />, rootEl) // 1️⃣
ReactDOM.render(Comp1, rootEl) // 2️⃣


错误示例 1️⃣ 会抛出以下警告 ⚠️:


Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <h1 />. Did you accidentally export a JSX literal instead of a component?


错误示例 2️⃣ 会抛出以下警告 ⚠️:


Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.


我们都知道,ReactDOM.render(element, container[, callback]) 第一个参数接收 React 元素。而 React 元素都是由 React.createElement() 返回的。我们从 React.createElement() 的语法上解释以上错误示例:

React.createElement(
  type,
  [props],
  [...children]
)


  • type 可以是标签名字符串(如 divspanh1 等),也可以是 React 组件类型(class 组件或函数组件),或者是 React Fragment 类型。
  • props 可选,组件属性
  • children 可选,子元素。含有多个子元素时,最终 React Element 的 props.children 会返回一个数组。


原因分析:

  1. ReactDOM.render(Element, rootEl) 相当于 ReactDOM.render(<h1>React Element.</h1>, rootEl)( 这里 Element 只是一个变量)。以小写字母开头的元素代表一个 HTML 内置组件,在编译时 <h1> 会生成响应的字符串 'h1' 传递给 React.createElement,是符合参数要求的。所以没问题。
  2. ReactDOM.render(<Comp1 />, rootEl)以大写字母开头的元素,对应着自定义组件,<Comp1 /> 会被编译为 React.createElement(Comp1)。而且 Comp1 正是 React 组件(本质上就是 JavaScript 函数),也符合参数要求,所以没问题。
  3. 按上面的规则,<Element /> 以大写字母开头,会被认为是 React 组件,但 Element 的类型是 "object",而不是 "function"(class 本质也是函数),不符合 React.createElement 参数要求,因此会被报错或警告。
  4. ReactDOM.render(Comp1, rootEl) 中的 Comp1 是一个 JavaScript 函数,而非 React 元素,因此也会报错。


实例


React 组件是一个函数或者类,而实际发挥作用的是 React 组件的实例对象。只有在组件实例化之后,每个组件实例才有自身的 props、state 或对 DOM 节点的引用。

function Child() {
  return <div>Child Component</div>
}
class Parent extends React.Component {
  // 需要注意的是,在组件将要被销毁的时候会触发此生命周期函数
  // 当组件从页面中“移除”,并不意味着组件实例被回收掉了
  // 仅在组件实例不再被任何地方引用,它才会被垃圾回收。
  componentWillUnmount() {
    // ...
  }
  render() {
    return (
      <div>
         <div>Parent Component</div>
         {/* 当父组件触发 render 之后,子组件就会被实例化 */}
         <Child />
      </div>
    )
  }
}


节点


很多情况下,我们会使用 PropTypes 来限制组件属性类型。这里我们提一下与本文相关的两种类型:PropTypes.nodePropTypes.element

import PropTypes from 'prop-types'
function MyComponent(props) {
  return (
    <div>
      <div>node: { props.node }</div>
      <div>element: { props.element }</div>
    </div>
  )
}
MyComponent.propTypes = {
  node: PropTypes.node,
  element: PropTypes.element
}


  • PropTypes.element:可以是 nullundefined 或 React 元素。
  • PropTypes.node: 可以是 nullundefined、字符串、React 元素或包含这些类型的数组。


The end.


目录
相关文章
|
10天前
|
前端开发 JavaScript 测试技术
React 分页组件 Pagination
本文介绍了如何在 React 中从零构建分页组件,涵盖基础概念、常见问题及解决方案。通过示例代码详细讲解了分页按钮的创建、分页按钮过多、初始加载慢、状态管理混乱等常见问题的解决方法,以及如何避免边界条件、性能优化和用户反馈等方面的易错点。旨在帮助开发者更好地理解和掌握 React 分页组件的开发技巧,提升应用的性能和用户体验。
36 0
|
14天前
|
移动开发 前端开发 API
React 拖拽组件 Drag & Drop
本文介绍了在 React 中实现拖拽功能的方法,包括使用原生 HTML5 Drag and Drop API 和第三方库 `react-dnd`。通过代码示例详细讲解了基本的拖拽实现、常见问题及易错点,帮助开发者更好地理解和应用拖拽功能。
47 9
|
9天前
|
前端开发 UED 开发者
React 分页组件 Pagination
本文介绍了如何在 React 中实现分页组件,从基础概念到常见问题及解决方案。分页组件用于将大量数据分成多个页面,提升用户体验。文章详细讲解了分页组件的基本结构、快速入门步骤、以及如何处理页面跳转不平滑、页码过多导致布局混乱、边界条件处理和数据加载延迟等问题。通过本文,读者可以全面了解并掌握 React 分页组件的开发技巧。
13 2
|
12天前
|
设计模式 前端开发 编译器
与普通组件相比,React 泛型组件有哪些优势?
与普通组件相比,React 泛型组件有哪些优势?
30 6
|
21天前
|
前端开发 JavaScript 安全
学习如何为 React 组件编写测试:
学习如何为 React 组件编写测试:
35 2
|
28天前
|
前端开发 JavaScript 测试技术
React 高阶组件 (HOC) 应用
【10月更文挑战第16天】高阶组件(HOC)是 React 中一种复用组件逻辑的方式,通过接受一个组件并返回新组件来实现。本文介绍了 HOC 的基础概念、核心功能和常见问题,包括静态方法丢失、ref 丢失、多个 HOC 组合和 props 冲突的解决方案,并提供了具体的 React 代码示例。通过本文,读者可以更好地理解和应用 HOC,提高代码的复用性和可维护性。
58 8
|
27天前
|
缓存 前端开发 JavaScript
前端serverless探索之组件单独部署时,利用rxjs实现业务状态与vue-react-angular等框架的响应式状态映射
本文深入探讨了如何将RxJS与Vue、React、Angular三大前端框架进行集成,通过抽象出辅助方法`useRx`和`pushPipe`,实现跨框架的状态管理。具体介绍了各框架的响应式机制,展示了如何将RxJS的Observable对象转化为框架的响应式数据,并通过示例代码演示了使用方法。此外,还讨论了全局状态源与WebComponent的部署优化,以及一些实践中的改进点。这些方法不仅简化了异步编程,还提升了代码的可读性和可维护性。
|
15天前
|
前端开发 UED
React 模态框 Modal 组件详解
【10月更文挑战第27天】本文介绍了如何在 React 中实现一个功能完善的模态框组件。从基础概念入手,逐步讲解了简单的模态框实现、CSS 样式、传递子组件、键盘事件处理等高级功能。同时,还探讨了常见问题及易错点,如背景点击关闭、键盘事件冲突和动画效果。通过本文,读者可以全面了解 React 模态框组件的实现细节。
29 0
|
1月前
|
前端开发 JavaScript 调度
React 组件状态(State)
10月更文挑战第8天
27 1
|
1月前
|
前端开发 JavaScript API
React将组件作为属性传递的最佳实践
本文探讨了在React中将组件作为属性传递的三种常见方式:作为元素传递、作为组件传递、作为函数传递。通过构建带图标的按钮组件,对比分析了每种方式的优缺点,最终推荐将组件作为函数传递,因为它提供了更好的可控性、灵活性和可扩展性。
41 0