新手搭建簡潔的Express-React-Redux腳手架

luffyZh發表於2018-05-05

寫在前面

我發現網上的很多新手教程都並不是完全針對新手的,新手在使用起來也是丈二的和尚摸不到頭腦,最近想用node做後端,react做前端搭建一個小系統,想把過程記錄下來,從頭開始搭建腳手架,絕對適合新手(本人也就是個前端小白)。既然是針對新手,那麼就除去那些複雜的內容,什麼服務端渲染之類的全部不考慮,使用的也絕對都是主流:Node+Express做後端,資料庫採用MongoDB,前端用React+React-Router,然後使用Redux做狀態管理,再搭配一個UI框架antd。其他的想用直接在這基礎之上新增就可以了,新手參照下面步驟完全可以自己搭建出來專案骨架以及通過文章掌握一些知識點。 下面是腳手架截圖:

首頁:

新手搭建簡潔的Express-React-Redux腳手架

使用者列表頁:

新手搭建簡潔的Express-React-Redux腳手架
我還喪心病狂的為你們配置了404頁(情不自禁給自己點贊):

新手搭建簡潔的Express-React-Redux腳手架
雖然只有三個頁面,但是麻雀雖小五臟俱全哦:包括前後端路由的配置、資料庫的連結,資料的獲取、react和redux的使用等等,老鐵們,說它是react全家桶不過分吧。

專案地址請點此處,喜歡的小夥伴可以star哦!

第一步 create-react-app

Facebook官方出的腳手架,基本配置完全夠用,初始化建立專案就不多BB了,你可以自己去看官網。這裡只講一句,因為要配置antd按需載入,可以按照antd官網一步步安裝,不過我在按照官網安裝的時候遇到了一些問題,最後還是按照自己的安裝來吧。
首先,安裝依賴項:

yarn add react-app-rewired react-app-rewire-less antd babel-plugin-import 
// react-app-rewired 是用來修改create-react-app的預設配置的
// babel-plugin-import 按需載入antd元件必須的外掛
// react-app-wire-less antd是依賴less的
複製程式碼

其次,進行配置:

  • 修改package.json檔案,將啟動方式變為rewired啟動
     "start": "react-app-rewired start",
     "build": "react-app-rewired build",
    複製程式碼
  • 在根目錄新增config-overrides.js檔案,配置antd按需載入
    /* config-overrides.js */
      const { injectBabelPlugin } = require('react-app-rewired');
      const rewireLess = require('react-app-rewire-less');
      
      module.exports = function override(config, env) {
         config = injectBabelPlugin(['import', { libraryName: 'antd', style: true }], config);
         config = rewireLess.withLoaderOptions({
           modifyVars: { "@primary-color": "#ADFF2F" }, // 可以在這裡修改antd的預設配置
         })(config, env);
          return config;
      };
    複製程式碼

至此,就可以在元件裡按需引用antd了。

新手搭建簡潔的Express-React-Redux腳手架

第二步 配置router

// 首先,16之後react-router和react-router-dom安裝一個即可
yarn add react-router-dom 
// 其次,使用BrowserRouter作為路由,同時需要history配合
yarn add history
// 最後,router的配置
...
import { Router, Switch, Route, Redirect} from 'react-router-dom';
import createBrowserHistory from 'history/createBrowserHistory';
... 
const router = (
  <Router history={history}>
    <Switch>
      <Route exact path="/home" component={App}/> // 首頁路由
      <Route path="/userList" component={UserList} /> //使用者列表頁
      <Redirect from='' to="/home" />
    </Switch>
  </Router>
);
ReactDOM.render(router, document.getElementById('root'));
registerServiceWorker();
複製程式碼

第三步 node + express

