react+webpack4搭建前端项目(二)react全家桶的使用

前言

react+webpack4搭建前端项目分为三个章节。链接如下。目的是实现从零搭建一个react后台管理系统
1、react+webpack4搭建前端项目(一)基础项目搭建
2、react+webpack4搭建前端项目(二)react全家桶的使用
3、react+webpack4搭建前端项目(三)打包优化
webpack配置的讲解
4、react+webpack4.x搭建前端项目(四)配置抽取和区分环境
5、react+webpack4.x搭建前端项目(五)多页面配置
6、react+webpack4.x多模块打包配置
这是第二章,react全家桶的使用

废话不多说啦。接着上一篇react+webpack4搭建前端项目(一)我们正式进入react全家桶技术篇章,如果对于项目不清楚或者在下面有什么疑惑,建议先看一下上一篇文章熟悉一下项目由来

使用react-router-dom管理路由,这里使用react-router4.x以后的版本,请注意,和3.x的使用还是有很大的区别

npm install -S react-router-dom

我们为什么使用react-router-dom呢?

先简单说下各自的功能:
react-router: 实现了路由的核心功能
react-router-dom: 基于react-router,加入了在浏览器运行环境下的一些功能,例如:Link组件,会渲染一个a标签,BrowserRouterHashRouter组件。显而易见react-router-dom功能更丰富,所以选择react-router-dom代替react-router

下面接着上一篇文章的项目,我们对项目进行改造:
新建blog,resume,user页面,如下

QQ截图20190813113835.png

分别编写blog,resume,user,home(上篇文章已完成)组件

此处删除home/index.less,修改home/index.js内容:

import React from 'react'
export default class HomeIndex extends React.Component {
    render(){
        return (
            <div>
                <p>HomeIndex</p>
            </div>
        )
    }
}

blog/index.js

import React from 'react'

export default class BlogIndex extends React.Component {
    render(){
        return (
            <div>
                <p>BlogIndex</p>
            </div>
        )
    }
}

resume/index.js

import React from 'react'

export default class ResumeIndex extends React.Component {
    render(){
        return (
            <div>
                <p>ResumeIndex</p>
            </div>
        )
    }
}

user/index.js

import React from 'react'

export default class UserIndex extends React.Component {
    render(){
        return (
            <div>
                <p>UserIndex</p>
            </div>
        )
    }
}

以上这些组件是最简单的组件

新建src/router.js

import React from "react"
import { Route,BrowserRouter,Link,Switch } from "react-router-dom"
import HomeIndex from "./home"
import BlogIndex from "./blog"
import ResumeIndex from "./resume"
import UserIndex from "./user"
class AppRouter extends React.Component {
    render(){
        return (
            <BrowserRouter>
                <ul>
                    <li><Link to="/home">home</Link></li>
                    <li><Link to="/blog">blog</Link></li>
                    <li><Link to="/resume">resume</Link></li>
                    <li><Link to="/user">user</Link></li>
                </ul>
                <div>
                    {/* Switch只显示一个组件。加exact表示精确匹配/。如果不加exact,/xxx也会匹配/。  */}
                    <Switch>
                        {/* exact */}
                        <Route path="/home" component={HomeIndex} />
                        <Route exact path="/blog" component={BlogIndex}/>
                        <Route exact path="/resume" component={ResumeIndex}/>
                        <Route exact path="/user" component={UserIndex}/>
                    </Switch>
                </div>
            </BrowserRouter>
        )
    }
}
export default AppRouter;

这里使用react-router-dom的history模式,简单写了一个导航,点击每个导航,跳转到相应的页面。运行npm run dev,打开http:localhost:8081,效果如图

1565673235831.gif

到此react-router-dom基本使用已经完成。

因为我们这里是配合项目使用,详细的react-router-dom不过多讲解,如果想学习更多基本用法,请查看官方文档。在后边随着项目的复杂,后边我们还会说一下嵌套路由,页面之间的跳转等等使用方法。

最后我们需要清除页面,标签的默认样式。代码可以去网上找一份,网上随处可见。
然后在项目根目录新建static/css/reset.min.css,在index.html模板引入

<link rel="stylesheet" href="/static/css/reset.min.css">

重新运行,你会发现找不到/static/css/reset.min.css。因为这里只是在index.html中引入了文件,但是并没有在webpack中处理静态文件,我们需要把static目录的内容通过webpack插架
编译构建到包里;此处需要用到copy-webpack-plugin

npm install -D copy-webpack-plugin

