新手搭建简洁的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 快速通道

相关文章
相关标签/搜索