天天看點

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

快速搭建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,裝一下依賴(好像不用裝,我們沒添加什麼,笑哭~~)。

         我們看一下現在的目錄結構

    

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

 四、按照上線項目标準完善目錄結構

         目前為止這個項目隻有一個預設頁面,放在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>
    )
  }
}      

   這樣我們建立幾個頁面 登入、注冊、預設頁等等,這個随意啦。然後我們看一下現在的目錄結構:

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

五、配置按需加載(俗稱切片打包)

       現在我們已經有了幾個最基礎的頁面了,我們開始做路由按需加載。

       為什麼要按需加載? 因為 單頁應用,隻有一個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: 有點懶,好久沒寫部落格了,将持續放點幹貨,希望能幫到你。

原文位址

https://www.cnblogs.com/pengfei-nie/p/10443310.html

繼續閱讀