Статьи

Создание анимированных компонентов, или Как React делает D3 лучше

D3 отлично. Как jQuery в мире визуализации веб-данных, он может делать все, что только можно придумать.

Многие из лучших визуализаций данных, которые вы видели в Интернете, используют D3. Это отличная библиотека, и с недавним обновлением v4 она стала более надежной, чем когда-либо.

Добавьте React, и вы сможете сделать D3 еще лучше.

Смотреть визуализировать данные с D3.js
Проиллюстрируйте свои данные с помощью JavaScript

Как и JQuery, D3 мощный, но низкий уровень. Чем больше ваша визуализация, тем сложнее становится работать с вашим кодом, тем больше времени вы тратите на исправление ошибок и начесывание волос.

Реакт может исправить это.

Вы можете прочитать мою книгу React + d3js ES6 для более глубокого понимания или продолжить чтение, чтобы узнать, как лучше интегрировать React и D3. В практическом примере мы увидим, как создавать декларативную анимацию на основе переходов.

Версия этой статьи также существует в виде встречи на D3 на YouTube .

Стоит ли React?

ОК, Реакт большой. Это добавляет тонну кода к вашей полезной нагрузке, и это увеличивает ваш след зависимости. Это еще одна библиотека, которую вы должны постоянно обновлять.

Если вы хотите использовать его эффективно, вам понадобится шаг сборки. Что-то, чтобы превратить код JSX в чистый JavaScript.

Настроить Webpack и Babel в наши дни очень просто: просто запустите create-react-app Он предоставляет вам JSX-компиляцию, современные функции JavaScript, линтинг, горячую загрузку и минимизацию кода для производственных сборок. Здорово.

Несмотря на размер и сложность инструментов, React того стоит, особенно если вы серьезно относитесь к своей визуализации. Если вы создаете одноразовое приложение, которое вам никогда не придется обслуживать, отлаживать или расширять, придерживайтесь чистого D3. Если вы создаете что-то реальное, я призываю вас добавить React к миксу.

Для меня главное преимущество в том, что React Force настоятельно рекомендует вам компоновать ваш код. Другие преимущества являются либо признаками компонентизации, либо становятся возможными благодаря этому.

Основные преимущества использования React с вашим кодом D3:

  • компонентизация
  • более легкое тестирование и отладка
  • Smart DOM перерисовывает
  • горячая загрузка

Компонентация побуждает вас создавать свой код как последовательность логических единиц — компонентов. С JSX вы можете использовать их так, как если бы они были элементами HTML: <Histogram /><Piechart /><MyFancyThingThatIMade /> Мы углубимся в это в следующем разделе.

Создание вашей визуализации как серии компонентов облегчает тестирование и отладку . Вы можете сосредоточиться на логических единицах по одному. Если компонент работает здесь, он будет работать и там. Если он проходит тесты и выглядит хорошо, он будет проходить тесты и выглядеть хорошо, независимо от того, как часто вы рендерите его, где бы вы его ни помещали, и не важно, кто его вызывает. 🙌

React понимает структуру вашего кода , поэтому знает, как перерисовывать только те компоненты, которые имеют изменения. Нет больше тяжелой работы, чтобы решить, что сделать заново, а что оставить в покое. Просто поменяй и забудь . Реакт может понять это самостоятельно. И да, если вы посмотрите на инструмент профилирования, вы увидите, что перерисовываются только части с изменениями.

Анимированный алфавит с включенной вспышкой

С помощью create-react-app чтобы настроить инструмент, React может использовать горячую загрузку . Допустим, вы создаете визуализацию из 30 000 точек данных. В чистом D3 вы должны обновлять страницу при каждом изменении кода. Загрузите набор данных, проанализируйте набор данных, визуализируйте набор данных, щелкните по нему, чтобы достичь состояния, которое вы тестируете … зевок.

С React -> без перезагрузки, без ожидания. Просто немедленные изменения на странице. Когда я впервые увидел его в действии, мне показалось, что он ест мороженое, а на заднем плане играет крещендо из увертюры 1812 года . Ум = взорван.

Преимущества компонентизации

Компоненты это, компоненты это. Бла бла бла. Почему ты должен заботиться? Ваш код данных уже работает. Вы строите это, вы отправляете это, вы делаете людей счастливыми.

