Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

比较与理解React的Components,Elements和Instances #30

Open
creeperyang opened this issue Apr 26, 2017 · 4 comments
Open

比较与理解React的Components,Elements和Instances #30

creeperyang opened this issue Apr 26, 2017 · 4 comments

Comments

@creeperyang
Copy link
Owner

creeperyang commented Apr 26, 2017

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

本文在翻译React Components, Elements, and Instances的基础上,主要专注理解React的一个核心理念:用Elements Tree描述UI。本文也应该是接下来几片React相关文章的开头,所以更合适的标题可能是:

React学习笔记一:Components,Elements和Instances

请注意,阅读本文最好对React有基本的了解,但React新手也应该可以畅通阅读。

从JSX出发

现在我们写React应用,相当部分都是在写JSX。

JSX本身是对JavaScript语法的一个扩展,看起来像是某种模板语言,但其实不是。但正因为形似HTML,描述UI就更直观了,也极大地方便了开发;你想如果我们没有HTML,必须手写一堆的document.createElement(),我想前端们肯定已经崩溃了。

不过如果你一直写JSX,并且从来没脱离过JSX,可能某种程度上会阻碍我们理解React。当我们有一个JSX片段,它实际上是调用React API构建了一个Elements Tree:

var profile = <div>
  <img src="avatar.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-ahas-a的关系:

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

Components Encapsulate Element Trees

当React碰到typefunction|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,类似只有renderclass

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-domreact-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。

@creeperyang creeperyang changed the title 从比较Components,Elements和Instances来理解React.js 比较与理解React的Components,Elements和Instances Apr 26, 2017
@cdll
Copy link

cdll commented Apr 27, 2017

羡慕你们能玩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
  )
})

@didmehh
Copy link

didmehh commented Feb 8, 2018

https://medium.com/@dan_abramov/react-components-elements-and-instances-90800811f8ca
这篇也挺棒的.

@zhongjie-chen
Copy link

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

@creeperyang
Copy link
Owner Author

@zhongjie-chen

  1. 是,虚拟 DOM 本质上就是 Elements tree。
  2. 是,所以需要优化。
  3. JSX 最终编译成 Elements tree。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants