express+mongodb+react+typescript+antd搭建管理後臺系統(後端+前端)(下)

react+typescript+antd搭建有登陸驗證的管理後臺

在上一篇文章中,我介紹了如何使用express+mongodb構建後端項目 ,如今我再來跟你們介紹如何使用react+typescript+antd來構建前端項目css

使用create-react-app生成react的typescript項目框架

  • 技術棧

react
typescript
antd
react-router-dom
react-redux
canvas
ES6
cookiehtml

  • 目錄結構

clipboard.png

assets——存放靜態資源
components——存放公共組件
pages——存放頁面
store——存放狀態管理數據流文件
utils——存放函數工具
api.ts——定義api接口文件
App.css——定義全局css
App.ts——定義項目入口組件
global.d.ts——定義全局的聲明文件
index.tsx——入口文件
router.ts——路由配置表前端

  • 全局安裝create-react-app
npm install -g create-react-app
  • 咱們將建立一個名爲 react-antd-ts 的新項目:
npx create-react-app antd-demo-ts --typescript
cd react-antd-ts
npm start

此時瀏覽器會訪問 http://localhost:3000/ ,看到 Welcome to React 的界面就算成功了react

  • 引入按需加載antd
npm install antd --save

此時咱們須要對 create-react-app 的默認配置進行自定義,這裏咱們使用 react-app-rewired (一個對 create-react-app 進行自定義配置的社區解決方案,而不須要eject暴露react的webpack配置文件)。webpack

引入 react-app-rewired 並修改 package.json 裏的啓動配置。因爲新的 react-app-rewired@2.x 版本的關係,你還須要安裝 customize-cra。ios

npm install react-app-rewired customize-cra --save

修改package.jsongit

"scripts": {
-   "start": "react-scripts start",
+   "start": "react-app-rewired start",
-   "build": "react-scripts build",
+   "build": "react-app-rewired build",
-   "test": "react-scripts test",
+   "test": "react-app-rewired test",
}
  • 使用 babel-plugin-import

babel-plugin-import 是一個用於按需加載組件代碼和樣式的 babel 插件(原理),如今咱們嘗試安裝它並修改 config-overrides.js 文件。github

npm install babel-plugin-import --save

而後在項目根目錄建立一個 config-overrides.js 用於修改默認配置
config-overrides.jsweb

const { override, fixBabelImports } = require('customize-cra');
module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
);
  • 使用react-hot-loader實現代碼修改熱更新

若是你試一下在現有項目上修改代碼,發現整個頁面都會刷新,這並非咱們想要的,若是想局部更新,咱們可使用react-hot-loader來實現mongodb

npm install --save react-hot-loader

修改App.tsx文件:
App.tsx

import React from 'react';
import logo from './logo.svg';
import { hot } from 'react-hot-loader/root'
import './App.css';

