加载中...

Webpack和React配合开发


经过两章的学习,大家都了解Webpack的基本和高阶一点的用法,那么最后一章来说说和现在前端届的明星React一起合作的故事。 本文适合对React有一定了解的朋友,如果还不知道React是啥,请前往react教程习一下基本的概念和例子。

什么是React

React是一个由Facebook开发的library,它的口号是“A JAVASCRIPT LIBRARY FOR BUILDING USER INTERFACES”,用于构建用户界面的库。他的特点是仅仅关注于UI层,和其他的一系列大型的框架(Ember.js和Angular.js等)的理念不同,上述两个框架给你提供了一整套的解决方案。还有一个重大的改革就是React采用了一种独特的技术被称为virtual DOM ,和其他传统框架的DOM渲染过程不同,提供了更高性能的渲染,

如果你对virtual DOM感兴趣,可以看看这个库:Matt-Esch/virtual-dom

如果你想深入了解React 请前往官方网站React.js

为什么要用React和Webpack配合在一起

现在有很多的构建工具,采用Webpack的原因就是简单易用,容易上手,并且对React完美的支持,比如说JSX语法,使用babel-loader以后可以很完美的支持,支持Hot Loading Component,让你不用忍受页面刷来刷去的痛苦,还有就是配置简单,loaders的魔力是没人能够拒绝的。

如果你的下一个项目采用React做为View的展示框架,不妨再使用Webpack一起搭建一个开发环境。

配置React和Webpack

现在开始配置,把两个库的魔力融合在一起,在这里同样是实践出真知,来做一个小项目一步步完成这部教程,项目很简单,不是已经被别人做烂了的Todo了,这个项目可以搜索github上的用户名称,并且展示出来搜索出来的信息。 先画一个原型图:

585b74a2d4fbd.png

从上图可以看出 项目有两个components,一个是搜索框,一个是展示列表,列表有几种状态,初始状态,正在读取数据,数据读取完毕展示列表。

根据原型图,项目应该有如下简单的结构

程序结构

  • app/
    • index.jsx(程序入口)
    • components/(组件文件夹)
      • plist.jsx(展示用户列表)
      • search.jsx(搜索框组件)
    • utils/(一些小工具)
  • package.json
  • webpack.config.js

配置Babel让React支持ES6

在前面的文章里面我们多次提到了Babel,对于React来说,Babel更是一个完美的伙伴。

同时React也做了更新,更好的支持了ES6的写法。

如果你现在还是用这种方法写你的component


  1. var List = React.createClass({
  2. getInitialState: function() {
  3. return ['a', 'b', 'c']
  4. },
  5. render: function() {
  6. return ( ... );
  7. }
  8. });

那么是时候采用ES6的写法了 React ES6 classes 这种写法看起来更棒,可读性也更强。一个直观的发现就是不用写getInitialState方法了,可以直接在constructor里面定义this.state的值,前端一直都是在高速前进的,采用最新的标准,使用最新的技术,这应该是每个人的追求,对吧。


  1. import React from 'react';
  2. class List extends React.Component {
  3. constructor() {
  4. super();
  5. this.state = ['a', 'b', 'c'];
  6. }
  7. render() {
  8. return (...);
  9. }
  10. }

安装babel-loader

为了让上述的写法变成现实。需要安装bable和babel的两个preset:


  1. //install babel core
  2. npm install babel-loader --save-dev
  3. //install es6 support and react support
  4. npm install babel-preset-es2015 babel-preset-react --save-dev

这里安装了babel的主体和两个babel的preset, 什么是preset呢,你可以把preset看成一个包,里面有各种各样一系列的插件。

  • babel-preset-es2015 es6语法包,有了这个,你的代码可以随意的使用es6的新特性啦,const,箭头操作符等信手拈来。
  • babel-preset-react react语法包,这个包,是专门作为react的优化,让你在代码中可以使用React ES6 classes的写法,同时直接支持JSX语法格式。 更多详情,请到Babel Plugins看一下这两个语法包都包括什么插件,每个插件都有什么特性。