Но делает ли вас код счастливым? С компонентами это может. Компоненты облегчают вашу жизнь, потому что они делают ваш код:

  • декларативный
  • многоразовый
  • понятный
  • организованная

Ничего страшного, если это звучит как модный суп. Позволь мне показать тебе.

Например, декларативный код — это тот код, в котором вы говорите, что хотите, а не как хотите. Вы когда-нибудь писали HTML или CSS? Вы знаете, как написать декларативный код! Congratz!

React использует JSX, чтобы ваш JavaScript выглядел как HTML. Но не волнуйтесь, все это компилируется в чистый JavaScript за кулисами.

Попробуйте угадать, что делает этот код:

 render() {
  // ...
  return (
    <g transform={translate}>
      <Histogram data={this.props.data}
         value={(d) => d.base_salary}
         x={0}
         y={0}
         width={400}
         height={200}
         title="All" />
      <Histogram data={engineerData}
         value={(d) => d.base_salary}
         x={450}
         y={0}
         width={400}
         height={200}
         title="Engineer" />
      <Histogram data={programmerData}
         value={(d) => d.base_salary}
         x={0}
         y={220}
         width={400}
         height={200}
         title="Programmer"/>
      <Histogram data={developerData}
         value={(d) => d.base_salary}
         x={450}
         y={220}
         width={400}
         height={200}
         title="Developer" />
    </g>
  )
}

Если вы догадались «Отрисовывает четыре гистограммы» , вы были правы. Ура.

После создания компонента гистограммы вы можете использовать его так, как будто это обычный фрагмент HTML. Гистограмма отображается везде, где вы поместили <Histogram />

В этом случае параметрами являются координаты xywidthheighttitledatavalue Они могут быть чем угодно, что нужно вашему компоненту.

Параметры выглядят как атрибуты HTML, но могут принимать любой объект JavaScript, даже функции. Это как HTML на стероидах.

С некоторым шаблоном и правильным набором данных, этот код выше дает вам такую ​​картинку. Сравнение распределения заработной платы для разных типов людей, которые пишут программное обеспечение.

4 гистограммы распределения зарплаты

Посмотрите на код еще раз. Заметьте, как многоразовые компоненты? Как будто <Histogram /> За кулисами он компилируется в вызов функции — (new Histogram()).render() Histogram<Histogram />

Компоненты React должны следовать принципам хорошего функционального программирования. Никаких побочных эффектов, безгражданства, идемпотентности, сопоставимости. Если вы действительно, действительно хотите нарушить правила.

В отличие от функций JavaScript, где следование этим принципам требует преднамеренных усилий, React затрудняет кодирование таким образом. Это победа, когда ты работаешь в команде.

Декларативность и возможность повторного использования делают ваш код понятным по умолчанию. Если вы когда-либо использовали HTML, вы можете прочитать, что делает этот код. Вы можете не понимать деталей, но если вы знаете немного HTML и JavaScript, вы знаете, как читать JSX.

Сложные компоненты состоят из более простых компонентов, которые состоят из еще более простых компонентов, которые в конечном итоге состоят из чистых элементов HTML. Это держит ваш код организованным .

Когда вы вернетесь через шесть месяцев, вы сможете посмотреть свой код и подумать: «Ах да, четыре гистограммы. Чтобы настроить это, я должен открыть компонент гистограммы и ковыряться.

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

Позвольте мне показать вам пример — анимированный алфавит.

Практический пример

Анимированный алфавит

Мы собираемся построить анимированный алфавит. Не потому, что это самый простой пример совместного использования React и D3, а потому, что он выглядит круто. Когда я показываю это на живых выступлениях, люди всегда оооооооооо, особенно когда я показываю доказательства того, что перерисовываются только элементы DOM с изменениями.

Это сокращенная версия более углубленной статьи о React и D3 и переходах, которую я разместил в своем блоге несколько месяцев назад. Мы собираемся замаскировать некоторые детали в этой версии, чтобы сделать ее краткой. Вы можете погрузиться в полную кодовую базу в репозитории GitHub .

Код основан на React 15 и D3 4.0.0. Некоторый синтаксис, который я использую, например свойства класса, еще не поддерживается в стабильной ES6, но должен работать, если вы используете create-react-app

—-

Для создания анимированного алфавита нам понадобятся два компонента:

  • AlphabetLetter
  • Letter