const App: React.FC = () => {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.tsx</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default process.env.NODE_ENV === "development" ? hot(App) : App
  • 使用less
npm install --save less-loader less

修改config-overrides.js:

const { override, fixBabelImports,addLessLoader,addPostcssPlugins } = require('customize-cra');
const rewireReactHotLoader = require('react-app-rewire-hot-loader-for-customize-cra')
module.exports = override(
  fixBabelImports('import', {
    libraryName: 'antd',
    libraryDirectory: 'es',
    style: 'css',
  }),
  addLessLoader({
    strictMath: true,
    noIeCompat: true,
    localIdentName: '[local]--[hash:base64:5]' // if you use CSS Modules, and custom `localIdentName`, default is '[local]--[hash:base64:5]'.
  }),
  addPostcssPlugins([require('autoprefixer')]),
  rewireReactHotLoader()
);

致此,能夠引用antd的組件了;注意:若是在構建的時候出現 import React from 'react' 的報錯,能夠npm install @types/react進行解決;這裏給你們科普一下typescript裏面的聲明文件知識,若是引入的模塊提示沒有找到聲明文件,通常能夠用npm install @types/xxxx(模塊名稱)來解決。關於引入模塊聲明文件的相關知識,能夠看看如下兩篇文章:
https://www.tslang.cn/docs/ha...
https://www.tslang.cn/docs/ha...

(爲節省篇幅,如下只講一些重點以及一些注意事項,因此會將一些已經寫好的組件和頁面放到項目中,再也不敘述這些頁面跟組件的構建,最後會附上項目的git地址,能夠自行參考)

  • 路由配置

單頁應用中的重要部分,就是路由系統。因爲不一樣普通的頁面跳轉刷新,所以單頁應用會有一套本身的路由系統須要維護。

咱們固然能夠手寫一個路由系統,可是,爲了快速有效地建立於管理咱們的應用,咱們能夠選擇一個好用的路由系統。本文選擇了react-router 4。這裏須要注意,在v4版本里,react-router將WEB部分的路由系統拆分至了react-router-dom,所以須要npmreact-router-dom

npm i --save react-router-dom

本例中咱們使用react-router中的BrowserRouter組件包裹整個App應用,在其中使用Route組件用於匹配不一樣的路由時加載不一樣的頁面組件。(也可使用HashRouter,顧名思義,是使用hash來做爲路徑)react-router推薦使用BrowserRouter,BrowserRouter須要history相關的API支持。

首先,須要在App.tsx中添加BrowserRouter組件,並將Route組件放在BrowserRouter內。其中Route組件接收兩個屬性:path和component,分別是匹配的路徑與加載渲染的組件,把App.tsx修改以下:
App.tsx

import React from 'react';
import {BrowserRouter,Route,Switch} from 'react-router-dom';
import {store} from './store';
import {Provider} from 'react-redux';
import Index from './components/Index';
import Login from './pages/login';
import Err from './pages/err';
import './App.css';
import { hot } from 'react-hot-loader/root'
import moment from 'moment';
import 'moment/locale/zh-cn';
moment.locale('zh-cn');
const App: React.FC = () => {
  return (
    <Provider store={store()}>
      <BrowserRouter>
        <Switch>
          <Route path="/login" exact component={Login}></Route>
          <Route path="/err" exact component={Err}></Route>
          <Route path='/' component={Index}/>
        </Switch>
      </BrowserRouter>
    </Provider>

  );
}

export default process.env.NODE_ENV === "development" ? hot(App) : App
  • 使用redux來管理數據流

redux是flux架構的一種實現,redux並非徹底依附於react的框架,實際上redux是能夠和任何UI層框架相結合的。所以,爲了更好得結合redux與react,對redux-flow中的store有一個更好的全局性管理,咱們還須要使用react-redux

npm install --save redux
npm install --save react-redux

同時,爲了更好地建立action和reducer,咱們還會在項目中引入redux-actions:一個針對redux的一個FSA工具箱,能夠相應簡化與標準化action與reducer部分。固然,這是可選的

npm install --save redux-actions

注意:若是出現如下狀況,上面有提到,須要 npm install @types/react-redux 來解決
clipboard.png

一、建立對應的action。

action是一個object類型,對於action的結構有Flux有相關的標準化建議FSA
一個action必需要包含type屬性,同時它還有三個可選屬性error、payload和meta。

(1) type屬性至關因而action的標識,經過它能夠區分不一樣的action,其類型只能是字符串常量或Symbol。
(2) payload屬性是可選的,可使任何類型。payload能夠用來裝載數據;在error爲true的時候,payload通常是用來裝載錯誤信息。
(3) error屬性是可選的,通常當出現錯誤時其值爲true;若是是其餘值,不被理解爲出現錯誤。
(4) meta屬性可使任何類型,它通常會包括一些不適合在payload中放置的數據。

使用redux-actions對actions進行建立與管理

(1) createAction(type, payloadCreator = Identity, ?metaCreator)
(2) createAction至關於對action建立器的一個包裝,會返回一個FSA,使用這個返回的FSA能夠建立具體的action。
(3) payloadCreator是一個function,處理並返回須要的payload;若是空缺,會使用默認方法。若是傳入一個Error對象則會自動將action的error屬性設爲true

如下以全局控制側邊欄的收開爲例,建立action和reducer
在根目錄下新建store文件夾,建立types.ts文件,用於存放action 的惟一標識符
store/types.ts

export const TOGGLE_SIDE_BAR = 'TOGGLE_SIDE_BAR';

建立models.ts,用於存放store中初始狀態的接口聲明
store/models.ts

export interface AppModel {
  collapsed:boolean;
}

建立actions.ts,用於對全部action進行管理
store/actions.ts

import {createAction} from 'redux-actions';

import {AppModel} from './models';

import {TOGGLE_SIDE_BAR} from './types';

const toggleSideBar = createAction<AppModel, boolean>(
  TOGGLE_SIDE_BAR,
  (collapsed: boolean) => {return {collapsed}}
);

export {
  toggleSideBar
}

建立reducers.ts
store/reducers.ts

import {handleActions} from 'redux-actions';
import {TOGGLE_SIDE_BAR} from './types';
import {AppModel} from './models'

// 初始的狀態,就像react中組件內的初始狀態,只不過這個是全局的。
const initialState: AppModel = {
  collapsed:false
};

export const toggleSideBarReducer = handleActions<AppModel>({
  [TOGGLE_SIDE_BAR]: (state:any, action:any) => {
    return {
      ...state,
      collapsed: !state.collapsed
    }
  }
}, initialState);

建立index.ts,用於組合store
store/index.ts

import { AppModel } from './models';
import { combineReducers } from 'redux';
import { toggleSideBarReducer } from './reducers';
import { Store, createStore, applyMiddleware } from 'redux';
import thunkMiddleware from 'redux-thunk';
// import {History} from 'history';
interface RootState {
  app: AppModel
}
const rootReducer = combineReducers({
  app: toggleSideBarReducer
});
export function store(initialState?:any): Store<RootState> {

  // store 中間件,根據我的需求添加
  const middleware = applyMiddleware(thunkMiddleware)

  return createStore(
    rootReducer,
    initialState,
    middleware
  ) as Store<RootState>;
}
  • api請求

一、在package.json裏面加入

"proxy": "http://localhost:3001",

二、建立api.ts,對全局接口請求進行定義

import http from './utils/http';
const getUserList=()=>{
  return http.get('/getUserList')
}
const register=(params:{name:string,password:string,phone:number,type:number})=>{
  return http.post('/register',params)
}
const login=(params:{password:string,phone:number})=>{
  return http.post('/login',params)
}
const logout=()=>{
  return http.post('/logout')
}
const userInfo=()=>{
  return http.post('/userInfo')
}
export {
  getUserList,
  register,
  login,
  logout,
  userInfo
}

三、建立utils文件夾,新建http.ts文件,對axios進行全局設置

npm install --save axios

utils/http.ts

import qs from 'qs'
import { message } from 'antd';
import axios,{ AxiosResponse, AxiosRequestConfig } from 'axios'
import { Modal } from 'antd';
import {createBrowserHistory} from 'history';
const axiosConfig: AxiosRequestConfig = {
  // 請求後的數據處理
  transformResponse: [(data: AxiosResponse) => {
    return data
  }],
  transformRequest: [(data: any) => {
    return qs.stringify(data)
  }],
  // 超時設置s
  timeout: 30000,
  // 跨域是否帶Token
  withCredentials: true,
  responseType: 'json',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded'
  }
}
// 取消重複請求
const pending: Array<{
  url: string,
  cancel: any
}> = []
const cancelToken = axios.CancelToken
const removePending = (config: any) => {

  // tslint:disable-next-line:forin
  for (const p in pending) {
    const item: any = p
    const list: any = pending[p]
    // 當前請求在數組中存在時執行函數體
    if (list.url === config.url + '&request_type=' + config.method) {
      // 執行取消操做
      list.cancel()
      // 從數組中移除記錄
      pending.splice(item, 1)
    }
  }
}