接下來,就是在專案裡新增後端,express。

  • 在根目錄下新建資料夾server,然後新建package.json,內容如下:
        {
            "name": "server",
            "version": "1.0.0",
            "description": "server config",
            "main": "server.js",
            "author": "luffy",
            "license": "MIT",
            "dependencies": {
              "babel-cli": "^6.26.0",
              "babel-preset-es2015": "^6.24.1",
              "body-parser": "^1.18.2",
              "express": "^4.16.3",
              "mongoose": "^5.0.16"
             },
            "scripts": {
              "start": "nodemon ./server.js",
              "build": "babel ./server.js --out-file server-compiled.js",
              "serve": "node server-compiled.js"
            }
      }
    
    複製程式碼

    這裡注意,原本的start命令應該是node,但是為了讓後端也達到修改程式碼自動更新的效果,需要全域性安裝nodemon,npm install nodemon -g

  • server資料夾下新建server.js檔案,內容如下:
      const express = require('express');
      const bodyParser = require('body-parser');
    
      const app = express();
      // 給app配置bodyParser中介軟體
      // 通過如下配置再路由種處理request時,可以直接獲得post請求的body部分
      app.use(bodyParser.urlencoded({ extended: true }));
      app.use(bodyParser.json());
      // 註冊路由
      const router = express.Router();
      // 路由中介軟體
      router.use((req, res, next) => {
        // 任何路由資訊都會執行這裡面的語句
        console.log('this is a api request!');
        // 把它交給下一個中介軟體,注意中介軟體的註冊順序是按序執行
        next();
      })
      // 獲取使用者列表資訊的路由
      router.get('/user/list', (req, res) => {
        const userList = [
          {
            name: 'luffy',
            age: 24,
            gender: '男'
          },{
            name: 'lfy',
            age: 23,
            gender: '女'
          }
        ];
        res.json(userList);
      });
      // 所有的路由會加上“/api”字首
      app.use('/api', router); //新增router中介軟體
      
      // express 自動幫我們建立一個server,封裝的node底層http
      app.listen(3003, () => {
        console.log('node server is listening 3003');
      });
    複製程式碼

這裡暫時沒有抽離路由部分,只是測試。

第四步,前後端測試

  • 分別執行後端和前端程式碼
    // 後端執行
    cd server && yarn start
    // 前端執行
    yarn start
    複製程式碼
  • 在瀏覽器訪問http://localhost:3003/api/user/list

新手搭建簡潔的Express-React-Redux腳手架

看到上圖說明後端執行正常。

  • 前端增加頁面UserList,從後端獲取資料渲染在元件裡
      import React, { Component } from 'react';
      import axios from 'axios';
      import { Table } from 'antd';
      
      class UserList extends Component {
        constructor(props) {
          super(props);
          this.state = { userList:[] };
        }
      
        componentDidMount() {
          // 獲取使用者列表
          axios.get('/api/user/list')
          .then((res) => {
            console.log(res);
            this.setState({ userList: res.data })
          })
          .catch(function (error) {
            console.log(error);
          });
        }
      
        render() {
          const columns = [{
            title: '姓名',
            dataIndex: 'name',
            key: 'name',
          }, {
            title: '年齡',
            dataIndex: 'age',
            key: 'age',
          }, {
            title: '性別',
            dataIndex: 'gender',
            key: 'gender',
          }];
          return (
            <div>
              <h1 style={{ textAlign: 'center' }}>使用者列表頁</h1>
              <div style={{ width: '50%', margin: '10px auto' }}>
                <Table dataSource={this.state.userList} columns={columns} />
              </div>
            </div>
          )
        }
      }
      export default UserList;
    複製程式碼

