当前位置:   article > 正文

来自一位正在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文件中编写一下代码

  1. const path = require('path');
  2. moudle.export = {
  3. /*这里是入口*/
  4. entry: path.join(__dirname, 'src/index.js'),
  5. /*这里是输出文件的出口,输出文件到dist文件夹,输出文件名为bundle.js*/
  6. output: {
  7. path: path.join(__dirname,'./dist'),
  8. filename: 'main.js'
  9. }
  10. };
  11. 复制代码

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

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

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

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

现在我们可以执行 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下输入一下代码:

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <body>
  8. <div id="app"></div>
  9. <script type="text/javascript" src="./main.js" charset="utf-8"></script>
  10. </body>
  11. </html>
  12. 复制代码

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

babel

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

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

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

(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下输入以下代码:

  1. {
  2. "presets": [
  3. "es2015",
  4. "react",
  5. "stage-0"
  6. ],
  7. "plugins": []
  8. }
  9. 复制代码

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

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

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

我们先要修改index.js

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

然后在编译器里面执行打包命令: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

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. ReactDom.render(
  4. <div>Hello React!</div>, document.getElementById('app'));
  5. 复制代码

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

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

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

  1. cd src
  2. mkdir component
  3. cd component
  4. mkdir Hello
  5. cd Hello
  6. touch Hello.js
  7. 复制代码

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

  1. import React, {Component} from 'react';
  2. export default class Hello extends Component {
  3. render() {
  4. return (
  5. <div>
  6. Hello,React!
  7. </div>
  8. )
  9. }
  10. }
  11. 复制代码

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

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

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. import Hello from './component/Hello/Hello';
  4. ReactDom.render(
  5. <Hello/>, document.getElementById('app'));
  6. 复制代码

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

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

命令优化

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

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

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

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

react-router

npm install react-router-dom --save

新建一个router文件夹和组件

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

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

src/router/router.js

  1. import React from 'react';
  2. import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
  3. import Home from '../pages/Home/Home';
  4. import Page1 from '../pages/Page1/Page1';
  5. const getRouter = () => (
  6. <Router>
  7. <div>
  8. <ul>
  9. <li><Link to="/">首页</Link></li>
  10. <li><Link to="/page1">Page1</Link></li>
  11. </ul>
  12. <Switch>
  13. <Route exact path="/" component={Home}/>
  14. <Route path="/page1" component={Page1}/>
  15. </Switch>
  16. </div>
  17. </Router>
  18. );
  19. export default getRouter;
  20. 复制代码

新建一个页面文件夹

  1. cd src
  2. mkdir pages
  3. 复制代码

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

  1. cd src/pages
  2. mkdir Home && touch Home/Home.js
  3. mkdir Page1 && touch Page1/Page1.js
  4. 复制代码

分别在页面中填充内容

src/pages/Home/Home.js

  1. import React, {Component} from 'react';
  2. export default class Home extends Component {
  3. render() {
  4. return (
  5. <div>
  6. this is home~
  7. </div>
  8. )
  9. }
  10. }
  11. 复制代码

src/pages/Page1/Page1.js

  1. import React, {Component} from 'react';
  2. export default class Page1 extends Component {
  3. render() {
  4. return (
  5. <div>
  6. this is Page1~
  7. </div>
  8. )
  9. }
  10. }
  11. 复制代码

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

修改src/index.js

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. import getRouter from './router/router';
  4. ReactDom.render(
  5. getRouter(), document.getElementById('app'));
  6. 复制代码

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

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

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

1.参考资料一

2.参考资料二

webpack-dev-server

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

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

webpack.dev.config.js

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

现在执行

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

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

接下来我们执行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上有后端服务的话,你可以这样启用代理:
  1. proxy: {
  2. "/api": "http://localhost:3000"
  3. }
  4. 复制代码
  • progress (CLI only)将编译输出到控制台。

webpack.dev.config.js

  1. devServer: {
  2. port: 8080,
  3. contentBase: path.join(__dirname, './dist'),
  4. historyApiFallback: true,
  5. host: '0.0.0.0'
  6. }
  7. 复制代码

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

package.json

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

现在我们可以执行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下代码:

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. import getRouter from './router/router';
  4. if (module.hot) {
  5. module.hot.accept();
  6. }
  7. ReactDom.render(
  8. getRouter(), document.getElementById('app'));
  9. 复制代码

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

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

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

src/pages/Home/Home.js

  1. import React, {Component} from 'react';
  2. export default class Home extends Component {
  3. constructor(props) {
  4. super(props);
  5. this.state = {
  6. count: 0
  7. }
  8. }
  9. _handleClick() {
  10. this.setState({
  11. count: ++this.state.count
  12. });
  13. }
  14. render() {
  15. return (
  16. <div>
  17. this is home~<br/>
  18. 当前计数:{this.state.count}<br/>
  19. <button onClick={() => this._handleClick()}>自增</button>
  20. </div>
  21. )
  22. }
  23. }
  24. 复制代码

此刻可以刷新一下浏览器,可以看到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

  1. {
  2. "presets": [
  3. "es2015",
  4. "react",
  5. "stage-0"
  6. ],
  7. "plugins": [
  8. "react-hot-loader/babel"
  9. ]
  10. }
  11. 复制代码

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

webpack.dev.config.js

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

3.修改src/index.js

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. import {AppContainer} from 'react-hot-loader';
  4. import getRouter from './router/router';
  5. /*初始化*/
  6. renderWithHotReload(getRouter());
  7. /*热更新*/
  8. if (module.hot) {
  9. module.hot.accept('./router/router', () => {
  10. const getRouter = require('./router/router').default;
  11. renderWithHotReload(getRouter());
  12. });
  13. }
  14. function renderWithHotReload(RootElement) {
  15. ReactDom.render(
  16. <AppContainer>
  17. {RootElement}
  18. </AppContainer>,
  19. document.getElementById('app')
  20. )
  21. }
  22. 复制代码

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


redux

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

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

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

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

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

  1. cd src && mkdir redux
  2. cd redux
  3. mkdir actions
  4. mkdir reducers
  5. touch reducers.js
  6. touch store.js
  7. touch actions/counter.js
  8. touch reducers/counter.js
  9. 复制代码

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

src/redux/actions/counter.js

  1. /*action*/
  2. export const INCREMENT = "counter/INCREMENT";
  3. export const DECREMENT = "counter/DECREMENT";
  4. export const RESET = "counter/RESET";
  5. export function increment() {
  6. return {type: INCREMENT}
  7. }
  8. export function decrement() {
  9. return {type: DECREMENT}
  10. }
  11. export function reset() {
  12. return {type: RESET}
  13. }
  14. 复制代码

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

src/redux/reducers/counter.js

  1. import {INCREMENT, DECREMENT, RESET} from '../actions/counter';
  2. /*
  3. * 初始化state
  4. */
  5. const initState = {
  6. count: 0
  7. };
  8. /*
  9. * reducer
  10. */
  11. export default function reducer(state = initState, action) {
  12. switch (action.type) {
  13. case INCREMENT:
  14. return {
  15. count: state.count + 1
  16. };
  17. case DECREMENT:
  18. return {
  19. count: state.count - 1
  20. };
  21. case RESET:
  22. return {count: 0};
  23. default:
  24. return state
  25. }
  26. }
  27. 复制代码

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

src/redux/reducers.js

  1. import counter from './reducers/counter';
  2. export default function combineReducers(state = {}, action) {
  3. return {
  4. counter: counter(state.counter, action)
  5. }
  6. }
  7. 复制代码

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

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

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

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

  1. import {createStore} from 'redux';
  2. import combineReducers from './reducers.js';
  3. let store = createStore(combineReducers);
  4. export default store;
  5. 复制代码

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

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

webpack.dev.config.js

  1. alias: {
  2. ...
  3. actions: path.join(__dirname, 'src/redux/actions'),
  4. reducers: path.join(__dirname, 'src/redux/reducers'),
  5. redux: path.join(__dirname, 'src/redux')
  6. }
  7. 复制代码

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

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

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

  1. cd src/pages
  2. mkdir Counter
  3. touch Counter/Counter.js
  4. 复制代码

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

  1. import React, {Component} from 'react';
  2. export default class Counter extends Component {
  3. render() {
  4. return (
  5. <div>
  6. <div>当前计数为(显示redux计数)</div>
  7. <button onClick={() => {
  8. console.log('调用自增函数');
  9. }}>自增
  10. </button>
  11. <button onClick={() => {
  12. console.log('调用自减函数');
  13. }}>自减
  14. </button>
  15. <button onClick={() => {
  16. console.log('调用重置函数');
  17. }}>重置
  18. </button>
  19. </div>
  20. )
  21. }
  22. }
  23. 复制代码

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

修改路由,添加Counter

src/router/router.js

  1. import React from 'react';
  2. import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
  3. import Home from 'pages/Home/Home';
  4. import Page1 from 'pages/Page1/Page1';
  5. import Counter from 'pages/Counter/Counter';
  6. const getRouter = () => (
  7. <Router>
  8. <div>
  9. <ul>
  10. <li><Link to="/">首页</Link></li>
  11. <li><Link to="/page1">Page1</Link></li>
  12. <li><Link to="/counter">Counter</Link></li>
  13. </ul>
  14. <Switch>
  15. <Route exact path="/" component={Home}/>
  16. <Route path="/page1" component={Page1}/>
  17. <Route path="/counter" component={Counter}/>
  18. </Switch>
  19. </div>
  20. </Router>
  21. );
  22. export default getRouter;
  23. 复制代码

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

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

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

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

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

npm install --save react-redux

src/pages/Counter/Counter.js

  1. import React, {Component} from 'react';
  2. import {increment, decrement, reset} from 'actions/counter';
  3. import {connect} from 'react-redux';
  4. class Counter extends Component {
  5. render() {
  6. return (
  7. <div>
  8. <div>当前计数为{this.props.counter.count}</div>
  9. <button onClick={() => this.props.increment()}>自增
  10. </button>
  11. <button onClick={() => this.props.decrement()}>自减
  12. </button>
  13. <button onClick={() => this.props.reset()}>重置
  14. </button>
  15. </div>
  16. )
  17. }
  18. }
  19. const mapStateToProps = (state) => {
  20. return {
  21. counter: state.counter
  22. }
  23. };
  24. const mapDispatchToProps = (dispatch) => {
  25. return {
  26. increment: () => {
  27. dispatch(increment())
  28. },
  29. decrement: () => {
  30. dispatch(decrement())
  31. },
  32. reset: () => {
  33. dispatch(reset())
  34. }
  35. }
  36. };
  37. export default connect(mapStateToProps, mapDispatchToProps)(Counter);
  38. 复制代码

下面我们需要传入store

找到src/index.js

  1. import React from 'react';
  2. import ReactDom from 'react-dom';
  3. import {AppContainer} from 'react-hot-loader';
  4. import {Provider} from 'react-redux';
  5. import store from './redux/store';
  6. import getRouter from 'router/router';
  7. /*初始化*/
  8. renderWithHotReload(getRouter());
  9. /*热更新*/
  10. if (module.hot) {
  11. module.hot.accept('./router/router', () => {
  12. const getRouter = require('router/router').default;
  13. renderWithHotReload(getRouter());
  14. });
  15. }
  16. function renderWithHotReload(RootElement) {
  17. ReactDom.render(
  18. <AppContainer>
  19. <Provider store={store}>
  20. {RootElement}
  21. </Provider>
  22. </AppContainer>,
  23. document.getElementById('app')
  24. )
  25. }
  26. 复制代码

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

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

  1. resolve: {
  2. alias: {
  3. ...
  4. redux: path.join(__dirname, 'src/redux')
  5. }
  6. }
  7. 复制代码

呐,上面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接口

  1. cd dist
  2. mkdir api
  3. cd api
  4. touch user.json
  5. 复制代码

dist/api/user.json

  1. {
  2. "msg": "phoebe",
  3. "name": "brickspert",
  4. "intro": "please give me a star"
  5. }
  6. 复制代码

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

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

找到src/redux/actions/userInfo.js

  1. export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";
  2. export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";
  3. export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";
  4. function getUserInfoRequest() {
  5. return {
  6. type: GET_USER_INFO_REQUEST
  7. }
  8. }
  9. function getUserInfoSuccess(userInfo) {
  10. return {
  11. type: GET_USER_INFO_SUCCESS,
  12. userInfo: userInfo
  13. }
  14. }
  15. function getUserInfoFail() {
  16. return {
  17. type: GET_USER_INFO_FAIL
  18. }
  19. }
  20. 复制代码

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

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

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

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

src/redux/reducers/userInfo.js

  1. import {GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL} from 'actions/userInfo';
  2. const initState = {
  3. isLoading: false,
  4. userInfo: {},
  5. errorMsg: ''
  6. };
  7. export default function reducer(state = initState, action) {
  8. switch (action.type) {
  9. case GET_USER_INFO_REQUEST:
  10. return {
  11. ...state,
  12. isLoading: true,
  13. userInfo: {},
  14. errorMsg: ''
  15. };
  16. case GET_USER_INFO_SUCCESS:
  17. return {
  18. ...state,
  19. isLoading: false,
  20. userInfo: action.userInfo,
  21. errorMsg: ''
  22. };
  23. case GET_USER_INFO_FAIL:
  24. return {
  25. ...state,
  26. isLoading: false,
  27. userInfo: {},
  28. errorMsg: '请求错误'
  29. };
  30. default:
  31. return state;
  32. }
  33. }
  34. 复制代码

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

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

src/redux/reducers.js

  1. import counter from 'reducers/counter';
  2. import userInfo from 'reducers/userInfo';
  3. export default function combineReducers(state = {}, action) {
  4. return {
  5. counter: counter(state.counter, action),
  6. userInfo: userInfo(state.userInfo, action)
  7. }
  8. }
  9. 复制代码

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

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

src/redux/actions/userInfo.js增加

  1. export function getUserInfo() {
  2. return function (dispatch) {
  3. dispatch(getUserInfoRequest());
  4. return fetch('http://localhost:8080/api/user.json')
  5. .then((response => {
  6. return response.json()
  7. }))
  8. .then((json) => {
  9. dispatch(getUserInfoSuccess(json))
  10. }
  11. ).catch(
  12. () => {
  13. dispatch(getUserInfoFail());
  14. }
  15. )
  16. }
  17. }
  18. 复制代码

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

npm install --save redux-thunk

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

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

src/redux/store.js

  1. import {createStore, applyMiddleware} from 'redux';
  2. import thunkMiddleware from 'redux-thunk';
  3. import combineReducers from './reducers.js';
  4. let store = createStore(combineReducers, applyMiddleware(thunkMiddleware));
  5. export default store;
  6. 复制代码

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

  1. cd src/pages
  2. mkdir UserInfo
  3. cd UserInfo
  4. touch UserInfo.js
  5. 复制代码

src/pages/UserInfo/UserInfo.js

  1. import React, {Component} from 'react';
  2. import {connect} from 'react-redux';
  3. import {getUserInfo} from "actions/userInfo";
  4. class UserInfo extends Component {
  5. render() {
  6. const {userInfo, isLoading, errorMsg} = this.props.userInfo;
  7. return (
  8. <div>
  9. {
  10. isLoading ? '请求信息中......' :
  11. (
  12. errorMsg ? errorMsg :
  13. <div>
  14. <p>用户信息:</p>
  15. <p>用户名:{userInfo.name}</p>
  16. <p>介绍:{userInfo.intro}</p>
  17. </div>
  18. )
  19. }
  20. <button onClick={() => this.props.getUserInfo()}>请求用户信息</button>
  21. </div>
  22. )
  23. }
  24. }
  25. export default connect((state) => ({userInfo: state.userInfo}), {getUserInfo})(UserInfo);
  26. 复制代码

紧接着就是添加路由

src/router/router.js

  1. import React from 'react';
  2. import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
  3. import Home from 'pages/Home/Home';
  4. import Page1 from 'pages/Page1/Page1';
  5. import Counter from 'pages/Counter/Counter';
  6. import UserInfo from 'pages/UserInfo/UserInfo';
  7. const getRouter = () => (
  8. <Router>
  9. <div>
  10. <ul>
  11. <li><Link to="/">首页</Link></li>
  12. <li><Link to="/page1">Page1</Link></li>
  13. <li><Link to="/counter">Counter</Link></li>
  14. <li><Link to="/userinfo">UserInfo</Link></li>
  15. </ul>
  16. <Switch>
  17. <Route exact path="/" component={Home}/>
  18. <Route path="/page1" component={Page1}/>
  19. <Route path="/counter" component={Counter}/>
  20. <Route path="/userinfo" component={UserInfo}/>
  21. </Switch>
  22. </div>
  23. </Router>
  24. );
  25. export default getRouter;
  26. 复制代码

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

combinReducers优化

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

src/redux/reducers.js

  1. import {combineReducers} from "redux";
  2. import counter from 'reducers/counter';
  3. import userInfo from 'reducers/userInfo';
  4. export default combineReducers({
  5. counter,
  6. userInfo
  7. });
  8. 复制代码

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增加

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

找到src/pages/Page1/Page1.js

  1. import React, {Component} from 'react';
  2. import './Page1.css';
  3. export default class Page1 extends Component {
  4. render() {
  5. return (
  6. <div className="page-box">
  7. this is page1~
  8. </div>
  9. )
  10. }
  11. }
  12. 复制代码

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

编译图片

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

webpack.dev.config.js rules增加

  1. {
  2. test: /\.(png|jpg|gif)$/,
  3. use: [{
  4. loader: 'url-loader',
  5. options: {
  6. limit: 8192
  7. }
  8. }]
  9. }
  10. 复制代码

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

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

  1. cd src/pages/Page1
  2. mkdir images
  3. 复制代码

将一张放到images文件夹中

修改代码,引入图片:

src/pages/Page1/Page1.js

  1. import React, {Component} from 'react';
  2. import './Page1.css';
  3. import image from './images/bg.jpg';
  4. export default class Page1 extends Component {
  5. render() {
  6. return (
  7. <div className="page-box">
  8. this is page1~
  9. <img src={image}/>
  10. </div>
  11. )
  12. }
  13. }
  14. 复制代码

效果图如下:

按需加载

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

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

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

首先添加依赖:

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

2.新建bundle.js

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

src/router/Bundle.js

  1. import React, {Component} from 'react'
  2. class Bundle extends Component {
  3. state = {
  4. // short for "module" but that's a keyword in js, so "mod"
  5. mod: null
  6. };
  7. componentWillMount() {
  8. this.load(this.props)
  9. }
  10. componentWillReceiveProps(nextProps) {
  11. if (nextProps.load !== this.props.load) {
  12. this.load(nextProps)
  13. }
  14. }
  15. load(props) {
  16. this.setState({
  17. mod: null
  18. });
  19. props.load((mod) => {
  20. this.setState({
  21. // handle both es imports and cjs
  22. mod: mod.default ? mod.default : mod
  23. })
  24. })
  25. }
  26. render() {
  27. return this.props.children(this.state.mod)
  28. }
  29. }
  30. export default Bundle;
  31. 复制代码

3.改造路由器

src/router/router.js

  1. import React from 'react';
  2. import {BrowserRouter as Router, Route, Switch, Link} from 'react-router-dom';
  3. import Bundle from './Bundle';
  4. import Home from 'bundle-loader?lazy&name=home!pages/Home/Home';
  5. import Page1 from 'bundle-loader?lazy&name=page1!pages/Page1/Page1';
  6. import Counter from 'bundle-loader?lazy&name=counter!pages/Counter/Counter';
  7. import UserInfo from 'bundle-loader?lazy&name=userInfo!pages/UserInfo/UserInfo';
  8. const Loading = function () {
  9. return <div>Loading...</div>
  10. };
  11. const createComponent = (component) => (props) => (
  12. <Bundle load={component}>
  13. {
  14. (Component) => Component ? <Component {...props} /> : <Loading/>
  15. }
  16. </Bundle>
  17. );
  18. const getRouter = () => (
  19. <Router>
  20. <div>
  21. <ul>
  22. <li><Link to="/">首页</Link></li>
  23. <li><Link to="/page1">Page1</Link></li>
  24. <li><Link to="/counter">Counter</Link></li>
  25. <li><Link to="/userinfo">UserInfo</Link></li>
  26. </ul>
  27. <Switch>
  28. <Route exact path="/" component={createComponent(Home)}/>
  29. <Route path="/page1" component={createComponent(Page1)}/>
  30. <Route path="/counter" component={createComponent(Counter)}/>
  31. <Route path="/userinfo" component={createComponent(UserInfo)}/>
  32. </Switch>
  33. </div>
  34. </Router>
  35. );
  36. export default getRouter;
  37. 复制代码

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

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

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

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

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

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

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

HtmlWebpackPlugin

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

现在开始执行命令:

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

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

  1. cd src
  2. touch index.html
  3. 复制代码

src/index.html

  1. <!doctype html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Document</title>
  6. </head>
  7. <body>
  8. <div id="app"></div>
  9. </body>
  10. </html>
  11. 复制代码

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

  1. var HtmlWebpackPlugin = require('html-webpack-plugin');
  2. plugins: [new HtmlWebpackPlugin({
  3. filename: 'index.html',
  4. template: path.join(__dirname, 'src/index.html')
  5. })],
  6. 复制代码

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

提取公共代码

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

话不多说,请往下看

webpack.dev.config.js

  1. var webpack = require('webpack');
  2. entry: {
  3. app: [
  4. 'react-hot-loader/patch',
  5. path.join(__dirname, 'src/index.js')
  6. ],
  7. vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
  8. }
  9. /*plugins*/
  10. new webpack.optimize.CommonsChunkPlugin({
  11. name: 'vendor'
  12. })
  13. 复制代码

这里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

  1. const path = require('path');
  2. var HtmlWebpackPlugin = require('html-webpack-plugin');
  3. var webpack = require('webpack');
  4. module.exports = {
  5. devtool: 'cheap-module-source-map',
  6. entry: {
  7. app: [
  8. path.join(__dirname, 'src/index.js')
  9. ],
  10. vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
  11. },
  12. output: {
  13. path: path.join(__dirname, './dist'),
  14. filename: '[name].[chunkhash].js',
  15. chunkFilename: '[name].[chunkhash].js'
  16. },
  17. module: {
  18. rules: [{
  19. test: /\.js$/,
  20. use: ['babel-loader'],
  21. include: path.join(__dirname, 'src')
  22. }, {
  23. test: /\.css$/,
  24. use: ['style-loader', 'css-loader']
  25. }, {
  26. test: /\.(png|jpg|gif)$/,
  27. use: [{
  28. loader: 'url-loader',
  29. options: {
  30. limit: 8192
  31. }
  32. }]
  33. }]
  34. },
  35. plugins: [
  36. new HtmlWebpackPlugin({
  37. filename: 'index.html',
  38. template: path.join(__dirname, 'src/index.html')
  39. }),
  40. new webpack.optimize.CommonsChunkPlugin({
  41. name: 'vendor'
  42. })
  43. ],
  44. resolve: {
  45. alias: {
  46. pages: path.join(__dirname, 'src/pages'),
  47. component: path.join(__dirname, 'src/component'),
  48. router: path.join(__dirname, 'src/router'),
  49. actions: path.join(__dirname, 'src/redux/actions'),
  50. reducers: path.join(__dirname, 'src/redux/reducers')
  51. }
  52. }
  53. };
  54. 复制代码

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

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

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

文件压缩

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

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

webpack.config.js

  1. const UglifyJSPlugin = require('uglifyjs-webpack-plugin')
  2. module.exports = {
  3. plugins: [
  4. new UglifyJSPlugin()
  5. ]
  6. }
  7. 复制代码

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

指定环境

原教程中解释到:

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

执行以下代码:

webpack.config.js

  1. module.exports = {
  2. plugins: [
  3. new webpack.DefinePlugin({
  4. 'process.env': {
  5. 'NODE_ENV': JSON.stringify('production')
  6. }
  7. })
  8. ]
  9. }
  10. 复制代码

可以发现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

  1. plugins: [
  2. new webpack.HashedModuleIdsPlugin(),
  3. new webpack.optimize.CommonsChunkPlugin({
  4. name: 'runtime'
  5. })
  6. ]
  7. 复制代码

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

public path

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

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

  1. output: {
  2. publicPath : '/'
  3. }
  4. 复制代码

打包优化

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

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

webpack.config.js

  1. const CleanWebpackPlugin = require('clean-webpack-plugin');
  2. plugins: [
  3. new CleanWebpackPlugin(['dist'])
  4. ]
  5. 复制代码

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

抽取css

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

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

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

webpack.config.js

  1. const ExtractTextPlugin = require("extract-text-webpack-plugin");
  2. module.exports = {
  3. module: {
  4. rules: [
  5. {
  6. test: /\.css$/,
  7. use: ExtractTextPlugin.extract({
  8. fallback: "style-loader",
  9. use: "css-loader"
  10. })
  11. }
  12. ]
  13. },
  14. plugins: [
  15. new ExtractTextPlugin({
  16. filename: '[name].[contenthash:5].css',
  17. allChunks: true
  18. })
  19. ]
  20. 复制代码

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

使用axios和middleware优化API请求

首先我们需要先安装axios

npm install --save axios

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

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

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

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

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

1.请求前dispatch REQUEST请求。

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

3.失败后dispatch FAIL请求。

好!!开始动手

  1. cd src/redux
  2. mkdir middleware
  3. cd middleware
  4. touch promiseMiddleware.js
  5. 复制代码

src/redux/middleware/promiseMiddleware.js

  1. import axios from 'axios';
  2. export default store => next => action => {
  3. const {dispatch, getState} = store;
  4. /*如果dispatch来的是一个function,此处不做处理,直接进入下一级*/
  5. if (typeof action === 'function') {
  6. action(dispatch, getState);
  7. return;
  8. }
  9. /*解析action*/
  10. const {
  11. promise,
  12. types,
  13. afterSuccess,
  14. ...rest
  15. } = action;
  16. /*没有promise,证明不是想要发送ajax请求的,就直接进入下一步啦!*/
  17. if (!action.promise) {
  18. return next(action);
  19. }
  20. /*解析types*/
  21. const [REQUEST,
  22. SUCCESS,
  23. FAILURE] = types;
  24. /*开始请求的时候,发一个action*/
  25. next({
  26. ...rest,
  27. type: REQUEST
  28. });
  29. /*定义请求成功时的方法*/
  30. const onFulfilled = result => {
  31. next({
  32. ...rest,
  33. result,
  34. type: SUCCESS
  35. });
  36. if (afterSuccess) {
  37. afterSuccess(dispatch, getState, result);
  38. }
  39. };
  40. /*定义请求失败时的方法*/
  41. const onRejected = error => {
  42. next({
  43. ...rest,
  44. error,
  45. type: FAILURE
  46. });
  47. };
  48. return promise(axios).then(onFulfilled, onRejected).catch(error => {
  49. console.error('MIDDLEWARE ERROR:', error);
  50. onRejected(error)
  51. })
  52. }
  53. 复制代码

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

  1. import {createStore, applyMiddleware} from 'redux';
  2. import combineReducers from './reducers.js';
  3. import promiseMiddleware from './middleware/promiseMiddleware'
  4. let store = createStore(combineReducers, applyMiddleware(promiseMiddleware));
  5. export default store;
  6. 复制代码

修改src/redux/actions/userInfo.js

  1. export const GET_USER_INFO_REQUEST = "userInfo/GET_USER_INFO_REQUEST";
  2. export const GET_USER_INFO_SUCCESS = "userInfo/GET_USER_INFO_SUCCESS";
  3. export const GET_USER_INFO_FAIL = "userInfo/GET_USER_INFO_FAIL";
  4. export function getUserInfo() {
  5. return {
  6. types: [GET_USER_INFO_REQUEST, GET_USER_INFO_SUCCESS, GET_USER_INFO_FAIL],
  7. promise: client => client.get(`http://localhost:8080/api/user.json`)
  8. }
  9. }
  10. 复制代码

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

接下来:

修改src/redux/reducers/userInfo.js

  1. case GET_USER_INFO_SUCCESS:
  2. return {
  3. ...state,
  4. isLoading: false,
  5. userInfo: action.result.data,
  6. errorMsg: ''
  7. };
  8. 复制代码

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

  1. const path = require('path');
  2. const HtmlWebpackPlugin = require('html-webpack-plugin');
  3. const webpack = require('webpack');
  4. commonConfig = {
  5. entry: {
  6. app: [
  7. path.join(__dirname, 'src/index.js')
  8. ],
  9. vendor: ['react', 'react-router-dom', 'redux', 'react-dom', 'react-redux']
  10. },
  11. output: {
  12. path: path.join(__dirname, './dist'),
  13. filename: '[name].[chunkhash].js',
  14. chunkFilename: '[name].[chunkhash].js',
  15. publicPath: "/"
  16. },
  17. module: {
  18. rules: [{
  19. test: /\.js$/,
  20. use: ['babel-loader?cacheDirectory=true'],
  21. include: path.join(__dirname, 'src')
  22. }, {
  23. test: /\.(png|jpg|gif)$/,
  24. use: [{
  25. loader: 'url-loader',
  26. options: {
  27. limit: 8192
  28. }
  29. }]
  30. }]
  31. },
  32. plugins: [
  33. new HtmlWebpackPlugin({
  34. filename: 'index.html',
  35. template: path.join(__dirname, 'src/index.html')
  36. }),
  37. new webpack.HashedModuleIdsPlugin(),
  38. new webpack.optimize.CommonsChunkPlugin({
  39. name: 'vendor'
  40. }),
  41. new webpack.optimize.CommonsChunkPlugin({
  42. name: 'runtime'
  43. })
  44. ],
  45. resolve: {
  46. alias: {
  47. pages: path.join(__dirname, 'src/pages'),
  48. components: path.join(__dirname, 'src/components'),
  49. router: path.join(__dirname, 'src/router'),
  50. actions: path.join(__dirname, 'src/redux/actions'),
  51. reducers: path.join(__dirname, 'src/redux/reducers')
  52. }
  53. }
  54. };
  55. module.exports = commonConfig;
  56. 复制代码

webpack.dev.config.js

  1. const merge = require('webpack-merge');
  2. const path = require('path');
  3. const commonConfig = require('./webpack.common.config.js');
  4. const devConfig = {
  5. devtool: 'inline-source-map',
  6. entry: {
  7. app: [
  8. 'react-hot-loader/patch',
  9. path.join(__dirname, 'src/index.js')
  10. ]
  11. },
  12. output: {
  13. /*这里本来应该是[chunkhash]的,但是由于[chunkhash]和react-hot-loader不兼容。只能妥协*/
  14. filename: '[name].[hash].js'
  15. },
  16. module: {
  17. rules: [{
  18. test: /\.css$/,
  19. use: ["style-loader", "css-loader"]
  20. }]
  21. },
  22. devServer: {
  23. contentBase: path.join(__dirname, './dist'),
  24. historyApiFallback: true,
  25. host: '0.0.0.0',
  26. }
  27. };
  28. module.exports = merge({
  29. customizeArray(a, b, key) {
  30. /*entry.app不合并,全替换*/
  31. if (key === 'entry.app') {
  32. return b;
  33. }
  34. return undefined;
  35. }
  36. })(commonConfig, devConfig);
  37. 复制代码

webpack.config.js

  1. const merge = require('webpack-merge');
  2. const webpack = require('webpack');
  3. const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
  4. const CleanWebpackPlugin = require('clean-webpack-plugin');
  5. const ExtractTextPlugin = require("extract-text-webpack-plugin");
  6. const commonConfig = require('./webpack.common.config.js');
  7. const publicConfig = {
  8. devtool: 'cheap-module-source-map',
  9. module: {
  10. rules: [{
  11. test: /\.css$/,
  12. use: ExtractTextPlugin.extract({
  13. fallback: "style-loader",
  14. use: "css-loader"
  15. })
  16. }]
  17. },
  18. plugins: [
  19. new CleanWebpackPlugin(['dist/*.*']),
  20. new UglifyJSPlugin(),
  21. new webpack.DefinePlugin({
  22. 'process.env': {
  23. 'NODE_ENV': JSON.stringify('production')
  24. }
  25. }),
  26. new ExtractTextPlugin({
  27. filename: '[name].[contenthash:5].css',
  28. allChunks: true
  29. })
  30. ]
  31. };
  32. module.exports = merge(commonConfig, publicConfig);
  33. 复制代码

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家自动化/article/detail/447333
推荐阅读
相关标签
  

闽ICP备14008679号