配置webpack

像我们在前面做到一样,创建webpack.config.js

  1. var path = require('path');
  2. var webpack = require('webpack');
  3. var HtmlwebpackPlugin = require('html-webpack-plugin');
  4.  
  5. var ROOT_PATH = path.resolve(__dirname);
  6. var APP_PATH = path.resolve(ROOT_PATH, 'app');
  7. var BUILD_PATH = path.resolve(ROOT_PATH, 'build');
  8.  
  9. module.exports= {
  10. entry: {
  11. app: path.resolve(APP_PATH, 'index.jsx')
  12. },
  13. output: {
  14. path: BUILD_PATH,
  15. filename: 'bundle.js'
  16. },
  17. //enable dev source map
  18. devtool: 'eval-source-map',
  19. //enable dev server
  20. devServer: {
  21. historyApiFallback: true,
  22. hot: true,
  23. inline: true,
  24. progress: true
  25. },
  26. //babel重要的loader在这里
  27. module: {
  28. loaders: [
  29. {
  30. test: /\.jsx?$/,
  31. loader: 'babel',
  32. include: APP_PATH,
  33. query: {
  34. //添加两个presents 使用这两种presets处理js或者jsx文件
  35. presets: ['es2015', 'react']
  36. }
  37. }
  38. ]
  39. },
  40. plugins: [
  41. new HtmlwebpackPlugin({
  42. title: 'My first react app'
  43. })
  44. ]
  45. }
这里还需要添加一个resolve的参数,把jsx这种扩展名添加进去,这样就可以在js中import加载jsx这种扩展名的脚本
  1. ...
  2. resolve: {
  3. extensions: ['', '.js', '.jsx']
  4. },
  5. ...

npm中添加webpack启动命令

就像第一章里面介绍的,把命令添加到package.json里面。

package.json

  1. ...
  2. "scripts": {
  3. "dev": "webpack-dev-server --progress --profile --colors --hot",
  4. "build": "webpack --progress --profile --colors",
  5. "test": "karma start"
  6. },
  7. ...

添加首页

安装React和React-Dom


  1. npm install react react-dom --save

让样式好看点,添加一个Bootstrap 4


  1. npm install bootstrap@4.0.0-alpha.2 --save-dev

为了处理scss 需要添加sass loader 第一节讲过 大家都没忘记吧?


  1. npm install css-loader style-loader sass-loader node-sass --save-dev

在webpack.config.js中添加处理的loader


  1. ...
  2. module: {
  3. loaders: [
  4. ...
  5. {
  6. test: /\.scss$/,
  7. loaders: ['style', 'css', 'sass']
  8. }
  9. ...
  10. ]
  11. },
  12. ...

index.jsx 使用es6的格式

  1. import '../node_modules/bootstrap/scss/bootstrap.scss';
  2. import React from 'react';
  3. import ReactDOM from 'react-dom';
  4.  
  5. class App extends React.Component{
  6.     constructor() {
  7.         super();
  8.     }
  9.     render() {
  10.         //JSX here!
  11.         return (
  12.           <div className="container">
  13.             <section className="jumbotron">
  14.               <h3 className="jumbotron-heading">Search Github Users</h3>
  15.             </section>
  16.           </div>
  17.         )
  18.     }
  19. };
  20.  
  21. const app = document.createElement('div');
  22. document.body.appendChild(app);
  23. ReactDOM.render(<App />, app);

再用webpack运行就可以看到结果了。


  1. npm run dev

大标题出现了。伟大的Babel和Bootstrap都被正确使用了。

585b74a383eb3.png

添加React Transform支持

更新上面所说的React Hot Loading已经过时了,开发者也宣布已经停止维护,现在有一个更强大的babel plugin: React Transform来代替他。