在build/webpack.base.config.js`中添加公用的插件plugins,

plugins:[
    new CopyWebpackPlugin([
        {
            from: utils.resolve('../static'),  // 从哪个目录copy
            to: "static", // copy到那个目录
            ignore: ['.*']
        }
    ])
]

重新运行,你会发现默认样式清除了!

引入antd

使用教程
这里已经很详细了,本项目使用的是按需加载方式,可以减小打包体积。

使用antd+react-router-dom封装导航组件

我们先看一下写出来项目目录

QQ截图20190815182309.png

由于代码量越来越大,这里不再给出详细代码,如果需要请点击 源码 下载 release 1.0.0 版本

下面讲一下这些目录的用途

1、assets是资源目录,放图片,css,js,字体等等
2、blog是博客模块的页面
pages目录是blog模块下的页面组件,在这里新建了两个页面add.js添加博客,list.js博客列表
index.js是管理bolg模块的子路由的组件,代码如下

import React from 'react'
import BlogListPage from "./pages/list"
import AddBlogPage from "./pages/add"

import { Route } from 'react-router-dom'

export default class BlogIndex extends React.Component {
    render(){
        return (
            <div>
                <p>BlogIndex</p>
                <Route path="/blog/list" component={BlogListPage} />
                <Route path="/blog/add" component={AddBlogPage} />
            </div>
        )
    }
}

这里使用<Route path="/blog/add" component={AddBlogPage} />添加二级子路由,但是要注意,第一级路由是不要加exact这个属性,这个属性表示精确匹配。如果父级路由加了这一属性,子路由就会匹配不到。

举个栗子:
<Route exact={true} path="/blog" component={BlogIndex}>如果这么写,当你输入/blog/add路径,会匹配不到任何路由。只有当你输入/blog路径时才会匹配。可以利用模糊匹配路径方式实现多级路由的管理。

3、home目录暂时没用
4、layout整个项目的公用布局组件 NavigationBar.js是上边的导航,SlideMenu.js是侧边菜单
5、resume是立即模块,没有实现二级路由
6、user用户管理模块,和blog的目录结构一样,实现二级路由
7、app.less是项目公用的样式文件,这里写了导航和侧边栏的样式
8、router.config.js是项目的路由和左侧菜单
9、router.js是项目的路由和整体的布局

实现的效果图:

QQ截图20190816085339.png

注意事项:
1、你会发现本项目html标签使用class属性来代替className属性。react本身的html标签是不支持class属性,只识别className属性编写类名。这里我们需要安装一个插件

npm install -D babel-plugin-react-html-attrs

然后再.babelrc文件的pulgins数组添加"react-html-attrs"即可

2、我们此处用的class组件来编写react组件,如果有需要也可以使用function组件来编写react组件。当我们使用class的时候,再class添加属性时,也就是下边的写法,项目在编译运行时报错

export default class BlogIndex extends React.Component {
    state = {
        test:"name"
    }
    click = ()=>{   
    }
}

报错如下:

QQ截图20190813152902.png

解决方法是:

npm install -D @babel/plugin-proposal-class-properties

然后再.babelrc文件的pulgins数组添加"@babel/plugin-proposal-class-properties"

使用mobx管理数据

在react中使用mobx,不仅需要使用mobx,还需要结合react的插件,那就是mobx-react
第一先安装这两个必须包

npm install -S mobx mobx-react

mobx的基本用法请看这里mobx
mobx-reactmobxreact的结合,提供Provider组件统一管理mobx数据;injectreact组件注入某个mobx实例;observer实现mobx实现react组件和mobx数据的双向绑定(和react-reduxconnect差不多)等等

创建mobx实例并在react入口文件引入

我们这里在user目录下先建store/UserList.js,创建管理用户列表页面的mobx实例

import { observable,action } from "mobx"

class UserListStore {

    @observable name;

    constructor(){
        this.name = "my name is user list;";
    }
    
}

export default new UserListStore();

下面我们在src下新建store/index.js目录,统一管理项目的mobx实例:

import UserListStore from "./../user/store/UserList"

const store = {
    UserListStore
}

export default store;

在修改src/index.js,导入文件

import { Provider } from "mobx-react"
import store from "./store"

使用Providerstore

<Provider {...store}>
    <AppRouter />
</Provider>

重新运行项目,不出所料报错。为什么呢?熟悉mobx的同学应该都知道,mobx的特色是使用装饰器来来修饰mobx实例中属性和方法,以及react-mobx也是通过装饰器来使用。

装饰器可以通过@关键字加上相关的方法。来达到为属性,方法,class添加其它功能的作用。装饰器作用的作用其实用很大,比如javaspring运用最广泛,想学习的同学可以去查相关资料。

我们需要在项目配置对装饰器的支持

安装npm install -D @babel/plugin-proposal-decorators,在.babelrc文件的pulgins数组添加

["@babel/plugin-proposal-decorators",{"legacy": true}], // 配置对装饰器的支持

"@babel/plugin-proposal-class-properties"修改成

 ["@babel/plugin-proposal-class-properties",{"loose":true}] // 支持类属性的插件

注意这项配置一定要在@babel/plugin-proposal-decorators之后,不然还是一样会报错。

react组件中使用mobx

通过inject把需要的mobx实例注入到react组件

修改src/user/pages/list.js

import React from 'react'
import {withRouter} from 'react-router-dom'
import {Button} from "antd"
import { inject, observer } from "mobx-react"

@inject("UserListStore")
class UserListPage extends React.Component {

    push = ()=>{
        this.props.history.push("/user/add?name=231");
    }
    render(){
        const {UserListStore} = this.props;
        return (
            <div>
                <p>UserListPage</p>
                <p>组件:{UserListStore.name}</p>
                <Button onClick={this.push}>添加用户</Button>
            </div>
        )
    }
}

export default withRouter(UserListPage);

发现mobx中的UserListStore实例注入到this.props

QQ截图20190816104214.png

使用observer实现组件和数据的双向绑定
在class组件使用@observer修改组件,添加setName方法

setName = ()=>{
    const {UserListStore} = this.props;
    UserListStore.setName("ha ha ha")
}

添加一个修改名称的按钮

<Button onClick={this.setName}>修改名字</Button>

点击按钮,你会发现{UserListStore.name}变成了ha ha ha。这说明组件个数据双向绑定已经成功

测试打包,一切正常!

引入mobx代码请下载 源码 releases 1.0.1

搭建mock服务(node)

为了更好的模拟前后端分离场景,新搭建一个服务

项目根目录创建mock目录,这里使用koa搭建一个node服务。koa搭建node服务比较简单,这里就不说怎么去搭建node服务了。如果有需要可以看我之前写的react项目整合express+mock实现模拟接口数据,这里只是express框架换成了koa框架。然后cd mock,执行npm run dev,服务正常启动。端口好是8082。

重点说一下mock,这里我们使用mockjs模拟数据,需要在mock目录安装mockjs

npm install -S mockjs

目录结构:


QQ截图20190816144713.png

我们新建一个user模块。user/data.js模拟数据库的记录。这里模拟产生10条用户记录,只要生成了,就不会发生变化
user/data.js

const Mock = require("mockjs")

const data = Mock.mock({
    'list|1-10': [{
        'id|+1': 1,
        'user_id|100-200': 1,
        'status|1': true, // 状态
        'user_name': '@cname', // 名称
        'avatar': "@image('150x150', '#4A7BF7', 'img', 'png', 'Tiger')",  // 头像
        'create_time': '@datetime("yyyy-MM-dd HH:mm:ss")', // 创建日期
    }]
});

// 这里模拟数据库里的用户记录

module.exports = data;

user/index.js内容如下


const Router = require("koa-router")
const router = new Router();
const Mock = require("mockjs")
const userlist = require("./data").list;


router.get("/api/user/list",async (ctx)=>{
    ctx.body = {code:0,message:"success",userlist}
});

router.post("/api/user/add",async (ctx)=>{
    let data = ctx.request.body;
    data.id = userlist[userlist.length-1].id + 1;
    const mock = Mock.mock({
        'user_id|100-200': 1,
        'status|1': false, // 状态
        'avatar': "@image('150x150', '#4A7BF7', 'img', 'png', 'Tiger')",  // 头像
        'create_time': '@datetime("yyyy-MM-dd HH:mm:ss")', // 创建日期
    })
    data = Object.assign(data,mock);
    userlist.push(data)
    ctx.body = {code:0,message:"success"}
});

module.exports = router;

这里模拟了请求用户列表(没有分页),添加用户接口。

postman请求接口数据,如下图

QQ截图20190816143728.png
QQ截图20190816150900.png

react项目请求mock数据

在前端项目安装

npm install -S axios

因为涉及到跨域,我们在开发环境需要在webpack.dev.config.js的devServer属性下添加代理

proxy: {
    // 接口请求代理
    "/api":{
        secure: false,
        target:"http://127.0.0.1:8082"
    }
},

然后需要修改在src/user/store/UserList.js,在这里获取请求用户列表接口,并赋值给实例的userList属性

import { observable,action } from "mobx"
import axios from "axios"

class UserListStore {

    @observable userList;

    constructor(){
        this.userList = [];
    }

    @action
    async getUserList(){
        const config = {method:"get",url:"/api/user/list"};
        const result = await axios(config);
        if(result.data.code === 0){
            const userList = result.data.data;
            this.userList = userList;
        }
    }
}

export default new UserListStore();

src/user/pages/list.js页面调用,和渲染用户列表

componentDidMount(){
    const {UserListStore} = this.props;
    UserListStore.getUserList();
}
render(){
    const {UserListStore} = this.props;
    return (
        <div>
            {
                UserListStore.userList.map((item,index)=><p key={index}>{item.user_name}</p>)
            }
        </div>
    )
}

如下图:


QQ截图20190816153402.png

mock+数据请求 源码 releases 1.0.2

到这里,react全家桶react+react-router+mobx+axios的使用,mock模拟后端数据已经完成。后边的事情就是业务逻辑页面的编辑(这里省略),打包的优化(下一篇讲述)。当然现在项目还有很多不完善,(模块的组织,目录结构的划分,请求实例的封装,试图和业务逻辑的抽离,样式的管理等等,这些和业务逻辑关联性比较强,这里不过多说明)

我们执行一下npm run build,打包成功。但是存在一个问题,打包出来的js高达1M多,太可怕了。现在这个项目还很简单,随着业务逻辑复杂和页面的增多,打包的js会越来越大。下一篇文章我们会一步步优化打包,从路由的懒加载,样式的抽离,公用第三方包的抽离得等方面优化

下一篇: react+webpack打包优化react+webpack4搭建前端项目(三)打包优化