Святой Грааль обсуждения - это лучший способ структурировать ваш проект. Ответ - нет. Но вот тот, в который я влюбился.

Тогда заклинание способ!

Сначала давайте поговорим о том, что делает структуру хорошей. Есть определенные элементы, которые хорошо используют организацию.

  • Легко ориентироваться
  • Хорошо масштабируется со временем
  • Логика свопа быстрая и без перерывов

Так что же такое мантра? Я не говорю о медитации при создании папок. Это архитектура приложения со спецификацией mantra js, разработанная Кадирой еще в те времена, когда meteor js был крутым ребенком.

В нем описана архитектура, которая должна быть поддерживаемой и рассчитанной на будущее. Они утверждали, что после установки он удовлетворит вашего внутреннего компьютерщика на срок до пяти лет. Кстати, ни кадира, ни метеор не продержались так долго.

Так что я двинулся дальше, отвернулся от метеора и нырнул в землю сырых пакетов. И mantra js была единственной вещью, которую я любил и пытался встроить.

Давайте посмотрим, как может выглядеть проект. Вот пример веб-сайта с несколькими маршрутами, компонентами, действиями и редукторами.

Останься со мной. Это не так запутанно, как кажется.

Модули

Давайте разберемся с этим и начнем с папки модулей. Это основная идея мантры и, на мой взгляд, то, что делает ее такой замечательной.

Модули - это в первую очередь бизнес. Это означает, что я не разделяю его на

  • компоненты
  • контейнеры
  • редукторы
  • действия

Прямо здесь. Вместо этого mantra добавляет слой поверх, чтобы обеспечить лучшую ремонтопригодность и, надеюсь, сделает это в будущем.

Одно важное правило заключается в том, что вы не каскадируете модули. Если у модуля должен быть подмодуль, создайте его отдельно. Преимущество в том, что вы видите все модули с первого взгляда. Необязательно открывать каждый модуль, чтобы посмотреть, что внутри.

Содержание модуля

Но что внутри модуля?

Папка Компоненты содержит все глупые компоненты, которые вам нужны в этом модуле и только в этом модуле. Ключевым моментом здесь является разделение проблем. Подробнее про здесь.

import React, { Component } from 'react'
import Text from '../../core/components/Text'
import * as colors from 'material-ui/styles/colors'
import GameTile from './GameTile'
export default class Games extends Component {
  render () {
    const {
      mobile
    } = this.props
    return (
      <div>
        some content
      </div>
    )
  }
}

Папка Контейнеры содержит логику. Что нужно вставить в мои тупые компоненты? Какие данные следует получить (с помощью действий диспетчеризации или состояния редукции)? Все эти опасения следует хранить здесь.

import React, { Component } from 'react'
import { connect } from 'react-redux'
import Games from '../components/Games'
import { actions as coreActions } from '../../core'
class Container extends Component {
  componentDidMount () {
    this.props.dispatch(coreActions.setMenuIndex(1))
  }
  render () {
    return (
      <Games
        {...this.props}
      />
    )
  }
}
export default connect((state) => {
  return {
    mobile: state.core.responsive.mobile
  }
})(Container)

Как видите, я ввел «базовый модуль» для выполнения общих действий. В какой-то момент вы не можете полностью изолировать модули. Некоторыми действиями нужно делиться, и я думаю, что это хороший компромисс.

Папка Маршруты содержит, как следует из названия, все маршруты, принадлежащие этому модулю. В этом случае у нас есть маршрут «/ games», который нужно подключить к контейнеру игр.

import React from 'react'
import { Route } from 'react-router-dom'
import Games from '../containers/Games'
export default (store) => {
  return (
    <Route exact path='/games' component={Games}/>
  )
}

Здесь также приветствуются дополнительные маршруты, например, «/ games /: id».

Дополнительные папки

Действиям и редукторам тоже нужно место для жизни.

Папка Действия содержит типы действий для вызовов redux.

export const MENU_TOGGLE = 'MENU_TOGGLE'
export const SET_RESPONSIVE_BREAKPOINT = 'SET_RESPONSIVE_BREAKPOINT'
export const SET_MENU_INDEX = 'SET_MENU_INDEX'

И фактические действия, которые можно отправить.

import * as TYPES from './actionTypes'
export function toggleMenu (open) {
  return {type: TYPES.MENU_TOGGLE, open}
}
export function setResponsiveBreakpoint (value) {
  return {type: TYPES.SET_RESPONSIVE_BREAKPOINT, value}
}
export function setMenuIndex (index) {
  return {type: TYPES.SET_MENU_INDEX, index}
}

