Гайд как писать на React
Coding
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 уже можно объявлять без конструктора.
Он обычно используется только в трёх случаях:
- Вам нужны props для того, чтобы поместить их в state.
- Вам нравится старый подход ??
- Вам нужно использовать
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 который приходит в компоненту должен быть описан с новой линии. Повышает читаемость.
Декораторы
Декораторы пока не являются стандартом и возможно будут переделаны в будущем, используйте на свой страх и риск (он минимален)
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);
Не забывайте ставить 👍 если вам понравилась статья и подписаться на канал