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

寫在前面

我發現網上的不少新手教程都並非徹底針對新手的,新手在使用起來也是丈二的和尚摸不到頭腦,最近想用node作後端,react作前端搭建一個小系統,想把過程記錄下來,從頭開始搭建腳手架,絕對適合新手(本人也就是個前端小白)。既然是針對新手,那麼就除去那些複雜的內容,什麼服務端渲染之類的所有不考慮,使用的也絕對都是主流:Node+Express作後端,數據庫採用MongoDB,前端用React+React-Router,而後使用Redux作狀態管理,再搭配一個UI框架antd。其餘的想用直接在這基礎之上添加就能夠了,新手參照下面步驟徹底能夠本身搭建出來項目骨架以及經過文章掌握一些知識點。 下面是腳手架截圖:html

首頁: 前端


用戶列表頁:

我還喪心病狂的爲大家配置了404頁(不由自主給本身點贊):

雖然只有三個頁面,可是麻雀雖小五臟俱全哦:包括先後端路由的配置、數據庫的連接,數據的獲取、react和redux的使用等等,老鐵們,說它是react全家桶不過度吧。

項目地址請點此處,喜歡的小夥伴能夠star哦!vue

第一步 create-react-app

Facebook官方出的腳手架,基本配置徹底夠用,初始化建立項目就很少BB了,你能夠本身去看官網。這裏只講一句,由於要配置antd按需加載,能夠按照antd官網一步步安裝,不過我在按照官網安裝的時候遇到了一些問題,最後仍是按照本身的安裝來吧。
首先,安裝依賴項:node

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的
複製代碼

其次,進行配置:react

  • 修改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了。ios

第二步 配置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。git

  • 在根目錄下新建文件夾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 -ggithub

  • 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');
      });
    複製代碼

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

第四步,先後端測試

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

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

  • 前端增長頁面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,沒錯,後續可能會用這個腳手架搭建一個博客,由於我發現做爲一個新手,有些教程確實不是很友好

如上圖,我已經安裝好了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);
            }); 
    複製代碼

    最後咱們將數據渲染到頁面上,效果以下:

以上,數據庫部分引入完成,詳細代碼能夠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的效果以下:

  • 配置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 快速通道

相關文章
相關標籤/搜索