来自一位正在react道路上爬行的小菜鸡如何制作一个简单的前端框架的分享

前言篇--笔者心得

刚从大学校园里跑出来,大学里做过的各种东西看似很多感觉很牛批,完了出来接触才发现自己是个小菜鸡...近段时间一直在学习项目需要的react方面的知识,入过的坑简直是不能太多(欲哭无泪)。在学习react的道路上一直在摸索,除了阅读了阮一峰老师写的关于react的知识点之外,还学习搭建了一个简单的框架,作为巩固近端时间学习的一个简单的方法。(学习搭建框架的这篇教程并非属于笔者所创,仅仅是用于笔者实践过后的一个分享,不喜勿喷=_= 更过更详细的介绍请移步猛戳 这里啦

项目须知

根据我们平常所见的项目,一般都会有一个较为完整的路径框架(大体框架的目录),便于文件的存放以及查找和管理,如下图所示(教程中的图)

此外,在需要的跟目录下新建文件是只需要右键安装好的git Bush here进行文件夹的新建即可

好啦,现在让我们愉快的开始前端框架的搭建吧哈哈哈~~

1.创建文件夹并进入(在此之前你需要保证已有git账号并且已经配置好需要的运行环境)

$ mkdir my-react-family && cd my-react-family

2. npm init

$ npm init

(输完整条指令时需要一直按enter直到下图出现)

webpack

1.安装webpack

$ npm install webpack@3 --save-dev

(可能有很多初学者跟笔者一样不大理解什么时候该用--save-dev,什么时候该用--save。笔者经过学习教程中了解到--save-dev是开发的时候依赖的东西,而--save是发布之后还一直依赖的东西,够清楚了吧~~)

2.根据webpack文档编写最基础的配置文件

新建webpack开发的配置文件 touch webpack.dev.config.js

接着在编译器的webpack dev.config.js文件中编写一下代码

const path = require('path');
moudle.export = {

    /*这里是入口*/
    entry: path.join(__dirname, 'src/index.js'),

    /*这里是输出文件的出口,输出文件到dist文件夹,输出文件名为bundle.js*/
    output: {
        path: path.join(__dirname,'./dist'),
        filename: 'main.js'
    }
};
复制代码

3.接下来你要学会的是如何编译webpack入口文件(讲真指令就这么几条,请用心记)~_~

$ mkdir src && touch ./src/index.js

在编译器中的src/index.js文件下输入以下代码

document.getElementById('app').innerHTML = "Nice to see you~~";
复制代码

现在我们可以执行 webpack --config -g webpack.dev.config.js

(Tips:这里的webpack需要全局安装,如果没有全局安装会报错 全局安装webpack的指令为: npm install --save-dev -g webpack)

现在可以看到在文件夹下面生成了distmain.js文件(但是教程生成的是bundle.js文件,我也不知道为啥不过好像不影响,所以没有深究)

4.现在可以来测试一下下啦

dist文件夹下新建一个index.html 紧接着在index.html下输入一下代码:

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="app"></div>
<script type="text/javascript" src="./main.js" charset="utf-8"></script>
</body>
</html>
复制代码

接下来就可以直接看到编译的成果啦哈哈哈,亲们请看图:

babel

(关于babel,哇~~说法有很多,但是简单的说它就是一个转码器(通俗易懂吧ahhh),可以将ES6的代码转换成ES5的代码,从而在现有的环境下执行。详细请猛戳这里

好啦接下来要执行以下指令便于代码的编译以及转换:

npm install --save-dev babel-core babel-loader babel-preset-es2015 babel-preset-react babel-preset-stage-0
复制代码

(Tips:

  • babel-core调用Babel的API进行转码
  • babel-loader
  • babel-preset-es2015 用于解析 ES6
  • babel-preset-react 用于解析 JSX
  • babel-preset-stage-0 用于解析 ES7 提案

接下来请执行以下指令:

touch .babelrc

(亲们注意一下呀,这条指令是在根目录,也就是在my-react-family下新建)

接着在.babelrc下输入以下代码:

 {
   "presets": [
     "es2015",
     "react",
     "stage-0"
   ],
   "plugins": []
 }
复制代码

好啦,接下来就是修改webpack.dev.config.js,添加babel-loader

/*src文件夹下面的以.js结尾的文件,要使用babel解析*/
 /*cacheDirectory是用来缓存编译结果,下次编译加速*/
 module: {
     rules: [{
         test: /\.js$/,
         use: ['babel-loader?cacheDirectory=true'],
         include: path.join(__dirname, 'src')
     }]
 }
复制代码

现在我们来简单的测试一下是否能转换义ES5

我们先要修改index.js

 /*使用es6的箭头函数*/
 var func = str => {
     document.getElementById('app').innerHTML = str;
 };
 func('我现在在使用Babel!');
复制代码

然后在编译器里面执行打包命令:webpack --config webpack.dev.config.js,再运行就可以看到以下的效果图啦~~~!

总结一下刚才我们做了啥??

我们刚才通过安装babel的依赖,在通过配置webpack,更改入口文件index.js内部的代码,将通过webpack --config webpack.dev.config.js指令打包,从而生成main.js。这时main.js文件后面的代码会跟index.js的意思一致,就只是es6es5的区别呀...神奇吧ahh

react

执行以下指令

$ npm install --save react react-dom

修改src/index.js,使用react

import React from 'react';
import ReactDom from 'react-dom';

ReactDom.render(
    <div>Hello React!</div>, document.getElementById('app'));
复制代码

接着执行打包命令webpack --config webpack.dev.config.js

打开index.html看看效果吧~~

紧接着就是制作我们的组件啦。我们把Hello React放到我们的组件里面,实现react的组件化~~

cd src
mkdir component
cd component
mkdir Hello
cd Hello
touch Hello.js
复制代码

接着在Hello.js当中键入以下代码:

import React, {Component} from 'react';

export default class Hello extends Component {
    render() {
        return (
            <div>
                Hello,React!
            </div>
        )
    }
}
复制代码

在这之前,要是小伙伴还不熟悉react组件化的写法,或者是不熟悉ES6的语法,笔者有个小小的建议,可以去学习一下阮一峰老师写的 ECMAScript6入门这能帮助比较全面的了解react方面的语法。

接着修改src/index.js,引用Hello组件

import React from 'react';
import ReactDom from 'react-dom';
import Hello from './component/Hello/Hello';

ReactDom.render(
    <Hello/>, document.getElementById('app'));
复制代码

最后在根目录里面执行打包命令webpack --config webpack.dev.config.js

好啦,此刻有没有很激动呀,尝试着打开index.html看看效果吧(react道路不好走呀,得坚持才能成功)

命令优化

有的小伙伴是不是觉得每次执行打包命令(webpack --config webpack.dev.config.js)的时候很麻烦??哈哈哈没事,我们现在可以此条命令进行优化,节省一定的时间哟~具体实现的操作如下:

修改package.json里面的script,增加dev-build

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev-build": "webpack --config webpack.dev.config.js"
  }
复制代码

现在,我们每次执行打包命令的时候,不必要每次都执行那么鬼长鬼长的一串命令啦,很nice有木有~~

react-router

npm install react-router-dom --save

新建一个router文件夹和组件

cd src
mkdir router && touch router/router.js
复制代码

接着按照react-router文档编辑一个比较基础的router.js,其中这里边会包含有两个页面,分别是HomePage1

src/router/router.js

import React from 'react';

import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';

import Home from '../pages/Home/Home';
import Page1 from '../pages/Page1/Page1';


const getRouter = () => (
    <Router>
        <div>
            <ul>
                <li><Link to="/">首页</Link></li>
                <li><Link to="/page1">Page1</Link></li>
            </ul>
            <Switch>
                <Route exact path="/" component={Home}/>
                <Route path="/page1" component={Page1}/>
            </Switch>
        </div>
    </Router>
);

export default getRouter;
复制代码

新建一个页面文件夹

cd src
mkdir pages
复制代码

新建两个页面用于存放组件

cd src/pages
mkdir Home && touch Home/Home.js
mkdir Page1 && touch Page1/Page1.js
复制代码

分别在页面中填充内容

src/pages/Home/Home.js

import React, {Component} from 'react';

export default class Home extends Component {
    render() {
        return (
            <div>
                this is home~
            </div>
        )
    }
}
复制代码

src/pages/Page1/Page1.js

import React, {Component} from 'react';

export default class Page1 extends Component {
    render() {
        return (
            <div>
                this is Page1~
            </div>
        )
    }
}
复制代码

现在基本的路由和页面都已经做好了,接下来我们要在入口文件src/index.js引用Router

修改src/index.js

import React from 'react';
import ReactDom from 'react-dom';

import getRouter from './router/router';

ReactDom.render(
    getRouter(), document.getElementById('app'));
复制代码

由于在命令优化中我们已经把webpack.dev.config.js打包命令进行优化,现在我们只需执行npm run dev-build即可。(注意此时要查看相应的页面不能只在index.html中点击浏览器了,我们需要在

这个webpack-dev-server来启动web服务器)

参考地址(摘自原教程):

1.参考资料一

2.参考资料二

webpack-dev-server

webpack-dev-server,简单的来说,是一个小型的静态文件服务器。使用它可以为webapck打包生成的资源文件提供web服务。

一般来说,,这里安装webpack-dev-config的时候,需要全局安装,全局安装的代码:`npm install webpack-dev-server@2 -g`
复制代码

webpack.dev.config.js

devServer: {
        contentBase: path.join(__dirname, './dist')
    }
复制代码

现在执行

webpack-dev-server --config webpack.dev.config.js

这时候就可以使用httpa://localhost:8080,就可以看到我们所建好的页面啦

Tips:webpack-dev-server编译之后的文件,都会存储在内存之中,但是这是我们是看不到的。你可以删除之前的dist/main.js文件,也可以正常的打开网站~~

每次执行webpack-dev-server --config webpack.dev.config.js,都要打很长的命令,这时候我们可以像之前的优化打包命令一样,修改package.json,增加script->start

 "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev-build": "webpack --config webpack.dev.config.js",
    "start": "webpack-dev-server --config webpack.dev.config.js"
  }
复制代码

接下来我们执行npm start就ok啦

***题外话:在原教程当中,提到了其他的配置项,在此我把他大概的在这儿再复述一遍哈(可能会略显多余,可不看)

  • color (CLI only) console中打印色彩日志
  • historyApiFallback任意的404响应都被替代为index.html。有什么用呢?你现在运行npm start,然后打开浏览器,访问http://localhost:8080,然后点击Page1到链接http://localhost:8080/page1,然后刷新页面试试。是不是发现刷新后404了。为什么?dist文件夹里面并没有page1.html,当然会404了,所以我们需要配置historyApiFallback,让所有的404定位到index.html
  • host 指定一个host,默认是localhost。如果你希望服务器外部可以访问,指定如下:host: "0.0.0.0"。比如你用手机通过IP访问。
  • hot启用Webpack的模块热替换特性。
  • port 配置要监听的端口。默认就是我们现在使用的8080端口。
  • proxy 代理。比如在localhost:3000上有后端服务的话,你可以这样启用代理:
  proxy: {
      "/api": "http://localhost:3000"
    }
复制代码
  • progress (CLI only)将编译输出到控制台。

webpack.dev.config.js

    devServer: {
        port: 8080,
        contentBase: path.join(__dirname, './dist'),
        historyApiFallback: true,
        host: '0.0.0.0'
    }
复制代码

现在CLI ONLY的需要在命令行中配置

package.json

"start": "webpack-dev-server --config webpack.dev.config.js --color --progress"
复制代码

现在我们可以执行npm start看看效果啦~~可以看到在http://localhost:8080/page1是没有啥大问题的了。

模块热替换 (Hot Modle Replacement)

这儿有一个神奇的现象,小伙伴们不妨尝试一下再组件中修改一下内容,浏览器会自动刷新哟啊哈哈哈,是不是超级神奇超级nice,感觉离成功又进了一大步。

接下来我们来研究一下webpack模块热替换教程(摘自原教程)

package.json增加--hot

"start": "webpack-dev-server --config webpack.dev.config.js --color --progress --hot"

index.js下添加module.hot.accept(),如下所示每当模块更新的时候,会通知index.js.

修改src/index.js下代码:

import React from 'react';
import ReactDom from 'react-dom';

import getRouter from './router/router';

if (module.hot) {
    module.hot.accept();
}

ReactDom.render(
    getRouter(), document.getElementById('app'));
复制代码

现在小伙伴们可以执行npm start看看效果啦,打开浏览器,再随意修改一下组件内部的内容,可以看到浏览器的内容也会随之更新哟啊哈哈

模块热替换其实蛮简单,也就是修改几行代码的事儿(当然这是基于教程讲到才会的哈)

接下来我们修改一下Home.js

src/pages/Home/Home.js

import React, {Component} from 'react';

export default class Home extends Component {
    constructor(props) {
        super(props);
        this.state = {
            count: 0
        }
    }

    _handleClick() {
        this.setState({
            count: ++this.state.count
        });
    }

    render() {
        return (
            <div>
                this is home~<br/>
                当前计数:{this.state.count}<br/>
                <button onClick={() => this._handleClick()}>自增</button>
            </div>
        )
    }
}
复制代码

此刻可以刷新一下浏览器,可以看到webpack更新之后的页面,count的值变为了0。

这有一点需要注意一下的就是,在react模块更新的同时,为了能保留state等页面中的其他状态,我们需要引入react-hot-loader

或许到这儿会有一些小伙伴不大理解webpack-dev-serverreact-hot-loader的区别。

其实区别在于webpack-dev-server自己的--hot模式只能即时刷新页面,但状态保存不住。因为React有一些自己语法(JSX)是HotModuleReplacementPlugin搞不定的,而react-hot-loader--hot基础上做了额外的处理,来保证状态可以存下来。

好啦,接下来我们需要加入react-hot-loader v3

执行以下程序安装依赖

npm install react-hot-loader@next --save-dev

接下来我们需要做一下修改

1..babelrc

{
  "presets": [
    "es2015",
    "react",
    "stage-0"
  ],
  "plugins": [
    "react-hot-loader/babel"
  ]
}
复制代码

2.webpack.dev.config.js入口增加react-hot-loader/patch

webpack.dev.config.js

 entry: [
        'react-hot-loader/patch',
        path.join(__dirname, 'src/index.js')
    ]
复制代码

3.修改src/index.js

import React from 'react';
import ReactDom from 'react-dom';
import {AppContainer} from 'react-hot-loader';

import getRouter from './router/router';

/*初始化*/
renderWithHotReload(getRouter());

/*热更新*/
if (module.hot) {
    module.hot.accept('./router/router', () => {
        const getRouter = require('./router/router').default;
        renderWithHotReload(getRouter());
    });
}

function renderWithHotReload(RootElement) {
    ReactDom.render(
        <AppContainer>
            {RootElement}
        </AppContainer>,
        document.getElementById('app')
    )
}
复制代码

好啦,现在执行npm start ,可以发现state就没有再更新啦~~又完成了一小步,甚是开心啊哈哈


redux

接下来我们就要开始集成redux咯~~

其实吧redux也没有我们想的那么难,只要把流程理清就差不多了,在这儿我还是建议大家伙儿去仔细的月度一下阮一峰老师的Redux入门教程(一):基本用法

现在我们先从比较简单的做起。先做一个计时器,包括自增、自减和重置

首先我们需要安装redux npm install --save redux

然后我们需要构建一个简单的目录结构

cd src && mkdir redux
cd redux
mkdir actions
mkdir reducers
touch reducers.js
touch store.js
touch actions/counter.js
touch reducers/counter.js
复制代码

接下来我们先来写action创建函数,通过创建antion函数,可以创建action

src/redux/actions/counter.js

/*action*/

export const INCREMENT = "counter/INCREMENT";
export const DECREMENT = "counter/DECREMENT";
export const RESET = "counter/RESET";

export function increment() {
    return {type: INCREMENT}
}

export function decrement() {
    return {type: DECREMENT}
}

export function reset() {
    return {type: RESET}
}
复制代码

再来写一个reducer,reducer是一个纯函数,用于接收actionstate,从而返回一个新的state

src/redux/reducers/counter.js

import {INCREMENT, DECREMENT, RESET} from '../actions/counter';

/*
* 初始化state
 */

const initState = {
    count: 0
};
/*
* reducer
 */
export default function reducer(state = initState, action) {
    switch (action.type) {
        case INCREMENT:
            return {
                count: state.count + 1
            };
        case DECREMENT:
            return {
                count: state.count - 1
            };
        case RESET:
            return {count: 0};
        default:
            return state
    }
}
复制代码

现在我们把项目中的reducers整合到一起

src/redux/reducers.js

import counter from './reducers/counter';

export default function combineReducers(state = {}, action) {
    return {
        counter: counter(state.counter, action)
    }
}
复制代码

到这儿咋们实践了很多,我看着原本的教程到这儿的时候其实是有点懵逼的,感觉一直跟着操作比较机械,虽然代码是一点一点跟着敲的,但是有些逻辑以及概念方面的东西还不是很能理解。所以咋们先缓缓,推荐去看一下react-redux的官方文档,请猛戳这里,也可以选择强戳这里....中文版的可能更加便于如我一般的小菜鸟们理解啊哈哈哈

其实只要理解最核心的一点大体上就ok了

reducer就是纯函数,接收stateaction,然后返回一个新的 state

接下来在src/redux/store.js添加一下代码

import {createStore} from 'redux';
import combineReducers from './reducers.js';

let store = createStore(combineReducers);

export default store;
复制代码

到这里应该可以使用redux啦~~是不是在nice的同时心里还稍稍着些许的成就感呀

接下来我们增加一下路径别名(简单的说这就是为了方便哈,也可以不用,只要自己引入组件的之后路径不错就行哈)

webpack.dev.config.js

   alias: {
            ...
            actions: path.join(__dirname, 'src/redux/actions'),
            reducers: path.join(__dirname, 'src/redux/reducers'),
            redux: path.join(__dirname, 'src/redux')
        }
复制代码

这时候得看一下自己之前的哥哥组件下的引入路径是否有问题哟,有问题的相应的改一下

接下来我们要开始搭配react使用啦

首先我们先来写一个Counter页面

cd src/pages
mkdir Counter
touch Counter/Counter.js
复制代码

接下来:src/pages/Counter/Counter.js

import React, {Component} from 'react';

export default class Counter extends Component {
    render() {
        return (
            <div>
                <div>当前计数为(显示redux计数)</div>
                <button onClick={() => {
                    console.log('调用自增函数');
                }}>自增
                </button>
                <button onClick={() => {
                    console.log('调用自减函数');
                }}>自减
                </button>
                <button onClick={() => {
                    console.log('调用重置函数');
                }}>重置
                </button>
            </div>
        )
    }
}
复制代码

到这儿我们需要修改一下路由,因为每写一个页面我们都要把它添加至路由当中,这样才能生效。

修改路由,添加Counter

src/router/router.js

import React from 'react';

import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';

import Home from 'pages/Home/Home';
import Page1 from 'pages/Page1/Page1';
import Counter from 'pages/Counter/Counter';

const getRouter = () => (
    <Router>
        <div>
            <ul>
                <li><Link to="/">首页</Link></li>
                <li><Link to="/page1">Page1</Link></li>
                <li><Link to="/counter">Counter</Link></li>
            </ul>
            <Switch>
                <Route exact path="/" component={Home}/>
                <Route path="/page1" component={Page1}/>
                <Route path="/counter" component={Counter}/>
            </Switch>
        </div>
    </Router>
);

export default getRouter;
复制代码

(小小的提示一下,当时我跟着原教程走的时候,有些引入组件的路径跟原教程的不大一样,但是不影响,只要稍稍调一下就行,希望小伙伴们在看的时候也能注意一下这一点,这里插入的代码跟原教程的一样。诶呀~~其实bug何其多,即使是跟着原教程来做可能还是会有的,但是办法总比困难多不是?坑嘛,多踩踩还是极好的)

现在可以npm start看看效果啦~~

一般情况下,要是不安装任何依赖,只能依靠手动监听以及手动引入store,但是这样会较为麻烦

react-redux提供了一个办法,connect。超级实用(具体怎么个实用法前面的材料有讲到哈,如我一般的小菜鸟们请仔细阅读)

到这里,我们先来安装react-redux

npm install --save react-redux

src/pages/Counter/Counter.js

import React, {Component} from 'react';
import {increment, decrement, reset} from 'actions/counter';

import {connect} from 'react-redux';

class Counter extends Component {
    render() {
        return (
            <div>
                <div>当前计数为{this.props.counter.count}</div>
                <button onClick={() => this.props.increment()}>自增
                </button>
                <button onClick={() => this.props.decrement()}>自减
                </button>
                <button onClick={() => this.props.reset()}>重置
                </button>
            </div>
        )
    }
}

const mapStateToProps = (state) => {
    return {
        counter: state.counter
    }
};

const mapDispatchToProps = (dispatch) => {
    return {
        increment: () => {
            dispatch(increment())
        },
        decrement: () => {
            dispatch(decrement())
        },
        reset: () => {
            dispatch(reset())
        }
    }
};

export default connect(mapStateToProps, mapDispatchToProps)(Counter);
复制代码

下面我们需要传入store

找到src/index.js

import React from 'react';
import ReactDom from 'react-dom';
import {AppContainer} from 'react-hot-loader';
import {Provider} from 'react-redux';
import store from './redux/store';

import getRouter from 'router/router';

/*初始化*/
renderWithHotReload(getRouter());

/*热更新*/
if (module.hot) {
    module.hot.accept('./router/router', () => {
        const getRouter = require('router/router').default;
        renderWithHotReload(getRouter());
    });
}

function renderWithHotReload(RootElement) {
    ReactDom.render(
        <AppContainer>
            <Provider store={store}>
                {RootElement}
            </Provider>
        </AppContainer>,
        document.getElementById('app')
    )
}
复制代码

现在我们尝试着打开npm start ,查看一下效果

我们可以看到会报错,别担心,我们只要把下面的一行代码删掉就ok啦

    resolve: {
        alias: {
            ...
            redux: path.join(__dirname, 'src/redux')
        }
    }
复制代码

呐,上面alias里面的那行,删掉之后妥妥的就能看到显示的界面咯。

缕一下缕一下:

1.Provider组件是让所有的组件可以访问到store。不用手动去传。也不用手动去监听。

2.connect函数作用是从Redux state 树中读取部分数据,并通过props 来把这些数据提供给要渲染的组件。也传递dispatch(action)函数到props

接下来我们要做的是如何发送异步请求!!!

做之前我们不妨先构思一下请求的步骤:

1.请求开始的时候,界面转圈提示正在加载。isLoading置为true

2.请求成功,显示数据。isLoading置为false,data填充数据。

3.请求失败,显示失败。isLoading置为false,显示错误信息。

接下来,我们来尝试写一个比较简单的后台请求用户信息的例子。

1.首先我们得先创建一个user.json,用于请求数据,相当于后台API接口

cd dist
mkdir api
cd api
touch user.json
复制代码

dist/api/user.json

{
  "msg": "phoebe",
  "name": "brickspert",
  "intro": "please give me a star"
}
复制代码

2.创建一个必要的action函数

cd src/redux/actions
touch userInfo.js
复制代码

找到src/redux/actions/userInfo.js

export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";
export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";
export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";

function getUserInfoRequest() {
    return {
        type: GET_USER_INFO_REQUEST
    }
}

function getUserInfoSuccess(userInfo) {
    return {
        type: GET_USER_INFO_SUCCESS,
        userInfo: userInfo
    }
}

function getUserInfoFail() {
    return {
        type: GET_USER_INFO_FAIL
    }
}
复制代码

上面中我们创建了正在请求,请求成功和请求失败三个action创建函数。

  1. 接下来我们要创建一个render

(前面已经提到render是根据stataction生成的新state的纯函数)。

cd src/redux/reducers
touch userInfo.js
复制代码

src/redux/reducers/userInfo.js

import {GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL} from 'actions/userInfo';


const initState = {
    isLoading: false,
    userInfo: {},
    errorMsg: ''
};

export default function reducer(state = initState, action) {
    switch (action.type) {
        case GET_USER_INFO_REQUEST:
            return {
                ...state,
                isLoading: true,
                userInfo: {},
                errorMsg: ''
            };
        case GET_USER_INFO_SUCCESS:
            return {
                ...state,
                isLoading: false,
                userInfo: action.userInfo,
                errorMsg: ''
            };
        case GET_USER_INFO_FAIL:
            return {
                ...state,
                isLoading: false,
                userInfo: {},
                errorMsg: '请求错误'
            };
        default:
            return state;
    }
}
复制代码

(根据教程里边讲到的,...state语法,和objiect.assign()作用是一样的的,合并新旧state。这里并没有实际的效果,但是为了规范,还是建议写上的哈~~)

接下来就是组合reducer(置于为什么组合,前面的例子中有讲过喔,忘记的就往回翻翻哈~~)

src/redux/reducers.js

import counter from 'reducers/counter';
import userInfo from 'reducers/userInfo';

export default function combineReducers(state = {}, action) {
    return {
        counter: counter(state.counter, action),
        userInfo: userInfo(state.userInfo, action)
    }
}
复制代码

4.在有了actionreducer的前提之下,我们需要调用把action里面的三个action函数和网络请求结合起来。

  • 请求中 dispatch getUserInfoRequest
  • 请求成功 dispatch getUserInfoSuccess
  • 请求失败 dispatch getUserInfoFail

src/redux/actions/userInfo.js增加

export function getUserInfo() {
    return function (dispatch) {
        dispatch(getUserInfoRequest());

        return fetch('http://localhost:8080/api/user.json')
            .then((response => {
                return response.json()
            }))
            .then((json) => {
                    dispatch(getUserInfoSuccess(json))
                }
            ).catch(
                () => {
                    dispatch(getUserInfoFail());
                }
            )
    }
}
复制代码

在这里我们需要引入redux-thunk中间件。

npm install --save redux-thunk

中间件的使用,作用和基本概念可以阅读一下Middleware

接下来我们需要引入react-thunk中间件。

src/redux/store.js

import {createStore, applyMiddleware} from 'redux';
import thunkMiddleware from 'redux-thunk';
import combineReducers from './reducers.js';

let store = createStore(combineReducers, applyMiddleware(thunkMiddleware));

export default store;
复制代码

好啦,接下来我么就可以尝试着写一个组件来验证一下啦,内心有点小激动呀有木有~~

cd src/pages
mkdir UserInfo
cd UserInfo
touch UserInfo.js
复制代码

src/pages/UserInfo/UserInfo.js

import React, {Component} from 'react';
import {connect} from 'react-redux';
import {getUserInfo} from "actions/userInfo";

class UserInfo extends Component {

    render() {
        const {userInfo, isLoading, errorMsg} = this.props.userInfo;
        return (
            <div>
                {
                    isLoading ? '请求信息中......' :
                        (
                            errorMsg ? errorMsg :
                                <div>
                                    <p>用户信息:</p>
                                    <p>用户名:{userInfo.name}</p>
                                    <p>介绍:{userInfo.intro}</p>
                                </div>
                        )
                }
                <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>
            </div>
        )
    }
}

export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo);
复制代码

