- Create React App boilerplate (doc)
- React Router (doc)
- React Redux (doc)
- Axios (doc)
- Sass (doc)
- Ant Design (doc)
Перед началом работы создайте и заполните .env
по примеру .env.example
.
Запуск локального сервера разработки (development)
npm start
or yarn start
Сборка билда (production)
npm run build
or yarn build
больше информации о командах можно найти тут.
Это необходимые пакеты большинство из котрых присутвуют во всех наших проектах.
- src/actions
- src/components
- src/components/PrivateRoute
- src/containers
- src/containers/Public/App/index.jsx
- src/sources
- src/sources/styles/styles.scss
- src/sources/styles/variables.scss
- src/utils
- src/config.js
- src/index.js
- .env.example
- .eslintrc
- .prettierrc
Предназначено для работы со сторонними ресурсами (например API) и состоянием приложения.
В большенстве случаев список файлов экшенов соответсвует сущностям из API проекта. В примере используется тестовое REST API jsonplaceholder которое имеет следующие сущности: posts, comments, users и другие. Согласно этим сущностям наша папка отвечает этим сущностям. Благодаря такому подходу легко работать с API и состоянием проекта так как все методы четко разбиты по сущностям.
Также этот подход предполагает что экшены будут импортированы на прямую с целевых компонентов без передачи их в функцию connect пакета react-redux.
Типичный пример такого экшена
// import axios instace
import { api } from '../config'
// import store for interact with redux
import store from '../store'
// import action types
import { actionTypes as usersActionTypes } from 'store/reducers/users'
import { actionTypes as userActionTypes } from 'store/reducers/user'
// common http request
export async function getUser(id = 1) {
let res = await api.get(`/users/${id}`)
if (res.status !== 200) throw new Error(`Can't fetch user by id ${id}`)
return res.data
}
// http request + redux store
export async function getUsers() {
let res = await api.get('/users')
if (res.status !== 200) throw new Error(`Can't fetch user list`)
store.dispatch({ type: usersActionTypes.SET, payload: res.data })
}
// common redux action
export function setUser(user) {
store.dispatch({ type: userActionTypes.SET, payload: user })
localStorage.setItem('user', JSON.stringify(user))
}
Предназначено для компонентов которые используются в дувух или более различных компонентах проекта. Например это могут быть хедер, футер, приватный роут и так далее.
Такие компоненты обязательно должны иметь описание параметров с помощью Prop Types в случае если эти компоненты принимают параметры.
Приватный роут предназначен для закрытых частей приложения которые требуют авторизации или определенных параметров пользователя.
Предназначено для компонентов которые разделены по роутам проекта. Содержит такие компоненты как App, Admin, Public и другие если необходимо. Имеют неограниченную вложенность компонентов.
Разделение на Public, Admin и тд.
зависит от проекта, этого разделения может не быть если приложение выполняте 1 функцию и имеет только скрытый контент или только публичный.
Компонент App
является точкой входа в приложение которая обязательна для любого проекта.
Несколько примеров
- Компонент отвечающий за публичную страницу с постом по пути
/post/1
беудет находится вsrc/containers/Public/Post
- Компонент отвечающий за приватную страницу списка пользователей по пути
/admin/users
беудет находится вsrc/containers/Admin/Users
Точка входа в приложение, в котором необходимо инициализировать приложение, подключать библиотеки конфигов, провайдеров.
Если приложение имеет авторизацию, необходимо получить данные о текущем пользователе перед отображением роутера дабы избежать несвоевременных редиректов.
Предназначена для шрифтов, картинок, стилей и другого медиа контента, которые должны находиться только в этой папке (по компонентам не раскидывать)
Предназначин для разметки каркаса приложения, общих стилей приложения таких как определение шрифта, его размера, цвета фона и так далее.
Предназначен для определения переменных стилей таких как цвета проекта (палитра), шрифты, размеры. Также хранит в себе общие миксины и функции.
Необходимо использовать общее переменные в стилях компонентов избегая дублей одних и тех же стилей.
Предназанчен для настройки состояния приложения (Redux)
Предназачно для содержания кастомного функционала (не компонентов) которые относятся к одному или более компонентам.
Это могут быть различные функции сортировки, переназначения стандартных функций типов, работа с историей роутов проекта и т.д.
Например история роутов которая доступна в компонентах проекта с помощью Router но недоступна например в экшенах и другом функционале не являющихся компонентами в роутере.
import createBrowserHistory from 'history/createBrowserHistory'
export default createBrowserHistory()
Или сортировка массива объектов по id
export function sortById(asc = true) {
return asc ? a.id - b.id : b.id - a.id
}
Предназначен для конфигурирования пакетов при инициализации проекта и определения общих констант приложения
Предназначен для рендера приложения и подключения стилей UI фреймвоков и других пакетов.
Содержит переменные окружения такие как
- Адреса и порты подключеня к API
- API ключи сторонних сервиов
- Пароли
- Персональные данные
После клонирования проекта необходимо создать и заполнить .env
файл согласно примеру .env.example
.
Предназначена для конфигурации линтера. Может меняться по соглашению команды проекта.
{
"extends": "react-app",
"plugins": ["prettier"],
"rules": {
"prettier/prettier": "error"
}
}
Конфигрурация притера (плагина для линтера). Может меняться по соглашению команды проекта.
{
"printWidth": 120,
"singleQuote": true,
"trailingComma": "es5",
"bracketSpacing": true,
"tabWidth": 2,
"arrowParens": "avoid",
"semi": false
}
Компонент должен иметь разделение на 4 логических части.
- Шапка компонента
- Тело компонента
- Определение propTypes и т.д.
- Экспорт компонента
Содержит импорт необходимых компонентов, библиотек, стилей, медиа и определение констант. Порядок должен быть таким
- Компоненты, библиотеки и утилиты
- Экшены
- Стили и медиа
- Определение констант
// components and utils
import React, { Component } from 'react'
import { notification, message, Icon, PageHeader } from 'antd'
import { Link } from 'react-router-dom'
import moment from 'moment'
import history from 'utils/history'
// actions
import * as usersActions from 'actions/users'
import * as postsActions from 'actions/posts'
import * as commentsActions from 'actions/comments'
// styles and images
import styles from './styles.module.scss'
import logo from 'sources/images/logo.png'
// define constants
const initState = { foo: 'bar' }
const tabs = ['Tab One', 'Tab two', 'Tab three']
Содержит переменные, функции и return в таком же порядке.
import React from 'react'
export default function SomeComponent(props) {
let foo = 'bar'
function lower(text) {
return text.toLowerCase()
}
return <span>{lower(`This functional component have ${foo}`)}</span>
}
Содержит такие части в указаном порядке
- Переменные, геттеры и сеттеры
- Функции жизненного цикла компонента (начиная с constructor)
- Функции компонента
- Render функция
class SomeComponent extends Component {
state = {
loading: false,
posts: []
}
isMounted = false
constructor(props) {
super(prosp)
this.state = props.defState ? _.cloneDeep(defState) : {}
this.used = false
}
componentDidMount = () => {
this.loadPosts()
}
shouldComponentUpdate = (nextProps) => {
return this.props.prop !== nextProps.prop
}
onClickRefresh = () => {
this.loadPosts()
}
loadPosts = async () => {
this.setState({ loading: true })
try {
let posts = await postsActions.getPosts()
this.setState({ posts })
} catch (e) {
message.error(e.message)
} finally {
this.setState({ loading: false })
}
}
render = () => {
let { posts } = this.state
const { prefix } = this.props
posts = posts.map(post => ({ ...post, title: prefix + post.title }))
return (
<div>
{posts.map(post => <PostItem key={post.id} {...post} />)}
<Button onClick={this.onClickRefresh}>Refresh posts</Button>
</div>
)
}
}
Эта часть не обязательна. Обязательным являеться только присвоение propTypes компоненту который является общим для других компонентов (которые находятся в src/components
).
SomeComponent.defaultProps = {
roles: ['Admin', 'Customer', 'Participant']
}
SomeComponent.propTypes = {
roles: PropTypes.arrayOf(PropTypes.string).isRequired,
message: PropTypes.string,
}
SomeComponent.contextTypes = {
router: React.PropTypes.object.isRequired
}
Если компонент не нуждается в оборачивании в какие либо HOC'и (прим. connect или withRouter) эго нужно экспортировать по-умолчанию
export default SomeComponent
Если компонент нужно обернуть и обертки имеют входные параметры их нужно писать непосредственно в параметре без создания дополнительных функций или констант (прим. mapPropsToState для connect)
export default connect(state => ({
user: state.user,
posts: state.posts,
}))(SomeComponent)
// or few wrappers
export default withRouter(
connect(state => ({
user: state.user,
posts: state.posts,
}))(SomeComponent)
)
Если структура стора содержит большую вложенность можно использовать селекторы которые должны быть расположены рядом с actionTypes в редьюсере.
// reducer
export const postsSelector = (state) => state.deep.chain.posts.list
// component
import { postsSelector } from 'store/reducers/posts'
export default connect(state => ({
user: postsSelector(state),
posts: state.posts,
}))(SomeComponent)