比较与理解 React 的 Components、Elements 和 Instances

发布于 2022-06-12 12:15:00 字数 8260 浏览 1030 评论 4

React 是目前(2017.04)流行的创建组件化UI的框架,自身有一套完整和强大的生态系统;同时它也是我目前工作中的主力框架,所以学习和理解React是很自然的需求。

<div>
<img href="https://www.wenjiangs.com/wp-content/uploads/2022/docimg15/320-1kgnb42oecz.png" className="profile" />
<h3>{[user.firstName, user.lastName].join(' ')}</h3>
</div>;

借助 babel-plugin-transform-react-jsx,上面的JSX将被转译成:

var profile = React.createElement("div", null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);

那么,React.createElement 是在做什么?看下相关部分代码:

var ReactElement = function(type, key, ref, self, source, owner, props) {
  var element = {
    // This tag allow us to uniquely identify this as a React Element
    $typeof: REACT_ELEMENT_TYPE,

    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };
  // ...
  return element;
};

ReactElement.createElement = function(type, config, children) {
  // ...
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props
  );
};

看起来,ReactElement.createElement 最终返回了一个对象,这个对象大概是这样的形状:

{
  type,
  key,
  props: {
    children
  }
}

非常明显,这就是一个 Elements Tree!很好,我们知道了 react 的 render 方法是返回一个 Elements Tree,react 的核心就是围绕 Elements Tree 做文章。

下面我们就主要讲讲 Components,Elements(Tree)和 Instances,以及三者之间的关系。

传统面向对象 UI 编程的痛点:管理实例

如果你是 React 的新手,那么之前你可能只接触过组件的类和实例(component classes and instances )。比如,你可能会
创建一个类来声明 Button 组件,当 app 运行时,屏幕上可能会有多个 Button 的实例,每个都有自己的属性和私有状态。这就是传统面向对象的 UI 编程,那么为什么要引入 Elements 的概念?

传统UI模型中,你必须自己负责创建和销毁子组件的实例(child component instances):

每个组件实例必须保存自己的 DOM nodes 和子组件实例的引用,并在对的时间创建,更新,销毁它们。代码的行数将会以可能的状态的数量的 平方 增长,而且组件可以直接访问子组件实例将会使解耦变得困难。

那么,React 有什么不同呢?

React 用 Elements Tree 描述 UI

An element is a plain object describing a component instance or DOM node and its desired properties.

一个元素(element)就是一个纯对象,描述了一个组件实例或 DOM node,以及它需要的属性。它仅仅包含这些信息:组件类型,属性(properties),及子元素。

元素不是实例,实际上,它更像是告诉React你需要在屏幕上显示什么的一种方式。它就是一个有2个数据域(field)的不可变描述对象(immutable description object):

{
  type: (string | ReactClass),
  props: Object
}

DOM Elements

当元素的 type 是 string 时,那么这个元素就表示一个 DOM node(type 的值就是 tagName,props 就是 attributes)。 这 node 就是 React 将渲染的。比如:

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

将被渲染成:

<button class='button button-blue'>
  <b>
    OK!
  </b>
</button>

注意下元素是怎么嵌套的。当我们想创建元素树时,我们设置 children 属性。

注意:子元素和父元素都只是描述,并不是实际的实例。当你创建它们的时候,它们并不指向屏幕上的任何东西。 显然,它们比DOM轻量多了,它们只是对象。

Component Elements

此外,元素的 type 也可以是 function 或者 class(即对应的 React Component):