紧接着就是添加路由

src/router/router.js

import React from 'react';

import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';

import Home from 'pages/Home/Home';
import Page1 from 'pages/Page1/Page1';
import Counter from 'pages/Counter/Counter';
import UserInfo from 'pages/UserInfo/UserInfo';

const getRouter = () => (
    <Router>
        <div>
            <ul>
                <li><Link to="/">首页</Link></li>
                <li><Link to="/page1">Page1</Link></li>
                <li><Link to="/counter">Counter</Link></li>
                <li><Link to="/userinfo">UserInfo</Link></li>
            </ul>
            <Switch>
                <Route exact path="/" component={Home}/>
                <Route path="/page1" component={Page1}/>
                <Route path="/counter" component={Counter}/>
                <Route path="/userinfo" component={UserInfo}/>
            </Switch>
        </div>
    </Router>
);

export default getRouter;
复制代码

到现在可以去运行一下啦(中途要是出现什么小bug,请尝试着努力的去修改哈,有bug才是正常的,多改改是好事儿)看看效果:

combinReducers优化

redux提供了一个combineReducers函数来合并reducer,不用我们自己合并哦。写起来简单,但是意思和我们自己写的combinReducers也是一样的。

src/redux/reducers.js

import {combineReducers} from "redux";

import counter from 'reducers/counter';
import userInfo from 'reducers/userInfo';


