Что это
Гайдлайны для 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, ... }, });
- Использовать Material UI Grid (https://material-ui.com/layout/grid/) везде где можно.
- Брать цвета из theme:
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 и т.д.