添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

英文原文 : React Components, Elements, and Instances

组件( Components )和组件实例(their Instances ),还有元素( elements )这几个概念困扰了很多React初学者。为什么同是需要渲染到屏幕上的东西却需要三个不同的概念呢 ?

管理实例 Manage the Instances

如果你是一个React新手,很可能之前你只跟组件类(component class)和实例(instance)打过交道。举个例子,你可能会通过创建一个类 class 来声明一个按钮组件( Button component )。应用程序运行的时候,在屏幕上你可能有这个组件( component )的好多个实例( instance ),每个实例都有自己的属性和仅仅属于自己的状态。这就是传统的面向对象UI编程。那么为什么还要引入元素( element )的概念呢?

在传统的UI模型中,怎么创建和销毁组件实例完全取决于你。如果一个表单组件( Form component )想渲染一个按钮组件( Button component ),它需要创建这个按钮的实例,然后在有任何新信息的时候手工保持该按钮实例到最新状态。举例如下 :

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);

这是一段伪代码,但它多多少少已经很接近你编写组合UI时,通过类似Backbone这样的库,用面向对象的方式可以一致地工作的最终代码了。

每个组件实例必须保持对它自己和所有孩子节点的DOM节点的引用(reference),而且还必须在合适的时机创建,更新和销毁它们。组件可能的状态增加时代码行数可能成平方地增长,而且父组件直接访问孩子组件实例,这会导致你在将来想解耦变得很困难。

那么React又有什么不同呢 ?

元素用来描述树 Elements Describe the Tree

在React中,这是元素element帮助我们的地方。一个元素是一个普通对象,他描述一个组件实例和它所要求的属性,或者一个DOM节点和它所要求的属性。它仅仅包含以下有关信息 :

  • 组件类型 (比如,这是一个按钮 Button)
  • 属性 (比如,它的颜色 color)
  • 它所包含的若干个子元素

一个元素并不是一个的实例。相反,它是一种告诉React你想在屏幕上看到什么的手段。元素上的任何方法你都不能调用。它只是一个不可变的描述性对象,仅有两个字段(field):type(string|ReactClass)props : Object

DOM元素 DOM Elements

当一个元素类型type是字符串(string)时,该元素表示一个DOM节点,其类型字符串是该DOM节点的标签名称,另外一个属性props对应地表示DOM节点属性。这是React要往屏幕上渲染的内容。例子如下 :

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

该元素只是以一种普通对象的形式描述了如下HTML片段 :

<button class='button button-blue'>
</button>

注意一下元素是怎么嵌套的。这里的约定是,当我们想创建一棵元素树的时候,我们将容器元素的属性props.children设置为一个或者多个子元素。

这里比较重要,值得一提的是,父子节点都是描述,而不是真正的实例。当你创建它们时,他们不指向屏幕上的任何东西。你可以创建它们然后把它们丢掉,这都没太大关系。

React元素很容易遍历,不需要分析,而且他们显然比真正的DOM元素更轻量级,因为他们只是普通的对象(object)!

组件元素 Component Elements

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

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

这正是React的核心概念。

描述组件的元素也还是元素,跟描述DOM节点的元素一样。他们也可以跟其他元素嵌套或者混在一起使用。

该功能能够让你定义DangerButton为一个指定颜色color的按钮Button并且彻底地无需担心Button是被渲染成了一个DOM <button>,一个DOM <div>还是一个其他什么东西 :

