一、安装 create-react-app 脚手架并创建APP
npm install -g create-react-app //全局安装 react 脚手架
create-react-app react-cli //通过脚手架创建app
npm run eject //安装默认配置信息
npm run start //运行
二、页面配置按需加载&路由封装
//安装依赖
npm i react-router-dom --save-dev
//封装异步加载组件,在pages文件目录下新建bundle.js文件
import React from 'react';
import { BrowserRouter, Route, Switch} from 'react-router-dom';
import Bundle from './Bundle';
import routerConfig from './routeConfig';
let configList = [];
function getRoutes(routeConfig) {
routeConfig.forEach(item => {
const {children, title, component, name, path} = item;
if(children) {
getRoutes(children)
};
const Obj = (props) => {
document.title = title;
return (<Bundle load={component}>{(Route) => <Route {...props}/>}</Bundle>)
};
configList.push(<Route key={name} exact path={path} component={Obj}/>)
})
return configList;
}
const BasicRoute = () => (
<BrowserRouter>
<Switch>
{getRoutes(routerConfig)}
</Switch>
</BrowserRouter>
);
export default BasicRoute;
删除src下原有文件(保留index.js和serviceWorker.js),index.js移动至page目录下,新建路由文件routerConfig.js和routes.js。
//routerConfig.js文件,存放路由集合
const Index = () => import('./containers/index/index');
const Login = () => import('./containers/login/login');
const Action = () => import('./containers/action/action');
const User = () => import('./containers/user/user');
const Info = () => import('./containers/user/info');
const config = [
{
path: '/index',
title: '首页',
name: 'index',
component: Index
},
{
path: '/login',
title: '登录',
name: 'login',
component: Login
},
{
path: '/Action',
title: '活动',
name: 'action',
component: Action
},
{
path: '/user',
title: '用户中心',
name: 'user',
component: User,
children: [
{
path: '/user/info',
title: '用户详情',
name: 'info',
component: Info
}
]
},
{
path: '*',
title: '首页',
component: Index,
name: 'index',
}
]
export default config;
//routes.js 路由文件,引入了Bundle.js按需加载
import React from 'react';
import { BrowserRouter, Route, Switch} from 'react-router-dom';
import Bundle from './Bundle';
import routerConfig from './routeConfig';
let configList = [];
function getRoutes(routeConfig) {
routeConfig.forEach(item => {
const {children, title, component, name, path} = item;
if(children) {
getRoutes(children)
};
const Obj = (props) => {
document.title = title;
return (<Bundle load={component}>{(Route) => <Route {...props}/>}</Bundle>)
};
configList.push(<Route key={name} exact path={path} component={Obj}/>)
})
return configList;
}
const BasicRoute = () => (
<BrowserRouter>
<Switch>
{getRoutes(routerConfig)}
</Switch>
</BrowserRouter>
);
export default BasicRoute;
修改/src/pages/index.js文件,引入路由文件
import React from 'react';
import ReactDom from 'react-dom';
import './index.scss';
import Routers from './routes';
import * as serviceWorker from '../serviceWorker';
function renderReactApp(){
ReactDom.render(
<Routers/>,
document.getElementById('root')
);
}
renderReactApp();
serviceWorker.unregister();
三、配置react-redux
//安装redux
npm install --save redux
npm install --save react-redux
新建store/index.js文件,引入reducers
import { createStore } from 'redux'
import rootReducer from './reducers'
let store = createStore(rootReducer)
//监听器
store.subscribe(() => {
console.log('state change',store.getState())
})
export default store;
配置reducers/index.js入口文件
import {combineReducers} from 'redux'
import market from './market'
const rootReducer = combineReducers({
market
})
export default rootReducer;
配置store/reducers/pay/index.js 模块
const price = 500
const market = (state = price, action) => {
switch (action.type) {
case '涨价':
return state += 10;
case '降价':
return state -= 10;
default:
return state;
}
}
export default market
修改src/pages/index.jsx文件,引入react-redux,通过Provider将组件包裹,注入store;
Provider的作用是把state传给它的所有子组件,方便下面的子组件共享数据。
import React from 'react';
import ReactDom from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import './index.scss';
import Routers from './routes';
import * as serviceWorker from '../serviceWorker';
function renderReactApp(){
ReactDom.render(
<Provider store={store}>
<Routers/>
</Provider>,
document.getElementById('root')
);
}
renderReactApp();
serviceWorker.unregister();
在pages/containers/index/index.jsx页面引入connect高阶组件,将指定的state和指定的action与React组件连接起来
import React, {PureComponent} from 'react';
import {Link} from "react-router-dom";
import { connect } from "react-redux";
class index extends PureComponent {
constructor(props){
super(props);
this.state = {
}
}
componentWillMount() {
}
render() {
const { PriceIncrease, PriceDecrease } = this.props;
return (
<div className="g-default">
<div>默认页</div>
<Link to={`/login`}>
跳转至登录<br/>
</Link>
<Link to={`/user`}>
跳转到用户中心页面
</Link>
<div>
<p>数值:{this.props.price}</p>
</div>
<button onClick={PriceIncrease}>物价上涨</button>
<button onClick={PriceDecrease}>物价下跌</button>
</div>
)
}
}
//获取指定state数据
function mapStateToProps(state) {
return {
price: state.market
}
}
//触发action
function mapDispatchToProps(dispatch) {
return {
PriceIncrease: () => dispatch({ type: '涨价' }),
PriceDecrease: () => dispatch({ type: '降价' }),
}
}
export default index = connect(mapStateToProps, mapDispatchToProps)(index);
四、异步方案react-thunk
Redux-Thunk是Redux的中间件,用来将组件异步获取数据的操作封装到action中去,以此来减少组件中复杂的异步操作。使用Redux-Thunk之后action可以返回一个函数(Redux的action默认只能返回对象)。
//安装
npm install --save redux-thunk
stroe/index.js 引入redux-thunk并配置
import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducers'
import thunk from 'redux-thunk';
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
//监听器
store.subscribe(() => {
console.log('state change',store.getState())
})
export default store;
异步操作封装到store/action.js文件
export const MARKET_TYPE_INCREASE = '涨价';
export const getListDataAction = () => {
return (dispatch) => {
setTimeout(() => {
dispatch({
type: MARKET_TYPE_INCREASE
});
}, 1000);
}
}
页面pages/index/index.jsx引入action文件
import * as action from 'src/pages/store/action.js'
//触发action
function mapDispatchToProps(dispatch) {
return {
PriceIncrease: () => dispatch(action.getListDataAction()),
PriceDecrease: () => dispatch({ type: '降价' }),
}
}
五、axios封装
在src下新建config/apimap.js文件,存放api映射,新建config/index.js文件,用来配置api host,可以根据process.env.NODE_ENV配置mock数据
//config/apimap.js
import { host } from './index'
const api = {
'user': '/user/user',
'list': '/user/list'
}
Object.keys(api).forEach(key => {
api[key] = host + api[key];
})
export default api;
//config/index.js
const is_prod = process.env.NODE_ENV === 'production'
//...配置mock逻辑
export const host = 'http://5cb051d4f7850e0014629aed.mockapi.io';
新建src/utils/api.js
import axios from 'axios';
import qs from 'qs';
import ApiMap from 'src/config/apimap'
axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8'
axios.defaults.transformRequest = function (data,b) {
if(data instanceof FormData){return data}
if(!b["Content-Type"]) {
return qs.stringify(data)
}else {
return data
}
}
//添加请求拦截器
axios.interceptors.request.use(function(config){
return config;
},function(error){
return Promise.reject(error);
});
//响应拦截器
axios.interceptors.response.use(response => {
let data = response.data
return typeof data === 'object' ? data : JSON.parse(data)
}, error => {
return error.response || error; // 返回接口返回的错误信息
})
let ajax = ({param = {},method = 'get',api,...options}) => {
let config = {
url: ApiMap[api] || api,
method: method,
...options
};
if(method === 'get'){
config.params = param
}else {
config.data = param
}
return axios(config).then(res => {
return res;
}, error => {
return error;
})
}
export default ajax;
六、整体项目目录结构