export default combineReducers({
    counter,
    userInfo
});
复制代码

devtool优化

不多说,为了便于调试,我们可以在webpack配置devtool!

找到webpack.dev.config.js

devtool: 'inline-source-map'

编译CSS

首先我们需要安装 Microsoft Windows SDK for Windows 7 and .NET Framework 4。

npm install css-loader style-loader --save-dev

css-loader使你能够使用类似@importurl(...)的方法实现 require()的功能;

style-loader将所有的计算后的样式加入到页面中,二者组合在一起能够把样式嵌入webpack打包后的js文件中。

webpack.dev.config.js rules增加

{
   test: /\.css$/,
   use: ['style-loader', 'css-loader']
}
复制代码

找到src/pages/Page1/Page1.js

import React, {Component} from 'react';

import './Page1.css';

export default class Page1 extends Component {
    render() {
        return (
            <div className="page-box">
                this is page1~
            </div>
        )
    }
}
复制代码

ok啦,现在可以npm start看看效果了。

编译图片

npm install --save-dev url-loader file-loader

webpack.dev.config.js rules增加

{
    test: /\.(png|jpg|gif)$/,
    use: [{
        loader: 'url-loader',
        options: {
            limit: 8192
        }
    }]
}
复制代码

options limit 8192意思是,小于等于8K的图片会被转成base64编码,直接插入HTML中,减少HTTP请求。

