Кампазыцыя супраць насьледаваньня (Composition vs Inheritance)

React мае магутную мадэль складаньня, і мы рэкамэндуем для паўторнага выкарыстаньня коду паміж кампанэнтамі выкарыстоўваць кампазыцыю замест насьледаваньня.

У гэтым разьдзеле мы разгледзім некалькі праблемаў, дзе распрацоўшчыкі — навічкі ў React’е часта імкнуцца да насьледаваньня, і пакажам, як мы можам вырашыць іх з дапамогай кампазыцыі.

Зьмяшчальнасьць (Containment)

Некаторыя кампанэнты ня ведаюць сваіх дзяцей раней часу. Гэта асабліва характэрна для кампанэнтаў, такіх як бакавая панэль (Sidebar) або дыялёг (Dialog), якія выконваюць ролю агульных “скрынак (boxes)”.

Мы рэкамэндуем, каб такія кампанэнты выкарыстоўвалі адмысловую ўласьцівасьць (prop) children, каб перадаць сыноўнія элемэнты (children elements) ) непасрэдна ў іхні вывад (output):

function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

Гэта дазваляе іншым кампанэнтам перадаваць адвольных дзяцей да іх устаўкай JSX:

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}

Паспрабаваць на CodePen.

Усё, што знаходзіцца ўнутры JSX-тэга <FancyBorder>, перадаецца ў кампанэнт FancyBorder, як уласьцівасьць (prop) children. Пасьля таго, як FancyBorder рэндэрыць {props.children} унутры <div>, перададзеныя элемэнты зьяўляюцца ў канчатковым вывадзе (final output).

Хоць гэта й менш распаўсюджана, але часам вам можа спатрэбіцца некалькі “дзір (holes)” у кампанэнце. У такіх выпадках вы можаце прыдумаць сваю ўласную канвэнцыю замест выкарыстаньня children:

function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}

Паспрабуйце на CodePen.

React-элемэнты, такія як <Contacts /> і <Chat />, — усяго толькі аб’екты, так што вы можаце перадаваць іх у якасьці уласьцівасьцяў (props), як і любыя іншыя даныя. Такі падыход можа нагадаць вам пра “slots” у іншых бібліятэках, але няма ніякіх абмежаваньняў на тое, што вы можаце перадаваць у якасьці ўласьцівасьцяў (props) у React’е.

Спэцыялізацыя (Specialization)

Часам мы ўважаем кампанэнты за “прыватныя выпадкі” іншых кампанэнтаў. Напрыклад, мы можам сказаць, што WelcomeDialog зьяўляецца прыватным выпадкам кампанэнта Dialog.

У React’е гэта таксама дасягаецца за кошт кампазыцыі, дзе больш “спэцыфічны” кампанэнт рэндэрыць больш “агульны” ды канфігуруе яго з дапамогай уласьцівасьцяў (props):

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
    </FancyBorder>
  );
}

function WelcomeDialog() {
  return (
    <Dialog
      title="Welcome"
      message="Thank you for visiting our spacecraft!" />
  );
}

Паспрабуйце на CodePen.

Кампазыцыя (composition) працуе аднолькава добра і для кампанэнтаў, азначаных як клясы:

function Dialog(props) {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        {props.title}
      </h1>
      <p className="Dialog-message">
        {props.message}
      </p>
      {props.children}
    </FancyBorder>
  );
}

class SignUpDialog extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.handleSignUp = this.handleSignUp.bind(this);
    this.state = {login: ''};
  }

  render() {
    return (
      <Dialog title="Mars Exploration Program"
              message="How should we refer to you?">
        <input value={this.state.login}
               onChange={this.handleChange} />
        <button onClick={this.handleSignUp}>
          Sign Me Up!
        </button>
      </Dialog>
    );
  }

  handleChange(e) {
    this.setState({login: e.target.value});
  }

  handleSignUp() {
    alert(`Welcome aboard, ${this.state.login}!`);
  }
}

Паспрабуйце на CodePen.

Дык што наконт насьледаваньня (So What About Inheritance)?

На Facebook’у мы выкарыстоўваем React у тысячах кампанэнтаў і не знайшлі аніякіх выпадкаў, дзе б мы маглі парэкамэндаваць стварэньне герархіі насьледаваньня кампанэнтаў (component inheritance hierarchies).

Уласьцівасьці (props) ды кампазыцыя (composition) даюць вам усю неабходную гнуткасьць (flexibility), каб наладзіць зьнешні выгляд і паводзіны кампанэнта ў відавочны й бясьпечны спосаб. Памятайце, што кампанэнты могуць прымаць адвольныя ўласьцівасьці (props), уключаючы прымітыўныя значэньні, React-элемэнты або функцыі.

Калі вы хочаце паўторна выкарыстоўваць не-UI функцыянальнасьць паміж кампанэнтамі, то мы прапануем экстрагаваць яе ў асобны модуль JavaScript. Кампанэнты могуць імпартаваць яго ды выкарыстоўваць гэную функцыю, або гэны аб’ект ці кляс безь яе або яго пашырэньня (extending).