Этот пример и дизайн делятся тем, что я узнал из книги Fullstack React. Я настоятельно рекомендую его как хороший ресурс для изучения React и его экосистемных технологий. проверьте это здесь: fullstackreact.com.

Представьте, что вам, как разработчику, было поручено создать MVP для стартап-продукта, который необходимо продемонстрировать потенциальным инвесторам.

Это приложение для голосования, вдохновленное Product Hunt и Reddit. В приложении товары отображаются в коллекции. Пользователи могут проголосовать за лучшие продукты, и приложение автоматически отсортирует их по количеству голосов, ставя наивысшее перед самым низким.

Функции приложения, которое мы будем создавать, очень просты:

  • Пользователи могут просматривать существующие / отображаемые продукты.
  • Пользователи могут голосовать за продукты, которые им нравятся.
  • Товары сортируются автоматически по подсчету голосов.

Вы можете просмотреть демонстрацию здесь.

Шаг 1: обо всем по порядку

Прежде всего, перейдите на Github и загрузите начальную папку, которую я уже создал, с необходимыми настройками для нашего приложения здесь. Скопируйте URL, предоставленный зеленой кнопкой клонирования / загрузки, и запустите по желаемому пути в командной строке. У вас должен быть уже установлен git.

git clone URL

После загрузки папки откройте ее в редакторе кода и просмотрите файлы и структуру папки. Это выглядит так:

├───src
|    ├───app.js
|    ├───seed.js
|    ├───style.css
└───vendor
    ├───bootstrap-3.3.7-dist
    ├───font-awesome-4.7.0
    ├───react.js
    ├───react-dom.js
    └───babel-standalone.js

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

В папке src находятся файлы app.js и seed.js. В файле app.js мы напишем большую часть кода для нашего приложения. Файл seed.js уже содержит набор данных для отображаемых продуктов.

Наш файл seed.js содержит следующий код

window.Seed = (function () {
    function generateVoteCount() {
      return Math.floor((Math.random() * 50) + 15);
    }
    const products = [
      {
        id: 1,
        title: 'Yellow Pail',
        description: 'On-demand sand castle construction expertise.',
        url: '#',
        votes: generateVoteCount(),
        submitterAvatarUrl: 'images/avatars/daniel.jpg',
        productImageUrl: 'images/products/image-aqua.png',
      },
                                ...
    ];
    return { products: products };
  }());

Этот код создает функцию generateVoteCount(), которую мы объясним позже, и массив products, содержащий данные наших продуктов. Они заключены в самозапускающуюся функцию и прикреплены к объекту window нашего браузера. Таким образом, мы можем получить к ним доступ в любом месте, где захотим.

Функция Seed в конечном итоге возвращает объект со свойством products и значением products. Это означает, что если мы выполним Seed.products, мы должны вернуть нам каждый объект продукта.

Файл response.js - это код, содержащий само ядро ​​React. Кроме того, response-dom.js - это код, который помогает нам отображать компоненты React, которые мы создали в HTML DOM. Наконец, babel-standalone.js - это код Babel, который переносит расширенный код JSX и ES6, с которым мы будем работать, в код ES5 (наиболее распространенная спецификация JavaScript, которую сегодня поддерживают большинство старых и современных браузеров).

Шаг 2: создайте компоненты

Нам нужно создать два компонента React. Мы назовем родительский компонент ProductList, а набор дочерних компонентов, который он содержит, будет Procuct.

Внутри файла app.js создайте родительский компонент, выполнив следующие действия:

class ProductList extends React.Component {
    render() {
        const products = Seed.products.map((product) => (
            <Product 
            id={product.id}
            title={product.title}
            description={product.description}
            url={product.url}
            votes={product.votes}
            submitterAvatarUrl={product.submitterAvatarUrl}
            productImageUrl={product.productImageUrl}
            />
        ));
        return (
            <div className="container">
                <h1>Popular products</h1>
                <hr />
                {products}
            </div>
        );
    }
}
ReactDOM.render(<ProductList />, document.getElementById('content'));