接下来可以尝试在Page1中插入图片

cd src/pages/Page1
mkdir images
复制代码

将一张放到images文件夹中

修改代码,引入图片:

src/pages/Page1/Page1.js

import React, {Component} from 'react';

import './Page1.css';

import image from './images/bg.jpg';

export default class Page1 extends Component {
    render() {
        return (
            <div className="page-box">
                this is page1~
                <img src={image}/>
            </div>
        )
    }
}
复制代码

效果图如下:

按需加载

一般情况下我们打包完之后,所有的页面只生成了一个build.js,当我们首屏加载的时候,就会很慢。因为他也下载了别的界面的Js。这极大程度上拖缓了加载进程。但是如果每个网页都打包了自己单独的js,在进入自己的页面时才会加载对应的js,那么首屏加载就会快很多。

react-router 2.0时代,按需加载需要用到的最关键的一个函数,就是require.ensure(),它是按需加载能够实现的核心。

但是在4.0版本,官方放弃了这种处理方式,采用了一个更加简洁的办法,如下:

首先添加依赖:

1.npm install bundle-loader --save-dev

2.新建bundle.js

cd src/router
touch Bundle.js
复制代码

src/router/Bundle.js

import React, {Component} from 'react'

class Bundle extends Component {
    state = {
        // short for "module" but that's a keyword in js, so "mod"
        mod: null
    };

