关于如何在React中组合组件的知识

324 阅读6分钟

当我们谈论构件时,我们谈论的是使用较小的碎片来组装一个较大的内聚单元。在React中,元素被用来创建组件。换句话说,像div、spans、form等常见的HTML片段被安排在一起以创建一个组件。在谈论React中的组件时,我们指的是现在把一个或多个组件拿出来,用它们来构建应用程序的一个更大的部分。所以在本教程中,我们将看看如何在利用组合的同时处理一棵组件树。


当前的单一项目

在这一点上,我们正在使用create react app来承载一个简单的react项目。index.js文件和item.jsx文件如下所示,它给了我们相关的输出。

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";
import Item from "./components/item";

ReactDOM.render(<Item />, document.getElementById("root"));
import React, { Component } from "react";

class Item extends Component {
  state = {
    count: 0
  };

  handleIncrement = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <React.Fragment>
        <div className="card mb-2">
          <h5 className={this.styleCardHeader()}>{this.styleCount()}</h5>
          <div className="card-body">
            <button
              onClick={() => {
                this.handleIncrement({ item });
              }}
              className="btn btn-lg btn-outline-secondary"
            >
              Increment
            </button>
          </div>
        </div>
      </React.Fragment>
    );
  }

  styleCardHeader() {
    let classes = "card-header h4 text-white bg-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items!" : count;
  }
}

export default Item;

这给了我们一种整洁的效果。当没有项目时,标题是黄色的文字。一旦你点击增加计数,标题就会变成蓝色,显示有多少个项目存在。


组成组件

现在,让我们创建一个新的(复数)组件,它将使用单数的在应用程序中组成一个更大的功能,而不是在反应应用程序中只渲染单个的。为了开始,我们可以在Visual Studio Code中的组件目录中添加一个新的items.jsx文件。
new items-jsx file react


渲染包含的组件

这个新的items.jsx组件将渲染一个items.jsx组件的列表。为了使其发挥作用,我们需要对index.js文件进行调整。这个文件将不再导入和渲染一个单独的 item.jsx 组件,但现在将导入这个新的组件 items.jsx 并渲染它。注意这个文件的更新。

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import "bootstrap/dist/css/bootstrap.css";
import Items from "./components/items";

ReactDOM.render(<Items />, document.getElementById("root"));

在items.jsx中添加一些代码

我们的新items.jsx文件中还没有任何代码。我们想用这个组件来渲染一个以上的 item.jsx 组件。我们可以先简单地渲染两个item.jsx组件,像这样。

import React, { Component } from "react";
import Item from "./item";

class Items extends Component {
  render() {
    return (
      <React.Fragment>
        <Item />
        <Item />
      </React.Fragment>
    );
  }
}

export default Items;

很好!这就渲染出了两个完全相互隔离的组件。


从硬编码到基于状态

在上面的例子中,组件被简单地硬编码到组件的渲染方法中。我们可以在组件中添加一个新的状态对象,它将持有一个对象的数组,而不是硬编码它们。有了这个对象,我们可以再次使用熟悉的 **map**方法来渲染多个组件。

import React, { Component } from "react";
import Item from "./item";

class Items extends Component {
  state = {
    items: [
      { id: 1, value: 0 }, 
      { id: 2, value: 0 }, 
      { id: 3, value: 0 }
    ]
  };
  render() {
    return (
      <React.Fragment>
        {this.state.items.map(item => (<Item key={item.id} />))}
      </React.Fragment>
    );
  }
}

export default Items;

现在我们有三个独特的组件被渲染出来了。

请注意,我们在react中渲染列表时加入了唯一键的要求。


向组件传递数据

我们现在已经建立了一种父/子关系。组件是组件的父对象。现在,我们希望能够将数据从父类传递给子类。我们可以用props对象来做这件事。每个 react 组件都有一个名为 props 的属性,这是一个可以用来传递数据的 JavaScript 对象。因此,首先,我们在实际的父组件中为被渲染的子组件设置一个属性。所以在下面突出显示的代码中,这个 **value**属性是使用父组件的状态来设置的。


items.jsx (parent!)

import React, { Component } from "react";
import Item from "./item";

class Items extends Component {
  state = {
    items: [
      { id: 1, value: 0 }, 
      { id: 2, value: 10 }, 
      { id: 3, value: 0 }
    ]
  };
  render() {
    return (
      <React.Fragment>
        {this.state.items.map(item => (
          <Item 
            key={item.id} 
            value={item.value} 
          />
        ))}
      </React.Fragment>
    );
  }
}

