一、前言

最近整理了一下项目骨架,顺便自定义了一个脚手架,方便日后使用。我会从头开始,步骤一步步写明白,如果还有不清楚的可以评论区留言。先大致介绍一下这个骨架,我们采用 create-react-app 搭建基础骨架,修改一些基础配置; 使用webpack的import模块实现按需加载(俗称切片打包); 引入 react-redux; 引入axios; 规划好项目的目录结构。我们大致就做这些事,大家可以根据自己项目需要,添加ui包等其他插件。博客的代码只是说明大致的流程,建议先拉代码,对比代码看博客。

二、目录

  1、安装 create-react-app 脚手架并创建APP

  2、按照上线项目标准完善目录结构

  3、配置按需加载(俗称切片打包)

  4、配置react-redux及redux-sagas(sagas是我个人习惯,挺好用的,不喜欢的可以不装)

  5、配置axios统一请求(cookie、拦截、统一报错等)

  6、代码地址 (如果觉得有用,记得给我 github 点个赞奥)   ps: 说不定博主还会开放几个私有仓库  ☺

三、安装 create-react-app 脚手架并创建APP

      npm install -g create-react-app   //本地全局安装 react 脚手架
create-react-app webapp //通过脚手架指令 创建 react webapp, 注意名字不能有大写字母,现在就可以直接跑了
npm run eject //新版本的脚手架把配置文件等都以依赖的形式放到 node_modules 中了, eject 一下,把配置信息释放出来
scripts/start.js //修改一下端口号,默认是3000,改成你想要的
npm i //装一下依赖

    我们直接用react的官方脚手架搭建最基础的骨架,通过 create-react-app 新建 react的webAPP。然后我们的项目就可以直接跑了,看一下package.json

文件,里面有关于项目启动、打包、测试的指令。执行 npm run start 就可以运行我们的项目了。

然后我们修改一下这个项目,因为现在 create-react-app 脚手架会把配置文件都以依赖的形式放到node_modelus里面,执行 npm run eject 把配置文件释放出来,然后执行 npm i,装一下依赖(好像不用装,我们没添加什么,笑哭~~)。

我们看一下现在的目录结构

    

 四、按照上线项目标准完善目录结构

目前为止这个项目只有一个默认页面,放在src录下。我们按照上线项目大致会用到的东西,先完善一下目录结构

          assets            静态资源,存放 字体、图片、css hack 等
components 建立公共组件文件夹,这是放公共组件的
layouts 建立布局文件夹 确定好你的项目布局样式
constants 全局常亮文件夹 存放全局常亮
helpers 公共函数文件夹 存放公共函数、一些插件的启动配置函数
modules 我们具体的功能模块 存放我们项目的实际页面
services 接口文件夹 存放所有请求
store 装redux的

我们在src目录下新建上述文件夹,具体功能都已标明了。出于规范化、模块化考虑我们暂时将文件如此分类。

然后我们新建几个简单的页面,内容自定义,可参考下述代码:

import React, { PureComponent } from 'react';

export default class Register extends PureComponent {
state = {} componentDidMount () {} render() {
return (
<div className="g-default">
默认页
</div>
)
}
}

   这样我们新建几个页面 登录、注册、默认页等等,这个随意啦。然后我们看一下现在的目录结构:

五、配置按需加载(俗称切片打包)

现在我们已经有了几个最基础的页面了,我们开始做路由按需加载。

为什么要按需加载? 因为 单页应用,只有一个html,一个主要的css、js。传统打包方式是将整个项目的js、css都打包成一个文件引入。用户浏览我们的页面

时就需要将整个项目拉下来才行(动不动就是几M甚至几十M),非常不友好。按需加载,按照路由切割js、css,用户看哪个,就加载哪个页面代码。首次加载体验非常好。

按需加载方法有很多,我们介绍一种目前配置简单、效率也高的一种。我们采用 webpack 的 import 模块来实现按需加载。

首先,封装一个异步加载模块的组件,然后用这个组件去引入要加载的模块。代码如下:

//这是异步加载组件的代码
import {PureComponent} from 'react';
export default class Bundle extends PureComponent {
constructor(props) {
super(props);
this.state = {
mod: null
};
} componentWillMount() {
this.load(this.props)
}
componentWillReceiveProps(nextProps) {
if (nextProps.load !== this.props.load) {
this.load(nextProps)
}
}
load(props) {
this.setState({
mod: null
});
//注意这里,使用Promise对象; mod.default导出默认
props.load().then((mod) => {
this.setState({
mod: mod.default ? mod.default : mod
});
});
} render() {
return this.state.mod ? this.props.children(this.state.mod) : null;
}
}
.............. //这是他的使用,新建Router.js文件,配置路由
import React from 'react';
import {BrowserRouter, Route, Switch} from 'react-router-dom';
import Bundle from './Bundle'; const Login = (props) => (<Bundle load={() => import('./modules/login')}>{(Login) => <Login {...props}/>}</Bundle>);
const Register = (props) => (<Bundle load={() => import('./modules/register')}>{(Register) => <Register {...props}/>}</Bundle>);
const Default = (props) => (<Bundle load={() => import('./modules/default')}>{(Default) => <Default {...props}/>}</Bundle>);
const Blog = (props) => (<Bundle load={() => import('./modules/blog')}>{(Blog) => <Blog {...props}/>}</Bundle>);
const User = (props) => (<Bundle load={() => import('./modules/user')}>{(User) => <User {...props}/>}</Bundle>); const BasicRoute = () => (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={Login}/>
<Route exact path="/register" component={Register}/>
<Route exact path="/default" component={Default}/>
<Route exact path="/blog" component={Blog}/>
<Route exact path="/user" component={User}/> <Route exact path="/" component={Login}/>
<Route exact
component={Login}/>
</Switch>
</BrowserRouter>
); export default BasicRoute; ............. //这是对index文件的修改
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import BasicRoute from './Router';
import * as serviceWorker from './serviceWorker';
import store from './store' ReactDOM.render( <BasicRoute />, document.getElementById('root')); // If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister();
 

   注意,我把之前的App.js文件删掉了,我们用不上。然后新建了一个Router.js,这个文件就是我们所有模块的路由配置。然后修改src/index.js 文件,引入我们的路由文件。现在我们的路由以及路由的按需加载就都OK了。我们可以多建几个文件加上内部路由跳转试一下。执行 npm run build 看我们build文件夹, build/static/js 可以看到我们已经实现切片打包了。

六、 配置react-redux及redux-sagas(sagas是我个人习惯,挺好用的不喜欢的可以不装)

为什么要装redux? 因为react单页应用,我们会涉及大量的数据,像用户信息等数据会在很多地方用到,这会导致组件间的数据传输很麻烦,所以我们使用redux,将变量统一管理,中心思想很简单。和我们定义一个命名空间,里面放很多变量,然后写一些方法指定性读取、修改这些变量一样,大致可以这么理解。

   然后,我们安装

      npm install --save redux        安装redux
npm install --save react-redux 安装react的绑定库
npm install --save redux-saga 安装sagas, Redux-saga是Redux的一个中间件,主要集中处理react架构中的异步处理工作

我习惯用sagas,不喜欢的可以不装哈。但是后序代码我都会用它来写。

  装好了依赖,接下来是如何使用。大致步骤是这样的,建立reducer和sagas,然后用redux的Provider组件包裹项目,注入redux,然后就可以在组建中使用了,我们贴一下代码

// src/index.js  引入redux,并注入数据
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux'
import './index.css';
import BasicRoute from './Router';
import * as serviceWorker from './serviceWorker';
import store from './store'
ReactDOM.render( <Provider store={store}> <BasicRoute /> </Provider>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
serviceWorker.unregister(); // src/store/index.js 配置redux和sagas,并引入我们的reducer和sagas
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer, { createReducer } from './reducers'
import rootSaga from './sagas'
const sagaMiddleware = createSagaMiddleware()
const middlewares = [sagaMiddleware]
const configureStore = (initialState = {}) => {
const store = createStore(
rootReducer,
initialState,
applyMiddleware(...middlewares),
)
sagaMiddleware.run(rootSaga)
store.runSaga = sagaMiddleware.run
store.asyncReducers = store.asyncReducers || {}
store.asyncSagas = store.asyncSagas || []
return store
}
export const injectAsyncReducer = ({ name, asyncReducer, store }) => {
if ( store.asyncReducers[name] ) return
store.asyncReducers[name] = asyncReducer
store.replaceReducer(createReducer(store.asyncReducers))
}
export const injectAsyncSagas = ({ name, sagas, store }) => {
if ( !store.asyncSagas.includes(name) ) {
sagas.forEach(store.runSaga)
store.asyncSagas.push(name)
}
}
export default configureStore({}) // 配置reducer入口文件
import { combineReducers } from 'redux'
import user from './user'
const rootReducer = {
user,
}
export const createReducer = asyncReducers => combineReducers({
...rootReducer,
...asyncReducers,
})
export default combineReducers(rootReducer) // user模块的 reducer
const initState = {
loading: false,
dataSource: {
list: [],
pagination: {
pageSize: 10,
current: 1,
},
},
}
const reducer = (state = initState, action) => {
switch (action.type) {
case 'saveAssetsLoading':
return {
...state,
loading: action.payload,
}
default:
return {
...state,
}
}
}
export default reducer // sagas 的入口文件
import { all } from 'redux-saga/effects'
import userSagas from './user'
const run = sagas => sagas.map(saga => saga())
export default function* rootSaga() {
yield all([
...run([
...userSagas,
]),
])
} // user模块的sagas
import { put, call, takeEvery } from 'redux-saga/effects'
import { login1, login2} from '../../services/user'
const sagas = {
* login1({ payload, callback }) {
const result = yield call(login1, payload)
if (callback) callback(result)
},
* login2({ payload, callback }) {
const result = yield call(login2, payload)
if (callback) callback(result)
},
}
export default Object.keys(sagas).map(item => {
return function * s() {
yield takeEvery(item, function *(args) {
try {
yield sagas[item](args)
} catch (e) {
console.log(e)
}
})
}
}) // 在user组件中使用
import React, { PureComponent } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'; export default
@connect(state => ({
user: state.user
}))
class User extends PureComponent {
state = {
}
componentDidMount () {
// this.fetch()
this.fetch2()
}
fetch = () => {
const params = {
test: 'web'
}
this.props.dispatch({
type: 'login1',
payload: params,
callback: result => {
console.log(result)
}
})
}
render() {
return (
<div className="g-login">
测试2
<br />
<Link to={`/default`}>
登录
</Link>
</div>
)
}
}

使用redux大致就是这样,先引入redux,然后做具体的文件配置,最后直接组件中使用即可。注意: sagas这里,我没有做作用域处理,sagas方法名不能重复。

七、配置axios统一请求(cookie、拦截、统一报错等)

  为什么封装axios? 首先,我们使用axios作为请求方式,各方面性能吧都不错。其次,在单页应用中,涉及到的请求会非常多,对于请求拦截、响应拦截、错误统一处理等常规操作,我们把axios进行二次封装会节省大量的代码,好处不用我多说了。下边是封装axios的流程,以及使用sagas调用的方式,直接贴代码了

// src/constants/index.js  设定请求的ip,这个根据个人情况来
export const ORIGIN = {
production: window.location.origin,
development: `http://${window.location.hostname}:3009`,
test: window.location.origin,
// dev: 'http://localhost:3009',
}[process.env.NODE_ENV || 'development'];

// 对axios的封装
import axios from 'axios'
import { ORIGIN } from '../constants' // 添加一个请求拦截器
axios.interceptors.request.use(config => {
return config // 暂时没啥好写的,我们只是个骨架
}, error => {
return Promise.reject(error);
})
// 添加一个响应拦截器
axios.interceptors.response.use(response => {
return response.data // 其他的不要了,只拿data就好
}, error => {
console.log(error.response)
if (error.response.status === 401) {
window.location.pathname = '/login'
}
// ......在做别的统一处理
return Promise.reject(error);
});
export default function request(url, options = {}) {
return axios({
url: /^http/.test(url) ? url : `${ORIGIN}${url}`,
method: 'get',
// 携带cookie信息
withCredentials: true,
...options,
data: options.body,
})
} // 添加具体的请求函数,供前端使用
import request from '../helpers/request'
import { stringify } from 'querystring'; // 测试1
export function login1(params) {
return request(`/xxx/xxx1?${stringify(params)}`)
} // 测试2
export function login2(data) {
return request(`/xxx/xxx2`, {
method: 'post',
body: data,
})
}

  这个代码里面都有注释,这里简单说明一下,这个地址常亮,大家根据自己实际情况来改。关于请求的封装,这里主要写了加cookie,未登录401报错直接跳到登录页,至于其他错误处理,大家根据自己项目错误码来就好。项目中涉及到一些node.js的小功能函数,大家一百度就知道了,比如说 stringify。封装好的请求要么直接用,要么在sagas里面用。大致就是这样。

八、代码地址 (如果觉得有用,记得给我 github 点个赞奥。)

  https://github.com/Aaron-China/react-cli

  这是代码地址,觉得不错,您别吝啬,  地址右上方start点一下,谢谢。

小结

  至此,一个精简的react骨架就出来了,没有做太多的配置,以免影响灵活度。这几项几乎都是项目中必须的东西。所以,就写到这。后期看看反应吧,把ui框架加上去,再做上菜单、权限的配置,再敲几个常用的页面。如果做的话,我会在git上开一个分支,不会影响这个基本骨架。如果博客中哪里写的有问题,欢迎评论区留言。

  ps: 有点懒,好久没写博客了,将持续放点干货,希望能帮到你。

快速搭建react项目骨架(按需加载、redux、axios、项目级目录等等)的更多相关文章

  1. react 实现路由按需加载

    import() 方法: async.js 文件内容: import React from 'react'; // import "babel-polyfill"; //compo ...

  2. React引入AntD按需加载报错

    背景:React使用create-react-app脚手架创建,然后yarn run eject暴露了配置之后修改less配置, 需求:实现antd组件按需加载与修改主题. 一开始是按照webpack ...

  3. vue项目实现按需加载的3种方式:vue异步组件技术、es提案的import()、webpack提供的require.ensure()

    1. vue异步组件技术 vue-router配置路由,使用vue的异步组件技术,可以实现按需加载. 但是,这种情况下一个组件生成一个js文件. 举例如下: { path: '/promisedemo ...

  4. react 使用antd 按需加载

    使用 react-app-rewired 1. 安装react-app-rewired: 由于新的 react-app-rewired@2.x 版本的关系,你还需要安装 customize-cra. ...

  5. react antd样式按需加载配置以及与css modules模块化的冲突问题

    通过create-react-app脚手架生成一个项目 然后运行npm run eject 把webpack的一些配置从react-scripts模块弹射出来, 方便自己手工增减,暴露出来的配置文件在 ...

  6. vue项目实现按需加载的3种方式

    vue异步组件技术 vue-router配置路由,使用vue的异步组件技术,可以实现按需加载.这种方式下一个组件生成一个js文件 用例: { path: '/promisedemo', name: ' ...

  7. react CRA antd 按需加载配置 lessloader

    webpack配置 webpack.config.dev.js, webpack.config.prod同理. 'use strict'; const autoprefixer = require(' ...

  8. route按需加载的3种方式:vue异步组件、es提案的import()、webpack的require.ensure()

    1. vue异步组件技术 vue-router配置路由,使用vue的异步组件技术,可以实现按需加载. 但是,这种情况下一个组件生成一个js文件.举例如下: { path: '/promisedemo' ...

  9. 使用脚手架快速搭建React项目

    create-react-app是Facebook官方推出的脚手架,基本可以零配置搭建基于webpack的React开发环境步骤: 打开控制台 进入你想要创建项目的目录文件下面 依次执行以下命令 np ...

随机推荐

  1. VGG16 ReNetInception network

    VGG16就是运用很简单的2个filter s=2 f=2 的pool以及3x3 same padding的filter. 每pool一下以后 翻倍filter的depth Resnet就是跳级传播结 ...

  2. 20175204 张湲祯 2018-2019-2《Java程序设计》第九周学习总结

    20175204 张湲祯 2018-2019-2<Java程序设计>第九周学习总结 教材学习内容总结 -第十一章JDBC和MySQL数据库要点: 1.下载MySQL和客户端管理工具navi ...

  3. Large-Margin Softmax Loss for Convolutional Neural Networks

    paper url: https://arxiv.org/pdf/1612.02295 year:2017 Introduction 交叉熵损失与softmax一起使用可以说是CNN中最常用的监督组件 ...

  4. The base and high address of the custom IP are not correctly reflected in xparameters.h in SDK

    This issue has been observed in 2015.3, 2015.4, and 2015.4.1 builds of Vivado. When you create and a ...

  5. node命令行工具—cf-cli

    音乐分享: 钢心 - <龙王> 初喜<冠军>后喜<龙王> (PS:听一次钢心乐队的演出后采访才知道 “龙王”隐喻的是一起喝酒的老铁....) ——————————— ...

  6. Java中int和String类型之间转换

    int –> String int i=123; String s=""; 第一种方法:s=i+""; //会产生两个String对象 第二种方法:s=S ...

  7. Android Wear 2.0 AlarmManager 后台定时任务

    以前在Android 4.0时,alarmManager 没什么问题.后来android为了优化系统耗电情况,引入了doze模式,参见此页 https://developer.android.com/ ...

  8. 五 Zabbix全网监控

    监控的作用 我们的职责   1.保障企业数据的安全可靠.   2.为客户提供7*24小时服务.   3.不断提升用户的体验.在关键时刻,提前提醒我们服务器要出问题了当出问题之后,可以便于找到问题的根源 ...

  9. 主席树——求区间第k个不同的数字(向右密集hdu5919)

    和向左密集比起来向右密集只需要进行小小的额修改,就是更新的时候从右往左更新.. 自己写的被卡死时间.不知道怎么回事,和网上博客的没啥区别.. /* 给定一个n个数的序列a 每次询问区间[l,r],求出 ...

  10. 关于haproxy

    高性能负载均衡软件 haproxy 一.四层和七层负载均衡的区别: 所谓的四层就是OSI参考模型中的第四层,四层负载均衡也称为四层交换机,他主要是通过分析IP层及TCP/UDP层的流量实现的基于IP加 ...