    componentWillMount() {
        this.load(this.props)
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.load !== this.props.load) {
            this.load(nextProps)
        }
    }

    load(props) {
        this.setState({
            mod: null
        });
        props.load((mod) => {
            this.setState({
                // handle both es imports and cjs
                mod: mod.default ? mod.default : mod
            })
        })
    }

    render() {
        return this.props.children(this.state.mod)
    }
}

export default Bundle;
复制代码

3.改造路由器

src/router/router.js

import React from 'react';

import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';

import Bundle from './Bundle';

import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';
import Page1 from 'bundle-loader?lazy&name=page1!pages/Page1/Page1';
import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter';
import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo';

const Loading = function () {
    return <div>Loading...</div>
};

const createComponent = (component) => (props) => (
    <Bundle load={component}>
        {
            (Component) => Component ? <Component {...props} /> : <Loading/>
        }
    </Bundle>
);

const getRouter = () => (
    <Router>
        <div>
            <ul>
                <li><Link to="/">首页</Link></li>
                <li><Link to="/page1">Page1</Link></li>
                <li><Link to="/counter">Counter</Link></li>
                <li><Link to="/userinfo">UserInfo</Link></li>
            </ul>
            <Switch>
                <Route exact path="/" component={createComponent(Home)}/>
                <Route path="/page1" component={createComponent(Page1)}/>
                <Route path="/counter" component={createComponent(Counter)}/>
                <Route path="/userinfo" component={createComponent(UserInfo)}/>
            </Switch>
        </div>
    </Router>
);

