Unetway

ReactJS - Рендеринг реквизита

Термин «render prop» относится к простой методике совместного использования кода между компонентами React с использованием prop, значение которого является функцией. Компонент с поддержкой render принимает функцию, которая возвращает элемент React и вызывает его вместо реализации собственной логики визуализации.

<DataProvider render={data => (
  <h1>Hello {data.target}</h1>
)}/>

Библиотеки, в которых используются реквизиты рендеринга, включают React Router и Downshift .

Использовать опоры рендеринга для перекрестных проблем

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

Например, следующий компонент отслеживает положение мыши в веб-приложении:

class MouseTracker extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>
        <h1>Move the mouse around!</h1>
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

Когда курсор перемещается по экрану, компонент отображает координаты (x, y) в a <p>.

Теперь возникает вопрос: как мы можем повторно использовать это поведение в другом компоненте? Другими словами, если другой компонент должен знать о позиции курсора, можем ли мы инкапсулировать это поведение, чтобы мы могли легко поделиться им с этим компонентом?

Поскольку компоненты являются базовой единицей повторного использования кода в React, попробуем немного переработать код, чтобы использовать <Mouse> компонент, который инкапсулирует поведение, которое нам нужно повторно использовать в другом месте.

// The <Mouse> component encapsulates the behavior we need...
class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/* ...but how do we render something other than a <p>? */}
        <p>The current mouse position is ({this.state.x}, {this.state.y})</p>
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse />
      </div>
    );
  }
}

Теперь <Mouse> компонент инкапсулирует все поведение, связанное с прослушиванием mousemoveсобытий и сохранением позиции (x, y) курсора, но он еще не может быть повторно использован повторно.

Например, предположим, что у нас есть <Cat>компонент, который отображает изображение кота, преследующего мышь по экрану. Мы могли бы использовать <Cat mouse={{ x, y }}> опору, чтобы сообщить компоненту координаты мыши, чтобы он знал, где разместить изображение на экране.

В качестве первого прохода, вы можете попробовать рендеринг <Cat> внутри <Mouse> render метода, как это:

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class MouseWithCat extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*
          We could just swap out the <p> for a <Cat> here ... but then
          we would need to create a separate <MouseWithSomethingElse>
          component every time we need to use it, so <MouseWithCat>
          isn't really reusable yet.
        */}
        <Cat mouse={this.state} />
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <MouseWithCat />
      </div>
    );
  }
}

Такой подход будет работать для нашего конкретного случая использования, но мы не достигли цели по-настоящему инкапсулировать поведение в многоразовом режиме. Теперь, каждый раз, когда нам нужна позиция мыши для другого варианта использования, мы должны создать новый компонент (то есть существенно другой <MouseWithCat>), который делает что-то конкретное для этого случая использования.

Вот где появляется рендеринг: вместо жесткого кодирования <Cat> внутри <Mouse> компонента и эффективного изменения его визуализированного вывода мы можем предоставить <Mouse> функцию prop, которую он использует, чтобы динамически определять, что делать рендерингу.

class Cat extends React.Component {
  render() {
    const mouse = this.props.mouse;
    return (
      <img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
    );
  }
}

class Mouse extends React.Component {
  constructor(props) {
    super(props);
    this.handleMouseMove = this.handleMouseMove.bind(this);
    this.state = { x: 0, y: 0 };
  }

  handleMouseMove(event) {
    this.setState({
      x: event.clientX,
      y: event.clientY
    });
  }

