ПРИМЕЧАНИЕ АВТОРА: Привет, ребята! эта статья устарела, потому что она не соответствует последней стабильной версии React и побуждает вас делать некоторые вещи, которые в настоящее время не являются лучшим способом их выполнения. Ознакомьтесь с обновленной версией, со всем новым контентом о веб-доступности и React Hooks, прямо здесь!

Прямо сейчас React берет на себя фронтенд-разработку, что, по крайней мере, для меня, довольно захватывающе. Завтра это может быть неправдой, но количество кодовых баз, которые сильно зависят от React, велико и продолжает расти. Если вы здесь и читаете это, вероятно, вам не нужно, чтобы я вам больше об этом рассказывал. А еще есть D3. Меньше людей и проектов, использующих D3, чем React, но с этим трудно спорить: когда дело доходит до создания визуализации данных в 2018 году, на самом деле нет ничего, что могло бы приблизиться, говорим ли мы об абсолютной глубине и мощности. библиотеки (вы видели, что D3 может делать с строковой интерполяцией и масштабами?), или огромное количество и качество поддержки сообщества и существующей информации.

И React, и D3 предоставляют программисту богатые ресурсы для запросов и манипуляций с DOM, которые оставляют альтернативы, такие как jQuery и даже собственный JavaScript, в пыли. Эта проблема? Они делают это совершенно по-разному, а это значит, что пытаться использовать оба одновременно не так просто, как хотелось бы. Обе библиотеки пытаются обновить DOM и, если ими не управлять должным образом, могут конфликтовать друг с другом и мешать обеим вам выдать ожидаемый результат. Что, позвольте мне сказать вам по опыту, утомляет, когда вы действительно хотели бы использовать оба сразу - если бы вы сказали, что создаете функцию, в которой вы хотите создавать графику SVG на основе пользовательского ввода (что мы собираемся делаю за секунду).

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