export default getRouter;
复制代码

现在我们可以打开npm start看一下具体的效果啦~~打开浏览器,看是不是进入新的页面,都会加载自己的JS的~

接下来你很快就会发现,打开浏览器的时候分不清是哪个页面的js。不要慌!!!我们只要修改webpack.dev.config.js,加个chunkFilename就ok啦

    output: {
        path: path.join(__dirname, './dist'),
        filename: 'bundle.js',
        chunkFilename: '[name].js'
    }
复制代码

ok了接下来你会看到运行是的名字变成了home.js哟~~,想知道为啥会产生这样的效果么?? 请看一下代码:

import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';

这是因为name = home,是不是通俗易懂呀

缓存

缓存功能是很必要的,因为无论是从消耗还是占内存来说,都会有影响。

原教程对于缓存的解释如下:

用户第一次访问首页,下载了home.js,第二次访问又下载了home.js~ 这肯定不行呀,所以我们一般都会做一个缓存,用户下载一次home.js后,第二次就不下载了。 有一天,我们更新了home.js,但是用户不知道呀,用户还是使用本地旧的home.js。出问题了~ 怎么解决?每次代码更新后,打包生成的名字不一样。比如第一次叫home.a.js,第二次叫home.b.js

ok,接下来我们可以照着文档来操作:

webpack.dev.config.js

   output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[hash].js',
        chunkFilename: '[name].[chunkhash].js'
    }
复制代码

现在我们每次打包的时候都会增加hash哟

打包之后相应的名字就会改变啦~~请看下图

HtmlWebpackPlugin

使用这个插件,它会自动的把js插入到模板的index.html中:

现在开始执行命令:

npm install html-webpack-plugin --save-dev

这时候我们需要在src路径下新建一个index.html模板:

cd src
touch index.html
复制代码

src/index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
复制代码

修改webpack.dev.config.js,添加pligin

var HtmlWebpackPlugin = require('html-webpack-plugin');

    plugins: [new HtmlWebpackPlugin({
        filename: 'index.html',
        template: path.join(__dirname, 'src/index.html')
    })],

复制代码

现在咋们npm install就可以正常的浏览网站啦~~(很激动有木有~~)现在我们可以把dist/index.html给删掉哟,因为打包后的文件是存在在内存之中的,咋们看不到。

提取公共代码

看到这个标题千万别懵,也别疑惑为啥要提取公共代码。。。。这肯定是为了减少资源消耗呀啊哈哈

话不多说,请往下看

webpack.dev.config.js

   var webpack = require('webpack');

    entry: {
        app: [
            'react-hot-loader/patch',
            path.join(__dirname, 'src/index.js')
        ],
        vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
    }
    
        /*plugins*/
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'
        })
复制代码

这里react等库生成打包到verdor.hash.js里面去~~

但是到这里你可能会发现,编译生成的文件app.[hash].js和vendor.[hash].js生成的hash是一样的,这是因为我们每次修改代码的时候都会导致vendor.[hash].js名字改变,那这样的话,我们提取出来的意义也就没有啦。(但是我们这里不做改变,因为存在chunkhash和webpack-dev-server --hot版本不兼容的问题....其实现在我们也可以不深究,到我们有了一定的阅历之后自然而然的就懂了哈)

构建生产环境

概念:开发环境(development)和生产环境(production)的构建目标差异很大。在开发环境中,我们需要具有强大的、具有实时重新加载(live reloading)或热模块替换(hot module replacement)能力的 source map 和 localhost server。而在生产环境中,我们的目标则转向于关注更小的 bundle,更轻量的 source map,以及更优化的资源,以改善加载时间。由于要遵循逻辑分离,我们通常建议为每个环境编写彼此独立的 webpack 配置。

开始执行操作:

touch webpack.config.js

根据原教程,在webpack.dev.config.js的基础上做了几个小小的修改:

1.先删除webpack-dev-server相关的东西~

2.devtool的值改成cheap-module-source-map

3.刚才说的hash改成chunkhash

webpack.config.js

const path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpack = require('webpack');

module.exports = {
    devtool: 'cheap-module-source-map',
    entry: {
        app: [
            path.join(__dirname, 'src/index.js')
        ],
        vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[chunkhash].js',
        chunkFilename: '[name].[chunkhash].js'
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader'],
            include: path.join(__dirname, 'src')
        }, {
            test: /\.css$/,
            use: ['style-loader', 'css-loader']
        }, {
            test: /\.(png|jpg|gif)$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8192
                }
            }]
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: path.join(__dirname, 'src/index.html')
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'
        })
    ],

    resolve: {
        alias: {
            pages: path.join(__dirname, 'src/pages'),
            component: path.join(__dirname, 'src/component'),
            router: path.join(__dirname, 'src/router'),
            actions: path.join(__dirname, 'src/redux/actions'),
            reducers: path.join(__dirname, 'src/redux/reducers')
        }
    }
};
复制代码

接下来我们要在package.json增加打包脚本

"build":"webpack --config webpack.config.js"

嘿,到这儿咋们执行npm run build就可以看到dist文件夹里面是不是生成了我们发布的文件啦~~~

文件压缩

咋们使用UglifyJSPliginL来压缩生成的文件。

npm i --save-dev uglifyjs-webpack-plugin

webpack.config.js

const UglifyJSPlugin = require('uglifyjs-webpack-plugin')

module.exports = {
  plugins: [
    new UglifyJSPlugin()
  ]
}
复制代码

哇执行以下npm run build,是不是发现一个很神奇的东西,打包的文件小了很多!!

指定环境

原教程中解释到:

许多 library将通过与 process.env.NODE_ENV环境变量关联,以决定 library中应该引用哪些内容。例如,当不处于生产环境中时,某些library为了使调试变得容易,可能会添加额外的日志记录(log)和测试(test)。其实,当使用process.env.NODE_ENV === 'production' 时,一些 library 可能针对具体用户的环境进行代码优化,从而删除或添加一些重要代码。我们可以使用webpack内置的DefinePlugin 为所有的依赖定义这个变量:

执行以下代码:

webpack.config.js

module.exports = {
  plugins: [
       new webpack.DefinePlugin({
          'process.env': {
              'NODE_ENV': JSON.stringify('production')
           }
       })
  ]
}
复制代码

可以发现npm run build之后vendor.[hash].js又变小了。

优化缓存

在这之前我们把[name].[hash].js变成[name].[chunkhash].js,发现运行之后的app.xxx.jsvendor.xxx.js不一样了,但是当随意修改一个组件的内容时发现组件的名字变化的同时,vendor.xxx.js名字也变了。但是这不是我们想要的哈,我们最初想要的是vendor.xxx.js的名字永久的不变。

那么接下来我们需要这么做:

webpack.config.js

 plugins: [
        new webpack.HashedModuleIdsPlugin(),
        new webpack.optimize.CommonsChunkPlugin({
    name: 'runtime'
})
    ]
复制代码

(注意这个顺序也是有讲究的,不能调换:CommonsChunkPlugin 的 'vendor' 实例,必须在 'runtime' 实例之前引入)

public path

到这一步之前,我们可以想象一个场景,我们的静态文件放在了单独的静态服务器上去了,那我们打包的时候,如何让静态文件的链接定位到静态服务器呢?

webpack.config.js output 中增加一个publicPath,我们当前用/,相对于当前路径,如果你要改成别的url,就改这里就好了。

    output: {
        publicPath : '/'
    }
复制代码

打包优化

我们现在可以看一下dist里面的文件,是不是发现多了好多,那是因为每次打包的文件都放在了里面,而且还混合了,所以我们当然是希望每次打包之前都能自动的清理一下dist下的文件啦~~

npm install clean-webpack-plugin --save-dev

webpack.config.js

const CleanWebpackPlugin = require('clean-webpack-plugin');


plugins: [
    new CleanWebpackPlugin(['dist'])
]
复制代码

吼啦,现在试试npm run build看看是不是dist文件夹下的多余的文件都被自动清理了??神奇吧~~

抽取css

目前我们可以看到,css都是直接打包进js里面的,我们当然是希望能够单独生成css文件啦~~

这里我们使用extract-text-webpack-plugin来实现。

npm install --save-dev extract-text-webpack-plugin

webpack.config.js

const ExtractTextPlugin = require("extract-text-webpack-plugin");

module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use: ExtractTextPlugin.extract({
          fallback: "style-loader",
          use: "css-loader"
        })
      }
    ]
  },
  plugins: [
     new ExtractTextPlugin({
         filename: '[name].[contenthash:5].css',
         allChunks: true
     })
  ]

复制代码

好啦到这里你可以试着npm run build,看看是不是在dist文件夹下生成了css文件哟~~

使用axios和middleware优化API请求

首先我们需要先安装axios

npm install --save axios

我们之前项目的一次API请求是这样写的哦~

action创建函数是这样的。比我们现在写的fetch简单多了。

export function getUserInfo() {
    return {
        types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL],
        promise: client => client.get(`http://localhost:8080/api/user.json`)
        afterSuccess:(dispatch,getState,response)=>{
            /*请求成功后执行的函数*/
        },
        otherData:otherData
    }
}
复制代码

然后在dispatch(getUserInfo())后,通过redux中间件来处理请求逻辑。

在这之前我们可以先来理一下:

1.请求前dispatch REQUEST请求。

2.成功后dispatch SUCCESS请求,如果定义了afterSuccess()函数,调用它。

3.失败后dispatch FAIL请求。

好!!开始动手

cd src/redux
mkdir middleware
cd middleware
touch promiseMiddleware.js
复制代码

src/redux/middleware/promiseMiddleware.js

import axios from 'axios';