现在每次修改一个component的代码,页面都会重新刷新,这会造成一个很不爽的问题,程序会丢失状态,当然现在在简单的程序中这个完全无所谓,但是假如程序变得越来越复杂,想要返回这种状态你可能又要经历一系列的点击等操作,会耗费一些时间。

隆重推出Babel-plugin-react-transform 名字挺长, 看起来挺吓人,其实你就可以想象用这个东西可以实时的对你的React Component做各种处理,它是基于Babel Plugin。 废话不多说,花点时间感受一下它是怎么玩的。

先安装


  1. npm install --save-dev babel-plugin-react-transform

这是个基本的架子,可以通过它完成各种transform,如果想实现Hot Module Replacement (说白了就是页面不刷新,直接替换修改的Component),再安装一个transform.


  1. npm install --save-dev react-transform-hmr

依赖就安装完毕了。

如果我们还要再来一个在页面上直接显示catch到的错误的transform,(不想打开console看到底有啥错误,直接显示在页面上多好),简单!再安装一个transform:


  1. npm install --save-dev react-transform-catch-errors redbox-react

依赖安装完毕,配置Babel,上面说到把Babel的配置都写在webpack.config.js中,这是一个不好的方法,因为随着Babel的config越来越多,那样会显得非常臃肿,把babel的配置分离出来。

在跟目录新建一个Babel的配置文件: .babelrc, 把原来的配置写进去。


  1. { "presets": ["react", "es2015"] }

现在在webpack里面的config就可以简化了,把那些query参数都删掉,简单了很多:


  1. ... module: { loaders: [ { test: /\.jsx?$/, loader: 'babel', include: APP_PATH }] }, ...

要让新建的两个transform生效,只需再安装一个present。


  1. npm install babel-preset-react-hmre --save-dev

安装完毕,将支持HMR和Catch Error的present添加到.babelrc

  1. ...
  2. module: {
  3. loaders: [
  4. {
  5. test: /\.jsx?$/,
  6. loader: 'babel',
  7. include: APP_PATH
  8. }]
  9. },
  10. ...

配置完毕! 启动npm run dev

看看效果。然后随便改动h1标题里面的文字,发现页面没有刷新,但是自动内容自动改变了,在render方法中故意弄一些错误,出现了可爱的红色错误提示,大功告成~~~

585b74a3a4fa7.png

继续项目

完美配置了Webpack和React的开发环境,现在让我们把小项目完成吧。

根据上面的图 把项目分为两个主要的component,一个是Search Box用来让用户填写用户名, 一个是List,用来展示搜索到用户的列表.

Search Box非常的简单 就是两个input,当用户点击Search的时候,把输入的名字发送到List的组件里面。

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. export default class Search extends React.Component {
  4.   constructor(props) {
  5.     super(props);
  6.     this.handleSearch = this.handleSearch.bind(this);
  7.   }
  8.   handleSearch() {
  9.      let name = ReactDOM.findDOMNode(this.refs.name).value;
  10.      if (name === '') {
  11.       return;
  12.     }
  13.     this.props.sendAction(name);
  14.   }
  15.   render() {
  16.     return (
  17.       <div>
  18.         <input type="text" ref="name" placeholder="enter the name you wanna search"/>
  19.         <button onClick={this.handleSearch}>Search</button>
  20.       </div>    
  21.      )
  22.   }
  23. }
List的目标是接受props传进来的name参数,发起一个ajax请求,然后用返回值更新整个列表,这个列表有几种状态 初始状态,第一次渲染返回一行提示文字。 Loading的状态,当有props传进来的时候,发起ajax请求,并且显示一个loading的提示。 完成状态,当请求完毕,渲染列表并且显示出来。

这个列表有不同的生命周期,这里简单介绍一下React Component的生命周期.

初始化的生命周期

585b74a3dc9db.png

如上图所描述的一样,当实例化一个Component的时候,会依次调用这些方法,所以在render完后做什么操作的话,可以把代码放到componentDidMount中。