httpRequest使用的是axios,使用fetch就可以,但是與時俱進,畢竟vue2.0推薦的是axios,而且文件良好,yarn add axios.

  • 同時啟動,前後端。 上面啟動專案需要先啟動後端,再啟動前端,至少需要開啟兩個命令列工具,一個工程兩個命令列感覺很不友好,雖然以前一直這麼做,O(∩_∩)O哈哈~。

    這裡使用 concurrently來幫我們同時執行兩條命令。

    yarn add concurrently 修改package.json下scripts程式碼如下:

    "scripts": {
      "react-start": "react-app-rewired start",
      "react-build": "react-app-rewired build",
      "start": "concurrently \"react-app-rewired start\" \"cd server && yarn start\"",
      "build": "concurrently \"react-app-rewired build\" \"cd server && yarn build\"",
      "test": "react-scripts test --env=jsdom",
      "eject": "react-scripts eject"
    },
    複製程式碼

    接下來,秩序執行yarn start就可以同時啟動前端和後端了。

  • 解決跨域

    第一種 create-react-app proxy屬性(推薦)

    只需在package.json增加下面這一條程式碼,即可實現跨域獲取資料,本專案前端是3000埠,後端是3003埠。配置如下:

    "proxy": "http://127.0.0.1:3003"

    第二種 node端解決跨域
    //allow custom header and CORS
      app.all('*',function (req, res, next) {
        res.header('Access-Control-Allow-Origin', '*');
        res.header('Access-Control-Allow-Headers', 'Content-Type, Content-Length, Authorization, Accept, X-Requested-With , yourHeaderFeild');
        res.header('Access-Control-Allow-Methods', 'PUT, POST, GET, DELETE, OPTIONS');
      
        if (req.method == 'OPTIONS') {
          res.send(200); /讓options請求快速返回/
        }
        else {
          next();
        }
      });
    複製程式碼

最後,需要特別注意的是,專案使用node+express作為後端提供API服務,因此後端並無任何渲染頁面,這跟使用node+express搭建部落格系統等有本質區別,所以我們並沒有後端的渲染頁面,也就是view,因此,所有的路由都需要使用res.json()作為返回而不能使用res.render()作為返回,否則會報錯Express Error: No default engine was specified and no extension was provided

第四步 連線資料庫

資料庫採用MongoDB,因此,node端需要安裝mongoose。yarn add mongoose

// 下面是關於mongoose的幾個概念:
Schema: 一種以檔案形式儲存的資料庫模型骨架,不具備資料庫的操作能力
Model: 由Schema釋出生成的模型,具有抽象屬性和行為的資料庫操作物件
Entity: 由Model建立的實體,它的操作也會影響資料庫
Schema、Model、Entity的關係請牢記,Schema生成Model,Model創造Entity,Model和Entity都可對資料庫操作造成影響,但Model比Entity更具操作性。 
複製程式碼

這裡關於MongoDB的安裝就不多說了,安裝完之後的各種配置直接問度娘就可以了。安裝完之後可以使用各種視覺化工具來檢視資料庫。這裡我安裝的是robo,並且建立了一個資料庫luffy_blog,沒錯,後續可能會用這個腳手架搭建一個部落格,因為我發現作為一個新手,有些教程確實不是很友好

新手搭建簡潔的Express-React-Redux腳手架

如上圖,我已經安裝好了MongoDB,並且新建了資料庫luffy_blog,並且新增了一條使用者資料,接下來我們就使用express配合MongoDB獲取資料傳遞給前端:

  • server目錄下新建db資料夾,用於處理資料庫相關

    目錄結構如下:
    - db
      - config // MongoDB的配置檔案
      - models // 資料模型model
      - schemas // 模型骨架schema
    複製程式碼
  • 對MongoDB進行配置

    config資料夾

    • config.js
      // 資料庫地址: 'mongodb://使用者名稱:密碼@ip地址:埠號/資料庫';
      // 一般如果沒設定使用者名稱和密碼直接寫IP地址就可以,資料庫你可以新建一個
      module.exports = {
        mongodb : 'mongodb://127.0.0.1:27017/luffy_blog'
      };
      複製程式碼
    • mongoose.js
      // 用於連線資料庫並且定義Schema和Model
      const mongoose = require('mongoose');
      const config = require('./config');
      module.exports = ()=>{
          mongoose.connect(config.mongodb);//連線mongodb資料庫
          // 例項化連線物件
          var db = mongoose.connection;
          db.on('error', console.error.bind(console, '連線錯誤:'));
          db.once('open', (callback) => {
              console.log('MongoDB連線成功!!');
          });
          return db;
      };
      複製程式碼
      上面就完成了連線資料庫的操作,接下來在server.js新增如下程式碼即可:
      // 連線mongodb資料庫
      const mongoose = require('./db/config/mongoose');
      const db = mongoose();
      複製程式碼

    接下來就是建立資料骨架和模型,完全按照mongoose的模板來就可以,接下來就以使用者模型user為例。

    schemas資料夾

    // UserSchema.js
      const mongoose = require('mongoose');
      const Schema = mongoose.Schema;
      //建立Schema
      const UserSchema = new Schema({
          name: String,
          password: String,
          email: String
      });
      module.exports = UserSchema;
    複製程式碼

    models資料夾

      // UserModel.js
      const mongoose = require('mongoose');
      const UserSchema = require('../schemas/UserSchema');
      //建立model,這個地方的user對應mongodb資料庫中users的conllection。
      const User = mongoose.model('user',UserSchema);
      module.exports = User;
    複製程式碼

    萬事俱備,只欠東風。資料庫我們有了,資料我們也有了,express對資料庫的連線也已經完成了,接下來只剩下將資料從資料庫取出以API形式返給前端。例如:我們將介面定義為如下形式:

    介面名稱: /api/user/list
    後端路由:
      const express = require('express');
      const User = require('../db/models/UserModel');// 引入模型
      
      const router = express.Router();
      
      router.get('/list', (req, res) => {
        User.find({}, (err, data) => {
          if (err) 
            next(err);
          res.json(data);
        });
      });
    瀏覽器訪問:
        瀏覽器可以登入http://localhost:3003/api/user/list訪問資料
    前端頁面獲取:
      axios.get('/api/user/list')
            .then(res => {
              return res.data;
            })
            .catch((error) => {
              console.log(error);
            }); 
    複製程式碼

    最後我們將資料渲染到頁面上,效果如下:

新手搭建簡潔的Express-React-Redux腳手架

以上,資料庫部分引入完成,詳細程式碼可以clone專案檢視。

第五步 增加redux進行狀態管理

重點來了,重點來了,重點來了(重要的事情說三遍)。提到react的專案,怎麼可能不使用redux來進行狀態管理呢。當然,現在的生態圈可能很多人對你說,用mobx吧,更簡單一般專案來說足夠了,但是我覺得,既然更簡單,那麼把redux學會了,再去看mobx吧,你們認為呢?

宣告一點,這裡不是講解什麼是redux,這裡是講解怎麼在專案裡使用redux,詳細的講解文章可以去看網上教程,入門的話推薦阮大神的,redux入門系列點此前往

接下來,我就當你已經瞭解redux是幹什麼的了,只不過怎麼引入到專案裡不是很清楚,因為網上的文章要不就是太深,深到還沒讀完第一段你就放棄了;要不就是太淺,永遠都是計數器例項,看完我也不清楚怎麼在專案裡進行狀態管理。如果是上面那樣,恭喜你,總算遇到我了,下面絕對會讓你學會如何使用redux。

  • 安裝redux相關依賴

    yarn add redux react-redux redux-logger redux-thunk 總所周知(我就當你知道),redux依賴各種中介軟體,我們這裡為了簡易起見只使用redux-logger和redux-thunk,一個是輸出redux日誌,另一個是讓我們方便進行非同步操作。更具體的,去看官方文件和各種教程。

  • 前端增加redux目錄

    // 目錄結構,ok就是redux三劍客
    - redux
      - actions
      - reducers
      - store
      - middleware
    複製程式碼
  • 完成各種基礎配置

    接下來,你只需按照下面幾步,就可以在專案裡引入redux。

    • 配置store
    // configureStore.js
      import { createStore, applyMiddleware } from 'redux';
      import thunkMiddleware from 'redux-thunk';
      import { logger } from '../middleware';
      import rootReducer from '../reducers';
      
      const nextReducer = require('../reducers');
      
      function configure(initialState) {
        const create = window.devToolsExtension
          ? window.devToolsExtension()(createStore)
          : createStore;
      
        const createStoreWithMiddleware = applyMiddleware(
          thunkMiddleware,
          logger,
        )(create);
      
        const store = createStoreWithMiddleware(rootReducer, initialState);
      
        if (module.hot) {
          module.hot.accept('../reducers', () => {
            store.replaceReducer(nextReducer);
          });
        }
        return store;
      }
      export default configure;
    複製程式碼

    配置好上面檔案之後,肯定是一堆報錯,沒關係,我們一點點來。上面用到了logger中介軟體以及reducer,接下來就配置這兩個。

    • 配置reducer和middleware
      /middleware/index.js
      // 你沒有看錯,中介軟體就是人家已經造好的輪子,我們直接拿來用就行。
      import logger from 'redux-logger';
      export {
        logger,
      };
      
      /reducers/index.js
      import { combineReducers } from 'redux';
      import user from './user/index'; // 一般會配置多個reducer然後使用combineReducers將他們合併起來
      const rootReducer = combineReducers({
        user
      });
      
      export default rootReducer;
      複製程式碼

