Създайте приложение React от нулата (част 7): Настройка на React и най-добри практики

Тази публикация е част от поредица от публикации за начинаещи, предназначена за хора, които използват
готови инструменти, шаблони или котлони за React, но желаят да научат и разбират как да създадат React приложение от самото начало.

Всички публикации в тази поредица:
Част 1: Въведение
Част 2: Инициализация и първият файл
Част 3: Използване на синтаксис ES2015
Част 4: Използване на ръководство за стил
Част 5: Настройка на Express сървър
Част 6: Използване на модулен пакет
Част 7: Създаване на реагиращи и най-добри практики
Част 8: Настройка на Redux
Част 9: Настройка на React Router
Част 10: TDD и настройка на Jest

Настройка на React

В тази публикация ще настроим React и ще създадем много прост компонент, след което ще преминем през някои от най-добрите практики, които трябва да имате предвид при разработването на React компоненти. От тук започва забавната част, така че нека да копаем веднага!

Инсталирайте React и React Dom пакетите като зависимости:

$ npm install --съхранявайте реагирайте-dom

След това отворете index.js и създайте много прост компонент React, който представлява нашето приложение. Вече имаме елемент с приложението id в нашия шаблон на шаблон index.pug, така че нека го използваме за монтиране на приложението.

/ **
 * index.js
 * /
import React от 'реагира';
import {render} от 'react-dom';
const MainApp = () => (
  

Здравейте, реагирайте!

);
// рендирайте приложението
render (, document.getElementById ('app'));

Този прост код създава функционален компонент MainApp без състояние и го монтира върху DOM елемент, който има приложение за идентификатор. Този код няма да работи веднага и ще получите грешка, ако се опитате да изградите пакета или да стартирате сървъра.

Причината за тази грешка е, че имаме синтаксис JSX вътре в нашия index.js файл, който Babel все още не разбира. За да позволим на Babel да интерпретира този синтаксис в нормален JavaScript, ще използваме предварително зададената React за Babel.

Инсталирайте пакета като зависимост:

$ npm install - запазване на babel-предварително зададена реакция

След това добавете предварителната настройка към списъка с предварително зададени файлове във .babelrc файл:

{
  "предварително зададени настройки": [
    "Es2015",
    "Етап-0",
    "Реагира"
  ],
  "плъгини": ["трансформиране на inline-среда-променливи"]
}

Трябва също да има грешка във връзката, която би ви попречила да изградите пакета. Линията се оплаква, защото index.js е JavaScript файл, който съдържа синтаксиса на JSX, но използва разширението js вместо jsx.

Можете да прочетете описанието на това правило тук. В раздела „Кога да не го използвам“ се казва, че не трябва да използвате това правило, ако не ви интересува ограничаването на разширението на файловете, съдържащи JSX синтаксис.

Можете да продължите да използвате това правило, но аз предпочитам да използвам js разширението за всички файлове, така че ще деактивирам това правило:

{
  "extends": "airbnb",
  "env": {
    "es6": вярно,
    "браузър": true,
    "възел": вярно
  }
  "правила": {
    "react / jsx-filename-extension": 0
  }
}

Активиране на HMR

Активирането на подмяна на горещ модул е ​​толкова просто, колкото добавяне на блок код:

/ **
 * index.js
 * /
import React от 'реагира';
import {render} от 'react-dom';
ако (module.hot) {
  module.hot.accept ();
}
const MainApp = () => (
  

Здравейте, реагирайте!

);
// рендирайте приложението
render (, document.getElementById ('app'));

Съвети и най-добри практики

Преди да продължим с урока, ще преминем през обобщен списък от съвети и най-добри практики, които научих от моя опит в работата с React, както и от четене и търсене в мрежата. Имайте това предвид, когато създавате вашите компоненти React.

Внос в зависимост от местния внос

Разделете вноса на зависимост от местния внос с нов ред. Вносът на зависимост трябва да бъде на първо място.

import React, {Component} от 'react';
импортиране на полезен модул от „полезен модул“;
импортирайте myLocalModule от './my-local-module';

Функционални компоненти без гражданство

Ако компонентът е компонент само за изобразяване или не е необходимо да използва състояние на обект, използвайте обикновена JavaScript функция вместо клас. Това се нарича функционален компонент без гражданство.

Така че вместо да правите това:

import React, {Component} от 'react';
клас MyComponent разширява Компонент {
  render () {
    връщане (
      
Здравейте!     );   } }
експортиране по подразбиране MyComponent;

Направите това:

import React от 'реагира';
const MyComponent = () => 
Здравейте!
;
експортиране по подразбиране MyComponent;