  render() {
    return (
      <div style={{ height: '100%' }} onMouseMove={this.handleMouseMove}>

        {/*
          Instead of providing a static representation of what <Mouse> renders,
          use the `render` prop to dynamically determine what to render.
        */}
        {this.props.render(this.state)}
      </div>
    );
  }
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

Теперь вместо эффективного клонирования <Mouse> компонента и жесткого кодирования что-то еще в его render методе для решения в конкретном случае использования, мы предоставляем renderопору, которая <Mouse> может использовать для динамического определения того, что она отображает.

Более конкретно, поддержка render является функцией prop, которую компонент использует, чтобы знать, что делать.

Этот метод делает поведение, которое нам необходимо для обмена чрезвычайно портативными. Чтобы получить это поведение, визуализируйте a <Mouse> с помощью render prop, который сообщает ему, что делать с текущим (x, y) курсора.

Интересно отметить рендеринг реквизита, что вы можете реализовать большинство компонентов более высокого порядка (HOC), используя обычный компонент с поддержкой render. Например, если вы предпочитаете использовать withMouse HOC вместо <Mouse> компонента, вы можете легко создать его с помощью регулярного выражения <Mouse> с помощью команды render:

// If you really want a HOC for some reason, you can easily
// create one using a regular component with a render prop!
function withMouse(Component) {
  return class extends React.Component {
    render() {
      return (
        <Mouse render={mouse => (
          <Component {...this.props} mouse={mouse} />
        )}/>
      );
    }
  }
}

Таким образом, использование средства поддержки рендеринга позволяет использовать любой шаблон.

Использование реквизита, отличного от render

Важно помнить, что только потому, что шаблон называется «рендеринг реквизита», вам не нужно использовать прозвище, названное renderдля использования этого шаблона . Фактически, любая поддержка, которая является функцией, которую компонент использует, чтобы знать, что делать, является технически «рендерингом».

Хотя в приведенных выше примерах renderмы можем так же легко использовать childrenопору!

<Mouse children={mouse => (
  <p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>

И помните, что на children самом деле прозвище не нужно называть в списке «атрибутов» в вашем элементе JSX. Вместо этого вы можете поместить его прямо внутри элемента!

<Mouse>
  {mouse => (
    <p>The mouse position is {mouse.x}, {mouse.y}</p>
  )}
</Mouse>

Вы увидите эту технику, используемую в API реакции реакции. Поскольку этот метод немного необычен, вы, вероятно, захотите явно указать, что children должна быть функция в вашем propTypes при разработке API, как это.

Mouse.propTypes = {
  children: PropTypes.func.isRequired
};

Будьте осторожны при использовании Render Props с React.PureComponent

Использование поддержки рендеринга может отрицать преимущество, которое приходит от использования, React.PureComponent если вы создаете функцию внутри render метода. Это связано с тем, что сравнение неглубоких falseопор всегда будет возвращаться для новых реквизитов, и каждый renderв этом случае будет генерировать новое значение для рендеринга.

Например, продолжая с нашим <Mouse> компонентом сверху, если Mouse были продлить React.PureComponent вместо React.Component, наш пример будет выглядеть следующим образом:

class Mouse extends React.PureComponent {
  // Same implementation as above...
}

class MouseTracker extends React.Component {
  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>

        {/*
          This is bad! The value of the `render` prop will
          be different on each render.
        */}
        <Mouse render={mouse => (
          <Cat mouse={mouse} />
        )}/>
      </div>
    );
  }
}

В этом примере каждый раз <MouseTracker> визуализирует, он генерирует новую функцию как значение <Mouse render> пропеллера, тем самым отрицая эффект <Mouse> распространения React.PureComponent в первую очередь!

Чтобы обойти эту проблему, вы иногда можете определить prop как метод экземпляра, например:

class MouseTracker extends React.Component {
  // Defined as an instance method, `this.renderTheCat` always
  // refers to *same* function when we use it in render
  renderTheCat(mouse) {
    return <Cat mouse={mouse} />;
  }

  render() {
    return (
      <div>
        <h1>Move the mouse around!</h1>
        <Mouse render={this.renderTheCat} />
      </div>
    );
  }
}

В случаях, когда вы не можете определить опору статически (например, потому что вам нужно закрыть реквизиты и/или состояние компонента), <Mouse> следует расширить React.Component.