重新看了一遍 React 16.2.0 的文档,针对 react 的变化,以及使用习惯做了重新的总结,上一篇 重新搭建 react 开发环境 已经完成,这里继续针对 react 项目搭建进一步记录。
源码
开发环境搭建
react 升级到 16
node: v8.9.0 | nam: v5.6.0 | yarn: v1.3.2 |
---|---|---|
react: v16.2.0 | redux: v3.7.2 | react-router-dom: v4.2.2 |
react-redux: v5.0.6 | react-router-redux: v5.0.0-alpha.9 | react-hot-loader: v4.0.0-beta.12 |
/src/
├── api
│ ├── request.js // 网络请求的封装文件
│ └── home.js // 抽取某个页面的网络请求部分
├── assets // 资源文件夹 图片、音频、视频等
├── components // 展示型组件,按照容器组件分文件夹
│ ├── App // App 容器组件的展示型子组件目录
│ └── index.js // 顶层zi'zu'jian
├── containers // 容器组件
│ └── App.js
├── modules // redux 模块,包括 reducer 部分与 action 部分
│ ├── home // 对应某个容器组件,集中了这个容器的 reducer 和 action
│ │ ├── actions.js
│ │ └── reducer.js
│ ├── reducers.js // 合并后的总的 reducer
│ └── types-constant.js // 抽取出的 type 常量
├── scss // 样式文件
│ ├── _common.scss
│ └── home.scss
├── store // store 配置
│ ├── configureStore.js
│ └── index.js
└── utils // 公用的工具函数
│ └── bindActions.js
├── index.html // 页面模板
├── routes.js // 路由配置
└── index.js // react 入口
/index.js
import './scss/index.scss';
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter } from 'react-router-redux';
import createHistory from 'history/createBrowserHistory';
// 引入 store
import store from './store/index';
// 引入路由配置组件
import Root from './routes';
const history = createHistory();
const mountNode = document.getElementById('app');
/*
react-redux 提供 Provider 组件,
被 Provider 组件包裹的整个 APP 中的每个组件,都可以通过 connect 去连接 store
*/
ReactDOM.render((
<Provider store={store}>
<ConnectedRouter history={history} basename="">
<Root/>
ConnectedRouter>
Provider>
), mountNode);
// 热模块更新 改为在 routes.js 中定义
/store/index.js
/store/configureStore.js
import { compose, createStore, applyMiddleware } from 'redux';
import { routerMiddleware } from 'react-router-redux';
// 引入thunk 中间件,处理异步操作
import thunk from 'redux-thunk';
import createHistory from 'history/createBrowserHistory';
const history = createHistory();
// 生成 router中间件
const routeMiddleware = routerMiddleware(history);
const middleware = [routeMiddleware, thunk];
/*
辅助使用chrome浏览器进行redux调试
*/
// 判断当前浏览器是否安装了 REDUX_DEVTOOL 插件
const shouldCompose =
process.env.NODE_ENV !== 'production' &&
typeof window === 'object' &&
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
/*
如果浏览器安装的 redux 工具,则使用 redux 工具 扩展过的 compose
compose 是一个 createStore 增强工具,
他是一个高阶函数,最终会返回新的增强后的 createStore
*/
const composeEnhancers = shouldCompose
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify here name, actionsBlacklist, actionsCreators and other options
})
: compose;
/*
调用 applyMiddleware ,使用 middleware 来增强 createStore
*/
const configureStore = composeEnhancers(applyMiddleware(...middleware))(
createStore
);
export default configureStore;
/modules/reducers.js
modules 目录用来存放所有容器组件对应的 reducer 和 action ,最终所有容器组件的 reducer 都会合并到
/modules/reducers.js
上
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import home from './home/reducer'; // 引入容器组件的局部 reducer
// 合并到主reducer
const reducers = {
home,
routing: routerReducer
};
export default combineReducers(reducers);
/routes.js
react-router-dom
版本为 4.2.2, API 与 3.x.x 相比有很大差别,更多使用方法查看这里
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { hot } from 'react-hot-loader'; // 引入 热更新的 hot 方法
import App from './containers/App';
import Test from './containers/Test';
const Root = () => (
<div>
<Switch>
<Route path="/" render={props => (
<App>
<Switch>
<Route path="/" exact component={Test} />
<Route path="/test" component={Test} />
<Route render={() => <Redirect to="/" />} />
Switch>
App>
)}
/>
<Route render={() => <Redirect to="/" />} />
Switch>
div>
);
// [email protected] 可以在这里设置热更新模块
export default hot(module)(Root);
/api/request.js
/*
API 接口配置
axios 参考文档:https://www.kancloud.cn/yunye/axios/234845
注意:basicRequestLink 在 /config/index.js 中配置,由 imports-loader 注入
*/
import axios from 'axios';
/* eslint-disable no-undef */
const basicHost = basicRequestLink;
console.log(basicHost);
// 删除底部 '/'
function deleteSlash (host) {
return host.replace(/\/$/, '');
}
// 添加头部 '/'
function addSlash (path) {
return /^\//.test(path) ? path : `/${path}`;
}
// 解析参数
function separateParams (url) {
const [path = '', paramsLine = ''] = url.split('?');
let params = {};
paramsLine.split('&').forEach((item) => {
const [key, value] = item.split('=');
params[key] = value;
});
return { path, params };
}
// 主要请求方法
export default function request(config) {
let {
method, url, data = {}, host, headers
} = config;
method = method && method.toUpperCase() || 'GET';
const { path, params } = separateParams(url);
url = host
? `${deleteSlash(host)}${addSlash(path)}`
: `${deleteSlash(basicHost)}${addSlash(path)}`;
return axios({
url,
method,
headers,
data: method === 'GET' ? undefined : data,
params: Object.assign(method === 'GET' ? data : {}, params)
}).catch(err => {
// 请求出错
console.log('request error, HTTP CODE: ', err.response.status);
return Promise.reject(err);
});
}
// 一些常用的请求方法
export const get = (url, data) => request({ url, data });
export const post = (url, data) => request({ method: 'POST', url, data });
export const put = (url, data) => request({ method: 'PUT', url, data });
export const del = (url, data) => request({ method: 'DELETE', url, data });
/containers/Home.js
下创建 home 页容器组件 import React, {Component} from 'react';
import HomeCom from '../components/Home/index';
class Home extends Component {
constructor(props) {
super(props);
}
render() {
return (<HomeCom/>);
}
}
export default Home;
/components/Home/index.js
下创建 Home 容器组件要引入的展示型组件 import React, {Component} from 'react';
export default class HomeCom extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="home-container">div>
);
}
}
/routes.js
更改之前的
/routes.js
文件
import React from 'react';
import { Route, Switch, Redirect } from 'react-router-dom';
import { hot } from 'react-hot-loader';
import App from './containers/App';
import Home from './containers/Home';
import Test from './containers/Test';
const Root = () => (
<div>
<Switch>
<Route path="/" render={props => (
<App>
<Switch>
<Route path="/" exact component={Home} />
<Route path="/home" component={Home} />
<Route path="/test" component={Test} />
<Route render={() => <Redirect to="/" />} />
Switch>
App>
)}
/>
<Route render={() => <Redirect to="/" />} />
Switch>
div>
);
export default hot(module)(Root);
/modules/home/reducer.js
reducer 函数的作用是,根据当前的状态去返回一个新的状态,state 参数是不可变的,返回的 state 一定是一个新的状态
// 将 action type 提取出来作为常量,防止编写错误
import {
CHANGE_INPUT_INFO,
GET_MEMBER_LIST
} from '../types-constant';
// state 初始化数据
const initialState = {
memberList: [],
inputInfo: {
name: '',
tel: ''
}
};
/*
对应不同 action.type 的处理函数,需要返回一个新的 state
也可以 switch 语句 处理不同 action.type
*/
const typesCommands = {
[CHANGE_INPUT_INFO](state, action) {
return Object.assign({}, state, { inputInfo: action.msg });
},
[GET_MEMBER_LIST](state, action) {
return Object.assign({}, state, { memberList: action.msg });
}
}
/*
这里会输出一个 reducer 函数,
reducer 函数的作用是,根据当前的状态去返回一个新的状态
state 参数是不可变的,这里返回的 state 一定是一个新的状态
*/
export default function home(state = initialState, action) {
const actionResponse = typesCommands[action.type];
return actionResponse ? actionResponse(state, action) : state;
}
/modules/home/actions.js
// 将 action.type 抽取为常量,减少出错
import {CHANGE_INPUT_INFO, GET_MEMBER_LIST} from '../types-constant';
// 将网络请求抽取出来,方便接口调试,函数返回 Promise
import {obtainMemberList, postNewMember} from '../../api/home';
// 获取 成员信息列表,
export function getMemberList() {
return dispatch => {
obtainMemberList()
.then(res => dispatch({type: GET_MEMBER_LIST, msg: res}))
.catch(err => console.log('error ', err));
}
}
// 改变新增的本地的成员信息
export function changeInputInfo(newMember) {
return {type: CHANGE_INPUT_INFO, msg: newMember};
}
/*
可以使用 async await 返回处理异步,返回的是一个 promise
如果发生错误,可以在这里使用 try catch 捕获,或者直接交给调用 action 的部分 通过 Promise 的 catch 方法捕获
*/
// 提交新成员信息
export function postNewInfo(newMember) {
console.log('newMember', newMember);
return async dispatch => {
await postNewMember(newMember)
const newData = await obtainMemberList();
dispatch({type: GET_MEMBER_LIST, msg: newData});
return 'success';
}
}
/api/home.js
下 import {get, post} from './request';
export function obtainMemberList() {
return get('/list').then(res => res.data);
}
export function postNewMember(newMember) {
if (!newMember.name) {
return Promise.reject('name is wrong');
}
return post('/list', newMember).then(res => res.data);
}
/modules/reducers.js
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
import home from './home/reducer';
// 合并到主reducer
const reducers = {
home,
routing: routerReducer
};
// combineReducers() 函数用于将分离的 reducer 合并为一个 reducer
export default combineReducers(reducers);
/containers/Home.js
修改 Home 组件代码
import React, {Component} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import HomeCom from '../components/Home/index';
import {getMemberList, changeInputInfo, postNewInfo} from '../modules/home/actions';
class Home extends Component {
constructor(props) {
super(props);
}
render() {
return (<HomeCom {...this.props}/>);
}
}
export default connect(
state => ({homeState: state.home}),
dispatch => ({
getMemberList: bindActionCreators(getMemberList, dispatch),
changeInputInfo: bindActionCreators(changeInputInfo, dispatch),
postNewInfo: bindActionCreators(postNewInfo, dispatch),
})
)(Home);