Паттерны React

Рекоммендации, представленные в этой статье, являются результатом, основанным на собственном опыте построения приложений на React.

Организация компонента

class definition
    constructor
    event handlers
    'component' lifecycle events
    getters
    render
defaultProps
proptypes
class Person extends React.Component {
  constructor (props) {
    super(props);

    this.state = { smiling: false };

    this.handleClick = () => {
      this.setState({smiling: !this.state.smiling});
    };
  }

  componentWillMount () {
    // add event listeners (Flux Store, WebSocket, document, etc.)
  },

  componentDidMount () {
    // React.getDOMNode()
  },

  componentWillUnmount () {
    // remove event listeners (Flux Store, WebSocket, document, etc.)
  },

  get smilingMessage () {
    return (this.state.smiling) ? "is smiling" : "";
  }

  render () {
    return (
      <div onClick={this.handleClick}>
        {this.props.name} {this.smilingMessage}
      </div>
    );
  },
}

Person.defaultProps = {
  name: 'Guest'
};

Person.propTypes = {
  name: React.PropTypes.string
};

Форматирование props

Объявляйте props с новой строки если их больше одного.

// bad
<Person
 firstName="Michael" />

// good
<Person firstName="Michael" />
// bad
<Person firstName="Michael" lastName="Chan" occupation="Designer" favoriteFood="Drunken Noodles" />

// good
<Person
 firstName="Michael"
 lastName="Chan"
 occupation="Designer"
 favoriteFood="Drunken Noodles" />

Операции с props

Используйте getters для вычисляемых свойств.

// bad
firstAndLastName () {
  return `${this.props.firstName} ${this.props.lastname}`;
}

// good
get fullName () {
  return `${this.props.firstName} ${this.props.lastname}`;
}

Смотри Cached State in render anti-pattern

Проверка состояния

Для проверки состояния компонента так же лучше использовать getters, префикс, которого должен начинаться с глагола.

// bad
happyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}

// good
get isHappyAndKnowsIt () {
  return this.state.happy && this.state.knowsIt;
}

Такие методы должны возвращать логическое значение.

Смотри Compound Conditions anti-pattern

Rendering

Держите логику внутри render.

// bad
renderSmilingStatement () {
  return <strong>{(this.state.isSmiling) ? " is smiling." : ""}</strong>;
},

render () {
  return <div>{this.props.name}{this.renderSmilingStatement()}</div>;
}

// good
render () {
  return (
    <div>
      {this.props.name}
      {(this.state.smiling)
        ? <span>is smiling</span>
        : null
      }
    </div>
  );
}

View компоненты

Не стоит мешать компоненты в кучу, лучше разбивать их на логические звенья.

// bad
class PeopleWrappedInBSRow extends React.Component {
  render () {
    return (
      <div className="row">
        <People people={this.state.people} />
      </div>
    );
  }
}

// good
class BSRow extends React.Component {
  render () {
    return <div className="row">{this.props.children}</div>;
  }
}

class SomeView extends React.Component {
  render () {
    return (
      <BSRow>
        <People people={this.state.people} />
      </BSRow>
    );
  }
}

Container компоненты

A container does data fetching and then renders its corresponding sub-component. That's it. — Jason Bonta

// bad
// CommentList.js

class CommentList extends React.Component {
  getInitialState () {
    return { comments: [] };
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return (
      <ul>
        {this.state.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}
// good
// CommentList.js

class CommentList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.comments.map(({body, author}) => {
          return <li>{body}—{author}</li>;
        })}
      </ul>
    );
  }
}

// CommentListContainer.js

class CommentListContainer extends React.Component {
  getInitialState () {
    return { comments: [] }
  }

  componentDidMount () {
    $.ajax({
      url: "/my-comments.json",
      dataType: 'json',
      success: function(comments) {
        this.setState({comments: comments});
      }.bind(this)
    });
  }

  render () {
    return <CommentList comments={this.state.comments} />;
  }
}

Смотри Making your app fast with high-performance components

Кеширование состояния в render

Не держите состояние в render.

// bad
render () {
  let name = `Mrs. ${this.props.name}`;

  return <div>{name}</div>;
}

// good
render () {
  return <div>{`Mrs. ${this.props.name}`}</div>;
}

// best
get fancyName () {
  return `Mrs. ${this.props.name}`;
}

render () {
  return <div>{this.fancyName}</div>;
}

Это в основном стилистические рекоммендации. Сомневаюсь, что от этого есть еще какая-то польза.

