Сборка мощных советов и приемов для начинающих React

sborka moshhnyh sovetov i priemov dlya nachinayushhih react

Это часть моей серии «React для начинающих», посвященная знакомству с React, его основным функциям и лучшим методам работы. Будет еще больше статей!

<< Начать сначала | < Предыдущий

Как видно из названия этой статьи, она рассчитана на начинающих.

На самом деле, я начал изучать React несколько месяцев назад. Чтение документации React, проектов с открытым кодом и статей о Medium мне очень помогло.

Без сомнения, я не эксперт по React. Поэтому я много читал на эту тему. Кроме того, создание небольших проектов помогло мне лучше узнать React. Попутно я усвоил несколько лучших практик и хочу поделиться с вами здесь. Итак начнем.

Назовите свои компоненты

Чтобы понять, в каком компоненте есть ошибка, важно всегда давать ему имя.

Тем более, когда вы начинаете использовать React Router или сторонние библиотеки.

// Avoid thoses notations 
export default () => {};
export default class extends React.Component {};

Есть дебаты относительно того, использовать ли импорт по умолчанию или импорт. Обратите внимание, что а импорт по умолчанию не гарантирует, что имя компонента последовательно в проекте. Кроме того, встряхивание деревьев будет менее эффективным.

Независимо от того, как вы выставите свой компонент, назовите его

Вам нужно определить имя класса или имя переменной (для функциональных компонентов), где размещен компонент.

React фактически выведет название компонента из него в сообщениях об ошибках.

export const Component = () => <h1>I'm a component</h1>;
export default Component;

// Define user custom component name
Component.displayName="My Component";

Вот мой последний совет по импорту (взято отсюда): Если вы используете ESLint, вам следует рассмотреть возможность установки следующих двух правил:

"rules": {
    // Check named import exists
    "import/named": 2, 
  
    // Set to "on" in airbnb preset
    "import/prefer-default-export": "off"
}

Предпочитайте функциональные компоненты

Если у вас много компонентов, целью которых является только отображение данных, воспользуйтесь преимуществами многих способов определения компонента React:

class Watch extends React.Component {
  render () {
    return <div>{this.props.hours}:{this.props.minutes}</div>
  }
}

// Equivalent functional component
const Watch = (props) =>
  <div>{props.hours}:{props.minutes}</div>;

Оба фрагмента определяют то же самое Watch компонент. Однако второй гораздо короче и даже падает this доступ к реквизитам в шаблоне JSX.

Замените divs на фрагменты

Каждый компонент должен придавать уникальный корневой элемент в качестве шаблона. Чтобы следовать этому правилу, обычное решение состоит в том, чтобы завернуть шаблон в формат a div.

React 16 предлагает нам новую функцию под названием Фрагменты. Теперь вы можете заменить ненужные divs с React.Fragmentс.

Исходным шаблоном будет содержимое фрагмента без обертки.

const Login = () => 
  <div><input name="login"/><input name="password"/></div>;

const Login = () =>
  <React.Fragment><input name="login"/><input name="password"/></React.Fragment>;

const Login = () => // Short-hand syntax
  <><input name="login"/><input name="password"/></>;

Будьте осторожны при настройке состояния

Как только ваше приложение React становится динамическим, вы должны иметь дело с состояниями компонентов.

Использование состояний кажется достаточно простым. Инициализируйте содержимое состояния в constructor а затем позвонить setState обновить состояние.

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

// Very bad pratice: do not use this.state and this.props in setState !
this.setState({ answered: !this.state.answered, answer });

// With quite big states: the tempatation becomes bigger 
// Here keep the current state and add answer property
this.setState({ ...this.state, answer });

Проблема в том, что React не гарантирует this.state и this.props иметь ценность, которую вы ожидаете. setState является асинхронным, так как обновление состояния является пакетным для оптимизации манипуляций DOM (см. детали в документации React и эту проблему).

// Note the () notation around the object which makes the JS engine
// evaluate as an expression and not as the arrow function block
this.setState((prevState, props) 
              => ({ ...prevState, answer }));

Чтобы предотвратить поврежденные состояния, вы должны использовать setState с параметром функции. Он обеспечивает надлежащее состояние и значение параметров.

Функции компонента связывания

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

Первое и законное решение появляется в документации React:

class DatePicker extends React.Component {
   handleDateSelected({target}){
     // Do stuff
   }
   render() {   
     return <input type="date" onChange={this.handleDateSelected}/>
   }
 }

Вас может разочаровать, когда вы узнаете, что это не работает.

Причина в том, что при использовании JSX, this значение не привязано к экземпляру компонента. Вот три варианта, чтобы заставить это работать:

// #1: use an arrow function
<input type="date" onChange={(event) => this.handleDateSelected(event)}/>

// OR #2: bind this to the function in component constructor
constructor () { 
  this.handleDateSelected = this.handleDateSelected.bind(this); 
}

// OR #3: declare the function as a class field (arrow function syntax)
handleDateSelected = ({target}) => {
   // Do stuff
}

Использование стрелковой функции в JSX, как в первом примере, поначалу кажется привлекательным. Но не делайте этого. На самом деле, ваша функция стрелки будет создаваться снова во время визуализации каждого компонента, и это повредит производительности.

Также будьте осторожны с последним решением. Он использует синтаксис полей класса, являющийся только предложением для ECMAScript.

Это означает, что вы должны использовать Babel для транспилирования кода. Если синтаксис не принят, код сломается.

Принять шаблон контейнера (даже с Redux)