Вижте колко струпване е премахнато? Можете също да го направите по-опростен, като експортирате самата функция:

import React от 'реагира';
експортиране по подразбиране () => 
Здравейте!
;

Аз обаче не предпочитам да правя това, защото това отстранява грешки по-трудно. Ако проверите Инструментите за реагиране на Dev, ще видите, че името на компонента е „Неизвестно“, тъй като функцията е анонимна.

Анонимен функционален компонент

По-добър подход би бил използването на нормална именувана функция вместо анонимна функция:

import React от 'реагира';
експорт по подразбиране функция MyComponent () {
  връщане 
Здравейте!
; }
Именен функционален компонент

Започнете с презентационни компоненти

Представящите компоненти са по-прости за дефиниране, по-лесни за разбиране и могат да се използват отново и отново, защото са независими от останалата част от приложението.

Ако разбиете приложението си до набор от презентационни компоненти, можете да ги поставите на една страница и да настроите техния дизайн и варианти, за да постигнете единна визия и усещане в цялото приложение.

Изградете своя компонент като презентационен компонент и добавете състояние само когато е необходимо, което ни води до следващия съвет.

Минимизиране на използването на състоянието

Използвайте щадящо състоянието във вашите компоненти и се уверете, че те използват състояние за потребителски интерфейс, а не за данни, с други думи, ако не го използвате в render (), тогава той не трябва да бъде в състояние. Не забравяйте, че трябва да използвате setState само ако искате да рендерирате вашия компонент.

Нека кажем, че имаме компонент, който се състои от един бутон. Този бутон може да бъде щракнат само веднъж и когато се щракне, на конзолата ще бъде регистрирано съобщение:

import React, {Component} от 'react';