В родительском компоненте мы намерены создать дочерний компонент на основе каждого объекта, доступного из Seed.products. Итак, мы установили реквизит. Теперь давайте фактически объявим дочерний компонент в том же файле с именем Product:

class Product extends React.Component {
    render() {
        return (
          <div className='container'>
            <div className="row">
            <div className='col-md-12'>
                <div className="main">
                <div className="image">  
                    <img src={this.props.productImageUrl} />
                </div> 
                <div className='header'>
                    <a>
                        <i className='fa fa-2x fa-caret-up' />
                    </a>
                    {this.props.votes}
                </div>
                <div className='description'>
                    <a href={this.props.url}>
                        {this.props.title}
                    </a>
                    <p>{this.props.description}
                    </p>
                </div>
              <div className='extra'>
                <span>Submitted by:</span>
                <img
                  className='avatar'
                  src={this.props.submitterAvatarUrl}
                />
              </div>
              </div>
            </div>
            </div>
          </div>
        );
      }
}

Мы можем ссылаться на React.Component и ReactDOM.render, потому что мы уже загрузили файлы response.js и response-dom.js. Они доступны для использования, хотя в настоящее время мы находимся в файле app.js. Создав компонент, ReactDOM.render(whatComponent, where) отображает его в DOM.

При запуске вашего живого сервера у вас должен появиться следующий экран:

Шаг 3: добавьте интерактивности

До сих пор мы могли кодировать компоненты нашего приложения, но они по-прежнему статичны. Как сделать их интерактивными?

При кодировании приложений React следуйте этому общему процессу:

  • Разделите пользовательский интерфейс приложения на компоненты
  • Создайте статическую версию приложения
  • Определите, какие данные являются состоянием
  • Определите, в каких компонентах должна жить каждая часть государства.
  • Начальные состояния жесткого кода
  • Добавить обратный поток данных от дочернего к родительскому через реквизиты
  • Добавить связь с сервером

Мы не будем делать все вышеперечисленное, но давайте перейдем к состоянию. Единственная часть данных в нашем приложении, которая может считаться отслеживаемой или постоянно изменяющейся, - это количество голосов. Помните: это свойство в коллекции продуктов в нашем файле seed.js. Голоса есть в каждом product объекте, поэтому он представляет наше состояние.

Зная наше состояние, где мы его инициализируем? Состояния в React автономны в определенных компонентах, в отличие от пропсов, которые передаются по наследству. Количество голосов как состояние принадлежит <Product />, но поскольку набор продуктов, который у нас есть, генерируется из <ProductList />, мы инициализируем состояние там. В <ProductList /> сделайте это перед render() методом:

constructor() {
        super();
        this.state = {
            products: []
        }
    }

При инициализации состояния в компоненте мы пытаемся определить, как оно должно выглядеть, оставляя его пустым. Наши продукты представляют собой массив, поэтому мы используем пустой массив. Мы инициализируем его внутри constructor() {}, потому что это фрагмент кода, который запускается при создании нашего компонента.

Давайте заставим наш компонент читать products из своего состояния, а не из файла. Добавьте:
componentDidMount() {
this.setState({ products: Seed.products })
}

, чтобы установить состояние для использования. Также обновите const products = Seed.products до const products = this.state.products. Чтобы заставить JavaScript отсортировать его по наибольшему количеству голосов, напишите вместо этого:

const products = this.state.products.sort((a, b) {
    b.votes - a.votes
});

В JavaScript sort(); внутри используется функция сравнения. Вы можете узнать об этом в документации.

Шаг 4: обработайте голосование за

Давайте перейдем к гиперссылке вокруг замечательного шрифта и значка курсора и создадим функцию с помощью onClick.

<a onClick={passTheId}>
    <i className='fa fa-2x fa-caret-up' />
 </a>

После того, как мы определили функцию, давайте ее создадим. Внутри компонента Product создайте функцию passTheId();:

constructor() {
        super();
        this.passTheId = this.passTheId.bind(this);
    }
    passTheId() {
        console.log('Id will be passed');
    }

