React 中关于组件,元素还有实例的关系说明


ref:https://facebook.github.io/react/blog/2015/12/18/react-components-elements-and-instances.html


对于很多人来说在 React 里面, 有这么几个名词总是让人迷惑, 组件, 他们的实例以及元素. 他们到底是什么关系呢,又有怎么样的区别呢?

#管理实例

如果刚开始接触到 React, 可能看到最多的就是通过组件这个类来创建实例来完成 React 的 render. 例如我们通过一个`类`定义了一个按钮的组件, 当 app 运行时, 你可能在 UI 上看到很多歌这个组件的实例,他们各自都在维护者自己的属性和状态. 这个是一个`完全面向对象`的方式来编程. 那么为什么还要介绍`元素`呢?

在一个传统的 UI 模型里,完全由你来掌控子组件的生命周期, 入股一个`Form` 表单组件希望渲染一个`按钮`组件,它就需要创建一个它的实例, 而且还要每次去维护它的状态.

```
class From extends TraditionalObjectOrientedView {
  render() {
    //Read some data passed to the view
    const { isSubmitted, buttonText } = this.attrs;
    
    if ( !isSubmitted && !this.button ) {
      //Form is not yet submitted, create the button!
      this.button = new Button({
        children: buttonText,
        color: 'blue'
      });
      this.el.appendChild(this.button.el);
    }
    if (this.button) {
      // The button is visible. Update its text!
      this.button.attrs.children = buttonText;
      this.button.render();
    }

    if (isSubmitted && this.button) {
      // Form was submitted. Destroy the button!
      this.el.removeChild(this.button.el);
      this.button.destroy();
    }

    if (isSubmitted && !this.message) {
      // Form was submitted. Show the success message!
      this.message = new Message({ text: 'Success!' });
      this.el.appendChild(this.message.el);
    }
  }
}
```

这段伪代码就是在说明如果你希望能够和如` Backbone` 这样的面向对象的框架一起工作时,当通过面向对象的方式是如何创建一个复合组件的.

**每一个组件的实例都要和 DOM 里的节点维护他们的引用,包括所有的子组件的实例,要一直保持他们的状态同步.** 这样的情况就会使代码量猛增. 而且父组件还要和子组件发生关系,这样在日后更加难以解耦. 这就是问题所在. 那么 React 是如何解决的呢?

#元素描述树

这个时候就应运而生了`元素`这个事物,它很好地解决了上面的问题. 一个`元素`就是一个简单的对象,它描述了一个组件实例或者一个 DOM 节点以及它的属性. 它包含了所有的组件数据,如类型( type:buttong),属性(it's color), 还有它的任何而子节点.

**一个元素并不是一个实例**,它只是用来告诉 React 它最后是一个什么样子,在这个元素里面没有任何的方法,它只是一个不可变的对象包含了两个属性 type 和 props.

## DOM 元素

当一个元素它的`类型`是字符串的时候,就表明它是一个 DOM 节点, 这个 type 就是它的 tag 命.而` props` 里对象的属性就是这个节点的所有属性.

如我们有这样一个 **元素**: 

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

对应的通过 React 的 render 最后对应的 DOM 就是:

```

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

```

**注意**在这个方式里我们如何来实现嵌套的节点树呢,通常我们是通过这个对象的 props 里的添加` children` 属性来实现**. 这里要说明的就是所有的对象对象描述 DOM 节点的,他们并不是对应的实例.

##组件元素

在写元素对象的时候,` type` 对象的值还可以是一个函数或者类,它代表着 React 的组件.

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

这个应该是 React 的一个核心想法.

一个元素来描述一个组件它同样是一个元素.正如它来描述 DOM 节点他们完全可以混用.

这样就可以定义一个`DangerButton`组件只是给它一个特殊的颜色属性基于` button` 元素.

```
const DangerButton = ({ children }) => ({
  type: Button, //已经定义的 button 元素
  props: {
    color: 'red',
    children: children
  }
});
```

你可以在一个单一的元素树里混用 DOM 和组件元素:

```
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>
);
```
通过上面这样的混用搭配就很好的完成了解耦,让这些元素之间可以表达` is-a` 或者` has-a` 的关系.

- `Button`就是一个对应的 DOM button, 还有特定的属性;
- `DangerButton` 就是一个特定的 Button 加上特定的属性;
- `DeleteAccount` 就是一个包含了 `Button `和 `DangerButton` 的` div`


##组件来封装元素树

当 React 遇到在一个元素的描述 type 的值是函数或者类的时候,它就知道去找对应的组件来完成渲染. 如:

```
{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}
```
React 就会问 button它会渲染成什么样,然后返回这样的结果:

```

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

紧接着 React 就会一层层往下找知道知道最会的对应的 DOM 节点元素. 还记得我们在一开始提到的那个` form` 的例子吗, 它可以写成这样的:


```
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'
    }
  };
};

```

也就是谁, **对于 React 组件来说, props 就是输入,而元素树就是输出**.

返回的元素树可以包含描述 dom 的元素( react element) 还可以包含组件元素,不管怎么样他们都会是相对独立自由组合的,而最后的产物就是 DOM 而已.

我们通过 React 来创建,更新,销毁这些实例,我们通过使用组件返回的元素来描述他们.而 React 来管理他们的实例.

##组件可是函数或者类

上面代码里的` Form`, `message` 还有` Button` 他们都是**React组件**.他们可以写成函数的形式,或者可以写成继承自` React.Component` 的类.下面这三种方式基本上是等效的:

```
// 1) As a function of props
const Button = ({ children, color }) => ({
  type: 'button',
  props: {
    className: 'button button-' + color,
    children: {
      type: 'b',
      props: {
        children: children
      }
    }
  }
});

// 2) Using the React.createClass() factory
const Button = React.createClass({
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
});

// 3) As an ES6 class descending from React.Component
class Button extends React.Component {
  render() {
    const { children, color } = this.props;
    return {
      type: 'button',
      props: {
        className: 'button button-' + color,
        children: {
          type: 'b',
          props: {
            children: children
          }
        }
      }
    };
  }
}
```

当一个组件用 class 的形式来定义的时候,它可以做更多的事情.你可以给它添加本地的状态, 一些自定义的逻辑在它渲染成 DOM 节点的时候.

一个函数组件就相对功能单一,它只是相当于一个类组件只有一个` render` 方法.除非你想要一些多余的功能一般函数组件就够用.

但是不管是函数的还是类的.他们都只是 React 的组件而已,他们都是通过 props 作为输入,返回 DOM 元素作为输出而已.

## 从上至下合作

当你调用:

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

```

React 就会通过这些属性来写询问这些组件到底要返回什么的元素,

```
// 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 `,当 react 在调用 `ReactDOM.render` 或者` setState` 的时候通过`reconciliation ` react 就可以得到最后的结果, 然后再通过` react-dom` 或者` react-native` 拿到最小的变化更新到对应的节点上.



....



#总结


一个元素就是一个描述的对象,它和组件 DOM 节点等都没有直接的关系. 创建他们就会变得很轻量级. 一个组件可以通过一个类含有一个`render` 方法的方式创建, 还可以是一个函数, 但是不管怎么样都是通过 props 作为输入然后返回元素树来输出.

一个实例可以通过组件类里的`this` 来应用.用来访问本地状态执行生命周期的方法.
函数类型的组件并没有实例一说,类组件有它的实例.

别自己直接去写一个元素,通过 React.createElement 或者 JSX 或者元素工厂方法来创建.