细读 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.


目录
相关文章
|
22天前
|
存储 前端开发 索引
REACT 在组件之间共享状态
REACT 在组件之间共享状态
|
1月前
|
前端开发 API
react怎么实现父子组件传值?
react怎么实现父子组件传值?
13 1
|
15天前
|
存储 前端开发 JavaScript
React中有效地使用props和state来管理组件的数据和行为
React中有效地使用props和state来管理组件的数据和行为
|
29天前
|
前端开发 JavaScript 开发者
在React中,如何利用生命周期方法管理组件的状态和行为?
【5月更文挑战第29天】在React中,如何利用生命周期方法管理组件的状态和行为?
24 3
|
7天前
|
缓存 前端开发 JavaScript
如何对 React 函数式组件进行优化
如何对 React 函数式组件进行优化
|
1月前
|
前端开发
探索React Hooks:一种全新的组件逻辑管理方式
React Hooks是React 16.8版本引入的一项新功能,它改变了我们编写React组件的方式。本文将从Hooks的起源讲起,逐步分析Hooks的优势,并通过具体示例展示Hooks在组件逻辑管理中的应用,旨在帮助读者更好地理解和运用React Hooks。
|
1月前
|
前端开发 JavaScript 开发者
React的函数组件与类组件:探索两者之间的区别
【4月更文挑战第25天】React提供函数组件和类组件,两者在语法、状态管理、生命周期和性能优化上有所不同。函数组件简单且易于理解,使用 Hooks 可添加状态管理;类组件支持复杂状态管理和生命周期方法,适用于需要精细控制更新的场景。随着 Hooks 的发展,函数组件功能增强,成为更多开发者的首选。选择组件类型应根据实际需求权衡。
|
1月前
|
设计模式 前端开发 API
React的高阶组件(HOC):使用与设计模式探讨
【4月更文挑战第25天】React的高阶组件(HOC)是一种复用和增强组件的高级模式,它接受组件并返回新组件。非侵入式增强使得HOC能在不修改原有组件代码的情况下添加功能。定义HOC后,将其应用于目标组件并渲染增强后的组件。常见设计模式包括属性代理、控制反转和装饰器。然而,使用时要注意避免滥用,保持命名清晰,关注性能优化。理解并恰当使用HOC能提升React应用的构建效率。
|
1月前
|
存储 前端开发 JavaScript
React的表单处理:受控组件与非受控组件深入解析
【4月更文挑战第25天】React表单处理涉及受控和非受控组件。受控组件通过状态管理表单数据,每次用户输入都触发状态更新,确保数据同步,适合实时交互但可能影响性能。非受控组件不直接管理状态,数据存储在DOM中,简化代码,适用于更新不频繁的场景,但在数据验证和同步上存在挑战。开发者应根据需求灵活选择。
|
1月前
|
前端开发 JavaScript 开发者
掌握React中的useEffect:函数组件中的魔法钩子
掌握React中的useEffect:函数组件中的魔法钩子