Гайд как писать на React в 2017

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

Начнём с простого.

Class Components

Компоненты на основе классов — это stateful компоненты (с внутренним состоянием). Использовать их надо только там где они нужны!

Давайте напишем один из них ✍

Импортируем зависимости

import React, { Component } from 'react'
import styles from './styles.css'

Отделяйте локально импортируемые зависимости от других. Повышает читаемость кода.

Инициализация state

import React, { Component } from 'react'
import styles from './styles.css'
export default class Container extends Component {
state = { name: 'Input' }

state уже можно объявлять без конструктора.

Он обычно используется только в трёх случаях:

  1. Вам нужны props для того, чтобы поместить их в state.
  2. Вам нравится старый подход ??
  3. Вам нужно использовать debounce или throttle для методов.

propTypes и defaultProps

import React, { Component } from 'react'
import { string } from 'prop-types'
import styles from './styles.css'
export default class Container extends Component {
static propTypes = {
title: string
}

static defaultProps = {
title: 'React'
}
  state = { name: 'Input' }

propTypes и defaultProps это статичные свойства. Объявлять их надо как можно выше (позже вам скажут спасибо многие разработчики).

Кстати, если вы используйте React 15.3.0 и выше, то теперь PropTypes это отдельная библиотека prop-types.

Методы

import React, { Component } from 'react'
import { string } from 'prop-types'
import Input from '../Input'
import styles from './styles.css'
export default class Container extends Component {
static propTypes = {
title: string
}

static defaultProps = {
title: 'React'
}
  state = { name: 'Input' }
  onInputChange = (e) => this.setState({ name: e.target.value })

Если ваш метод попадёт в children, то надо убедиться, что this будет указывать на компонент родителя.

Раньше это делали так

this.onInputChange.bind(this) // старый подход

Но с приходом ES6 появились arrow function () => {} — этот подход более чистый и компактный, используйте его.

Деструктуризация Props

import React, { Component } from 'react'
import { string } from 'prop-types'
import Input from '../Input'
import styles from './styles.css'
export default class Container extends Component {
static propTypes = {
title: string
}

static defaultProps = {
title: 'React'
}
  state = { name: 'Input' }
  onInputChange = (e) => this.setState({ name: e.target.value })
  render() {
const { title } = this.props;
    return (
<Input
onChange={this.onInputChange}
title={title}
/>
)
}

С помощью деструктуризации теперь можно писать вот так

const {
name,
title
} = this.props

Вместо

const title = this.props.title
const name = this.props.name

Каждый props который приходит в компоненту должен быть описан с новой линии. Повышает читаемость.

Декораторы

Edit: Декораторы пока не являются стандартом и возможно будут переделаны в будущем, используйте на свой страх и риск (он минимален)
import React, { Component } from 'react'
import { string } from 'prop-types'
import { connect } from 'react-redux'
import Input from '../Input'
import styles from './styles.css'
const mapStateToProps = state => ({ title: state.title })
@connect(mapStateToProps)
export default class Container extends Component {
static propTypes = {
title: string
}

static defaultProps = {
title: 'React'
}
  state = { name: 'Input' }
  onInputChange = (e) => this.setState({ name: e.target.value })
  render() {
const {
title
} = this.props;
    return (
<Input
onChange={this.onInputChange}
title={title}
/>
)
}

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

Без них мы бы до сих пор делали это так

class Container extends Component {
...
}
export default connect(mapStateToProps)(Container);

Лучше всего выносить логику с redux в отдельный компонент, который будет контейнером для текущего.

- Profile
--- index.js
--- ProfileContainer.js <-- логика с redux
--- Profile.js <-- ваш компонент


Functional Components

У этих компонентов нету состояния (stateless). Они чисты и понятны, используйте их как можно чаще.

Чем они отличаются от statefull компонентов ?

propTypes

import React from 'react'
import { string } from 'prop-types'
import style from './styles.css'
Stateless.propTypes = {
title: string
}
// Component declaration

Объявление proptypes вынесены из компонента.

Деструктуризация Props и defaultProps

import React from 'react'
import { string } from 'prop-types'
import style from './styles.css'
Stateless.propTypes = {
title: string
}
function Stateless(props) {
const { title } = props;
return (
<div>{title || 'React'}</title>
)
}

Теперь наш компонент это функция и мы можем делать так

import React from 'react'
import { string } from 'prop-types'
import style from './styles.css'
Stateless.propTypes = {
title: string
}
// defalutProps можно объявлять в самих props - title = 'React'
function Stateless({ title = 'React' }) {
return ( <div>{title}</div> )
}

Выглядит классно, но можно ещё лучше

import React from 'react'
import { string } from 'prop-types'
import style from './styles.css'
Stateless.propTypes = {
title: string
}
const Stateless = ({ title = 'React' }) => <div>{title}</div>
Некоторые разработчики не советуют так делать, наша функция unnamed и при возникновении ошибки вы увидите в консоле <<anonymous>>.
Но это не проблема если ваш Babel настроен правильно.


Как улучшить код дальше ?

HOC

Многие привыкли делать запросы на сервер в методе componentDidMount , но с появлением такого подхода как Higher-Order Components стоит вынести их повыше.

К примеру у нас есть компонент Profile и для его отображения ему нужен объект user приходящий с сервера.

Создадим HOC который будет принимать пользователя и передавать его в Profile.

// HOC withUser
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { fetchUser } from 'actions';
const mapDispatchToProps = {
fetchUser
};
const withUser = (WrapedComponent) => {
@connect(null, mapDispatchToProps)
class AsyncComponent extends Component {
componentWillMount = async () => {
const user = await fetchUser(); // наш запрос
this.setState({ user });
}
    render() {
const { user } = this.state;
const newProps = { user };
if (!user) return null; // если user ещё нет вернём null
      return (
<WrapedComponent {...this.props} {...newProps} />
)
}
}
return AsyncComponent;
}
export default withUser;

Многим код покажется сложноват, но поверьте, тут всё просто. withUser — это обёртка над нашим компонентом, которая не даст ему отрендериться пока не придёт user.

Как и с чем использовать ?

Если вашей компоненте нужен user, то просто добавим наш HOC как декоратор

import withUser from 'decorators/withuser';
@withUser
class Profile extends Component {
...
  render() {
const { user } = this.props;
return ( <div>{user.name}</div> )
}
}

Также с помощью декораторов и HOC можно делать интересные вещи. К примеру давайте отображать загрузочный экран пока user идёт к нам с сервера.

// Loading
const Loading = (component) => component ?
component : <div>Loading...</div>

// Profile
import withUser from 'decorators/withuser'
import Loading from 'components/Loading'
import { compose } from 'redux'
class Profile extends Component {
...
}
export default compose(
withUser,
Loading
)(Profile)

Reselect

Библиотека которая не даст компоненту перерисовываться, если входные данные (props) не изменились.

Recompose

Это как lodash только для реакта.

Redux-actions

Уменьшит код в 2 раза. Повысит читаемость.

Пример

До

export function increment(counter) {
return {
type: INCREMENT,
payload: {
counter
}
}
}

После

const increment = createAction(INCREMENT);

И напоследок

Надеюсь теперь, с помощью этой статьи, вы прокачайте ваш проект!

Ниже есть список статей который поможет улучшить ваш код и разобраться как работает Virtual DOM на самом деле.

Если вы есть в Телеграмме, то тут https://t.me/frontandend я делюсь Фронтэндом.

На этом всё! 👍

Оригинал


CLAP. CLAP. CLAP.

Потому что, почему бы и нет? Ты можешь.👏

Что ещё почитать ?

  1. Как работает Virtual DOM ?
  2. Почему Reducer должен быть “чистой” функцией в Redux ?
  3. Паттерны в Реакт (кратко и без воды)