Что это

Гайдлайны для ReactJS, как правильно писать код фронтенда. Рекомендуется сперва прочитать про frontend на ReactJS.

Структура проекта

frontend/
    index.js
    translation.js
    user.js
    ...
    actions/
        cards.js
    assets/
        styles/
            mainStyle.js
            myCoolControlStyle.js
            ...
        img/
            my_img1.png
            my_img2.png
    containers/
        Main.js
        MyCoolControl.js
        Settings.js
        ...
    reducers/
        CardReducer.js

index.js

В корне всегда index.js - точка входа, она же передаётся в webpack (entry: './frontend/index.js').

translation.js

translation.js - всегда делаем такой файл для удобства работы с переводами.

потом можно писать вот так:

import {tr} from '../translation';
...
<Typography>{tr('Hello')}</Typography>

user.js

user.js - всегда делаем такой файл для удобства работы с правами.

потом можно писать вот так:

import {checkRights, RIGHTS_SETUP} from '../user';
...
checkRights(userState.currentUser, RIGHTS_SETUP)

action.js

action.js - всегда делаем такой файл чтобы напрямую не дёргать axios или ему подобное.

actions

здесь каждый файл - набор action'ов определённого рода, например:

import {postRequest} from '../action';

export const CARDS_LISTED = 'CARDS_LISTED';
export const CARD_ADDED = 'CARD_ADDED';

export function cardsList() {
    const request = postRequest('/list-cards', {});
    return {
        type: CARDS_LISTED,
        payload: request
    };
}

export function cardAdd(data) {
    const request = postRequest('/add-card', data);
    return {
        type: CARD_ADDED,
        payload: request
    };
}

т.е. никакой логики, просто создаётся action и возвращается.

reducers

по одному редусеру на каждый action файл, например CardReducer.js:

import {
    CARDS_LISTED,
    CARD_ADDED,
} from '../actions/cards';

const intialState =
{
    cards: []
};

export default function(state = intialState, action)
{
    switch (action.type) {
    case CARDS_LISTED: {
        if (action.error) {
            return {...state, cards: [] };
        }
        return {...state, cards: action.payload};
    }
    case CARD_ADDED: {
        if (action.error) {
            return state;
        }
        return {...state, cards: action.payload};
    }
    default:
        break;
    }
    return state;
}

containers

В React есть понятие умных и тупых компонент, обычно умные компоненты называются "containers", а тупые - "components", мы этого различия не делаем и называем их все "containers" и кладём в один каталог, поскольку часто один компонент может переделаться в другой, а лазить по коду и менять пути не хочется.

assets/styles

не используем css, все стили лежат в jss в отдельном каталоге и подцепляются из компонент:

mainStyle.js:

const mainStyle = theme => ({
    wrapper : {
        flexGrow: 1,
    },
});

export default mainStyle;
Main.js:
...
import withStyles from '@material-ui/core/styles/withStyles';
import mainStyle from '../assets/styles/mainStyle.js';

class Main extends Component {
    render() {
        const { classes } = this.props;
        return (<div className={classes.wrapper} />);
    }
}

export default withStyles(mainStyle)(Main);


преимущество jss над css в том что там у нас есть ссылка на 'theme', мы можем привязать стиль к теме.

assets/img

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

Использовать dev console браузера и React Developer Tools

Во время разработки всегда держать консоль браузера открытой, а также поставить расширение "React Developer Tools":

в консоли во время отладки используется redux-logger, можно смотреть что летит в backend, что возвращается, а также детальное состояние store'а.


react dev tools позволяют инспектировать пропсы и стейты компонентов.

Не игнорировать warning'и eslint

Сейчас eslint настроен в максимально жёстком режиме, т.е. "давать разработчику по рогам" при любой малейсшей оплошности. т.е. стоит заиспользовать двойные кавычки
вместо одинарных - ошибка сборки. Однако некоторые вещи всё-таки настроены в warning'и, например неиспользуемые модули (посколько в процессе разработки очень часто что-то добавляешь/удаляешь, не хочется каждый раз ловить на этом ошибки). Это не означает что можно оставлять висеть кучу неиспользуемых модулей,
это всё-равно надо исправлять в конце. Тем более что IDE в этом помогает:

Take advantage of redux middleware

action'ы, которые шлют что-то на backend могут долго не получать ответа, мы используем redux middleware, который шлёт дополнительный action ASYNC_START перед отправкой запроса и ASYNC_END после, это можно использовать для того чтобы показывать индикатор ожидания например:

...
import {
    ASYNC_START,
} from '../action';

const intialState =
{
    areas: [],
    areasInProgress: 0
};

export default function(state = intialState, action)
{
    switch (action.type) {
    case ASYNC_START: {
        if ((action.subtype === QUEUES_AREAS_LISTED) ||
            (action.subtype === QUEUES_AREA_ADDED) ||
            (action.subtype === QUEUES_AREA_UPDATED) ||
            (action.subtype === QUEUES_AREA_REMOVED)) {
            return {...state, areasInProgress: state.areasInProgress + 1 };
        }
        break;
    }
    case QUEUES_AREAS_LISTED: {
        if (action.error) {
            return {...state, areas: [], areasInProgress: state.areasInProgress - 1 };
        }
        return {...state, areas: action.payload, areasInProgress: state.areasInProgress - 1};
    }
    case QUEUES_AREA_ADDED:
    case QUEUES_AREA_REMOVED:
    case QUEUES_AREA_UPDATED: {
        if (action.error) {
            return {...state, areasInProgress: state.areasInProgress - 1 };
        }
        return {...state, areas: action.payload, areasInProgress: state.areasInProgress - 1};
    }
    ...
}

Использовать redux-form где это возможно

TODO: сделать пример...

Вёрстка

  • Избегать значений в пикселях, по возможности брать размеры из theme:
const contentAreaStyle = theme => ({
    paper: {
        padding: theme.spacing.unit * 2,
        ...
    },
});

const pluginCardStyle = theme => ({
    item: {
        color: `${theme.palette.text.primary}`
        ...
    },
    itemLink: {
        backgroundColor: `${theme.palette.action.selected}`,
        ...
    },
    selected: {
        backgroundColor: `${theme.palette.primary.light}`,
        ....
    },
});

handleChange

Использовать вот такой паттерн когда в форме много полей, которые надо редактировать:

handleChange = name => evt => {
    this.setState({
        [name] : evt.target.value
    });
}
...
<TextField value={this.state.days} onChange={this.handleChange('days')}/>
<TextField value={this.state.hours} onChange={this.handleChange('hours')}/>

чтоб не плодит кучу функций вида handleDaysChange, handleHoursChange и т.д.