快速搭建react項目骨架(按需加載、redux、axios、項目級目錄等等)


一、前言

       最近整理了一下項目骨架,順便自定義了一個腳手架,方便日后使用。我會從頭開始,步驟一步步寫明白,如果還有不清楚的可以評論區留言。先大致介紹一下這個骨架,我們采用 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: 有點懶,好久沒寫博客了,將持續放點干貨,希望能幫到你。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM