@AlexKindGeek

Нормально ли написаны компоненты??

Привет. Ребят, необходим взгляд со стороны на мой код.
Нормально ли написаны компоненты??
Возможно есть моменты, на которые стоит обратить внимание?

UserListComponent
import React, { Component, Fragment } from "react";
import { connect } from "react-redux";

/**Components */
import InfiniteScroll from "react-infinite-scroll-component";
import { BeatLoader } from "react-spinners";
import User from "./User";

/**Actions */
import { getListOfUsers, clearData, setUserChat } from "../../../actions";

/**Configs, Utils */

/**Material UI */

class UserList extends Component {
  state = {
    per_page: 15
  };

  componentDidMount() {
    this.getChatsUser();
  }

  /**Called when we change chat type (all, unread, read etc.) */
  componentDidUpdate(prevProps) {
    const { clearData } = this.props;
    if (prevProps.params !== this.props.params) {
      clearData();
      this.getChatsUser();
    }
  }

  fetchMoreData = () => {
    this.getChatsUser();
  };

  /**
   * Get query of params from props and called api action for getting users.
   * When fetchMoreData is called, we should increase per_page and
   * override userData at reducer (getListOfUsers action)
   */
  getChatsUser = () => {
    const { getListOfUsers, params } = this.props;
    const { per_page } = this.state;
    const query = Object.assign({}, params, { per_page });
    getListOfUsers(query).then(() => {
      this.setState({
        per_page: this.state.per_page + 5
      });
    });
  };

  render() {
    const {
      chatReducer: { userData },
      setUserChat
    } = this.props;
    return (
      <Fragment>
        <InfiniteScroll
          dataLength={userData.length}
          next={this.fetchMoreData}
          hasMore={true}
          height={520}
          loader={
            <BeatLoader
              className="chat-loader"
              sizeUnit="px"
              size={12}
              color="#123abc"
            />
          }
          endMessage={
            <p style={{ textAlign: "center" }}>
              <b>Yay! You have seen it all</b>
            </p>
          }
        >
          {userData.map((row, index) => (
            <User setUserForChat={setUserChat} key={row.id} userData={row} />
          ))}
        </InfiniteScroll>
      </Fragment>
    );
  }
}

const mapDispatchToProps = {
  getListOfUsers,
  clearData,
  setUserChat
};

const mapStateToProps = (state) => ({
  chatReducer: state.chatReducer
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(UserList);

User Component
import React from "react";
import moment from "moment";

class User extends React.Component {
  formatTime = (data) => {
    if (data) {
      const started = moment.unix(data).format("HH:mm MM/DD/YYYY");
      const finished = moment().format("HH:mm MM/DD/YYYY");
      let minutes = moment(finished).diff(moment(started), "minutes");
      const days = moment(finished).diff(moment(started), "days");
      const hours = Math.floor(minutes / 60);
      minutes %= 60;
      if (days > 0) {
        return moment.unix(data).format("MM/DD/YYYY");
      }
      if (hours > 0) {
        return `${hours}h`;
      }
      return `${minutes}m`;
    }
  };

  render() {
    const {
      userData,
      userData: { user_id, message_created_at, message, unread_amount },
      setUserForChat
    } = this.props;
    return (
      <div
        className="user-data-container"
        onClick={() => setUserForChat(userData)}
      >
        <div className="user-cell-container">
          <div className="user-id-container">User # {user_id}</div>
          <div className="message-time-container">
            {this.formatTime(message_created_at)}
          </div>
        </div>
        <div className="user-cell-container">
          <div className="last-message-container">
            {message || "No messages"}
          </div>
          {unread_amount > 0 ? (
            <div className="unread-message-container">{unread_amount}</div>
          ) : (
            ""
          )}
        </div>
      </div>
    );
  }
}

export default User;

  • Вопрос задан
  • 115 просмотров
Решения вопроса 1
rockon404
@rockon404 Куратор тега React
Frontend Developer
1. formatTime - хелпер, я не буду таратить время на анализ качества его реализации, скажу лишь, что в компоненте ему не место. Перенесите в папку lib, utils или как там она у вас называется, импортируйте где нужен и используйте.

2. Закроем, пока глаза на пункт 1. formatTime у вас написан стрелочной функцией, но он никуда не передается и в нем не используется ключевое слово this - определять его стрелочной функцией было бессмысленно. Изучите вопрос и постарайтесь понять зачем их используют.
spoiler
привязка контекста

То же с getChatsUser - функция никуда не передается.

3. Никогда не используйте тернарки если, альтернативный кейс null. Замените на:
render() {
  /* ... */
  const hasUnreadMessages = unread_amount > 0;
  
  return (
    <div>
      {hasUnreadMessages && (
        <div className="unread-message-container">{unread_amount}</div>
      )}
    </div>
  );
}


4. Зачем передаете в mapStateToProps все состояние редьюсера? Передавайте только необходимые компоненту данные:
export const userDataSelector = state => state.chatReducer.userData;

const mapStateToProps = state => ({
  userData: userDataSelector(state),
});

Селекторы очень полезная вещь. В реальных приложениях они часто помногу раз переиспользуются и в случае изменения структуры store, вам придется изменить только селекторы, вместо изменения реализаций mapStateToProps по всему приложению. Так же, они, зачастую, короче.

Почитайте про библиотеку reselect.

5. Вместо:
const query = Object.assign({}, params, { per_page });

лаконичней и проще для анализа:
const query = { ...params, per_page };

6. Правильное обновление состояния на основе предыдущего:
this.setState(prevState => ({
  per_page: prevState.per_page + 5,
}));


7. Использование trailing comma - очень хорошая практика, советую ей не пренебрегать.

8. Более оптимизированным вариантом для списков, будет вместо определения стрелочных функции в элементах каждый render:
<div onClick={() => setUserForChat(userData)}>
использовать data-атрибуты:
<div data-id={userData.id} onClick={setUserForChat}>

Реализацию setUserForChat придется изменить:
setUserForChat = e => {
  const { id } = e.target.dataset;
  // some code with using id
}
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@abberati
frontend-разработчик
хороший тон - в тернарных операторах последним операндом вместо "" ставить null
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы