Този пример и дизайн споделят това, което научих от книгата 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. Това означава, че ако изпълним Seed.products, трябва да имаме върнат всеки продуктов обект.

Файлът react.js е кодът, съдържащ самото ядро ​​на React. Освен това react-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, защото вече сме заредили файловете react.js и react-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>

След като сме дефинирали функцията, нека всъщност я създадем. Вътре в продуктовия компонент създайте функция passTheId();:

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

Свързахме функцията с ключовата дума this, защото само вградени функции като render() имат достъп да използват тази дума.

Нека създадем друга функция в компонента ProductList. Това ще актуализира състоянието, работещо с функцията handleUpVote на продуктовия компонент.

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 трябва да се третират като неизменни. Тоест те не трябва да се променят директно. Горната функция ще направи това с помощта на Object.assign(); на JavaScript, като създаде привидно нов масив, наречен 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 връзка и клонирайте на вашия компютър.

Ако тази статия ви е харесала, ръкопляскайте ме, за да я видят повече хора. Благодаря ви, че прочетохте.

Можете да прочетете повече от моето писане в моя блог: Stellar Code.