export default Items;

孩子现在可以接受来自props对象的属性。在item**.jsx**文件中,我们现在可以使用从父类传递来的值,在渲染时初始化每个项目的计数。这是一个关键点!用来初始化component的数据不再是来自它自己的状态对象,而是来自它的父级状态对象,它通过prop.jsx将数据传递下来。


item.jsx(孩子!)。

import React, { Component } from "react";

class Item extends Component {
  state = {
    count: this.props.value
  };

  handleIncrement = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <React.Fragment>
        <div className="card mb-2">
          <h5 className={this.styleCardHeader()}>{this.styleCount()}</h5>
          <div className="card-body">
            <button
              onClick={item => {
                this.handleIncrement({ item });
              }}
              className="btn btn-lg btn-outline-secondary"
            >
              Increment
            </button>
          </div>
        </div>
      </React.Fragment>
    );
  }

  styleCardHeader() {
    let classes = "card-header h4 text-white bg-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items!" : count;
  }
}

export default Item;

现在,当页面第一次加载,所有的组件被初始化时,我们可以看到第二个元素的起始计数是10。
react child components initialized via parent state

这是为什么呢?因为在父项.jsx中,状态对象看起来是这样的。

state = {
  items: [
    { id: 1, value: 0 }, 
    { id: 2, value: 10 }, 
    { id: 3, value: 0 }
  ]
};

通过道具传递子对象

除了通过在被渲染的项目上设置的属性将数据从父代传递给子代之外,我们还可以在被渲染的项目的开头和结尾标签之间放置标记。然后,这些标记可以通过this.props.children在子项中被获取。让我们在这里修改父项中的标记。我们修改,使其有一个开头和结尾标签。然后我们在这些开头和结尾标签之间有一些html。


items.jsx(父级!)。

import React, { Component } from "react";
import Item from "./item";

class Items extends Component {
  state = {
    items: [{ id: 1, value: 0 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
  };
  render() {
    return (
      <React.Fragment>
        {this.state.items.map(item => (
          <Item key={item.id} value={item.value}>
            <div className="card-footer text-muted">
              Footer passed via prop.children!
            </div>
          </Item>
        ))}
      </React.Fragment>
    );
  }
}

export default Items;

现在看看我们如何在子组件中访问这个children属性。


item.jsx (child!)

import React, { Component } from "react";

class Item extends Component {
  state = {
    count: this.props.value
  };

  handleIncrement = e => {
    this.setState({ count: this.state.count + 1 });
  };

  render() {
    return (
      <React.Fragment>
        <div className="card mb-2">
          <h5 className={this.styleCardHeader()}>{this.styleCount()}</h5>
          <div className="card-body">
            <button
              onClick={item => {
                this.handleIncrement({ item });
              }}
              className="btn btn-lg btn-outline-secondary"
            >
              Increment
            </button>
          </div>
          {this.props.children}
        </div>
      </React.Fragment>
    );
  }

  styleCardHeader() {
    let classes = "card-header h4 text-white bg-";
    classes += this.state.count === 0 ? "warning" : "primary";
    return classes;
  }

  styleCount() {
    const { count } = this.state;
    return count === 0 ? "No Items!" : count;
  }
}

export default Item;

这在浏览器中给我们提供了以下输出。
react prop children example


使子组件动态化

children道具也可以传递动态数据。这里有一个更新的版本,现在在页脚区域显示每个的id。

import React, { Component } from "react";
import Item from "./item";

class Items extends Component {
  state = {
    items: [{ id: 1, value: 0 }, { id: 2, value: 10 }, { id: 3, value: 0 }]
  };
  render() {
    return (
      <React.Fragment>
        {this.state.items.map(item => (
          <Item key={item.id} value={item.value}>
            <div className="card-footer text-muted">
              Footer For Item Number <b className="badge badge-info">{item.id}</b>
            </div>
          </Item>
        ))}
      </React.Fragment>
    );
  }
}

export default Items;

dyamic data with react children


组成React组件总结

在本教程中,我们学习了一些关于如何在React中组合组件的知识。当定义一个组件时,render()函数可以包含对其他组件的引用,正如我们在上面看到的。当一个组件包含另一个组件时,这就是我们所说的组合或合成组件的意思。通过使用组合和道具,你有能力以你认为合适的方式定制组件的外观和行为。