Папка Reducers, очевидно, содержит редукторы для этого модуля.

import * as TYPES from '../actions/actionTypes'
const defaultState = {
  menu: {
    open: false,
    index: -1
  },
  mobileView: true,
  responsive: {
    mobile: false,
    tablet: false,
    desktop: true
  }
}
function toggleMenu (state, action) {
  return {
    ...state,
    menu: {
      ...state.menu,
      open: typeof action.open === 'undefined' ? !state.menu.open : action.open
    }
  }
}
function setMenuIndex (state, action) {
  return {
    ...state,
    menu: {
      ...state.menu,
      index: action.index
    }
  }
}
function setResponsiveBreakpoint (state, action) {
  return {
    ...state,
    responsive: {
      mobile: action.value <= 768,
      tablet: action.value > 768 && action.value <= 1200,
      desktop: action.value > 1200
    }
  }
}
export default function (state = defaultState, action) {
  switch (action.type) {
    case TYPES.MENU_TOGGLE:
      return toggleMenu(state, action)
    case TYPES.SET_RESPONSIVE_BREAKPOINT:
      return setResponsiveBreakpoint(state, action)
    case TYPES.SET_MENU_INDEX:
      return setMenuIndex(state, action)
    default:
      return state
  }
}

Вы также можете разделить это на несколько файлов, если хотите.

А теперь самое важное - клей! После того, как вы определили свои папки с их логикой и визуальными компонентами, вы хотите их раскрыть, верно? Здесь на помощь приходит index.js папки модуля. Он собирает все необходимые части модуля и предоставляет их для дальнейшего использования. Что-то вроде этого:

import * as actions from './actions'
import reducers from './reducers'
import routes from './routes'
export {
  actions,
  reducers,
  routes
}

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

Заставить его работать

Итак, теперь самое интересное. После определения наших модулей нам нужно развернуть их в нашем приложении. Давайте вернемся к нашему обзору:

Сначала объединим все редукторы. В «src / redurs / index.js» мы объединяем все редукторы, предоставляемые нашими модулями.

export { reducer as core } from '../modules/core'
export { reducer as games } from '../modules/games'
export { reducer as anotherModule } from '../modules/anotherModule'

Позже это можно использовать для объединения нашего магазина только с:

import * as reducers from '../reducers'

И все наши редукторы из всех определенных модулей готовы к работе. Аккуратный!

То же самое и с маршрутами. Загляните внутрь содержимого AppRoutes.js:

import React from 'react'
import { Switch } from 'react-router-dom'
import Application from './modules/core/containers/Application'
import { routes as home } from './modules/home'
import { routes as games } from './modules/games'
import { routes as team } from './modules/team'
import { routes as contact } from './modules/contact'
import { routes as impressum } from './modules/impressum'
export default (store) => {
  return (
    <Switch>
      <Application>
        {home(store)}
        {games(store)}
        {team(store)}
        {contact(store)}
        {impressum(store)}
      </Application>
    </Switch>
  )
}

В этом файле представлены все маршруты, определенные в наших модулях. Они встроены в общий компонент приложения, определенный в основном модуле. Приложение представляет собой оболочку с верхним и нижним колонтитулами и принимает дочерние элементы в качестве опоры.

Теперь просто импортируйте эти маршруты в свой Root.js, и все готово к развертыванию.

import React from 'react'
import { Provider } from 'react-redux'
import * as OfflinePluginRuntime from 'offline-plugin/runtime'
import configureStore from './configs/configureStore'
import createRoutes from './AppRoutes'
import createHistory from 'history/createBrowserHistory'
import { ConnectedRouter } from 'react-router-redux'
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'
import getMuiTheme from 'material-ui/styles/getMuiTheme'
import theme from './theme'
import './main.less'
const history = createHistory()
const store = configureStore({}, history)
OfflinePluginRuntime.install()
export default () => (
  <Provider store={store}>
    <MuiThemeProvider muiTheme={getMuiTheme(theme)}>
      <ConnectedRouter history={history}>
        {createRoutes(store)}
      </ConnectedRouter>
    </MuiThemeProvider>
  </Provider>
)

Заключение

Теперь у нас есть несколько модулей, которые изолированы друг от друга. Это позволяет их легко поменять на другие. Кроме того, операторы импорта теперь очень короткие. Дальнейшие изменения внутри модуля делают отвлечение при кодировании из другого модуля устаревшим. Ремонтопригодность намного лучше, чем поиск файлов, связанных с определенной проблемой. Он хорошо масштабируется благодаря тому, что требуется всего несколько точек привязки и меньше беспорядка внутри папки модулей.

Добавка

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



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