Мы связали функцию с ключевым словом this, потому что только встроенные функции, такие как render (), имеют доступ к использованию этого слова.

Давайте создадим еще одну функцию в компоненте ProductList. Он обновит состояние, работая с handleUpVote функцией компонента Product.

handleProductUpVote = (productId) => {
    const nextProducts = this.state.products.map((product) => {
      if (product.id === productId) {
        return Object.assign({}, product, {
          votes: product.votes + 1,
        });
      } else {
        return product;
      }
    });
    this.setState({
      products: nextProducts,
    });
  }

Состояния в React следует рассматривать как неизменяемые. То есть их нельзя изменять напрямую. Вышеупомянутая функция сделает это с помощью JavaScript Object.assign();, создав, казалось бы, новый массив с именем nextProducts. Это похоже на существующее состояние, но с изменением количества голосов. nextProducts затем устанавливается как новое состояние. Это кажется странным, но это то, что команда React рекомендует для повышения производительности.

Мы хотим передать идентификатор продукта из дочернего Product компонента в родительский ProductList компонент, поэтому давайте сделаем handleProductUpVote доступным для дочернего компонента в качестве свойств:

const productComponents = products.map((product) => (
      <Product
        key={'product-' + product.id}
        id={product.id}
        title={product.title}
        description={product.description}
        url={product.url}
        votes={product.votes}
        submitterAvatarUrl={product.submitterAvatarUrl}
        productImageUrl={product.productImageUrl}
        onVote={this.handleProductUpVote}
      />
    ));

Мы добавили onVote={this.handleProductUpVote}. Таким образом, на дочернем уровне мы можем получить к нему доступ через this.props

passTheId() {
        console.log('Id will be passed');
        this.props.onVote(this.props.id)
    }

Весь ваш app.js файл должен выглядеть так:

class ProductList extends React.Component {
    state = {
        products: [],
      };
      componentDidMount() {
        this.setState({ products: Seed.products });
      }
      handleProductUpVote = (productId) => {
        const nextProducts = this.state.products.map((product) => {
          if (product.id === productId) {
            return Object.assign({}, product, {
              votes: product.votes + 1,
            });
          } else {
            return product;
          }
        });
        this.setState({
          products: nextProducts,
        });
      }

    render() {
        const products = this.state.products.sort((a, b) => (
            b.votes - a.votes
        ));
        const productComponents = products.map((product) => (
            <Product
              key={'product-' + product.id}
              id={product.id}
              title={product.title}
              description={product.description}
              url={product.url}
              votes={product.votes}
              submitterAvatarUrl={product.submitterAvatarUrl}
              productImageUrl={product.productImageUrl}
              onVote={this.handleProductUpVote}
            />
          ));
        return (
            <div className="container">
                <h1>Popular products</h1>
                <hr />
                {productComponents}
            </div>
        );
    }
}
class Product extends React.Component {
    constructor() {
        super();
        this.passTheId = this.passTheId.bind(this);
    }
    passTheId() {
        console.log('Id will be passed');
        this.props.onVote(this.props.id);
    }

    render() {
        return (
          <div className='container'>
            <div className="row">
            <div className='col-md-12'>

                <div className="main">
                <div className="image">  
                    <img src={this.props.productImageUrl} />
                </div> 
                <div className='header'>
                    <a onClick={this.passTheId}>
                        <i className='fa fa-2x fa-caret-up' />
                    </a>
                    {this.props.votes}
                </div>
                <div className='description'>
                    <a href={this.props.url}>
                        {this.props.title}
                    </a>
                    <p>
                        {this.props.description}
                    </p>
                </div>
              <div className='extra'>
                <span>Submitted by:</span>
                <img
                  className='avatar'
                  src={this.props.submitterAvatarUrl}
                />
              </div>
              </div>

            </div>
            </div>
          </div>
        );
      }
}
ReactDOM.render(<ProductList />, document.getElementById('content'));

Обновите браузер, и вы должны увидеть работающее приложение. Посмотреть демонстрацию.

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

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

Вы можете прочитать больше моих статей в моем блоге: Звездный код.