Мы собираемся использовать React для рендеринга элементов SVG, и мы будем использовать D3 для переходов, интервалов и некоторой математики.

Алфавитный компонент

Компонент AlphabetLetter

Начнем со скелета, подобного этому:

 // src/components/Alphabet/index.jsx
import React, { Component } from 'react';
import ReactTransitionGroup from 'react-addons-transition-group';
import * as d3 from 'd3';

require('./style.css');

import Letter from './Letter';

class Alphabet extends Component {
  static letters = "abcdefghijklmnopqrstuvwxyz".split('');
  state = {alphabet: []}

  componentWillMount() {
    // starts an interval to update alphabet
  }

  render() {
    // spits out svg elements
  }
}

export default Alphabet;

Мы импортируем наши зависимости, добавляем стилизацию и определяем компонент Alphabet Он содержит список доступных букв в свойстве статических lettersalphabet Нам также понадобится componentWillMountrender

Лучшее место для создания нового алфавита каждые 1,5 секунды — в componentWillMount

 // src/components/Alphabet/index.jsx
  componentWillMount() {
    d3.interval(() => this.setState({
       alphabet: d3.shuffle(Alphabet.letters)
         .slice(0, Math.floor(Math.random() * Alphabet.letters.length))
         .sort()
    }), 1500);
  }