当props属性发生变化以后的生命周期

585b74a40f847.png

如上图描述,当这个组件的props发送变化以后,会依次调用这些方法,当大体清楚了整个lifeCycle,是时候继续列表组件了。

  1. import React from 'react';
  2. //自定义了一个ajax的方法,非常简单,支持promise
  3. import {get} from '../utils/ajax';
  4.  
  5. export default class Plist extends React.Component {
  6.  
  7.   constructor(props) {
  8.     super(props);
  9.     this.state = {"loading":false, "list": []};
  10.   }
  11.   //当初次渲染完毕 设置该组件的属性firstView为true
  12.   componentDidMount() {
  13.     this.setState({"firstView": true});
  14.   }
  15.   //当传入的props有变化,请注意看上面第二张图,就是时候发起请求 更新列表的内容了
  16.   componentWillReceiveProps(nextProps) {
  17.     let keyword = nextProps.keyword;
  18.     //loading设为true,firstView设为false
  19.     this.setState({"loading": true, 'firstView': false});
  20.     let url = `https://api.github.com/search/users?q=${keyword}`;
  21.     //发起ajax请求
  22.     get(url).then((data) => {
  23.       //更新本组件的state
  24.       this.setState({"loading":false, "list": data.items});
  25.     }).catch((error) => {
  26.       console.error(error);
  27.     });
  28.   }
  29.  
  30.   render() {
  31.     const imgStyle = {
  32.       width: '50px'
  33.     }
  34.     //添加一些if else的判断,用来展示不同的内容
  35.     if (this.state.firstView) {
  36.       return (
  37.         <h2>Enter name to search</h2>
  38.       )
  39.     }
  40.     if (this.state.loading) {
  41.       return (
  42.         <h2>Loading result...</h2>
  43.       );
  44.     } else {
  45.       if (this.state.list.length === 0) {
  46.         return (
  47.           <h2>No result.</h2>
  48.         )
  49.       } else {
  50.         return (
  51.           <div className="row">
  52.             {this.state.list.map(people=>{
  53.               return (
  54.                 <div className="card">
  55.                   <img src={people.avatar_url} style={imgStyle}/>
  56.                   <p className="card-text">
  57.                     {people.login}
  58.                   </p>
  59.                 </div>
  60.               )
  61.             })}
  62.          </div>       
  63.         );
  64.       }
  65.     }
  66.   }
  67. }
最后把两个组件引用到App里面
  1. import '../node_modules/bootstrap/scss/bootstrap.scss';
  2. import React from 'react';
  3. import Search from './components/search';
  4. import Plist from './components/plist';
  5.  
  6. class App extends React.Component {
  7.  
  8.   constructor(props) {
  9.     super(props);
  10.     this.state = {"keyword": ""};
  11.     this.refreshKeyword = this.refreshKeyword.bind(this);
  12.   }
  13.  
  14.   refreshKeyword(name) {
  15.     this.setState({"keyword": name});
  16.   }
  17.  
  18.   render() {
  19.     return (
  20.       <div className="container">
  21.         <section className="jumbotron">
  22.           <h3 className="jumbotron-heading">Search Github Users</h3>
  23.           <Search sendAction={this.refreshKeyword}/>
  24.         </section>
  25.  
  26.         <Plist keyword={this.state.keyword}/>
  27.       </div>    
  28.     );
  29.   }
  30. }
大功告成!现在让webpack跑起来,看看成果。随便搜索一个关键词试一试:

没有搜索的初始化界面

585b74a43e623.png

搜索以后的结果


总结

这一节其实没有多少新的东西 经过前两节的学习 我们应该很容易自己就能做到React的配置了。这里我们使用一个小项目,更好的帮助大家了解。Webpack给React带来更多的便利。希望以后再做React的项目的时候,不妨采用Webpack来进行配合。由于篇幅的限制,这里没有介绍使用karma和webpack测试React Component,以后会专门介绍。


还没有评论.