设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

React组件,元素和实例

2016-1-17 18:30| 发布者: joejoe0332| 查看: 1339| 评论: 0|原作者: 祝青, leoxu, gracesyuan|来自: oschina

摘要: 如果你是 React 新手,也许你以前只使用过组件类和实例。例如你也许会通过创建一个类的方式声明一个 Button 组件。当 App 运行的时候,你也许会在屏幕上创建几个 Button 组件的引用,每个引用都有自己的属性和本地状 ...

组件,实例和元素之间的不同点困扰了很多 React 初学者。为什么会有三个不同的术语指向屏幕的绘图。


管理实例

如果你是 React 新手,也许你以前只使用过组件类和实例。例如你也许会通过创建一个类的方式声明一个 Button 组件。当 App 运行的时候,你也许会在屏幕上创建几个 Button 组件的引用,每个引用都有自己的属性和本地状态。这是传统的面向对象 UI 程序。那为什么会有元素呢?

在这个传统的 UI 模块里,由你来管理子组件实例的创建和销毁。如果一个 Form 组件想渲染一个 Button 组件,它需要创建它的实例,并手动的保持信息的更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Form 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 这样库时,你最终还是或多或少的像通常那样写一个复合 UI 代码。


每一个组件都得保留到其DOM节点还有其子组件实例的引用,并且在正确时间对它们进行创建、更新和销毁操作。代码行数量的增长是潜在的组件语句数量的平方,而父组件得直接访问它们的子组件实例,使得将来要对它们进行解耦会很困难。

React 如何不同了呢?

元素来描述树

在React中,由元素来解决问题。元素就是一个单纯的对象,它描述了一个组件实例或者DOM节点机器需要的属性。 它只包含有关于组件类型(例如:一个Button),组件的属性(例如:它的颜色),以及它里面的子元素这些信息。

元素并不是一个实在的实例,而是一种告诉React你想要在屏幕上看到什么东西的方式。你不能在元素上面调用方法。它只是一个不可修改的描述性对象,带有两个属性域:type:string|ReactClass)和props:Object1 。



 DOM 元素

当元素的类型为string时,就用它来代表以元素type值作为标记名称、以props值作为其属性的DOM节点。这就是React将会渲染的东西。例如:

1
2
3
4
5
6
7
8
9
10
11
12
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}



这个元素只是一种将下面的HTML表示为一个纯对象的方式:

1
2
3
4
5
<button class='button button-blue'>
  <b>
    OK!
  </b>
</button>



注意元素是如何被嵌套的。根据约定,当我们想要常见一个元素树时,我们会将一个或者更多个子元素指定为它们容器元素的children属性。

重要的是子元素和父元素都只是描述性的,而不是实实在在的实例。当你创建它们时,它们并不代表屏幕上的任何东西。你可以将它们创建出来然后仍在一旁,并没有什么关系。

React 元素容易移动,不需要做什么转换,当然也不实际的DOM元素更轻量——它们都只是对象而已啦!


组件元素

    不过,元素的类型也可以是一个对应到React组件的函数或者类:

1
2
3
4
5
6
7
{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}

    这是  React  的核心概念。

描述一个组件的元素还是一个元素,就跟一个描述DOM节点的元素没什么两样。它们可以相互嵌套和混合:

 这个特性然你可以用一个带有特殊的 color 属性的Button来定义一个DangerButton,不用担心Button会被渲染成一个DOM(button)、一个<div>还是别的什么东西:

1
2
3
4
5
6
7
const DangerButton = ({ children }) => ({
  type: Button,
  props: {
    color: 'red',
    children: children
  }
});

你可以在一个单独的元素中混合并匹配DOM和组件元素:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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的话 JSX:

1
2
3
4
5
6
7
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 在一个<div>中包含一个Button和一个DangerButton.


组件封装了元素树

当 React 发现了一个带有函数或者类类型的元素,它就会知道要根据提供的对应 props 请求向那个组件请求其所渲染的元素。

当它发现这个元素时:

1
2
3
4
5
6
7
{
  type: Button,
  props: {
    color: 'blue',
    children: 'OK!'
  }
}



React 会向 Button 请求其所渲染的东西。Button 将会返回这个元素:

1
2
3
4
5
6
7
8
9
10
11
12
{
  type: 'button',
  props: {
    className: 'button button-blue',
    children: {
      type: 'b',
      props: {
        children: 'OK!'
      }
    }
  }
}



React 会重复这一过程,知道它已经知悉了页面上每个组件所依赖的 DOM 标记元素。