Мы используем d3.interval( //.., 1500) В каждом периоде мы перетасовываем доступные буквы, выделяем случайное количество, сортируем их и обновляем состояние компонента с помощью setState()

Это гарантирует, что наш алфавит будет как случайным, так и в алфавитном порядке. setState()

Наша декларативная магия начинается с метода render

 // src/components/Alphabet/index.jsx
render() {
  let transform = `translate(${this.props.x}, ${this.props.y})`;

  return (
    <g transform={transform}>
      <ReactTransitionGroup component="g">
        {this.state.alphabet.map((d, i) => (
          <Letter d={d} i={i} key={`letter-${d}`} />
        ))}
      </ReactTransitionGroup>
    </g>
  );
}

Мы используем SVG-преобразование, чтобы переместить наш алфавит в указанную (x, y)ReactTransitionGroupthis.state.alphabetLetter

Каждое Letterdi Атрибут key Использование ReactTransitionGroup

ReactTransitionGroup

В дополнение к обычным хукам жизненного цикла, которые сообщают нам, когда компонент монтируется, обновляется и демонтируется, ReactTransitionGroup предоставляет нам доступ к componentWillEntercomponentWillLeave Заметили что-то знакомое?

componentWillEnter.enter()componentWillLeave.exit()componentWillUpdate.update()

«То же самое» — сильная концепция; они аналогичны. Хуки D3 работают с целым выбором — группами компонентов, в то время как хиты React работают с каждым компонентом индивидуально. В D3 повелитель диктует, что происходит; в React каждый компонент знает, что делать.

Это облегчает понимание кода React. Я думаю. ¯ \ _ (ツ) _ / ¯

ReactTransitionGroupеще больше хуков , но эти три — все, что нам нужно. Хорошо, что и в componentWillEntercomponentWillLeave«Переход выполнен. Реагируйте, вернемся к вам » .

Моя благодарность Мишель Тилли за то, что она написала о ReactTransitionGroupо переполнении стека .

Компонент письма

Теперь мы готовы к классным вещам — компоненту, который может декларативно переходить в визуализацию и из нее.

Базовый скелет для нашего компонента Letter

 // src/components/Alphabet/Letter.jsx

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as d3 from 'd3';

class Letter extends Component {
    state = {
      y: -60,
      x: 0,
      className: 'enter',
      fillOpacity: 1e-6
    }
    transition = d3.transition()
                   .duration(750)
                   .ease(d3.easeCubicInOut);

    componentWillEnter(callback) {
      // start enter transition, then callback()
    }

    componentWillLeave(callback) {
      // start exit transition, then callback()
    }

    componentWillReceiveProps(nextProps) {
      if (this.props.i != nextProps.i) {
        // start update transition
      }
    }

    render() {
       // spit out a <text> element
    }
};

export default Letter;

Мы начнем с некоторых зависимостей и определим компонент Letter В большинстве случаев вы хотели бы избежать использования state Это то, что реквизит для. С переходами мы используем состояние, потому что оно помогает нам поддерживать реальность React в синхронизации с реальностью D3.

Тем не менее, эти магические значения по умолчанию могут быть реквизитами по умолчанию. Это сделало бы наш Alphabet

componentWillEnter

Мы помещаем входной переход в componentWillEnter

 // src/components/Alphabet/Letter.jsx
    componentWillEnter(callback) {
      let node = d3.select(ReactDOM.findDOMNode(this));

      this.setState({x: this.props.i*32});

      node.transition(this.transition)
        .attr('y', 0)
        .style('fill-opacity', 1)
        .on('end', () => {
            this.setState({y: 0, fillOpacity: 1});
            callback()
        });
    }

Мы используем reactDOM.findDOMNode()d3.select() Теперь все, что может сделать D3, может сделать наш компонент. Yessss! 🙌

Затем мы обновляем this.state.x Ширина — это значение, которое мы просто знаем ™. Помещение xiLetter

Когда Letter Чтобы оживить движение вниз и стать видимым, мы используем переход D3.

Мы используем node.transition(this.transition) Любые изменения .attr.style

Это сбивает с толку React, потому что предполагает, что это лорд и хозяин DOM. Таким образом, мы должны синхронизировать реальность React с реальной реальностью, используя обратный вызов: .on('end', …) Мы используем setState()callback Реакт теперь знает, что это письмо закончено.

componentWillLeave

Выходной переход идет в componentWillLeave() Та же концепция, что и выше, только наоборот.

 // src/components/Alphabet/
  componentWillLeave(callback) {
    let node = d3.select(ReactDOM.findDOMNode(this));

    this.setState({className: 'exit'});

    node.transition(this.transition)
      .attr('y', 60)
      .style('fill-opacity', 1e-6)
      .on('end', () => {
          callback()
      });
  }

На этот раз мы обновляем состояние, чтобы изменить classNamex Это потому, что x

Выходной переход сам по себе является обратным входному переходу: буква перемещается вниз и становится невидимой. После перехода мы сообщаем React, что все в порядке, чтобы удалить компонент.

componentWillReceiveProps

Переход обновления идет в componentWillReceiveProps()

 // src/components/Alphabet/Letter.jsx
  componentWillReceiveProps(nextProps) {
    if (this.props.i != nextProps.i) {
      let node = d3.select(ReactDOM.findDOMNode(this));

      this.setState({className: 'update'});

      node.transition(this.transition)
        .attr('x', nextProps.i*32)
        .on('end', () => this.setState({x: nextProps.i*32}));
    }
  }

Вы уже знаете образец, не так ли? Обновить состояние, сделать переход, синхронизировать состояние с реальностью после перехода.

В этом случае мы изменим className

оказывать

После всей этой магии перехода вы можете подумать: «Святая корова, как мне это сделать !?» . Я не виню тебя!

Но мы сделали всю тяжелую работу. Рендеринг прост:

 // src/components/Alphabet/Letter.jsx
  render() {
    return (
      <text dy=".35em"
          y={this.state.y}
          x={this.state.x}
          className={this.state.className}
          style={{fillOpacity: this.state.fillOpacity}}>
        {this.props.d}
      </text>
    );
  }

Мы возвращаем элемент SVG <text>(x, y)classNamefillOpacity Это показывает единственную букву, данную d

Как уже упоминалось: использование состояния для xyclassNamefillOpacity Вы обычно используете реквизит для этого. Но состояние — это самый простой способ взаимодействия между методами рендеринга и жизненного цикла.

Вы знаете основы!

Boom. Вот и все. Вы знаете, как создать анимированную декларативную визуализацию. Это довольно круто, если вы спросите меня.

Вот как это выглядит в действии:

Такие приятные переходы, и все, что вам нужно было сделать, это перебрать массив и отобразить некоторые компоненты <Letter> Как это круто? 😉

В заключение

Теперь вы достаточно хорошо понимаете React, чтобы принимать технические решения. Вы можете посмотреть на проект и решить: «Да, это больше, чем просто игрушка. Компоненты и отладка помогут мне ».

Для дополнительного удовольствия вы также знаете, как использовать React и D3 вместе для создания декларативной анимации. Самый трудный подвиг в былые времена.

Чтобы узнать больше о правильной интеграции React и D3, ознакомьтесь с моей книгой React + d3js ES6 .

Эта статья была рецензирована Марком Брауном и Джеком Франклином . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!