React HN. Туториал

Что такое React и зачем он нужен?

В процессе изучения новой библиотеки React от Facebook натолкнулась на занимательнейший репозиторий: https://github.com/mking/react-hn. Дописав немного доп. функциональности (возможность перехода по страницам новостей в пределах локального сайта), решила перевести аннотацию на русский язык. Форк доступен по адресу: https://github.com/Sacret/react-hn.


React HN

Перед Вами – визуальный туториал, посвященный библиотеке React. Этот туториал покажет Вам, как "вырастить" React UI из небольших модульных частей. К концу этот урока Вы создадите главную страницу "Хакерских новостей" на React.

Обратите внимание: этот туториал посвящен React, Browserify, CSS. Он не рассказывает об обработке событий (это не нужно для главной страницы новостей), state (это не нужно для главной страницы новостей) или Flux.

Туториал состоит из 5 частей:

  1. Установка
  2. Компонент NewsItem
  3. Компонент NewsHeader
  4. Компонент NewsList
  5. Отображение реальных данных из Hacker News API
  6. Дополнительная функциональность для ссылки "More"

Установка

  1. Создайте структуру проекта.
    mkdir -p hn/{build/js,css,html,img,js,json}
    cd hn
    

    Обратите внимание: мы будем создавать проект "с нуля". Решение из репозитория упомянуто в основном в качестве справки.

  2. Скачайте образец данных в /json.
  3. Загрузите y18.gif и grayarrow2x.gif в /img.
  4. Создайте /package.json.
    {
      "name": "hn",
      "version": "0.1.0",
      "private": true,
      "browserify": {
        "transform": [
          ["reactify"]
        ]
      }
    }
    
  5. Установите Browserify, React и все необходимые инструменты.
    # These dependencies are required for running the app.
    npm install --save react jquery lodash moment
    
    # These dependencies are required for building the app.
    npm install --save-dev browserify watchify reactify
    
    # These dependencies are globally installed command line tools.
    npm install -g browserify watchify http-server
    

Компонент NewsItem

  1. Отобразить заголовок.
  2. Добавить домен.
  3. Добавить описание.
  4. Добавить порядковый номер и количество голосов.

Заголовок NewsItem

  1. Создайте новый JS-файл: /js/NewsItem.js.
    var $ = require('jquery');
    var React = require('react');
    
    var NewsItem = React.createClass({
      render: function () {
        return (
          <div className="newsItem">
            <a className="newsItem-titleLink" href={this.props.item.url}>{this.props.item.title}</a>
          </div>
        );
      }
    });
    
    module.exports = NewsItem;      
    Обратите внимание: у Вас должна быть возможность вставлять этот код напрямую в JS-файл.
  2. Создайте новый JS-файл: /js/NewsItemTest.js.
    var $ = require('jquery');
    var NewsItem = require('./NewsItem');
    var React = require('react');
    
    $.ajax({
      url: '/json/items.json'
    }).then(function (items) {
      // Log the data so we can inspect it in the developer console.
      console.log('items', items);
      // Use a fake rank for now.
      React.render(<NewsItem item={items[0]} rank={1}/>, $('#content')[0]);
    });     
    Обратите внимание: это позволит Вам разрабатывать компонент NewsItem в изоляции, без необходимости добавления в полное приложение.
  3. Создайте новый CSS-файл: /css/NewsItem.css.
    .newsItem {
      color: #828282;
      margin-top: 5px;
    }
    
    .newsItem-titleLink {
      color: black;
      font-size: 10pt;
      text-decoration: none;
    }    
  4. Создайте новый CSS-файл: /css/app.css.
    body {
      font-family: Verdana, sans-serif;
    }  
  5. Создайте новый HTML-файл: /html/NewsItem.html.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>NewsItem</title>
        <link href="../css/NewsItem.css" rel="stylesheet">
        <link href="../css/app.css" rel="stylesheet">
      </head>
      <body>
        <div id="content"></div>
        <script src="../build/js/NewsItemTest.js"></script>
      </body>
    </html>
    
  6. Запустите Watchify. Он скомпилирует Ваши React (JSX) компоненты в обыкновенный JavaScript.
    watchify -v -o build/js/NewsItemTest.js js/NewsItemTest.js
    
  7. Запустите HTTP-сервер.
    http-server -p 8888
    
  8. Откройте ссылку http://localhost:8888/html/NewsItem.html. Вы увидите следующее:

Домен для NewsItem

  1. Редактируйте JS-файл.
    // ...
    var url = require('url');
    
    var NewsItem = React.createClass({
      // ...
    
      getDomain: function () {
        return url.parse(this.props.item.url).hostname;
      },
    
      render: function () {
        return (
          <div className="newsItem">
            ...
            <span className="newsItem-domain">
              ({this.getDomain()})
            </span>
          </div>
        );
      }
     
    Обратите внимание: этот код должен быть добавлен в существующий файл /js/NewsItem.js.
  2. Редактируйте CSS-файл.
    .newsItem-domain {
      font-size: 8pt;
      margin-left: 5px;
    } 
    Обратите внимание: этот код должен быть добавлен в существующий файл /css/NewsItem.css.
  3. Обновите страницу. Вы увидите следующее:

Описание для NewsItem

  1. Редактируйте JS-файл. Обратите внимание: мы выделили участок кода для заголовка в отдельный метод.
    // ...
    var moment = require('moment');
    
    var NewsItem = React.createClass({
      // ...
    
      getCommentLink: function () {
        var commentText = 'discuss';
        if (this.props.item.kids && this.props.item.kids.length) {
          // This only counts top-level comments.
          // To get the full count, recursively get item details for this news item.
          commentText = this.props.item.kids.length + ' comments';
        }
    
        return (
          <a href={'https://news.ycombinator.com/item?id=' + this.props.item.id}>{commentText}</a>
        );
      },
    
      getSubtext: function () {
        return (
          <div className="newsItem-subtext">
            {this.props.item.score} points by <a href={'https://news.ycombinator.com/user?id=' + this.props.item.by}>{this.props.item.by}</a> {moment.utc(this.props.item.time * 1000).fromNow()} | {this.getCommentLink()}
          </div>
        );
      },
    
      getTitle: function () {
        return (
          <div className="newsItem-title">
            ...
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsItem">
            {this.getTitle()}
            {this.getSubtext()}
          </div>
        );
      }
     
  2. Редактируйте CSS-файл.
    .newsItem-subtext {
      font-size: 7pt;
    }
    
    .newsItem-subtext > a {
      color: #828282;
      text-decoration: none;
    }
    
    .newsItem-subtext > a:hover {
      text-decoration: underline;
    }
  3. Обновите страницу. Вы увидите следующее:

Порядковый номер и количество голосов для NewsItem

  1. Редактируйте JS-файл.
    var NewsItem = React.createClass({
      // ...
    
      getRank: function () {
        return (
          <div className="newsItem-rank">
            {this.props.rank}.
          </div>
        );
      },
    
      getVote: function () {
        return (
          <div className="newsItem-vote">
            <a href={'https://news.ycombinator.com/vote?for=' + this.props.item.id + '&dir=up&whence=news'}>
              <img src="../img/grayarrow2x.gif" width="10"/>
            </a>
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsItem">
            {this.getRank()}
            {this.getVote()}
            <div className="newsItem-itemText">
              {this.getTitle()}
              {this.getSubtext()}
            </div>
          </div>
        );
      }
     
  2. Редактируйте CSS-файл.
    .newsItem {
      /* ... */
      align-items: baseline;
      display: flex;  
    }
    
    .newsItem-itemText {
      flex-grow: 1;
    }
    
    .newsItem-rank {
      flex-basis: 25px;
      font-size: 10pt;
      text-align: right;
    }
    
    .newsItem-vote {
      flex-basis: 15px;
      text-align: center;
    }
  3. Обновите страницу. Вы увидите следующее: Вы только что добавили компонент новости с помощью React.

Компонент NewsHeader

  1. Отобразить логотип и заголовок.
  2. Добавить ссылок меню.
  3. Добавить ссылку для логина.

  1. Создайте новый JS-файл: /js/NewsHeader.js.
    var $ = require('jquery');
    var React = require('react');
    
    var NewsHeader = React.createClass({
      getLogo: function () {
        return (
          <div className="newsHeader-logo">
            <a href="https://www.ycombinator.com"><img src="../img/y18.gif"/></a>
          </div>
        );
      },
    
      getTitle: function () {
        return (
          <div className="newsHeader-title">
            <a className="newsHeader-textLink" href="https://news.ycombinator.com">Hacker News</a>
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsHeader">
            {this.getLogo()}
            {this.getTitle()}
          </div>
        );
      }
    });
    
    module.exports = NewsHeader;
     
  2. Создайте новый JS-файл: /js/NewsHeaderTest.js.
    var $ = require('jquery');
    var NewsHeader = require('./NewsHeader');
    var React = require('react');
    
    React.render(<NewsHeader/>, $('#content')[0]);
    
  3. Создайте новый CSS-файл: /css/NewsHeader.css.
    .newsHeader {
      align-items: center;
      background: #ff6600;
      color: black;
      display: flex;
      font-size: 10pt;
      padding: 2px;
    }
    
    .newsHeader-logo {
      border: 1px solid white;
      flex-basis: 18px;
      height: 18px;
    }
    
    .newsHeader-textLink {
      color: black;
      text-decoration: none;
    }
    
    .newsHeader-title {
      font-weight: bold;
      margin-left: 4px;
    }
  4. Создайте новый HTML-файл: /html/NewsHeader.html.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>NewsHeader</title>
        <link href="../css/NewsHeader.css" rel="stylesheet">
        <link href="../css/app.css" rel="stylesheet">
      </head>
      <body>
        <div id="content"></div>
        <script src="../build/js/NewsHeaderTest.js"></script>
      </body>
    </html>
    
  5. Запустите Watchify.
    watchify -v -o build/js/NewsHeaderTest.js js/NewsHeaderTest.js
    
  6. Запустите HTTP-сервер, если необходимо.
    http-server -p 8888
    
  7. Откройте http://localhost:8888/html/NewsHeader.html. Вы должны увидеть следующее:

Меню для NewsHeader

  1. Редактируйте JS-файл.
    // ...
    var _ = require('lodash');
    
    var NewsHeader = React.createClass({
      // ...
    
      getNav: function () {
        var navLinks = [
          {
            name: 'new',
            url: 'newest'
          },
          {
            name: 'comments',
            url: 'newcomments'
          },
          {
            name: 'show',
            url: 'show'
          },
          {
            name: 'ask',
            url: 'ask'
          },
          {
            name: 'jobs',
            url: 'jobs'
          },
          {
            name: 'submit',
            url: 'submit'
          }
        ];
    
        return (
          <div className="newsHeader-nav">
            {_(navLinks).map(function (navLink) {
              return (
                <a key={navLink.url} className="newsHeader-navLink newsHeader-textLink" href={'https://news.ycombinator.com/' + navLink.url}>
                  {navLink.name}
                </a>
              );
            }).value()}
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsHeader">
            ...
            {this.getNav()}
          </div>
        );
      }
     
  2. Редактируйте CSS-файл.
    .newsHeader-nav {
      flex-grow: 1;
      margin-left: 10px;
    }
    
    .newsHeader-navLink:not(:first-child)::before {
      content: ' | ';
    }
  3. Обновите страницу. Вы должны увидеть следующее:

Ссылка логина для NewsHeader

  1. Редактируйте JS-файл.
    var NewsHeader = React.createClass({
      // ...
    
      getLogin: function () {
        return (
          <div className="newsHeader-login">
            <a className="newsHeader-textLink" href="https://news.ycombinator.com/login?whence=news">login</a>
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsHeader">
            ...
            {this.getLogin()}
          </div>
        );
      }
    
  2. Редактируйте CSS-файл.
    .newsHeader-login {
      margin-right: 5px;
    }
    
  3. Обновите страницу. Вы должны увидеть следующее: Вы только что добавили хедер сайта с помощью React.

Компонент NewsList

  1. Отобразить заголовок и новости.
  2. Добавить ссылку "More".

Заголовок и новости для NewsList

  1. Создайте новый JS-файл: /js/NewsList.js.
    var _ = require('lodash');
    var NewsHeader = require('./NewsHeader');
    var NewsItem = require('./NewsItem');
    var React = require('react');
    
    var NewsList = React.createClass({
      render: function () {
        return (
          <div className="newsList">
            <NewsHeader/>
            <div className="newsList-newsItems">
              {_(this.props.items).map(function (item, index) {
                return <NewsItem key={item.id} item={item} rank={index + 1}/>;
              }.bind(this)).value()}
            </div>
          </div>
        );
      }
    });
    
    module.exports = NewsList;
    
  2. Создайте новый JS-файл: /js/NewsListTest.js.
    var $ = require('jquery');
    var NewsList = require('./NewsList');
    var React = require('react');
    
    $.ajax({
      url: '/json/items.json'
    }).then(function (items) {
      React.render(<NewsList items={items}/>, $('#content')[0]);
    });
    
  3. Создайте новый CSS-файл: /css/NewsList.css.
    .newsList {
      background: #f6f6ef;
      margin-left: auto;
      margin-right: auto;
      width: 85%;
    }
    
  4. Создайте новый HTML-файл: /html/NewsList.html.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>NewsList</title>
        <link href="../css/NewsHeader.css" rel="stylesheet">
        <link href="../css/NewsItem.css" rel="stylesheet">
        <link href="../css/NewsList.css" rel="stylesheet">
        <link href="../css/app.css" rel="stylesheet">
      </head>
      <body>
        <div id="content"></div>
        <script src="../build/js/NewsListTest.js"></script>
      </body>
    </html>
    
  5. Запустите Watchify.
    watchify -v -o build/js/NewsListTest.js js/NewsListTest.js
  6. Запустите HTTP-сервер, если необходимо.
    http-server -p 8888
  7. Откройте http://localhost:8888/html/NewsList.html. Вы увидите следующее:

Ссылка "More" для NewsList

  1. Редактируйте JS-файл.
    var NewsList = React.createClass({
      // ...
    
      getMore: function () {
        return (
          <div className="newsList-more">
            <a className="newsList-moreLink" href="https://news.ycombinator.com/news?p=2">More</a>
          </div>
        );
      },
    
      render: function () {
        return (
          <div className="newsList">
            ...
            {this.getMore()}
          </div>
        );
      }
    
  2. Редактируйте CSS-файл.
    .newsList-more {
      margin-left: 40px; /* matches NewsItem rank and vote */
      margin-top: 10px;
      padding-bottom: 10px;
    }
    
    .newsList-moreLink {
      color: black;
      font-size: 10pt;
      text-decoration: none;
    }
    
  3. Обновите страницу. Вы должны увидеть следующее:

Использование Hacker News API

  1. Создайте новый JS-файл: /js/app.js.
    var _ = require('lodash');
    var $ = require('jquery');
    var NewsList = require('./NewsList');
    var React = require('react');
    
    // Get the top item ids
    $.ajax({
      url: 'https://hacker-news.firebaseio.com/v0/topstories.json',
      dataType: 'json'
    }).then(function (stories) {
      // Get the item details in parallel
      var detailDeferreds = _(stories.slice(0, 30)).map(function (itemId) {
        return $.ajax({
          url: 'https://hacker-news.firebaseio.com/v0/item/' + itemId + '.json',
          dataType: 'json'
        });
      }).value();
      return $.when.apply($, detailDeferreds);
    }).then(function () {
      // Extract the response JSON
      var items = _(arguments).map(function (argument) {
        return argument[0];
      }).value();
    
      // Render the items
      React.render(<NewsList items={items}/>, $('#content')[0]);
    });
  2. Создайте новый HTML-файл: html/app.html.
    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8">
        <title>Hacker News</title>
        <link href="../css/NewsHeader.css" rel="stylesheet">
        <link href="../css/NewsItem.css" rel="stylesheet">
        <link href="../css/NewsList.css" rel="stylesheet">
        <link href="../css/app.css" rel="stylesheet">
      </head>
      <body>
        <div id="content"></div>
        <script src="../build/js/app.js"></script>
      </body>
    </html>
    
  3. Запустите Watchify.
    watchify -v -o build/js/app.js js/app.js
  4. Запустите HTTP-сервер, если необходимо.
    http-server -p 8888
  5. Откройте http://localhost:8888/html/app.html.

Дополнение для ссылки "More"

  1. Редактируйте JS-файл: /js/NewsList.js.
    var NewsList = React.createClass({
      // ...
    
      statics: {
        getQueryVariable: function getQueryVariable(variable) {
          var query = window.location.search.substring(1);
          var vars = query.split("&");
          for (var i = 0; i < vars.length; i++) {
            var pair = vars[i].split("=");
            if (pair[0] == variable) {
              return pair[1];
            }
          }
          return false;
        },
      },
    
      getMore: function () {
        var page = 2;
        if (NewsList.getQueryVariable('p')) {
          page = parseInt(NewsList.getQueryVariable('p')) + 1;
        }
        return (
          <div className="newsList-more">
            <a className="newsList-moreLink" href={"http://localhost:8888/html/app.html?p=" + page}>More</a>
          </div>
        );
      },
    
      render: function () {
        var page = 0;
        if (parseInt(NewsList.getQueryVariable('p'))) {
          page = parseInt(NewsList.getQueryVariable('p')) - 1;
        }
        return (
          <div className="newsList">
            <NewsHeader/>
            <div className="newsList-newsItems">
              {_(this.props.items).map(function (item, index) {
                var rank = index + 1 + page * 30;
                return <NewsItem key={item.id} item={item} rank={rank}/>;
              }.bind(this)).value()}
            </div>
            {this.getMore()}
          </div>
        );
      }
    
  2. Редактируйте JS-файл: /js/app.js.
    // ...
    
    $.ajax({
      url: 'https://hacker-news.firebaseio.com/v0/topstories.json',
      dataType: 'json'
    }).then(function (stories) {
      // Get the item details in parallel
      var currPage = parseInt(NewsList.getQueryVariable('p'));
      var start = 0, end = 30;
      if (currPage) {
        start = (currPage - 1) * 30;
        end = currPage * 30;
      }
      var detailDeferreds = _(stories.slice(start, end)).map(function (itemId) {
        return $.ajax({
          url: 'https://hacker-news.firebaseio.com/v0/item/' + itemId + '.json',
          dataType: 'json'
        });
      }).value();
      return $.when.apply($, detailDeferreds);
    })
    
    // ...
    
  3. Запустите Watchify.
    watchify -v -o build/js/app.js js/app.js
  4. Запустите HTTP-сервер, если необходимо.
    http-server -p 8888
  5. Откройте http://localhost:8888/html/app.html.
    Вы только что реализовали главную страницу "Хакерских новостей" с помощью React.

Последние твиты

Контакты