文章目录
参考整体结构目录configStore.jshistory.jsrootReducer.jsrouteConfig.jsindex.jsRoot.jscomponent.jsxcomponent.lesscomponent/index.jscomponent/style.lesscomponents/route.jscomponent/redux/actions.jscomponent/redux/actions.jscomponent/redux/constants.jscomponent/redux/initialState.jscomponent/redux/reducer.jscomponent/redux/xxxService.jsindex -> route.js[ package.json ]
参考整体结构目录
configStore.js
import { createStore
, applyMiddleware
, compose
} from 'redux';
import thunk
from 'redux-thunk';
import { routerMiddleware
} from 'react-router-redux';
import history
from './history';
import rootReducer
from './rootReducer';
const router
= routerMiddleware(history
);
const middlewares
= [
thunk
,
router
,
];
let devToolsExtension = f
=> f
;
if (process
.env
.NODE_ENV === 'development') {
const { createLogger
} = require('redux-logger');
const logger
= createLogger({ collapsed
: true });
middlewares
.push(logger
);
if (window
.devToolsExtension
) {
devToolsExtension
= window
.devToolsExtension();
}
}
export default function configureStore(initialState
) {
const store
= createStore(rootReducer
, initialState
, compose(
applyMiddleware(...middlewares
),
devToolsExtension
));
if (module
.hot
) {
module
.hot
.accept('./rootReducer', () => {
const nextRootReducer
= require('./rootReducer').default;
store
.replaceReducer(nextRootReducer
);
});
}
return store
;
}
history.js
import createHistory
from 'history/createBrowserHistory';
const history
= createHistory();
export default history
;
rootReducer.js
import { combineReducers
} from 'redux';
import { routerReducer
} from 'react-router-redux';
import homeReducer
from '../features/home/redux/reducer';
import commonReducer
from '../features/common/redux/reducer';
import examplesReducer
from '../features/examples/redux/reducer';
const reducerMap
= {
router
: routerReducer
,
home
: homeReducer
,
common
: commonReducer
,
examples
: examplesReducer
,
};
export default combineReducers(reducerMap
);
routeConfig.js
import { App
} from '../features/home';
import { PageNotFound
} from '../features/common';
import homeRoute
from '../features/home/route';
import commonRoute
from '../features/common/route';
import examplesRoute
from '../features/examples/route';
import _
from 'lodash';
const childRoutes
= [
homeRoute
,
commonRoute
,
examplesRoute
,
];
const routes
= [{
path
: '/',
component
: App
,
childRoutes
: [
...childRoutes
,
{ path
: '*', name
: 'Page not found', component
: PageNotFound
},
].filter(r
=> r
.component
|| (r
.childRoutes
&& r
.childRoutes
.length
> 0)),
}];
function handleIndexRoute(route
) {
if (!route
.childRoutes
|| !route
.childRoutes
.length
) {
return;
}
const indexRoute
= _
.find(route
.childRoutes
, (child
=> child
.isIndex
));
if (indexRoute
) {
const first
= { ...indexRoute
};
first
.path
= '';
first
.exact
= true;
first
.autoIndexRoute
= true;
route
.childRoutes
.unshift(first
);
}
route
.childRoutes
.forEach(handleIndexRoute
);
}
routes
.forEach(handleIndexRoute
);
export default routes
;
index.js
import React
from 'react';
import { AppContainer
} from 'react-hot-loader';
import { render
} from 'react-dom';
import configStore
from './common/configStore';
import routeConfig
from './common/routeConfig';
import Root
from './Root';
const store
= configStore();
function renderApp(app
) {
render(
<AppContainer
>
{app
}
</AppContainer
>,
document
.getElementById('root')
);
}
renderApp(<Root store
={store
} routeConfig
={routeConfig
} />);
if (module
.hot
) {
module
.hot
.accept('./common/routeConfig', () => {
const nextRouteConfig
= require('./common/routeConfig').default;
renderApp(<Root store
={store
} routeConfig
={nextRouteConfig
} />);
});
module
.hot
.accept('./Root', () => {
const nextRoot
= require('./Root').default;
renderApp(<Root store
={store
} routeConfig
={routeConfig
} />);
});
}
Root.js
import React
from 'react';
import PropTypes
from 'prop-types';
import { Provider
} from 'react-redux';
import { Switch
, Route
} from 'react-router-dom';
import { ConnectedRouter
} from 'react-router-redux';
import history
from './common/history';
function renderRouteConfigV3(routes
, contextPath
) {
const children
= [];
const renderRoute = (item
, routeContextPath
) => {
let newContextPath
;
if (/^\//.test(item
.path
)) {
newContextPath
= item
.path
;
} else {
newContextPath
= `${routeContextPath}/${item.path}`;
}
newContextPath
= newContextPath
.replace(/\/+/g, '/');
if (item
.component
&& item
.childRoutes
) {
const childRoutes
= renderRouteConfigV3(item
.childRoutes
, newContextPath
);
children
.push(
<Route
key
={newContextPath
}
render
={props
=> <item
.component
{...props
}>{childRoutes
}</item
.component
>}
path
={newContextPath
}
/>
);
} else if (item
.component
) {
children
.push(<Route key
={newContextPath
} component
={item
.component
} path
={newContextPath
} exact
/>);
} else if (item
.childRoutes
) {
item
.childRoutes
.forEach(r
=> renderRoute(r
, newContextPath
));
}
};
routes
.forEach(item
=> renderRoute(item
, contextPath
));
debugger
return <Switch
>{children
}</Switch
>;
}
export default class Root extends React.Component {
static propTypes
= {
store
: PropTypes
.object
.isRequired
,
routeConfig
: PropTypes
.array
.isRequired
,
};
render() {
const children
= renderRouteConfigV3(this.props
.routeConfig
, '/');
debugger
return (
<Provider store
={this.props
.store
}>
<ConnectedRouter history
={history
}>{children
}</ConnectedRouter
>
</Provider
>
);
}
}
component.jsx
import React
, { Component
} from 'react';
import PropTypes
from 'prop-types';
import { Link
} from 'react-router-dom';
import { bindActionCreators
} from 'redux';
import { connect
} from 'react-redux';
import * as actions
from './redux/actions';
export class SidePanel extends Component {
static propTypes
= {
examples
: PropTypes
.object
.isRequired
,
actions
: PropTypes
.object
.isRequired
,
};
render() {
return (
<div className
="examples-side-panel">
<ul
>
<li
>
<Link to
="/examples">Welcome
</Link
>
</li
>
<li
>
<Link to
="/examples/counter">Counter Demo
</Link
>
</li
>
<li
>
<Link to
="/examples/reddit">Reddit
API Demo
</Link
>
</li
>
<li
>
<Link to
="/">Back to start page
</Link
>
</li
>
</ul
>
<div className
="memo">
This is a Rekit feature that contains some examples
for you to quick learn how Rekit works
. To remove it just
delete the feature
.
</div
>
</div
>
);
}
}
function mapStateToProps(state
) {
return {
examples
: state
.examples
,
};
}
function mapDispatchToProps(dispatch
) {
return {
actions
: bindActionCreators({ ...actions
}, dispatch
),
};
}
export default connect(mapStateToProps
, mapDispatchToProps
)(SidePanel
);
component.less
@
import '../../styles/mixins';
.examples
-side
-panel
{
position
: fixed
;
box
-sizing
: border
-box
;
overflow
: auto
;
top
: 0;
left
: 0;
margin
: 0;
padding
: 40px
;
width
: 260px
;
height
: 100%;
background
-color
: #f7f7f7
;
ul
,
li
{
padding
: 0;
margin
: 0;
list
-style
: none
;
}
li
{
padding
: 8px
;
}
a
{
color
: #
2db7f5
;
text
-decoration
: none
;
&:hover
{
color
: #f90
;
}
}
.memo
{
&:before
{
content
: ' ';
display
: block
;
height
: 2px
;
width
: 60px
;
margin
-bottom
: 10px
;
background
-color
: #ddd
;
}
font
-size
: 13px
;
margin
-top
: 40px
;
line
-height
: 150%;
color
: #aaa
;
}
}
component/index.js
export { default as SidePanel
} from './SidePanel';
export { default as WelcomePage
} from './WelcomePage';
export { default as CounterPage
} from './CounterPage';
export { default as RedditListPage
} from './RedditListPage';
export { default as Layout
} from './Layout';
component/style.less
@
import '../../styles/mixins';
@
import './SidePanel';
@
import './WelcomePage';
@
import './CounterPage';
@
import './RedditListPage';
@
import './Layout';
components/route.js
import {
WelcomePage
,
CounterPage
,
RedditListPage
,
Layout
,
} from './';
export default {
path
: 'examples',
name
: 'Examples',
component
: Layout
,
childRoutes
: [
{ path
: '', name
: 'Welcome page', component
: WelcomePage
},
{ path
: 'counter', name
: 'Counter page', component
: CounterPage
},
{ path
: 'reddit', name
: 'Reddit list page', component
: RedditListPage
},
],
};
component/redux/actions.js
export { counterPlusOne
} from './counterPlusOne';
export { counterMinusOne
} from './counterMinusOne';
export { counterReset
} from './counterReset';
export { fetchRedditList
, dismissFetchRedditListError
} from './fetchRedditList';
component/redux/actions.js
export { counterPlusOne
} from './counterPlusOne';
export { counterMinusOne
} from './counterMinusOne';
export { counterReset
} from './counterReset';
export { fetchRedditList
, dismissFetchRedditListError
} from './fetchRedditList';
component/redux/constants.js
export const EXAMPLES_COUNTER_PLUS_ONE = 'EXAMPLES_COUNTER_PLUS_ONE';
export const EXAMPLES_COUNTER_MINUS_ONE = 'EXAMPLES_COUNTER_MINUS_ONE';
export const EXAMPLES_COUNTER_RESET = 'EXAMPLES_COUNTER_RESET';
export const EXAMPLES_FETCH_REDDIT_LIST_BEGIN = 'EXAMPLES_FETCH_REDDIT_LIST_BEGIN';
export const EXAMPLES_FETCH_REDDIT_LIST_SUCCESS = 'EXAMPLES_FETCH_REDDIT_LIST_SUCCESS';
export const EXAMPLES_FETCH_REDDIT_LIST_FAILURE = 'EXAMPLES_FETCH_REDDIT_LIST_FAILURE';
export const EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR = 'EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR';
component/redux/initialState.js
const initialState
= {
count
: 0,
redditList
: [],
fetchRedditListPending
: false,
fetchRedditListError
: null,
};
export default initialState
;
component/redux/reducer.js
import initialState
from './initialState';
import { reducer
as counterPlusOneReducer
} from './counterPlusOne';
import { reducer
as counterMinusOneReducer
} from './counterMinusOne';
import { reducer
as counterResetReducer
} from './counterReset';
import { reducer
as fetchRedditListReducer
} from './fetchRedditList';
const reducers
= [
counterPlusOneReducer
,
counterMinusOneReducer
,
counterResetReducer
,
fetchRedditListReducer
,
];
export default function reducer(state
= initialState
, action
) {
let newState
;
switch (action
.type
) {
default:
newState
= state
;
break;
}
return reducers
.reduce((s
, r
) => r(s
, action
), newState
);
}
component/redux/xxxService.js
import axios
from 'axios';
import {
EXAMPLES_FETCH_REDDIT_LIST_BEGIN,
EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
} from './constants';
export function fetchRedditList(args
= {}) {
return dispatch
=> {
dispatch({
type
: EXAMPLES_FETCH_REDDIT_LIST_BEGIN,
});
const promise
= new Promise((resolve
, reject
) => {
const doRequest
= axios
.get('http://www.reddit.com/r/reactjs.json');
doRequest
.then(
res
=> {
dispatch({
type
: EXAMPLES_FETCH_REDDIT_LIST_SUCCESS,
data
: res
.data
,
});
resolve(res
);
},
err
=> {
dispatch({
type
: EXAMPLES_FETCH_REDDIT_LIST_FAILURE,
data
: { error
: err
},
});
reject(err
);
}
);
});
return promise
;
};
}
export function dismissFetchRedditListError() {
return {
type
: EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR,
};
}
export function reducer(state
, action
) {
switch (action
.type
) {
case EXAMPLES_FETCH_REDDIT_LIST_BEGIN:
return {
...state
,
fetchRedditListPending
: true,
fetchRedditListError
: null,
};
case EXAMPLES_FETCH_REDDIT_LIST_SUCCESS:
return {
...state
,
redditList
: action
.data
.data
.children
,
fetchRedditListPending
: false,
fetchRedditListError
: null,
};
case EXAMPLES_FETCH_REDDIT_LIST_FAILURE:
return {
...state
,
fetchRedditListPending
: false,
fetchRedditListError
: action
.data
.error
,
};
case EXAMPLES_FETCH_REDDIT_LIST_DISMISS_ERROR:
return {
...state
,
fetchRedditListError
: null,
};
default:
return state
;
}
}
index -> route.js
import {
WelcomePage
,
CounterPage
,
RedditListPage
,
Layout
,
} from './';
export default {
path
: 'examples',
name
: 'Examples',
component
: Layout
,
childRoutes
: [
{ path
: '', name
: 'Welcome page', component
: WelcomePage
},
{ path
: 'counter', name
: 'Counter page', component
: CounterPage
},
{ path
: 'reddit', name
: 'Reddit list page', component
: RedditListPage
},
],
};
[ package.json ]
{
"name": "rekit-boilerplate-cra",
"version": "0.1.0",
"private": true,
"dependencies": {
"autoprefixer": "7.1.6",
"axios": "^0.18.0",
"babel-core": "6.26.0",
"babel-eslint": "7.2.3",
"babel-jest": "20.0.3",
"babel-loader": "7.1.2",
"babel-plugin-lodash": "^3.3.2",
"babel-plugin-syntax-dynamic-import": "^6.18.0",
"babel-preset-react-app": "^3.1.1",
"babel-runtime": "6.26.0",
"case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "1.1.3",
"css-loader": "0.28.7",
"dotenv": "4.0.0",
"dotenv-expand": "4.2.0",
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0",
"eslint-loader": "1.9.0",
"eslint-plugin-flowtype": "2.39.1",
"eslint-plugin-import": "2.8.0",
"eslint-plugin-jsx-a11y": "5.1.1",
"eslint-plugin-react": "7.4.0",
"express-history-api-fallback": "^2.2.1",
"extract-text-webpack-plugin": "3.0.2",
"file-loader": "1.1.5",
"fs-extra": "3.0.1",
"html-webpack-plugin": "2.29.0",
"jest": "20.0.4",
"less": "^3.0.1",
"less-loader": "^4.1.0",
"lodash": "^4.17.5",
"nock": "^9.2.3",
"node-sass-chokidar": "^1.2.1",
"object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8",
"promise": "8.0.1",
"raf": "3.4.0",
"react": "^16.2.0",
"react-dev-utils": "^5.0.0",
"react-dom": "^16.2.0",
"react-hot-loader": "^4.0.0",
"react-redux": "^5.0.7",
"react-router-dom": "^4.2.2",
"react-router-redux": "5.0.0-alpha.9",
"redux": "^3.7.2",
"redux-logger": "^3.0.6",
"redux-mock-store": "^1.5.1",
"redux-thunk": "^2.2.0",
"rekit-core": "^2.3.0",
"rekit-studio": "^2.3.0",
"sass-loader": "^6.0.7",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "0.11.4",
"url-loader": "0.6.2",
"webpack": "3.8.1",
"webpack-dev-server": "2.9.4",
"webpack-manifest-plugin": "1.3.2",
"whatwg-fetch": "2.0.3"
},
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom"
},
"rekit": {
"devPort": 6075,
"studioPort": 6076,
"plugins": [],
"css": "less"
},
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,mjs}"
],
"setupFiles": [
"<rootDir>/config/polyfills.js",
"<rootDir>/tests/setup.js"
],
"testMatch": [
"<rootDir>/tests/**/*.test.{js,jsx,mjs}"
],
"testEnvironment": "node",
"testURL": "http://localhost",
"transform": {
"^.+\\.(js|jsx|mjs)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.css$": "<rootDir>/config/jest/cssTransform.js",
"^(?!.*\\.(js|jsx|mjs|css|json)$)": "<rootDir>/config/jest/fileTransform.js"
},
"transformIgnorePatterns": [
"[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"
],
"moduleNameMapper": {
"^react-native$": "react-native-web"
},
"moduleFileExtensions": [
"web.js",
"mjs",
"js",
"json",
"web.jsx",
"jsx",
"node"
]
},
"eslintConfig": {
"extends": "react-app"
}
}