клас MyComponent разширява Компонент {
  състояние = {
    clickedOnce: false,
  };
  handleClick = () => {
    ако (! this.state.clickedOnce) {
      console.log ( "Кликнали ');
    }
    this.setState ({
      clickedOnce: true,
    });
  }
  компонентDidUpdate () {
    console.log ( "Актуализирано! ');
  }
  render () {
    връщане (
      
        <бутон onClick = {this.handleClick}> Кликнете върху мен            );   } }
експортиране по подразбиране MyComponent;

Това е пример за лоша реализация, използвайки държавата, за да зададете флаг, щракнатOnce, който указва дали бутона може да бъде щракнат отново. Всеки път, когато се натисне бутона, компонентът ще се изобразява отново, въпреки че не е необходимо.

Приложението рендерира при натискане на бутон

Това ще бъде по-добре:

import React, {Component} от 'react';

клас MyComponent разширява Компонент {
  clickedOnce = невярно;
  
  handleClick = () => {
    ако (! this.clickedOnce) {
      console.log ( "Кликнали ');
    }
    this.clickedOnce = true;
  }
  компонентDidUpdate () {
    console.log ( "Актуализирано! ');
  }
  render () {
    връщане (
      
        <бутон onClick = {this.handleClick}> Кликнете върху мен            );   } }
експортиране по подразбиране MyComponent;

Тази реализация използва свойство клас вместо държавен ключ, тъй като флагът clickedOnce не представлява състояние на потребителски интерфейс и следователно не трябва да живее вътре в състоянието на компонента. С тази нова реализация щракването върху бутона повече от веднъж вече не задейства актуализация.

Винаги определяйте propTypes и defaultProps

Всички компоненти трябва да имат propTypes и defaultProps, дефинирани възможно най-високо в компонента. Те служат като документация за компоненти и трябва да бъдат веднага видими за други разработчици, които четат файла.

Тъй като React v15.5, React.PropTypes се е преместил в друг пакет, така че нека да инсталираме този пакет като зависимост:

$ npm install - запазване на типа поддръжка

За функционални компоненти без гражданство:

Функциите са повдигнати в JavaScript, което означава, че можете да използвате функция преди нейното деклариране:

import React от 'реагира';
импортиране на PropTypes от 'prop-type';
MyComponent.propTypes = {
  заглавие: PropTypes.string,
};
MyComponent.defaultProps = {
  заглавие: „Прост брояч“,
};
експорт по подразбиране функция MyComponent (подпори) {
  връщане 

{props.title}

; }

ESLint ще се оплаче от използването на функцията преди нейното дефиниране, но с цел по-добра документация на компонентите, нека деактивираме това правило за свързване на функциите чрез промяна на файла .eslintrc:

{
  ...
  "правила": {
    "react / jsx-filename-extension": 0,
    "без използване преди дефиниране": [
      "Грешка",
      {
        "функции": невярно
      }
    ]
  }
}

За компоненти, базирани на клас:

За разлика от функциите, класовете в JavaScript не са повдигнати, така че не можем просто да правим MyComponent.propTypes = ... преди да дефинираме самия клас, но можем да определим propTypes и defaultProps като свойства на статичен клас:

import React, {Component} от 'react';
импортиране на PropTypes от 'prop-type';
клас MyComponent разширява Компонент {
  статични propTypes = {
    заглавие: PropTypes.string,
  };
  static defaultProps = {
    заглавие: „Прост брояч“,
  };
  render () {
    връщане 

{this.props.title}

;   } }
експортиране по подразбиране MyComponent;

Инициализиране на държавата

Състоянието може да се инициализира в конструктора на компонента:

клас MyComponent разширява Компонент {
  конструктор (подпори) {
    супер (подпори);
    this.state = {
      брой: 0,
    };
  }
}

По-добър начин е да се инициализира държавата като свойство на класа:

клас MyComponent разширява Компонент {
  конструктор (подпори) {
    супер (подпори);
  }
  състояние = {
    брой: 0,
  };
}

Това изглежда много по-добре, по-чисто, по-четимо и също допринася за документацията за компоненти. Обектът на състоянието трябва да се инициализира след propTypes и defaultProps:

import React, {Component} от 'react';
импортиране на PropTypes от 'prop-type';
клас MyComponent разширява Компонент {
  // propTypes идва на първо място
  статични propTypes = {
    заглавие: PropTypes.string,
  };
  // defaultProps идва на второ място
  static defaultProps = {
    заглавие: „Прост брояч“,
  };
  // конструктор идва тук
  конструктор () {
    ...
  }
  // тогава идва държавата
  състояние = {
    брой: 0,
  };
}

Предайте функция за setState

Реактивната документация обезсърчава разчитането на this.state и this.props стойности за изчисляване на следващото състояние, защото React ги актуализира асинхронно. Това означава, че състоянието може да не се промени веднага след извикване setState ().

клас MyComponent разширява Компонент {
  състояние = {
    брой: 10,
  }
  onClick = () => {
    console.log (this.state.count); // 10
    
    // броя няма да се промени веднага
    this.setState ({count: this.state.count + this.props.step});
    
    console.log (this.state.count); // все още 10
  }
}

Въпреки че това работи за прости сценарии и състоянието все още ще се актуализира правилно, това може да доведе до неочаквано поведение при по-сложни сценарии.

Обмислете този сценарий, имате компонент, който прави един бутон. След натискане на този бутон се извиква метод на дръжка:

клас MyComponent разширява Компонент {
  static defaultProps = {
    стъпка: 5,
  }
  статични propTypes = {
    стъпка: PropTypes.Number,
  }
  
  състояние = {
    брой: 10,
  }
  
  handleClick = () => {
    this.doSomething ();
    this.doSomethingElse ();
  }
  doSomething = () => {
    this.setState ({count: this.state.count + this.props.step});
  }
  doSomethingElse = () => {
    this.setState ({count: this.state.count - 1});
  }
  render () {
    връщане (
      
        

Текущият брой е: {this.state.count}

        <бутон onClick = {this.handleClick}> Кликнете върху мен            );   } }

Бутонът призовава handleClick () при щракване, което от своя страна извиква doSomething (), след това doSomethingElse (). И двете функции ще променят стойността на броя в рамките на държавата.

Логично 10 + 5 е 15, след това извадете 1 и резултатът трябва да е 14, нали? Е, в този случай не е - стойността на броя след първото щракване е 9, а не 14. Това се случва, защото стойността на this.state.count все още е 10, когато се извиква doSomethingElse (), а не 15.

За да поправите това, можете да използвате втора форма на setState (), която приема функция, а не обект. Тази функция ще получи предишното състояние като първи аргумент, а реквизитите по време на актуализацията се прилагат като втори аргумент:

this.setState ((prevState, реквизит) => ({
  брой: prevState.count + props.step
}))

Можем да използваме този формуляр, за да оправим нашия пример:

клас MyComponent разширява Компонент {
  ...
  handleClick = () => {
    this.doSomething ();
    this.doSomethingElse ();
  }
  doSomething = () => {
    this.setState ((prevState, реквизит) => ({
      брой: prevState.count + props.step
    }));
  }
  doSomethingElse = () => {
    this.setState (prevState => ({
      брой: prevState.count - 1
    }));
  }
  ...
}

С тази имплементация броят се актуализира правилно от 10 до 14 до 18 и така нататък. Простите математики отново имат смисъл!

Използвайте стрелките като свойства на клас

Тази ключова дума винаги е объркваща за разработчиците на JavaScript и нейното поведение не е по-малко объркващо в React компоненти. Знаете ли как тази ключова дума се променя в компонент React? Помислете следния пример:

import React, {Component} от 'react';
клас MyComponent разширява Компонент {
  състояние = {
    брой: 0,
  };
  onClick () {
    console.log (this.state);
  }
  render () {
    връщане (
      
        

Броят е: {this.state.count}

        <бутон onClick = {this.onClick}> Кликнете върху мен            );   } }
експортиране по подразбиране MyComponent;

Щракването върху бутона ще доведе до грешка:

Uncaught TypeError: Не може да се чете свойството „state“ на неопределено

Това е така, защото onClick, като метод на клас, не е обвързан по подразбиране. Има няколко начина за коригиране на това. (каламбур предназначен, разбираш ли?)

Един от начините е да свържете функцията с правилния контекст, докато я прехвърлите във функцията render ():

<бутон onClick = {this.onClick.bind (това)}> Кликнете върху мен 

Или можете да избегнете промяна на контекста, като използвате функция със стрелка в render ():

<бутон onClick = {e => this.onClick (e)}> Кликнете върху мен 

Тези два метода обаче имат леки разходи за изпълнение, тъй като функцията ще бъде преразпределена за всеки рендер. За да избегнете тази малка цена на производителност, можете да свържете функцията вътре в конструктора:

import React, {Component} от 'react';
клас MyComponent разширява Компонент {
  конструктор (подпори) {
    супер (подпори);
    this.onClick = this.onClick.bind (това);
  }
  ...
  render () {
    ...
    <бутон onClick = {this.onClick}> Кликнете върху мен 
    ...
  }
}
експортиране по подразбиране MyComponent;

Тази техника е по-добра, но лесно бихте могли да се увлечете и да свършите с нещо, което изглежда така:

конструктор (подпори) {
  // това е лошо, наистина лошо
  this.onClick = this.onClick.bind (това);
  this.onChange = this.onChange.bind (това);
  this.onSubmit = this.onSubmit.bind (това);
  this.increaseCount = this.increaseCount.bind (това);
  this.decreaseCount = this.decreaseCount.bind (това);
  this.resetCount = this.resetCount.bind (това);
  ...
}

Тъй като използваме Babel и имаме поддръжка за свойствата на класа, по-добрият начин би бил да използваме стрелките при определяне на методите на класа:

клас MyComponent разширява Компонент {
  ...
  onClick = () => {
    // „това“ е запазено
    console.log (this.state);
  }
  render () {
    връщане (
      
        

{this.state.count}         <бутон onClick = {this.onClick}> Кликнете върху мен            );   } }

Унищожи обекта на подпори

Когато компонентът има много подпори, унищожете реквизитния обект, поставяйки всяко свойство в своя собствен ред.

За функционални компоненти без гражданство:

експорт по подразбиране функция MyComponent ({
  първо име,
  фамилия,
  имейл адрес,
  описание,
  onChange,
  onSubmit,
}) {
  връщане (
    
      

{FirstName}       ...        ); }

Аргументите по подразбиране не са извинение за отпадане на defaultProps. Както споменахме по-рано, винаги трябва да дефинирате propTypes и defaultProps.

За компоненти, базирани на клас:

клас MyComponent разширява Компонент {
  ...
  render () {
    const {
      първо име,
      фамилия,
      имейл адрес,
      описание,
      onChange,
      onSubmit,
    } = this.props;
    връщане (
      
        

{FirstName}         ...            );   } }

Това е по-чисто, улеснява пренареждането на свойствата и улеснява добавянето / премахването на свойства към / от списъка, като същевременно генерира четена разлика за Git. Помислете следното:

Разликата е по-четена, когато всяка собственост е на нов ред

От дясната страна можете лесно да разберете коя собственост е добавена, но от лявата страна знаете само, че нещо се е променило в тази линия и трябва да погледнете наистина внимателно, за да разберете коя част от реда е била променило.

Условно изобразяване

Когато трябва да изобразите един от два компонента или блока от JSX код въз основа на условие, използвайте термичен израз:

isLoggedIn
  ? 
Добре дошли, {username}!
  : <бутон onClick = {this.login}> Вход

Ако кодът се състои от повече от един ред, използвайте скоби:

isLoggedIn? (
  
    Добре дошли, {usename}!    ): (   <бутон onClick = {this.login}>     Влизам    )

Ако трябва да направите един компонент или блок от JSX код въз основа на условие, вместо това използвайте оценка на късо съединение:

isComplete && 
Вие сте готови!

За повече от един ред използвайте скоби:

завършено е && (
  
    Ти си готов!    )

Ключовият атрибут

Често срещан модел е да се използва Array.prototype.map и подобни методи на масив във функцията render () и е лесно да се забрави за ключовия атрибут. Примирението е достатъчно трудно, не го затруднявайте. Винаги не забравяйте да поставите ключа там, където му принадлежи. (каламбур, предназначен отново, разбирате ли?)

render () {
  връщане (
    <Ул> {
      {
        items.map (item => (
          
  •             {име на предмета}                    ))        }        ); }
  • Когато използвате map (), filter () или подобни методи на масив, вторият параметър за обратния разговор е индексът на елемента. По принцип е лоша идея да използвате този индекс като ключ. React използва атрибута ключ, за да идентифицира кои елементи са променени, добавени или премахнати, а стойността на ключа трябва да е стабилна и да идентифицира уникално всеки елемент.

    В случаите, когато масивът е сортиран или елемент е добавен в началото на масива, индексът ще бъде променен, въпреки че елементът, представляващ този индекс, може да е същият. Това води до ненужни рендери, а в някои случаи води до показване на грешни данни.

    Използвайте UUID или ShortID, за да генерирате уникален идентификатор за всеки елемент, когато е създаден за първи път, и го използвайте като ключова стойност.

    Нормализиране на държавата

    Опитайте се да нормализирате обекта на състояние и да го поддържате възможно най-плосък. Влагането на данни в състояние означава, че за обновяването им е необходима по-сложна логика. Представете си колко грозно би било да актуализирате дълбоко вложено поле - изчакайте, не си представяйте, тук:

    // държавният обект
    състояние = {
      ...
      публикации: [
        ...,
        {
          мета: {
            id: 12,
            автор: '...',
            публично: невярно,
            ...
          }
          ...
        }
        ...
      ],
      ...
    };
    // да актуализирате 'public' до 'true'
    this.setState ({
      ... this.state,
      публикации: [
        ... this.state.posts.slice (0, индекс),
        {
          ... this.state.posts [индекс],
          мета: {
            ... this.state.posts [индекс] .meta,
            публично: вярно,
          }
        }
        ... this.state.posts.slice (индекс + 1),
      ]
    });

    Този код е не само грозен, но и може да принуди несвързаните компоненти да се представят отново, дори ако данните, които показват, всъщност не са променени. Това е така, защото актуализирахме всички предци в дървото на държавата с нови препратки към обекти.

    Актуализирането на същото поле би било по-просто, ако преструктурираме дървото на състоянието:

    състояние = {
      публикации: [
        10,
        11,
        12,
      ],
      мета: {
        10: {
          id: 10,
          автор: „автор-а“,
          публично: невярно,
        }
        11: {
          id: 11,
          автор: „автор-б“,
          публично: невярно,
        }
        12: {
          id: 12,
          автор: „author-c“,
          публично: невярно,
        }
      }
    }
    this.setState ({
      мета: {
        ... this.state.meta,
        [документ за самоличност]: {
          ... this.state.meta [ID],
          публично: вярно,
        }
      }
    })

    Използвайте имена на класове

    Ако някога сте били в ситуация, в която трябва да използвате условно име на клас, може би сте написали нещо, което изглежда така:

      ...

    Това става наистина грозно, ако имате множество условни имена на класове. Вместо да използвам тройка, използвайте пакет имена на класове:

    импортиране на имена на класове от „имена на класове“;
    const класове = имена на класове ('раздел', {'is-active': isActive});
      ...

    Един компонент на файл

    Това не е трудно правило, но предпочитам да съхранявам всеки компонент в един файл и да експортирам този компонент по подразбиране. Нямам конкретна причина за това, но просто го намирам за по-чист и организиран - високи пет, ако и вие го направите!

    заключение

    Няма един правилен начин за разработване на компонент React. Преминахме няколко от най-добрите практики и модели, които ще ви помогнат да разработите компоненти за многократна употреба и поддръжка и много ми е любопитно да знам какви модели и практики предпочитате и защо.

    В следващата част на тази серия ще създадем просто To-Do приложение и ще приложим някои от тези най-добри практики и модели.

    Полезна ли беше тази статия? Моля, кликнете върху бутона Клоп по-долу или ме последвайте за повече.

    Благодаря за четенето! Ако имате обратна връзка, оставете коментар по-долу.

    Отидете на част 7-b: Създаване на просто приложение за ToDo (Скоро)