React 就像是一个小孩,会针对每个“X 是 Y”的命题提出“什么是 Y”这种问题,要你来做出解释,直到他们搞明白了事情的所有细节。

还记得上面的 From 示例吗?它可以用 React 写成下面这个样子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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 节点的元素,以及描述了其它组件的元素。这样就让你能将 UI 的各个独立部分组装起来,而不用去依赖于它们的内部 DOM 结构。

我们让 React 来创建、更新和销毁实例,用元素来描述它们并将它们从组件那里返回,而 React 会负责管理这些实例。


组件可以是类或者函数

在上述的代码中, Form, Message, 和 Button 都是 React 组件。他们可以想上面那样写成函数, 也可以是继承自 React.component 的类。这里这三种声明组件的方式几乎是对等的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// 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
          }
        }
      }
    };
  }
}



当把组件定义成类是,会比一个函数式的组件更强大一些。它可以在对应的 DOM 节点创建或者销毁时存储一些本地状态并执行一些自定义的逻辑。

函数式组件功能较少不过更简单,使用一个 render() 方法的效果同类组件相似。除非你需要使用一种只在类中具备的功能特性,我们建议你还是使用函数式的组件。

不过,函数亦或者类,基本上他们都是 React 的组件,所以都是以 props 作为输入,以返回的元素作为输出。


自上而下的整合

当你像下面这样调用:

1
2
3
4
5
6
7
ReactDOM.render({
  type: Form,
  props: {
    isSubmitted: false,
    buttonText: 'OK!'
  }
}, document.getElementById('root'));



React 会根据这些 props 向 From 组件请求其返回的元素树。最终它会以一种相对更简单的原语来“提炼”其对你的组件树的理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 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 中所谓整合过程的一部分,这一过程开始于你调用 ReactDOM.render() 或者 setState() 时。在整合的末尾,React 就知道了结果 DOM 树,还有一个像 react-dom 或者 react-native 这样的渲染器,应用了需要更新到 DOM 节点(或者是 React Native 环境下的平台独立的视图)上的必要修改的最小集合。

这种逐步提炼的过程也是 React 容易优化的原因所在。如果你的组件树的某些部分对于 React 的访问效率而言变得过大,你可以告诉他跳过这个“提炼”的过程并且比较这个数的特定部分是不是没有发生改变。如果其是不可修改,那么要 props 有没有发生改变会非常快,因此 React 和不可变的方法在一起能运作的很好,并且最少的工作量就能产生很棒的优化效果。

你可能已经注意到这篇博客的字里行间许多都是关于组建和元素的,而关于其实例并没有聊那么多。事实上,实例比起大多数面向对象 UI 框架中,其在 React 的重要性要小得多。


只有声明为类的组件拥有实例,而你从来不会去直接创建它们: React 会为你做这些事儿。而存在的父组件访问子组件实例的机制, 只被用于必要的操作(比如设置一个输入域的焦点),并且一般应该避免这样做。

React 会为每个类组件负责创建一个实例,因此你可以用一种面向对象的方法使用方法和本地状态编写组件,但除此之外,实例在 React 的编程模型中并不是特别重要,并且它是有 React 自身去管理的。

总结

元素时一个纯对象,描述了你想要在屏幕上显示的 DOM 节点或者其它组件。元素可以在他们的 props 中包含其它元素。创建一个 React 组件是廉价的。元素一旦被创建,就从不会被改变。

组件可以用几种不同的方式进行声明。它可以是一个带有 render() 方法的类。也可在简单的情况下被定义成一个函数。两种情况中它都是以 props 作为输入,以返回的元素树作为输出。

当组件会接收一些 props 作为输入,这是因为一个特定的父组件会返回一个带有其类型和 props 的元素。这就是为什么人们会说在 React 中 props 会单项流转: 从父元素到子元素。


实例是你在你写的组件类中使用 this 表示的东西,这在存储本地状态和对生命周期内的事件做出反应中是有用的。

函数式组件根本没有实例。类组件有实例,但是你不必自己创建一个组件实例 —— React 自己处理。

最终,使用 React.createElement()JSX, 或者 element factory helper 创建元素。不必通过在代码中写元素的简单类 —— 只要知到它们是隐藏的类就可以了。

深入阅读


1.所有的 React 元素由于安全原因都需要一个额外的 $$typeof: Symbol.for('react.element')字段声明。在上面的例子中遗漏了这段。这个博客的入口为元素使用内联对象,是为了让你知道下面会发生什么,但是代码不能运行,除非对元素你添加了 $$typeof,或者修改代码去使用 React.createElement() 或 JSX。


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部