前言篇--笔者心得
刚从大学校园里跑出来,大学里做过的各种东西看似很多感觉很牛批,完了出来接触才发现自己是个小菜鸡...近段时间一直在学习项目需要的react方面的知识,入过的坑简直是不能太多(欲哭无泪)。在学习react的道路上一直在摸索,除了阅读了阮一峰老师写的关于react的知识点之外,还学习搭建了一个简单的框架,作为巩固近端时间学习的一个简单的方法。(学习搭建框架的这篇教程并非属于笔者所创,仅仅是用于笔者实践过后的一个分享,不喜勿喷=_= 更过更详细的介绍请移步猛戳 这里啦)
项目须知
根据我们平常所见的项目,一般都会有一个较为完整的路径框架(大体框架的目录),便于文件的存放以及查找和管理,如下图所示(教程中的图)
好啦,现在让我们愉快的开始前端框架的搭建吧哈哈哈~~
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)
现在可以看到在文件夹下面生成了dist
和main.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
用于解析 ES6babel-preset-react
用于解析 JSXbabel-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
的意思一致,就只是es6
和es5
的区别呀...神奇吧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
,其中这里边会包含有两个页面,分别是Home
和Page1
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
中点击浏览器了,我们需要在
参考地址(摘自原教程):
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-server
和react-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
是一个纯函数,用于接收action
和state
,从而返回一个新的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
就是纯函数,接收state
和 action
,然后返回一个新的 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.请求开始的时候,界面转圈提示正在加载。isLoadin
g置为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
创建函数。
- 接下来我们要创建一个
render
(前面已经提到render
是根据stat
和action
生成的新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.在有了action
和reducer
的前提之下,我们需要调用把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
使你能够使用类似@import
和 url(...)
的方法实现 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.j
s,第二次叫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.js
和vendor.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.js
和webpack.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甚是开心呐本教程最主要的就是为了笔者巩固所学以及分享而用,纯属分享不带任何商业目的哈有意向的可以去看看原教程,猛戳这里。同时也希望这篇文章对刚入门的你们有所帮助。