const DangerButton = ({ children }) => ({
  type: Button,
  props: {
    color: 'red',
    children: children
});

在同一棵元素树中,你可以混合和匹配使用DOM和组件元素如下 :

const DeleteAccount = () => ({
  type: 'div',//DOM元素
  props: {
    children: [{
      type: 'p',//DOM元素
      props: {
        children: 'Are you sure?'
    }, {
      type: DangerButton,//组件元素
      props: {
        children: 'Yep'
    }, {
      type: Button,//组件元素
      props: {
        color: 'blue',
        children: 'Cancel'
});

或者如果你喜欢JSX方式,它是这个样子:

const DeleteAccount = () => (
    <p>Are you sure?</p>
    <DangerButton>Yep</DangerButton>
    <Button color='blue'>Cancel</Button>
  </div>

这种混合和匹配的使用方式(mix and match)将组件之间解耦开来,因为它们在组合的过程中排他地使用了is-a和has-a关系 :

  • Button 是一个带有特定属性的 DOM <button>
  • DangerButton 是一个带有特定属性的 Button
  • DeleteAccount在一个<div>中包含了一个Button和一个DangerButton

组件封装了元素树 Components Encapsulate Element Trees

当React看到一个元素(element)的类型(type)是函数(function)或者类(class)时,它知道利用这个组件(component)的相应属性(props)得知它需要渲染(render)到什么元素(element)。

举例来讲,当它看到这样一个元素:

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

React 会询问 Button 它需要渲染到什么上去。然后 Button 返回了这样元素 :

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

React会重复这个步骤,直到它弄明白了页面上所有组件(component)底层的DOM标签元素(DOM tag element)是什么。

React就像一个小孩儿,先是十万个为什么,然后每个问题打破沙锅问到底,直到你向他解释得他们能够自己指出世界上每一个小东西都是什么。

还记得上面的Form例子吗? 它可以使用React重写成下面这样:

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组件(component),属性(props)是输入,一棵元素树(element tree)是输出。

返回的元素树既可以包含描述DOM节点的元素,也可以包含描述其他组件的元素。这样你就可以组合页面上相互独立的各部分而无需依赖对他们内部细节的了解。

我们让React创建,更新和销毁实例。我们使用组件返回的元素描述组件而React负责管理组件的实例。

Components Can Be Classes or Functions 组件可以是类或者函数

在上面的代码中,Form,Message,和Button都是React组件。它们可以被写成类似上面的函数,可以是通过React.createClass来创建的类,也可以写成继承自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 使用工厂方法 React.createClass()定义一个组件
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 ES6风格,使用继承自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作为输入,返回元素作为输出。

Top-Down Reconciliation 自顶向下的协调

当你这么调用时 :

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

React会询问 Form组件在这个属性props设置下会返回什么样的元素。它会使用类似下面的简化原语逐级细化对你的组件树的理解:

// 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()的时候会开始。协调过程结束时,React知道了最终DOM树应该是什么样子,然后一个类似于react-domt或者react-native渲染器会计算出更新DOM节点所需要的变化的最小集合应用到DOM树上(如果是React Native,则会渲染到平台相关视图)。

这个逐级细化的过程也是React应用易于优化的原因。如果你的组件树变得很大,对于React变得没办法高效地访问,你就可以告诉React,跳过那些相关属性没有发生变化的部分的"细化"而只处理树中有变化的差异部分

你可能注意到这篇博客讲了一堆关于组件(component)和元素(element)的东西,几乎没怎么说实例(instance)。真正的原因是,跟在其他绝大多数面向对象UI框架中的情况不同,React中实例没有那么重要。

只有以类形式定义的组件才会有实例存在,而且你从来不用直接创建它们的实例:React替你做了这些事情。尽管存在父组件实例访问子组件实例的机制,它们也仅仅用作命令式动作(比如设置某个域field的焦点focus)而且应该尽量被避免。

React处理了每个类组件的实例创建,所以你可以以面向对象的方式编写组件,可以带有方法(method)和本地状态(local state),但是除此之外,在React的编程模型中,实例没有那么重要,它们都是由React自己来管理的。

Summary 总结

一个元素element是一个普通对象(plain object),描述了对于一个DOM节点或者其他组件component,你想让它在屏幕上呈现成什么样子。元素element可以在它的属性props中包含其他元素(译注:用于形成元素树)。创建一个React元素element成本很低。元素element创建之后是不可变的。

一个组件component可以通过多种方式声明。可以是带有一个render()方法的类,简单点也可以定义为一个函数。这两种情况下,它都把属性props作为输入,把返回的一棵元素树作为输出。

一个实例instance是你在所写的组件类component class中使用关键字this所指向的东西(译注:组件实例)。它用来存储本地状态和响应生命周期事件很有用。

函数式组件(Functional component)根本没有实例instance。类组件(Class component)有实例instance,但是永远也不需要直接创建一个组件的实例,因为React帮你做了这些。

最后,创建元素的话,使用 React.createElement(),JSX 或者一个 元素工厂助手(element factory helper)。Don’t write elements as plain objects in the real code—just know that they are plain objects under the hood.

延伸阅读 Further Reading

译注 : 这部分内容不是翻译自原文,只是跟本文主题很相关。

英文原文 : React Components, Elements, and Instances组件(Components)和组件实例(their Instances),还有元素(elements)这几个概念困扰了很多React初学者。为什么同是需要渲染到屏幕上的东西却需要三个不同的概念呢 ?管理实例 Manage the Instances如果你是一个React新手,很可能之前你... interface Component&lt;P = {}, S = {}, SS = any&gt;{ // ..... } 组件基类的接口定义,P表示props,S表示state,SS未知,默认可以啥也不传 import React, { Component } from 'r...
React —— 组件实例的三大核心属性React —— 组件实例的三大核心属性一、state介绍初始化1.借助类的构造器对state进行初始化2.简写正确地使用 State1. 不要直接修改 State2. State 的更新可能是异步的3. State 的更新会被合并,而不是覆盖实例二、props三、refs React —— 组件实例的三大核心属性 一、state 1.借助类的构造器对state进行初始化 class Clock extends React.Component {
React选择元素 React选择元素 react-select-element实现标准的HTML <select />行为,而不使用任何<form />元素。 (当然,它可以组成实现它们的其他组件。) 您可以按原样使用它,也可以使用它extend自己的组件,修改其行为以适合您的需求。 虽然组件将一些className属性附加到其元素,但该包不包含任何CSS样式表。 示例实现包含,可以帮助您开始自己的。 import Select from 'react-select-element' const Select = require ( 'react-select-element' ) 在React中实施 任何一个: < Select index = { this . state . index } onChange = { ( index )
翻译自:https://engineering.hexacta.c... 截止目前我们已经可以使用JSX来创建并渲染页面DOM。在这一节我们将会把重点放在如何更新DOM上。 在介绍setState之前,更新DOM只能通过更改入参并再次调用render方法来实现。如果我们想实现一个时钟,代码大概下面这个样子: const rootDom =...
:warning: 该项目已被取代,并且不再维护。 模板React组件 模板可轻松开始使用TypeScript开发React组件。 克隆此存储库,并将此自述文件改编为您的React组件组件的代码位于src/lib文件夹中。 如果执行yarn build ,则此文件夹的内容将被捆绑。 只需根据您的需要修改Component.tsx文件开始,然后使用yarn start项目即可查看所做的更改。 将所有外部必需的功能,组件和变量导出到包入口点index.ts 。 然后,在安装软件包之后,可以在其他项目中使用这些导出。 要为您的组件编写单元测试( ),请在组件旁边添加一个文件,文件结尾为.test.tsx 。 然后使用yarn test执行yarn test 。 通过修改src/stories Component.stories.tsx文件,将组件用例记录为交互式故事( ),并随时为其他组件
内存走查的时候发现,app退出后堆栈中存在两个闪屏页的Activity,如上图所示。 导出hprof文件后,重新用AndroidStudio打开,找到SplashActivity如下图: 选择蓝色的这一行Jump to Source 出现了如下代码: private void createContext() { final ReactInstanceManager
constituent:常可与component换用,指某一整体不可少的部分或成分。 element:指一个整体必不可少或固有的部分,强调一个复杂整体中最基本、最简单的元件、元素或成分等。 ingredient
React 中,父组件获取子组件实例有几种常见的方法: 1. 使用 Refs:在父组件中,可以通过使用 `ref` 属性为子组件创建一个 ref,并将其附加到子组件实例上。然后可以通过 `ref.current` 来访问子组件实例。例如: ```jsx class ParentComponent extends React.Component { constructor(props) { super(props); this.childRef = React.createRef(); componentDidMount() { console.log(this.childRef.current); // 访问子组件实例 render() { return <ChildComponent ref={this.childRef} />; class ChildComponent extends React.Component { render() { return <div>Hello, World!</div>; 2. 使用回调函数:在子组件中定义一个接受子组件实例作为参数的回调函数,并通过属性传递给子组件。子组件在合适的时机调用该回调函数,将自身实例作为参数传递给父组件。例如: ```jsx class ParentComponent extends React.Component { handleChildRef = (childRef) => { console.log(childRef); // 访问子组件实例 render() { return <ChildComponent onRef={this.handleChildRef} />; class ChildComponent extends React.Component { componentDidMount() { this.props.onRef(this); // 将子组件实例传递给父组件 render() { return <div>Hello, World!</div>; 这两种方法都可以让父组件获取子组件实例。在选择使用哪种方法时,可以根据具体的场景和需求来决定。一般来说,使用 Refs 是最常用的方式,但如果需要在子组件挂载后立即获取实例,则使用回调函数的方式更适合。