const service = axios.create(axiosConfig)

// 添加請求攔截器
service.interceptors.request.use(
  (config: any) => {
    removePending(config)
    config.cancelToken = new cancelToken((c: any) => {
      pending.push({ url: config.url + '&request_type=' + config.method, cancel: c })
    })
    return config
  },
  (error: any) => {
    return Promise.reject(error)
  }
)

// 返回狀態判斷(添加響應攔截器)
service.interceptors.response.use(
  (res: any) => {
    removePending(res.config)
    if (res.data) {
      // LoadingInterface.close();
      if (res.status === 200) {
        if (res.data.status === 200) {
          return res.data.data
        } else if (res.data.status === 403) {
          // 未登陸或者token過時,重定向到登陸頁面
          Modal.info({
            title: '通知',
            content:'登陸信息已過時,請從新登陸',
            onOk(){
              const history = createBrowserHistory()
              history.push('/login')
              window.location.reload()
            }
          })
        } else {
          message.error(res.data.message)
          return Promise.reject(res.data.message)
        }
      } else {
        message.error( res.statusText )
        return Promise.reject(res.statusText)
      }

    }
  },
  (error: any) => {
    message.error('請求失敗,請稍後再試')
    return Promise.reject(error)
  }
)

export default service

好嘞,基本的東西就介紹到這裏了,完整的項目能夠到個人git上去下載;
後端git地址:https://github.com/SuperMrBea...
前端git地址:https://github.com/SuperMrBea...
項目在線預覽地址:http://www.wxdriver.com (帳號:15900000000 密碼:123456)
上一篇文章的地址:https://segmentfault.com/a/11...

相關文章
相關標籤/搜索