轻松入门React和Webpack

小广告:更多内容能够看个人博客读书笔记javascript

最近在学习React.js,以前都是直接用最原生的方式去写React代码,发现组织起来特别麻烦,以前听人说用Webpack组织React组件驾轻就熟,就花了点时间学习了一下,收获颇丰css

说说React

一个组件,有本身的结构,有本身的逻辑,有本身的样式,会依赖一些资源,会依赖某些其余组件。好比平常写一个组件,比较常规的方式:html

  • 经过前端模板引擎定义结构
  • JS文件中写本身的逻辑
  • CSS中写组件的样式
  • 经过RequireJS、SeaJS这样的库来解决模块之间的相互依赖

那么在React中是什么样子呢?前端

结构和逻辑

在React的世界里,结构和逻辑交由JSX文件组织,React将模板内嵌到逻辑内部,实现了一个JS代码和HTML混合的JSX。java

结构

在JSX文件中,能够直接经过React.createClass来定义组件:node

javascriptvar CustomComponent = React.creatClass({
    render: function(){
        return (<div className="custom-component"></div>);
    }
});

经过这种方式能够很方便的定义一个组件,组件的结构定义在render函数中,但这并非简单的模板引擎,咱们能够经过js方便、直观的操控组件结构,好比我想给组件增长几个节点:react

javascriptvar CustomComponent = React.creatClass({
    render: function(){
        var $nodes = ['h','e','l','l','o'].map(function(str){
            return (<span>{str}</span>);
        });
        return (<div className="custom-component">{$nodes}</div>);
    }
});

经过这种方式,React使得组件拥有灵活的结构。那么React又是如何处理逻辑的呢?webpack

逻辑

写过前端组件的人都知道,组件一般首先须要相应自身DOM事件,作一些处理。必要时候还须要暴露一些外部接口,那么React组件要怎么作到这两点呢?git

事件响应

好比我有个按钮组件,点击以后须要作一些处理逻辑,那么React组件大体上长这样:程序员

javascriptvar ButtonComponent = React.createClass({
    render: function(){
        return (<button>屠龙宝刀,点击就送</button>);
    }
});

点击按钮应当触发相应地逻辑,一种比较直观的方式就是给button绑定一个onclick事件,里面就是须要执行的逻辑了:

javascriptfunction getDragonKillingSword() {
    //送宝刀
}
var ButtonComponent = React.createClass({
    render: function(){
        return (<button onclick="getDragonKillingSword()">屠龙宝刀,点击就送</button>);
    }
});

但事实上getDragonKillingSword()的逻辑属于组件内部行为,显然应当包装在组件内部,因而在React中就能够这么写:

javascriptvar ButtonComponent = React.createClass({
    getDragonKillingSword: function(){
        //送宝刀
    },
    render: function(){
        return (<button onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
    }
});

这样就实现内部事件的响应了,那若是须要暴露接口怎么办呢?

暴露接口

事实上如今getDragonKillingSword已是一个接口了,若是有一个父组件,想要调用这个接口怎么办呢?

父组件大概长这样:

javascriptvar ImDaddyComponent = React.createClass({
    render: function(){
        return (
            <div>
                //其余组件
                <ButtonComponent />
                //其余组件
            </div>
        );
    }
});

那么若是想手动调用组件的方法,首先在ButtonComponent上设置一个ref=""属性来标记一下,好比这里把子组件设置成<ButtonComponent ref="getSwordButton"/>,那么在父组件的逻辑里,就能够在父组件本身的方法中经过这种方式来调用接口方法:

javascriptthis.refs.getSwordButton.getDragonKillingSword();

看起来屌屌哒~那么问题又来了,父组件但愿本身可以按钮点击时调用的方法,那该怎么办呢?

配置参数

父组件能够直接将须要执行的函数传递给子组件:

javascript<ButtonComponent clickCallback={this.getSwordButtonClickCallback}/>

而后在子组件中调用父组件方法:

javascriptvar ButtonComponent = React.createClass({
    render: function(){
        return (<button onClick={this.props.clickCallback}>屠龙宝刀,点击就送</button>);
    }
});

子组件经过this.props可以获取在父组件建立子组件时传入的任何参数,所以this.props也常被当作配置参数来使用

屠龙宝刀每一个人只能领取一把,按钮点击一下就应该灰掉,应当在子组件中增长一个是否点击过的状态,这又应当处理呢?

组件状态

在React中,每一个组件都有本身的状态,能够在自身的方法中经过this.state取到,而初始状态则经过getInitialState()方法来定义,好比这个屠龙宝刀按钮组件,它的初始状态应该是没有点击过,因此getInitialState方法里面应当定义初始状态clicked: false。而在点击执行的方法中,应当修改这个状态值为click: true

javascriptvar ButtonComponent = React.createClass({
    getInitialState: function(){
        //肯定初始状态
        return {
            clicked: false
        };
    },
    getDragonKillingSword: function(){
        //送宝刀

        //修改点击状态
        this.setState({
            clicked: true
        });
    },
    render: function(){
        return (<button onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
    }
});

这样点击状态的维护就完成了,那么render函数中也应当根据状态来维护节点的样式,好比这里将按钮设置为disabled,那么render函数就要添加相应的判断逻辑:

javascriptrender: function(){
    var clicked = this.state.clicked;
    if(clicked)
        return (<button disabled="disabled" onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
    else 
        return (<button onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
}

小节

这里简单介绍了经过JSX来管理组件的结构和逻辑,事实上React给组件还定义了不少方法,以及组件自身的生命周期,这些都使得组件的逻辑处理更增强大

资源加载

CSS文件定义了组件的样式,如今的模块加载器一般都可以加载CSS文件,若是不能通常也提供了相应的插件。事实上CSS、图片能够看作是一种资源,由于加载过来后通常不须要作什么处理。

React对这一方面并无作特别的处理,虽然它提供了Inline Style的方式把CSS写在JSX里面,但估计没有多少人会去尝试,毕竟如今CSS样式已经再也不只是简单的CSS文件了,一般都会去用Less、Sass等预处理,而后再用像postcss、myth、autoprefixer、cssmin等等后处理。资源加载通常也就简单粗暴地使用模块加载器完成了

组件依赖

组件依赖的处理通常分为两个部分:组件加载和组件使用

组件加载

React没有提供相关的组件加载方法,依旧须要经过<script>标签引入,或者使用模块加载器加载组件的JSX和资源文件。

组件使用

若是细心,就会发现其实以前已经有使用的例子了,要想在一个组件中使用另一个组件,好比在ParentComponent中使用ChildComponent,就只须要在ParentComponentrender()方法中写上<ChildComponent />就好了,必要的时候还能够传些参数。

疑问

到这里就会发现一个问题,React除了只处理告终构和逻辑,资源也无论,依赖也无论。是的,React将近两万行代码,连个模块加载器都没有提供,更与Angularjs,jQuery等不一样的是,他还不带啥脚手架...没有Ajax库,没有Promise库,要啥啥没有...

虚拟DOM

那它为啥这么大?由于它实现了一个虚拟DOM(Virtual DOM)。虚拟DOM是干什么的?这就要从浏览器自己讲起

如咱们所知,在浏览器渲染网页的过程当中,加载到HTML文档后,会将文档解析并构建DOM树,而后将其与解析CSS生成的CSSOM树一块儿结合产生爱的结晶——RenderObject树,而后将RenderObject树渲染成页面(固然中间可能会有一些优化,好比RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操做DOM结构,渲染引擎会暴露一些接口供JavaScript调用。因为这两块相互分离,通讯是须要付出代价的,所以JavaScript调用DOM提供的接口性能不咋地。各类性能优化的最佳实践也都在尽量的减小DOM操做次数。

而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大体上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又经过在这个虚拟DOM上实现了一个 diff 算法找出最小变动,再把这些变动写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,并且因为减小了实际DOM操做次数,性能会有较大提高

道理我都懂,但是为何咱们没有模块加载器?

因此就须要Webpack了

说说Webpack

什么是Webpack?

事实上它是一个打包工具,而不是像RequireJS或SeaJS这样的模块加载器,经过使用Webpack,可以像Node.js同样处理依赖关系,而后解析出模块之间的依赖,将代码打包

安装Webpack

首先得有Node.js

而后经过npm install -g webpack安装webpack,固然也能够经过gulp来处理webpack任务,若是使用gulp的话就npm install --save-dev gulp-webpack

配置Webpack

Webpack的构建过程须要一个配置文件,一个典型的配置文件大概就是这样

javascriptvar webpack = require('webpack');
var commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');

module.exports = {
    entry: {
        entry1: './entry/entry1.js',
        entry2: './entry/entry2.js'
    },
    output: {
        path: __dirname,
        filename: '[name].entry.js'
    },
    resolve: {
        extensions: ['', '.js', '.jsx']
    },
    module: {
        loaders: [{
            test: /\.js$/,
            loader: 'babel-loader'
        }, {
            test: /\.jsx$/,
            loader: 'babel-loader!jsx-loader?harmony'
        }]
    },
    plugins: [commonsPlugin]
};

这里对Webpack的打包行为作了配置,主要分为几个部分:

  • entry:指定打包的入口文件,每有一个键值对,就是一个入口文件
  • output:配置打包结果,path定义了输出的文件夹,filename则定义了打包结果文件的名称,filename里面的[name]会由entry中的键(这里是entry1和entry2)替换
  • resolve:定义了解析模块路径时的配置,经常使用的就是extensions,能够用来指定模块的后缀,这样在引入模块时就不须要写后缀了,会自动补全
  • module:定义了对模块的处理逻辑,这里能够用loaders定义了一系列的加载器,以及一些正则。当须要加载的文件匹配test的正则时,就会调用后面的loader对文件进行处理,这正是webpack强大的缘由。好比这里定义了凡是.js结尾的文件都是用babel-loader作处理,而.jsx结尾的文件会先通过jsx-loader处理,而后通过babel-loader处理。固然这些loader也须要经过npm install安装
  • plugins: 这里定义了须要使用的插件,好比commonsPlugin在打包多个入口文件时会提取出公用的部分,生成common.js

固然Webpack还有不少其余的配置,具体能够参照它的配置文档

执行打包

若是经过npm install -g webpack方式安装webpack的话,能够经过命令行直接执行打包命令,好比这样:

shell$webpack --config webpack.config.js

这样就会读取当前目录下的webpack.config.js做为配置文件执行打包操做

若是是经过gulp插件gulp-webpack,则能够在gulpfile中写上gulp任务:

javascriptvar gulp = require('gulp');
var webpack = require('gulp-webpack');
var webpackConfig = require('./webpack.config');
gulp.task("webpack", function() {
    return gulp
        .src('./')
        .pipe(webpack(webpackConfig))
        .pipe(gulp.dest('./build'));
});

组件编写

使用Babel提高逼格

Webpack使得咱们可使用Node.js的CommonJS规范来编写模块,好比一个简单的Hello world模块,就能够这么处理:

javascriptvar React = require('react');

var HelloWorldComponent = React.createClass({
    displayName: 'HelloWorldComponent',
    render: function() {
        return (

<div>Hello world</div>

);
    }
});

module.exports = HelloWorldComponent;

等等,这和以前的写法没啥差异啊,依旧没有逼格...程序员敲码要有geek范,要逼格than逼格,这太low了。如今都ES6了,React的代码也要写ES6,babel-loader就是干这个的。Babel可以将ES6代码转换成ES5。首先须要经过命令npm install --save-dev babel-loader来进行安装,安装完成后就可使用了,一种使用方式是以前介绍的在webpack.config.js的loaders中配置,另外一种是直接在代码中使用,好比:

javascriptvar HelloWorldComponent = require('!babel!jsx!./HelloWorldComponent');

那咱们应当如何使用Babel提高代码的逼格呢?改造一下以前的HelloWorld代码吧:

javascriptimport React from 'react';

export default class HelloWorldComponent extends React.Component {
    constructor() {
        super();
        this.state = {};
    }
    render() {
        return (

<div>Hello World</div>

);
    }
}

这样在其余组件中须要引入HelloWorldComponent组件,就只要就能够了:

javascriptimport HelloWorldComponent from './HelloWorldComponent'

怎么样是否是更有逼格了?经过import引入模块,还能够直接定义类和类的继承关系,这里也再也不须要getInitialState了,直接在构造函数constructor中用this.state = xxx就行了

Babel带来的固然还不止这些,在其帮助下还能尝试不少优秀的ES6特性,好比箭头函数,箭头函数的特色就是内部的this和外部保持一致,今后能够和that_this说再见了

javascript['H', 'e', 'l', 'l', 'o'].map((c) => {
    return (<span>{c}</span>);
});

其余还有不少,具体能够参照Babel的学习文档

样式编写

我是一个强烈地Less依赖患者,脱离了Less直接写CSS就会出现四肢乏力、不想干活、心情烦躁等现象,并且还不喜欢在写Less时候加前缀,日常都是gulp+less+autoprefixer直接处理的,那么在Webpack组织的React组件中要怎么写呢?

没错,依旧是使用loader

能够在webpack.config.js的loaders中增长Less的配置:

javascript{
  test: /\.less$/,
  loader: 'style-loader!css-loader!autoprefixer-loader!less-loader'
}

经过这样的配置,就能够直接在模块代码中引入Less样式了:

javascriptimport React from 'react';

require('./HelloWorldComponent.less');

export default class HelloWorldComponent extends React.Component {
    constructor() {
        super();
        this.state = {};
    }
    render() {
        return (

<div>Hello World</div>

);
    }
}

其余

Webpack的loader为React组件化提供了不少帮助,像图片也提供了相关的loader:

javascript{ test: /\.png$/, loader: "url-loader?mimetype=image/png" }

更多地loader能够移步webpack的wiki

在Webpack下实时调试React组件

Webpack和React结合的另外一个强大的地方就是,在修改了组件源码以后,不刷新页面就能把修改同步到页面上。这里须要用到两个库webpack-dev-serverreact-hot-loader

首先须要安装这两个库,npm install --save-dev webpack-dev-server react-hot-loader

安装完成后,就要开始配置了,首先须要修改entry配置:

javascriptentry: {
  helloworld: [
    'webpack-dev-server/client?http://localhost:3000',
    'webpack/hot/only-dev-server',
    './helloworld'
  ]
},

经过这种方式指定资源热启动对应的服务器,而后须要配置react-hot-loader到loaders的配置当中,好比个人全部组件代码所有放在scripts文件夹下:

javascript{
  test: /\.js?$/,
  loaders: ['react-hot', 'babel'],
  include: [path.join(__dirname, 'scripts')]
}

最后配置一下plugins,加上热替换的插件和防止报错的插件:

javascriptplugins: [
  new webpack.HotModuleReplacementPlugin(),
  new webpack.NoErrorsPlugin()
]

这样配置就完成了,可是如今要调试须要启动一个服务器,并且以前配置里映射到http://localhost:3000,因此就在本地3000端口起个服务器吧,在项目根目录下面建个server.js:

javascriptvar webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');

new WebpackDevServer(webpack(config), {
  publicPath: config.output.publicPath,
  hot: true,
  historyApiFallback: true
}).listen(3000, 'localhost', function (err, result) {
  if (err) console.log(err);
  console.log('Listening at localhost:3000');
});

这样就能够在本地3000端口开启调试服务器了,好比个人页面是根目录下地index.html,就能够直接经过http://localhost:3000/index.html访问页面,修改React组件后页面也会被同步修改,这里貌似使用了websocket来同步数据。图是一个简单的效果:

Alt text

结束

React的组件化开发颇有想法,而Webpack使得React组件编写和管理更加方便,这里只涉及到了React和Webpack得很小一部分,还有更多的最佳实践有待在学习的路上不断发掘

相关文章
相关标签/搜索