export default  store => next => action => {
    const {dispatch, getState} = store;
    /*如果dispatch来的是一个function,此处不做处理,直接进入下一级*/
    if (typeof action === 'function') {
        action(dispatch, getState);
        return;
    }
    /*解析action*/
    const {
        promise,
        types,
        afterSuccess,
        ...rest
    } = action;

    /*没有promise,证明不是想要发送ajax请求的,就直接进入下一步啦!*/
    if (!action.promise) {
        return next(action);
    }

    /*解析types*/
    const [REQUEST,
        SUCCESS,
        FAILURE] = types;

    /*开始请求的时候,发一个action*/
    next({
        ...rest,
        type: REQUEST
    });
    /*定义请求成功时的方法*/
    const onFulfilled = result => {
        next({
            ...rest,
            result,
            type: SUCCESS
        });
        if (afterSuccess) {
            afterSuccess(dispatch, getState, result);
        }
    };
    /*定义请求失败时的方法*/
    const onRejected = error => {
        next({
            ...rest,
            error,
            type: FAILURE
        });
    };

    return promise(axios).then(onFulfilled, onRejected).catch(error => {
        console.error('MIDDLEWARE ERROR:', error);
        onRejected(error)
    })
}
复制代码

修改src/redux/store.js来应用这个中间件

import {createStore, applyMiddleware} from 'redux';
import combineReducers from './reducers.js';

import promiseMiddleware from './middleware/promiseMiddleware'

let store = createStore(combineReducers, applyMiddleware(promiseMiddleware));

export default store;
复制代码

修改src/redux/actions/userInfo.js

export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";
export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";
export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";

export function getUserInfo() {
    return {
        types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL],
        promise: client => client.get(`http://localhost:8080/api/user.json`)
    }
}
复制代码

呐,现在看看是不是超级清晰~~

接下来:

修改src/redux/reducers/userInfo.js

case GET_USER_INFO_SUCCESS:
           return {
               ...state,
               isLoading: false,
               userInfo: action.result.data,
               errorMsg: ''
           };
复制代码

action.userInfo修改成了action.result.data。中间件请求成功,会给action增加一个result字段来存储响应结果哦~不用手动传了。

npm start看看,我们发现了一个问题他会报一个错

没错,这是因为我们之前在清dist的时候把它清掉啦啊哈哈,现在只需按照着了路径手动新建即可,步骤跟之前是一样的哈~~

合并提取webpack公共配置

webpack.common.config.js主要是用来存放webpack.dev.config.jswebpack.config.js共同存放的东西,你也不想每次都要在这两个文件里边敲一样的东西的吧ahhh

在这之前我们先要配置一下webpack-merge

npm install --save-dev webpack-merge

touch webpack.common.config.js

webpack.common.config.js

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');

commonConfig = {
    entry: {
        app: [
            path.join(__dirname, 'src/index.js')
        ],
        vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
    },
    output: {
        path: path.join(__dirname, './dist'),
        filename: '[name].[chunkhash].js',
        chunkFilename: '[name].[chunkhash].js',
        publicPath: "/"
    },
    module: {
        rules: [{
            test: /\.js$/,
            use: ['babel-loader?cacheDirectory=true'],
            include: path.join(__dirname, 'src')
        }, {
            test: /\.(png|jpg|gif)$/,
            use: [{
                loader: 'url-loader',
                options: {
                    limit: 8192
                }
            }]
        }]
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: 'index.html',
            template: path.join(__dirname, 'src/index.html')
        }),
        new webpack.HashedModuleIdsPlugin(),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'vendor'
        }),
        new webpack.optimize.CommonsChunkPlugin({
            name: 'runtime'
        })
    ],

    resolve: {
        alias: {
            pages: path.join(__dirname, 'src/pages'),
            components: path.join(__dirname, 'src/components'),
            router: path.join(__dirname, 'src/router'),
            actions: path.join(__dirname, 'src/redux/actions'),
            reducers: path.join(__dirname, 'src/redux/reducers')
        }
    }
};

module.exports = commonConfig;
复制代码

webpack.dev.config.js

const merge = require('webpack-merge');
const path = require('path');

const commonConfig = require('./webpack.common.config.js');

const devConfig = {
    devtool: 'inline-source-map',
    entry: {
        app: [
            'react-hot-loader/patch',
            path.join(__dirname, 'src/index.js')
        ]
    },
    output: {
        /*这里本来应该是[chunkhash]的,但是由于[chunkhash]和react-hot-loader不兼容。只能妥协*/
        filename: '[name].[hash].js'
    },
    module: {
        rules: [{
            test: /\.css$/,
            use: ["style-loader", "css-loader"]
        }]
    },
    devServer: {
        contentBase: path.join(__dirname, './dist'),
        historyApiFallback: true,
        host: '0.0.0.0',
    }
};

module.exports = merge({
    customizeArray(a, b, key) {
        /*entry.app不合并,全替换*/
        if (key === 'entry.app') {
            return b;
        }
        return undefined;
    }
})(commonConfig, devConfig);
复制代码

webpack.config.js

const merge = require('webpack-merge');

const webpack = require('webpack');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const ExtractTextPlugin = require("extract-text-webpack-plugin");

const commonConfig = require('./webpack.common.config.js');

const publicConfig = {
    devtool: 'cheap-module-source-map',
    module: {
        rules: [{
            test: /\.css$/,
            use: ExtractTextPlugin.extract({
                fallback: "style-loader",
                use: "css-loader"
            })
        }]
    },
    plugins: [
        new CleanWebpackPlugin(['dist/*.*']),
        new UglifyJSPlugin(),
        new webpack.DefinePlugin({
            'process.env': {
                'NODE_ENV': JSON.stringify('production')
            }
        }),
        new ExtractTextPlugin({
            filename: '[name].[contenthash:5].css',
            allChunks: true
        })
    ]

};

module.exports = merge(commonConfig, publicConfig);
复制代码

好啦,到这里基本上算是大功告成啦,很开心有木有!!!其实只要认真的照着教程敲一遍(不懂的再看着原教程),真的是蛮不错的。笔者跟着教程巧了一遍,这次写这篇博客主要也是想巩固一下之前学到的知识点,因为在之前很多概念性的东西还没很能理解,但是走第二遍的时候发现都懂了很多新的东西ahhh甚是开心呐本教程最主要的就是为了笔者巩固所学以及分享而用,纯属分享不带任何商业目的哈有意向的可以去看看原教程,猛戳这里。同时也希望这篇文章对刚入门的你们有所帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值