配置好logger的效果如下:

新手搭建簡潔的Express-React-Redux腳手架

  • 配置action

    說是配置action,其實action並不是配置得來的,而是我們將整個應用的狀態都交給了redux來進行管理,所以我們如果想進行資料的更新,就必須通過redux來進行,redux為我們提供的更新資料的方式就是dispatch action。下面就以獲取使用者列表資料為例,真真切切的使用redux。

    /actions/User.js
    import {
      FETCH_ALL_USER_LIST,
      FETCH_ALL_USER_LIST_SUCCESS,
      FETCH_ALL_USER_LIST_FAIL
    } from '../../constants/ActionTypes';
    import axios from 'axios';
    
    // 獲取使用者列表
    const getAllUserList = () => ({
      type: FETCH_ALL_USER_LIST,
    });
    const getAllUserListSuccess = (payload) => ({
      type: FETCH_ALL_USER_LIST_SUCCESS,
      payload
    });
    const getAllUserListFail = () => ({
      type: FETCH_ALL_USER_LIST_FAIL
    });
    export const fetchAllUserList = () => (dispatch) => {
      dispatch(getAllUserList());
      // 獲取使用者列表
      // 因為設定了proxy的緣故,所以不需要寫http://localhost:3003
      // 會自動定向到後端伺服器
      return axios.get('/api/user/list')
              .then(res => {
                return dispatch(getAllUserListSuccess(res.data));
              })
              .catch((error) => {
                console.log(error);
                dispatch(getAllUserListFail());
              }); 
    };
    
    複製程式碼

    上面就是一個完整的觸發action獲取資料的過程,一般包括請求資料,請求資料成功和請求資料失敗三個階段。

  • 將元件分為容器元件containers和展示元件components

    同理,這兩者區別還是去看大牛們的講解,他們講的很細緻,我這裡只講一點,既然引入了redux,那麼資料肯定不是在頁面裡componentDidMount()通過ajax獲取到的,上面提到了,是通過action觸發的,因此需要狀態元件將頁面所需的state和資料操作API傳給展示元件。

    // /containers/UserList.js容器元件
      import { connect } from 'react-redux';
      import UserList from '../components/UserList';
      import {
        fetchAllUserList
      } from '../redux/actions/User';
      
      const mapStateToProps = state => ({
        list: state.user.userList.list,
      });
      
      const mapDispatchToProps = dispatch => ({
        fetchAllUserList() {
          dispatch(fetchAllUserList());
        }
      });
      
      export default connect(
        mapStateToProps,
        mapDispatchToProps
      )(UserList);
      
    // /components/UserList.js 展示元件
      import React, { Component } from 'react';
      import { Table } from 'antd';
      
      class UserList extends Component {
        constructor(props) {
          super(props);
          this.state = { userList: this.props.list };
        }
      
        componentDidMount() {
          this.props.fetchAllUserList(); //獲取資料渲染頁面
        }
      
       ...
      }
      export default UserList;
    複製程式碼

寫在最後

總算是寫完個人的第一篇純技術性文章了,雖然沒什麼技術性,但是我覺得還是很有意義的,至少我覺得我寫的東西應該會讓新手或者在校生理解吧,希望大家多多指正!最後的最後,放上程式碼,小夥伴如果喜歡不要吝惜你們的star! 接下來可能會用這個腳手架做一些系統之類的練練手,主要目的也是增強能力。

GitHub:https://github.com/luffyZh/express-react-scaffold.git 快速通道

相關文章