(Если вы любите-любите-любите спойлеры, вы можете проверить конечный результат этого урока здесь: https://d3-react-tutorial.herokuapp.com/, или вы можете следить за ним в коде база здесь: https://github.com/LeighSteiner/d3-react-tutorial.)

Настройка

Идея здесь не в том, чтобы создать полномасштабное приложение, а в создании модульного набора практик, который вы можете перенести в любой проект с помощью React + D3, поэтому мы собираемся сделать все как можно проще, а это означает ... создать-реагировать-приложение! Идите и create-react-app <your-app-name> в свой терминал (не забудьте установить его глобально, если вы еще этого не сделали), npm install --save d3, и встретимся здесь снова.

Все готово? Потрясающие. Ваша файловая структура должна выглядеть примерно так:

Волшебство

Итак, цель здесь - не допустить, чтобы махинации React с виртуальной DOM мешали более непосредственному хирургическому подходу D3. И самый простой способ сделать это - буквально разделить их. (Я сказал вам, что это будет легко. Не так ли? Что ж, это будет легко). Я уже сказал, что мы хотели создать функцию, в которой наше приложение будет рисовать графику SVG на основе какого-либо пользовательского ввода. Первое, что нужно сделать, это подумать о том, каковы будут обязанности React и с чем мы хотим, чтобы D3 справлялся. Мы знаем, что React довольно хорош, когда дело доходит до форм, управления и (временного) хранения пользовательского ввода, и что D3 - это горячая стрелка, когда мы говорим о SVG, особенно о SVG, которые динамически основаны на данных. Похоже, у нас хорошее и четкое разделение труда, не так ли?

Хороший принцип, как правило, при создании приложений - позволить каждому компоненту делать одну вещь, чтобы он мог делать это хорошо и тщательно, поэтому давайте создадим компонент для наших задач контроллера (Controller.js), где мы позволим React запустить шоу, и наши задачи рисования, где D3 будет вызывать кадры (Viz.js), оба находятся в той же папке src. В Viz, несмотря на то, что мы будем экспортировать компонент React, мы собираемся помешать React делать большинство вещей, которые мы обычно хотим, пока он находится в Controller, даже если мы собираемся хранить всю информацию насчет того, что мы хотим, чтобы D3 рисовал, мы даже не собираемся импортировать библиотеку D3 в файл.

Controller.js

Это наш файл с подходящим названием, в котором хранится форма, принимающая наши пользовательские данные. Под капотом он также будет отслеживать каждую форму, которую пользователь создает и хочет нарисовать, в ее состоянии. Если вы уже достаточно знакомы с React, это на 100% просто. Мы просто записываем в форму любую информацию, которая нам нужна от пользователя (в данном случае цвет, размер и каждый кружок, запрашиваемые пользователем), а также соответствующие функции обработки изменений для сохранения текущей формы в состоянии компонента React. Ваш код на этом этапе может выглядеть примерно так:

Веселье начинается, когда пользователь делает свой самый удачный выбор и решает, что он готов пойти дальше и нажать эту кнопку рисования. Наше состояние React не только будет отслеживать выбор, который пользователь делает в процессе, но также будет вести текущий счет предыдущих выборов, чтобы мы могли нарисовать столько кругов, сколько захотим. Итак, когда пользователь отправляет свой выбор, мы собираемся взять выбранный цвет и ширину и добавить их в список toDraw. И этот список будет передан нашей D3-eriffic Viz в качестве реквизита, который будет обрабатывать все с этого момента. Чтобы держать вас в курсе, вот как должен выглядеть наш Controller.js файл, когда мы с ним закончим:

Ta-Da!

Viz.js

Мы собираемся сделать Viz настолько простым, насколько это возможно, насколько это возможно в React. Мы хотим, чтобы React встал и выпил - по сути, все, что нам нужно в нашем методе рендеринга, - это пустой div с каким-то мгновенным идентификатором, на который можно ссылаться и повесить вещи, когда мы начинаем рисовать.

Я знаю, что продолжаю это повторять, но вот где становится круто. Готов поспорить, вы уже примерно знаете, как React обновляет DOM так быстро и дешево, верно? По сути (и чрезвычайно вкратце) всякий раз, когда происходит изменение состояния или свойств компонента, React сравнивает свою модель того, как должна выглядеть DOM (то есть виртуальная DOM) и как она есть на самом деле, и только украшает те элементы, которые действительно изменились. Возможно, вы не знали, что React поставляется со встроенным переключателем, который мы можем щелкнуть, чтобы избавиться от всего этого модного дерьма. Я говорю о крючке жизненного цикла shouldComponentUpdate. (Вы можете узнать больше об этом здесь. Примечание. Документы React предупреждают, что будущие версии React могут не реализовать это идеально, и предлагают использовать PureComponent вместо стандартного, если вам нужно контролировать обновления. Однако соответствующий код мы рассматриваем, останется почти таким же в этом случае, поэтому я пошел дальше и сохранил все в базовых компонентах класса.)

Итак, для компонента Viz мы хотим, чтобы React делал как можно меньше. Мы вообще не хотим, чтобы React обновлял DOM, потому что мы позволяем D3 делать все обновления в этом компоненте. Наш новый удобный обработчик жизненного цикла будет почти пустой функцией, которая просто возвращает false во всех случаях. Вот так:

shouldComponentUpdate() {
    return false;
}

Это убирает с пути React. Но где мы позволим D3 делать свое дело? Достаточно логично, что мы хотим нарисовать наш исходный SVG и дочерние элементы в хуке жизненного цикла componentDidMount (в этом случае вы могли бы так же легко использовать componentWillMount, но я использую первый, чтобы ничего не блокировалось, что не должно быть, если, скажем, мы хотим отобразить наш SVG на основе данных, которые могут поступать, например, асинхронно). Если у вас есть очень простая функция, вы можете остановиться прямо здесь - нарисуйте один раз, и все готово. Но, вероятно, если вы все равно разговариваете с пользователем, как мы здесь, мы хотим продолжать с ним разговаривать. Мы не просто хотим, чтобы наш пользователь мог добавить один круг или ждать, пока он решит, сколько кругов ему нужно всего, прежде чем мы их нарисуем - мы хотим, чтобы пользователь мог видеть свое графическое обновление как они добавляют к этому. Итак, что мы собираемся сделать, это вытащить код D3, который мы написали, в его собственную функцию, которая просто принимает свойства компонента в качестве аргумента. Вот как выглядит мой:

draw(props) {
    const w = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
    const h = Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
    d3.select('.viz').append('svg')
    .attr('height', h)
    .attr('width', w)
    .attr('id', 'svg-viz')
const bubbles = props.shapes
   let min = bubbles[0].number
   let max = bubbles[0].number
   for (let i = 1 ; i < bubbles.length; i ++){
    if (bubbles[i].number > max) {
      max = bubbles[i].number
    }
    if(bubbles[i].number < min) {
      min = bubbles[i].number
    }
   }
   const radiusScale = d3.scaleSqrt().domain([0, max]).range([0, max])
const simulation = d3.forceSimulation()
  .force('x', d3.forceX(w/3).strength(0.05))
  .force('y', d3.forceY(h/3).strength(0.05))
  .force('charge', d3.forceManyBody().strength(-1300))
  .force('collide', d3.forceCollide(d => radiusScale(d.number)+1))
const circles = d3.select('#svg-viz').selectAll('circle')
   .data(props.shapes)
   .enter()
   .append('svg:circle')
   .attr('r', d => d.width/2+"px")
   .style('fill', (d) => d.color ? d.color : 'purple')
simulation.nodes(props.shapes)
 .on('tick', ticked)
 
 function ticked() {
     circles
     .attr('cx', d => d.x)
     .attr('cy', d => d.y)
   }
  }

Как видите, d3 захватывает тот единственный <div className="viz"/>, который возвращает метод рендеринга, и использует его, чтобы вешать SVG, где мы рисуем все круги, которые нам отправил пользователь. Что хорошо в том, чтобы сделать это таким образом, так это то, что не только все наши элементы SVG сгенерированы в этой функции, но и все стили, которые к ним применяются, тоже прямо здесь - нам не нужно искать наши файлы CSS, чтобы понять почему вещи выглядят так, как они выглядят, и если мы позже захотим экспортировать созданный файл SVG (но, эй, это еще одна запись в блоге), нам не придется делать какие-то причудливые обратные сальто для чтения файлов, чтобы убедиться, что мы ' у меня есть все, что нам нужно.

Готово и вычищено, верно? Не совсем! Как я уже сказал, сначала мы вызовем это в нашей componentDidMount функции, где мы передадим это this.props в качестве единственного аргумента. Но как насчет того, когда наш пользователь добавит новый круг? Мы уже сказали компоненту не обновляться!

componentWillReceiveProps очень удобно нам на помощь. Этот хук принимает входящие новые реквизиты в качестве своего параметра, который мы можем сравнить с существующими реквизитами, чтобы решить, есть ли какое-либо изменение, которое имеет для нас значение (например, длина нашего нового массива фигур отличается от нашего старого), и обновить соответственно. (Примечание: когда выйдет React 17, этот метод станет устаревшим и может быть заменен на getDerivedStateFromProps - как указано выше, однако принцип остается тем же в любой версии.) В случае нашего небольшого приложения для рисования, если мы получили новые пузыри, то мы хотим перерисовать наш SVG с обновленным списком. Итак, в этом случае наш хук может выглядеть примерно так:

componentWillReceiveProps(nextProps) {
   if(this.props.shapes.length !== nextProps.shapes.length){
     d3.select('.viz > *').remove();
     this.draw(nextProps)
   }
  }

Что дальше, спросите вы? Что ж, у нас есть компонент контроллера, который организует и отправляет наши данные для визуализации в наш компонент d3-viz, который рисует наш SVG, обновляет его соответствующим образом и убирает беспорядок, когда это будет сделано.

Ребята, я думаю, мы сделали это.

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

(Если вы хотите получить дополнительные сведения о том, как я это сделал, это напоминание о том, чтобы проверить кодовую базу на https://github.com/LeighSteiner/d3-react-tutorial, поиграть с приложением на https: //d3-react-tutorial.herokuapp.com/ , и отправьте мне пинг с вопросами, идеями или тем, как вы хотите, чтобы ваши данные были популярными.)

✉️ Подпишитесь на рассылку еженедельно Email Blast 🐦 Подпишитесь на CodeBurst на Twitter , просмотрите 🗺️ План развития веб-разработчиков на 2018 год и 🕸️ Изучите веб-разработку с полным стеком .