{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

An element describing a component is also an element, just like an element describing the DOM node. They can be nested and mixed with each other.

这是 React 的核心 idea:一个描述组件的元素同样是元素,和描述 DOM node 的元素没什么区别。它们可以互相嵌套和混合。

你可以混合搭配 DOM 和 Component Elements:

const DeleteAccount = () => ({
  type: 'div',
  props: {
    children: [{
      type: 'p',
      props: {
        children: 'Are you sure?'
      }
    }, {
      type: DangerButton,
      props: {
        children: 'Yep'
      }
    }, {
      type: Button,
      props: {
        color: 'blue',
        children: 'Cancel'
      }
   }]
 }
});

或者,如果你更喜欢 JSX:

const DeleteAccount = () => (
  <div>
    <p>Are you sure?</p>
    <DangerButton>Yep</DangerButton>
    <Button color='blue'>Cancel</Button>
  </div>
);

这种混合搭配帮助组件可以彼此解耦,因为它们可以仅仅通过组合(composition)就能表达 is-a 和 has-a 的关系:

  • Button 是有特定属性(specific properties)的DOM <button>
  • DangerButton 是有特定属性的 Button
  • DeleteAccount 在 <div> 里包含了 Button 和 DangerButton

Components Encapsulate Element Trees

当 React 碰到 type 是 function|class时,它就知道这是个组件了,它会问这个组件:给你适当的props,你返回什么元素(树)?。比如当它看到:

{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

React 会问 Button 要渲染什么,Button 返回:

{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

React 会重复这种过程,直到它知道页面上所有的组件想渲染出什么 DOM nodes。对 React 组件来说,props 是输入,元素树(Elements tree)是输出。

我们选择让 React 来 创建,更新,销毁 实例,我们用元素来描述它们,而 React 负责管理这些实例。

Components Can Be Classes or Functions

声明组件的 3 种方式:

  1. class,推荐。
  2. React.createClass,不推荐。
  3. function,类似只有 render 的 class

Top-Down Reconciliation

ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}, document.getElementById('root'));

const Form = ({ isSubmitted, buttonText }) => {
  if (isSubmitted) {
    // Form submitted! Return a message element.
    return {
      type: Message,
      props: {
        text: 'Success!'
      }
    };
  }

  // Form is still visible! Return a button element.
  return {
    type: Button,
    props: {
      children: buttonText,
      color: 'blue'
    }
  };
};

当你调用 ReactDOM.render 时,React 会问 Form 组件,给定这些 props,它要返回什么元素。React 会以更简单的基础值逐渐提炼(refine)它对 Form 组件的理解,这个过程如下所示:

// React: You told me this...
{
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}

// React: ...And Form told me this...
{
  type: Button,
  props: {
    children: 'OK!',
    color: 'blue'
  }
}

// React: ...and Button told me this! I guess I'm done.
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}

上面是被 React 叫做 reconciliation 的过程的一部分。每当你调用 ReactDOM.render() 或 setState() 时,都会开始 reconciliation 过程。在 reconciliation 结束时,React 知道了结果的 DOM 树,一个如 react-dom 或  react-native 的 renderer 会应用必须的最小变化来更新 DOM nodes(或平台特定的视图,如 React Native)。

这种渐进式的提炼(refining)过程也是 React 应用可以容易优化的原因。如果组件树的某部分大太了,你可以让 React 跳过这部分的 refining,如果相关 props 没有变化。如果 props 是 immutable 的话,非常容易比较它们是否变化, 所以 React 可以和 immutability 搭配一起并提高效率。

你可能注意到这篇文章讲了很多关于组件和元素,却没讲实例。这是因为相比传统面向对象的UI框架,在React中实例没那么重要。

仅仅以类声明的组件才有实例,并且你从来不会直接创建它——React 为你创建它。尽管有父组件实例访问子组件实例的机制,但这只是在必要的情况下才使用,通常应该避免。

总结

元素(Element)是 React 的一个核心概念。一般情况下,我们用 React.createElement|JSX 来创建元素,但不要以对象来手写元素,只要知道元素本质上是对象即可。

本文围绕 Components,Elements 和 Instances 来讲解了元素,而下一篇文章将借助 snabbdom 来讲 virtual-dom :怎么从元素生成对应的 dom,怎么diff元素来最小更新 dom。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

遮了一弯 2022-05-04 09:27:34

@zhongjie-chen

  1. 是,虚拟 DOM 本质上就是 Elements tree。
  2. 是,所以需要优化。
  3. JSX 最终编译成 Elements tree。
貪欢 2022-05-04 08:52:28

写的非常好,问几个疑惑的地方
1、虚拟dom对比是组件变成元素后再对比的吗
2、如果组件写的比较多,是不是增加了reconciliation的时间
3、是不是可以理解组件写入jsx中就是元素了

请远离我﹫ 2022-04-30 18:57:46

羡慕你们能玩commonjs的,可怜的amd只能用这种玩法

requirejs([
  'react'
  ,'ReactDOM'
], (React, ReactDOM)=>{
  ReactDOM.render(
    React.createElement('section', {
      className: 'main'
    }, [
      React.createElement('a', {
        href: '/'
      }, 'im a link')
      ,React.createElement('footer', {
        style: {
          position: 'fixed'
          ,left: 0
          ,right: 0
          ,bottom: 0
        }
      }, [
        //React Element List...
      ])
    ])
    ,document.body
  )
})
~没有更多了~

关于作者

拥有

暂无简介

0 文章
0 评论
741 人气
更多

推荐作者

花开柳相依

文章 0 评论 0

zyhello

文章 0 评论 0

故友

文章 0 评论 0

对风讲故事

文章 0 评论 0

Oo萌小芽oO

文章 0 评论 0

梦明

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文