Смотри Computed Props pattern

Проверка существования

Не проверяйте существование props. Используйте defaultProps.

// bad
render () {
  if (this.props.person) {
    return <div>{this.props.person.firstName}</div>;
  } else {
    return null;
  }
}

// good
class MyComponent extends React.Component {
  render() {
    return <div>{this.props.person.firstName}</div>;
  }
}

MyComponent.defaultProps = {
  person: {
    firstName: 'Guest'
  }
};

Эта рекоммендация только для объектов или массивов. Используйте PropTypes.shape для уточнения типов вложенных данных, ожидаемых компонентом.

Установка состояния из props

Не установливайте состояние из props без необходимости.

// bad
getInitialState () {
  return {
    items: this.props.items
  };
}

// good
getInitialState () {
  return {
    items: this.props.initialItems
  };
}

Смотри Props in getInitialState Is an Anti-Pattern

Именнование обработчиков

// bad
punchABadger () { /*...*/ },

render () {
  return <div onClick={this.punchABadger} />;
}

// good
handleClick () { /*...*/ },

render () {
  return <div onClick={this.handleClick} />;
}

Название обработчиков должно:

  • начинаться с handle;
  • заканчиваться названием события, которое он слушает (Click, Change);
  • быть в настоящем времени.

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

Для наглядности и лучшей читаемости можно добавить дополнительную информацию между handle и названием события. Например, onChange -> handleNameChange.

Именнование событий

Используйте простые и понятные имена событий.

class Owner extends React.Component {
  handleDelete () {
    // handle Ownee's onDelete event
  }

  render () {
    return <Ownee onDelete={this.handleDelete} />;
  }
}

class Ownee extends React.Component {
  render () {
    return <div onChange={this.props.onDelete} />;
  }
}

Ownee.propTypes = {
  onDelete: React.PropTypes.func.isRequired
};

Stateless function

Функция без состояния ( далее Простые Копоненты) прекрасный способ определить универсальный компонент. Они не содержат состояния (state) или ссылку на DOM элемент (ref), это просто функции.

const Greeting = () => <div>Hi there!</div>

В них передаются параметры (props) и контекст

const Greeting = (props, context) =>
  <div style={{color: context.color}}>Hi {props.name}!</div>

Они могут определять локальные переменные, если используете блоки ({})

const Greeting = (props, context) => {
  const style = {
    fontWeight: "bold",
    color: context.color,
  }
  return <div style={style}>{props.name}</div>
}

Тот же результат можно получить используя функцию:

const getStyle = context => ({
  fontWeight: "bold",
  color: context.color,
})
const Greeting = (props, context) =>
  <div style={getStyle(context)}>{props.name}</div>

В простых компонентах так же можно определить defaultProps, propTypes и contextTypes

Greeting.propTypes = {
  name: PropTypes.string.isRequired
}
Greeting.defaultProps = {
  name: "Guest"
}
Greeting.contextTypes = {
  color: PropTypes.string
}

Условные операторы

Можете использовать обычный if/else синтаксис в компонентах:

if

{ condition && Rendered when `truthy` }

unless

{ condition || Rendered when `falsey` }

if-else

{ condition
  ? Rendered when `truthy`
  : Rendered when `falsey`
}

if-else (большой блок)

{ condition ? (
    Rendered when `truthy`
) : (
    Rendered when `falsey`
)}

Higher-order component

Функция высшего порядка - это функция которая может принимать в качестве аргументов другие функции и/или возвращать функции.

Вы уже используете компоненты-контейнеры, это просто контейнеры, обернутые в функцию. Давайте начнем с простого Greeting компонента.

const Greeting = ({ name }) => {
  if (!name) { return <div>Connecting...</div> }
  return <div>Hi {name}!</div>
}

Если он получит props.name, он отрендерит данные. В противном случае он отрендерит “Connecting…”. Теперь немного более высокий порядок:

const Connect = ComposedComponent =>
  class extends React.Component {
    constructor() {
      super()
      this.state = { name: "" }
    }

    componentDidMount() {
      // this would fetch or connect to a store
      this.setState({ name: "Michael" })
    }

    render() {
      return (
        
      )
    }
  }

Это функция которая возвращает компонент, который рендерит компонент, который мы передали в качестве аргумента (ComposedComponent)

Далее мы оборачиваем компоте в эту функцию.

const ConnectedMyComponent = Connect(Greeting)

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