w3ctech

React Elements VS React Components

翻译文章
原文:https://tylermcginnis.com/react-elements-vs-react-components/
原文作者:Tyler

如翻译有问题,麻烦请指出,(菜鸟级博文翻译员不胜感激!)。


几个月之前,我在Twitter提出了一个问题

I'm big on precise language when teaching but I haven't nailed down a great verbiage for using a component. Thoughts?
我在教学时非常重视精确的语言,但是我还没有找到一个很好的语言来使用组件。有想法吗?

// 函数定义
function add(x, y) {
    return x + y;
}

// 函数调用
add(1, 2);

// 组件定义
class Icon extends Component {}

// 组件调用???

让我感到惊讶的不是围绕着这个问题的一些困惑,而是我收到了大量不准确的回答。

造成我困惑的主要原因是JSX和在React中实际发生了什么之间,通常没有讨论抽象层。为了回答这个问题,我们需要深入挖掘这种抽象。

让我们从React的基础知识开始吧。什么是React?他是构建用户界面的库。无论React或者React生态系统看起来有多复杂,React的核心是——构建UIs。有了这个思路,我们先看我们的第一个定义,元素(Element)。简言之,React元素描述了展现在屏幕中你想要看到的内容。复杂点说,React元素是DOM节点的一个对象表示(object representation)。注意,我使用了词语是“描述”。重要的是要注意,Reast元素不是你在屏幕上看到的东西,而仅仅是一个对象表示。这里有几个原因。首先是JavaScript对象是轻量级的——React可以在没有太大的开销的情况下创建和销毁这些元素。第二,React能够分析这个对象,通过和前一个对象表示进行比较来区别其中的变化,进而更新那些仅仅发生改变的实际DOM(actual 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'
    }
}

当使用ReactDOM.render将它渲染成DOM,我们将会得到一个新的DOM节点,像这样

Login

到目前为止,都很好。有趣的是学习React,通常第一件事情教你的是组件。“组件(Components)是构建React的块”。注意,然而,这篇文章我们是以元素开始介绍的。是因为,一旦你了解了元素,那么理解组件会是一个平滑的过度。组件是一个函数或者一个类,它可 选的接收输入并返回一个React元素。

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

通过上面的函数定义, 我们有一个Button组件,它接收onLogin的输入并且返回一个React元素。需要注意的一件事是,Button组件接收一个onLogin方法作为它的prop。为了将将其传递给DOM对象表示,我们将它作为第二个参数传递给createElement,就像id·属性一样。

到目前为止,我们只介绍了使用本地HTML元素(span, div等)的“type”属性创建React元素,但是也可以将其它React组件传递给createElement的第一个参数。

const element = React.createElement(
    User,
    {name: 'TylerMcGinnis'},
    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元素,一个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中被称为reconciliation,并且每次调用setState或者ReactDOM.render时触发。

现在再来看看我们最初提出的问题

// 函数定义
function add(x, y) {
    return x + y;
}

// 函数调用
add(1, 2);

// 组件定义
class Icon extends Component {}

// 组件调用???

现在我们用用解答这个问题的所有知识,除了一个重要的部分。奇怪的是,如果你已经在任何时间使用React,你没有使用React.createElement来创建你的DOM对象表示。相反的,你可能使用JSX。前面我写过“造成我困惑的主要原因是JSX和在React中实际发生了什么之间,通常没有讨论抽象层。”。这个抽象层是JSX总是通过Babel传递给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 })
  )
}

转换之后的结果:

function Button ({ addFriend }) {
  return (
    Add Friend
  )
}

function User ({ name, addFriend }) {
  return (

      {name}


  )
}

所以最后,当我们将我们的组件写成<Icon />,我们应该将它称为什么?

因为JSX被传递之后,我们可以称它为“创建一个元素(creating an element)”,这正是发生了什么。

React.createElement(Icon, null)

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

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

Hello!

更多请参考:“React Components, Instances, and Elements”

w3ctech微信

扫码关注w3ctech微信公众号

共收到0条回复