Последнее, но не менее важное, шаблон дизайна контейнера. Это позволяет придерживаться принципа разделения проблем в компоненте React.

export class DatePicker extends React.Component {
  state = { currentDate: null };

  handleDateSelected = ({target}) =>
     this.setState({ currentDate: target.value });

  render = () => 
     <input type="date" onChange={this.handleDateSelected}/>
}

Один компонент обрабатывает визуализацию шаблона и действия пользователя в одном месте. Давайте вместо этого используем два компонента:

const DatePicker = (props) => 
  <input type="date" onChange={props.handleDateSelected}/>
        
export class DatePickerController extends React.Component { 
  // ... No changes except render function ...
  render = () => 
     <DatePicker handleDateSelected={this.handleDateSelected}/>;
}

Вот в чем хитрость. DatePickerContainer обрабатывает взаимодействия с пользователем и вызовы API, если это необходимо. Тогда он предоставляет a DatePicker и снабжает реквизит.

Благодаря этому шаблону контейнерный компонент заменяет презентационный компонент. Этот функциональный компонент становится бесполезным без реквизита.

export const DatePickerContainer = 
 connect(mapStateToProps, mapDispatchToProps)(DatePickerController);

Кроме того, если вы используете Redux в качестве менеджера состояния для программы, он также хорошо подключается к этому шаблону.

The connect функция вводит props в компонент. В нашем случае он будет питать контроллер, который пересылает эти компоненты реквизиты.

Таким образом, оба компонента смогут получить доступ к данным Redux. Вот полный код шаблона проектирования контейнера (без синтаксиса Redux или полей класса).

Бонус: исправить сверление опор

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

Как глубоко вложенные тупые компоненты могут получить доступ к данным основного компонента? На самом деле, они не могут, но вы можете исправить это:

  • заворачивание немого компонента в контейнер (он становится умным)
  • или передайте реквизит от верхнего компонента

Второе решение предполагает, что компоненты между верхним компонентом и немым компонентом должны будут передавать ненужные опоры.

const Page = props => <UserDetails fullName="John Doe"/>;
   
const UserDetails = props => 
<section>
    <h1>User details</h1>
    <CustomInput value={props.fullName}/> // <= No need fullName but pass it down
</section>;

const inputStyle = {
   height: '30px',
   width: '200px',
	fontSize: '19px',
   border: 'none',
   borderBottom: '1px solid black'
};
const CustomInput = props => // v Finally use fullName value from Page component
   <input style={inputStyle} type="text" defaultValue={props.value}/>;

Сообщество React назвало эту проблему опорное бурение.

Page является основным компонентом, загружающим пользовательские данные. Необходимо передать эти данные UserDetails чтобы взять это CustomInput.

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

Не беспокойтесь, я научу вас трем способам исправить это. Первые два метода появляются в документации Context API: детский реквизит и визуализировать сопротивления.

// #1: Use children prop
const UserDetailsWithChildren = props => 
<section>
    <h1>User details (with children)</h1>
    {props.children /* <= use children */} 
</section>;

// #2: Render prop pattern
const UserDetailsWithRenderProp = props => 
<section>
    <h1>User details (with render prop)</h1>
    {props.renderFullName() /* <= use passed render function */}
</section>;

const Page = () => 
<React.Fragment>
    {/* #1: Children prop */}
    <UserDetailsWithChildren>
        <CustomInput value="John Doe"/> {/* Defines props.children */}
    </UserDetailsWithChildren>
  
    {/* #2: Render prop pattern */}
    {/* Remember: passing arrow functions is a bad pratice, make it a method of Page class instead */}
    <UserDetailsWithRenderProp renderFullName={() => <CustomInput value="John Doe"/>}/>
</React.Fragment>;

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

const Page = () =>  
<PageContent>
  <RightSection> 
    <BoxContent>
      <UserDetailsWithChildren>
          <CustomInput value="John Doe"/>
      </UserDetailsWithChildren>
    </BoxContent>
  </RightSection>
</PageContent>

В третьем примере используется экспериментальный контекстный API.

const UserFullNameContext = React.createContext('userFullName');

const Page = () => 
<UserFullNameContext.Provider value="John Doe"> {/* Fill context with value */}
    <UserDetailsWithContext/>
</UserFullNameContext.Provider>;

const UserDetailsWithContext = () => // No props to provide
<section>
    <h1>User details (with context)</h1>
    <UserFullNameContext.Consumer> {/* Get context value */}
        { fullName => <CustomInput value={fullName}/> }
    </UserFullNameContext.Consumer>
</section>;

Я не рекомендую этот метод, потому что он использует экспериментальную функцию. (И именно поэтому API недавно изменился на второстепенную версию.) Кроме того, это заставляет вас создать глобальную переменную для хранения контекста, и ваш компонент получает непонятную новую зависимость (контекст может содержать что угодно).

Это!

Спасибо, что прочли. Надеюсь, вы узнали несколько интересных советов о React!

Если вы нашли эту статью полезной, пожалуйста, нажмите на ? нажмите несколько раз, чтобы другие нашли статью и выразили свою поддержку! ?

Не забудьте подписаться на меня, чтобы получать уведомления о моих будущих статьях ?

Это часть моей серии «React для начинающих», посвященная знакомству с React, его основным функциям и лучшим методам работы.

<< Начать сначала | < Предыдущий

➥ JavaScript

➥ Советы и рекомендации

Добавить комментарий

Ваш адрес email не будет опубликован.