省油の灯

React 元素 vs React 组件

省油の灯 · 2017-04-28翻译 · 399阅读 原文链接

React 元素 vs React 组件

Dec 15, 2016

几个月前,我曾想过的一个简单的问题被我发布到Twitter上。

令我感到惊讶的不是这个问题的联合混乱,反而是我收到的不准确的答复。

Instances / Instantiation Rendering Evaluation Invocation “Using it :)”

对于混乱的主要原因是,在JSX与React-land上实际发生的事情之间有一个经常不被人们谈及的抽象层。 为了回答这个问题,我们需要深入了解这个抽象层。

我们先来看看React的绝对基础。什么是React?它是一个构建用户界面的库。无论React或React生态系统如何复杂,这都是React的核心----构建UI。考虑到这一点,我们得出了我们的第一个定义,一个元素。简单地说,一个React元素“描述”了你想在屏幕上看到什么。并不是简单的说,一个React元素是一个DOM节点的对象表示。注意我使用了“描述”这个词。重要的是要注意,一个React元素实际上并不是您在屏幕上看到的内容,而只是一个对象表示。这有一些原因。第一个原因,JavaScript对象是轻量级的----React可以创建和销毁这些元素,而不需要太多的开销。第二个原因是React能够分析对象,将其与先前的对象表示进行区分,以查看发生了什么变化,然后在这些发生改变的时候更新实际的DOM。这有一些性能提升。

为了创建一个DOM节点(也称为React元素)来表示对象,我们可以使用React的createElement方法。

const element = React.createElement(
  'div',
  {id: 'login-btn'},
  'Login'
)

createElement包含三个参数。 第一个是标签名称字符串(div,span等),第二个是您想要的元素的任何属性,第三个是元素的内容或子元素,在这种情况下(上述代码中)为文本“Login”。 上面的createElement调用将返回具有此形式的对象。

{
  type: 'div',
  props: {
    children: 'Login',
    id: 'login-btn'
  }
}

当它被渲染到DOM(使用ReactDOM.render)时,我们将有一个新的DOM节点,看起来就像这样,

<div id='login-btn'>Login</div>

到现在为止还挺好。 学习React有什么有趣的是,它通常你教的第一件的事是组件。 “组件是React的组成部分”。 不过请注意,我们用元素开始了这篇文章。 原因是因为一旦了解元素,理解组件就是平滑过渡的。 组件是一个函数或一个Class,它可以接受输入并返回一个React元素。

function Button ({ onLogin }) {
  return React.createElement(
    'div',
    {id: 'login-btn', onClick: onLogin},
    'Login'
  )
}

根据定义,我们有一个Button组件,它接受一个onClick输入并返回一个React元素。 有一点需要注意的是,我们的Button组件接收一个onClick输入作为其支持。 要将其传递给DOM的对象表示,我们将它作为createElement的第二个参数传递,就像我们的id属性一样。

让我们进一步深入。

直到现在,我们只涵盖使用原生HTML元素(“span”,“div”等)的“type”属性创建React元素,但您也可以将其他React组件传递给createElement的第一个参数。

const element = React.createElement(
  User,
  {name: 'Tyler McGinnis'},
  null
)

但是,与HTML标签名称不同,如果React看到一个类或一个函数作为第一个参数,那么在给出相应的props之后,它将检查它渲染哪些元素。 React将继续这样做,直到不再有createElement调用具有一个类或一个函数作为其第一个参数。 我们来看看这个代码的执行。

function Button ({ addFriend }) {
  return React.createElement(
    "button",
    { onClick: addFriend },
    "Add Friend"
  )
}

function User({ name, addFriend }) {
  return React.createElement(
    "div",
    null,
    React.createElement(
      "p",
      null,
      name
    ),
    React.createElement(Button, { addFriend })
  )
}

在上面的代码中,我们有两个组件。Button和User。 User的对象在DOM中将表现为一个带有两个子节点的“div”,其中子节点分别为一个包含user的name属性的"p"元素和一个Button组件。 现在我们来回调一下createElement的调用,

function Button ({ addFriend }) {
  return {
    type: 'button',
    props: {
      onClick: addFriend,
      children: 'Add Friend'
    }
  }
}

function User ({ name, addFriend }) {
  return {
    type: 'div',
    props: {
      children: [
        {
          type: 'p',
          props: {
            children: name
          }
        },
        {
          type: Button,
          props: {
            addFriend
          }
        }
      ]
    }
  }
}

你会在上面的代码中注意到,我们有四种不同的类型属性“button”,“div”,“p”和“Button”。 当React看到一个具有函数或类类型的元素(像我们上面的“type:Button”)时,它会在给定相应的 props的情况下查询该组件来知道它返回的元素。 考虑到这一点,在此过程结束时,React具有DOM树的完整对象表示。 在我们的例子中,可以这样看来,

{
  type: 'div',
  props: {
    children: [
      {
        type: 'p',
        props: {
          children: 'Tyler McGinnis'
        }
      },
      {
        type: 'button',
        props: {
          onClick: addFriend,
          children: 'Add Friend'
        }
      }
    ]
  }
}

这个整个过程称为React中的对帐,每次调用setState或ReactDOM.render时都会触发它。

所以现在再来看看我们的这个博客文章引发的最初的问题,

在这一点上,我们有需要的所有的知识,用来回答这个问题,除了一个重要的一块。很有可能您在任何时候使用React,您都不会使用React.createElement来创建DOM的对象。 相反,您可能正在使用JSX。 本文中我写到:"混乱的主要原因是在JSX与React-land之间实际发生的事情并没有经常涉及到到抽象层。" 这个抽象层是由JSX通过Babel被transpiling(transpiling是一种特殊的compiling,这种transpiling是指编译与被编译两者的抽象层次相同,比如:js和ts)到React.createElement调用(通常情况下)。

看看我们前面的例子,这段代码

function Button ({ addFriend }) {
  return React.createElement(
    "button",
    { onClick: addFriend },
    "Add Friend"
  )
}

function User({ name, addFriend }) {
  return React.createElement(
    "div",
    null,
    React.createElement(
      "p",
      null,
      name
    ),
    React.createElement(Button, { addFriend })
  )
}

上代码被编译的JSX的结果,如下

function Button ({ addFriend }) {
  return (
    <button onClick={addFriend}>Add Friend</button>
  )
}

function User ({ name, addFriend }) {
  return (
    <div>
      <p>{name}</p>
      <Button addFriend={addFriend}/>
    </div>
  )
}

那么最后,当我们写出这样的组件时,我们称呼它是什么,比如<Icon/>?

我们可以称之为“创建一个元素”,因为JSX被编译了,这就是刚刚发生的事情,我们创建了一个元素:

React.createElement(Icon, null)

所有这些例子都是“创建一个React元素”:

React.createElement(
  'div',
  { className: 'container' },
  'Hello!'
)

<div className='container'>Hello!</div>

<Hello />

想要阅读更多关于这类主题的文章,请参阅 “React Components, Instances, and Elements” by Dan Abramov.

译者省油の灯尚未开通打赏功能

相关文章