actions/ todos.js components/ todos/ TodoItem.js ... constants/ actionTypes.js reducers/ todos.js index.js rootReducer.js
app/ controllers/ models/ views/
The module is a functionally complete fragment of the program.
Modular programming is the organization of a program as a set of small independent blocks, called modules, whose structure and behavior obey certain rules.
app/ modules/ Module1/ … index.js Module2/ … index.js … index.js core/ … index.js routes.js store.js
AppContainer
, necessary for react-hot-reload
, with the Root
component attached. Root
contains only the Provider , providing communication with redux
and react-router
, which defines the entry point into the application using indexRoute
. The component can be transferred to the npm-package and connected in any application, since it only initializes the infrastructure and does not contain the logic of the object model. import 'isomorphic-fetch' import './styles/app.sass' import React from 'react' import ReactDOM from 'react-dom' import { AppContainer } from 'react-hot-loader' import browserHistory from './core/history' import Root from './core/containers/Root' import store from './store'; import routes from './routes'; ReactDOM.render( <AppContainer> <Root store={store} history={browserHistory} routes={routes}/> </AppContainer>, document.getElementById('root'));
import React from 'react' import PropTypes from 'prop-types' import {Provider} from "react-redux" import {Router} from "react-router" const Root = props => ( <Provider store={props.store}> <Router history={props.history} routes={props.routes} /> </Provider>) Root.propTypes = { history: PropTypes.object.isRequired, routes: PropTypes.array.isRequired, store: PropTypes.object.isRequired } export default Root
export const defineModule = ( title, path, component, reducer = (state = {}) => state, onEnter = null) => { return {title, path, component, reducer, onEnter} }
modules
folder. modules/ Profile/ Profile.js index.js
import React from 'react' import PropTypes from 'prop-types' const Profile = props => (<h2>, {props.name}</h2>) Profile.propTypes = { name: PropTypes.string.isRequired } export default Profile
const SET_NAME = 'Profile/SetName' const reducer (state = {name: ''}, action) => { switch(action.type){ case SET_NAME: {…state, name: action.name} } } export default defineModule(' ', '/profile, Profile')
import Profile from './Profile' export default { Profile }
This step can be avoided, but for clarity, we will leave the manual initialization of the modular structure. Two lines of import / export is not so difficult to write.
CamelCase
and /
for better readability in action titles. In order to make it easier to collect, you can use this function: export const combineName = (...parts) => parts .filter(x => x && toLowerCamelCase(x) != DATA) .map(x => toUpperCamelCase(x)) .reduce((c,n) => c ? c + '/' + n : n) const Module = 'Profile' const SET_NAME = combineName(Module, 'SetName')
core/components/App.js
Note that the same array is transferred to the Navigation
component as to the router to avoid duplication. import React from 'react' import PropTypes from 'prop-types' import Navigation from './Navigation' const App = props => ( <div> <h1>{props.title}</h1> <Navigation routes={props.routes}/> {props.children} </div>) App.propTypes = { title: PropTypes.string.isRequired, routes: PropTypes.array.isRequired } export default App
/profile
contains basic /profile
information, and /profile/transactions
, a list of user transactions. Suppose We want to always display the user name in your personal account, and below display the component with two tabs: “general information” and “transactions”. <Router> <Route path="/profile" component={Profile}> <Route path="/info" component={Info}/> <Route path="/transactions" component={Transaction}/> </ Route > </Router>
Profile
component will display the user name and tabs, and Info
and Transactions
will display the profile details and the list of transactions, respectively. But it is also necessary to support the option when the module components do not need an additional grouping module (for example, the order list and the order viewing window are independent pages).defineModule
function or an array of such objects. All components will be added to the list of routes without additional nesting.children
key, which contains a structure similar to the modules/index.js
. In this case, one of them should be called Index
. It will be used as an IndexRoute
. Then we get the structure corresponding to the "personal account". export const flatModules = modules => Object.keys(modules) .map(x => { const res = Array.isArray(modules[x]) ? modules[x] : [modules[x]] res.forEach(y => y[MODULE] = x) return res }) .reduce((c,n) => c.concat(n))
Route
components, but also just an array with ordinary objects, which we will use. export const getRoutes = (modules, store, App, Home, title = '') => [ { path: '/', title: title, component: App, indexRoute: { component: Home }, childRoutes: flatModules(modules) .map(x => { if (!x.component) { throw new Error('Component for module ' + x + ' is not defined') } const route = { path: x.path, title: x.title, component: x.component, onEnter: x.onEnter ? routeParams => { x.onEnter(routeParams, store.dispatch) } : null } if(x.children){ if(!x.children.Index || !typeof(x.children.Index.component)){ throw new Error('Component for index route of "' + x.title + '" is not defined') } route.indexRoute = { component: x.children.Index.component } route.childRoutes = Object.keys(x.children).map(y => { const cm = x.children[y] if (!cm.component) { throw new Error('Component for module ' + x + '/' + y + ' is not defined') } return { path: x.path + cm.path, title: cm.title, component: cm.component, onEnter: cm.onEnter ? routeParams => { cm.onEnter(routeParams, store.dispatch) } : null } }) } return route }) } ]
modules/index.js
will automatically initialize new routes. If the developer forgets to announce the route or gets confused in the agreements, he will see an unambiguous error message in the console. const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose; const createAppStore = (reducer, ...middleware) => { middleware.push(thunk) const store = createStore( reducer, composeEnhancers(applyMiddleware(...middleware))) return store } export default createAppStore
export const combineModuleReducers = modules => { const reducers = {} const flat = flatModules(modules) for (let i = 0; i < flat.length; i++) { const red = flat[i].reducer if (typeof(red) !== 'function') { throw new Error('Module ' + i + ' does not define reducer!') } reducers[flat[i][MODULE]] = red if(typeof(flat[i].children) === 'object'){ for(let j in flat[i].children){ if(typeof(flat[i].children[j].reducer) !== 'function'){ throw new Error('Module ' + j + ' does not define reducer!') } reducers[j] = flat[i].children[j].reducer } } } return reducers }
export default createAppStore(combineReducers(combineModuleReducers(modules)))
modules/index.js
. For personal account it will be Profile
Source: https://habr.com/ru/post/326484/
All Articles