Webpack&React (四) Webpack 和 React

原文链接

React与Webpack很合拍.尽管你可以使用其它的React构建工具,但Webpack仍是一个不错的选择并且配置简单.在这章,我们将扩展我们的配置,之后我们将为我们进一步的开发应用设置一个好的起点.

React是什么?

Facebook的React改变了我们前端开发的方式.另外感谢React Native使开发并不局限于web中.React是小而强的.

React不是如Angular.js或Ember这样的框架. 框架往往提供了大量的开箱即用的解决方案. 对于React你将不得不从单独的库中进行组装.这两种方法都有其优点.使用框架更易上手,但是一切都必需循规蹈矩,基于库的方式给了你足够的灵活性.但同样也考验你对架构的把握.

React对web开发者提出了虚拟DOM的概念.React自已管理DOM方式与之前的所有库和框架都不同.React会在它认为最合适的时候分批的将改变应用于真实的DOM中.

JSX和虚拟DOM

React提供了一个高级的API用来生成虚拟DOM. 使用API可以生成复杂的结构非常笨重.因此我们通常不会手写它.作为替代,使用一些中间格式来转换到它.Facebook的JSX是一个流行的格式.

JSX是JavaScript的超集.允许你混合XMLish语法到JavaScript中.看下面的示例:

const Names = () => {
  const names = ['John', 'Jill', 'Jack'];

  return (
    <div>
      <h2>Names</h2>

      {/* This is a list of names */}
      <ul className="names">{
        names.map(name =>
          <li className="name">{name}</li>
        )
      }</ul>
    </div>
  );
};

如果你不了解JSX语法前你会觉得这很奇怪.但是当你开始了解它,就会明白为什么是这样的.

注意 render()必需反回一个单节点.反回多个将不工作.

JSX对比HTML

在JSX中我们混入了一些有点像HTML的JavaScript.注意我们怎样处理属性,我们使用className替代传统HTML中的class,尽管JSX起初使用会觉得有点奇怪,随着时间的推移将会习惯.

React开发者从DOM的局限性中摆脱了出来.React是高性能的.尽管这是有代价的.这个库不是你想的那样小.一般的小应用程序打包后在150-200k左右.使用gzip压缩过会变的很小.

Babel

Babel在社区中反响强列.它让我们可以现在就使用JavaScript的最新技术.它会把新的功能代码转变为浏览器知道的格式.你甚至可以用它来开发你自已语言的功能.Babel的内置的JSX支持将为我们带来便利.

Babel支持超过ES6标准的ES7的一些实验功能.其中一些功能是核心的,而另一些可能马上被废弃.这个分为下面几个阶段:

  • 阶段 0 - Strawman
  • 阶段 1 - Proposal
  • 阶段 2 - Draft
  • 阶段 3 - Candidate
  • 阶段 4 - Finished

要非常小心阶段0的功能.它是不稳定的.如果你愿意冒险,你可以在练习时尝试它们.

Babel online 可以看到它生成的代码.

配置 babel-loader

在Webpack中使用Babel可以通过babel-loader,它可以把基于ES6的模块定义打包成ES5规范.安装babel-loader

npm i babel-loader babel-core --save-dev

babel-core包含Babel的核心逻辑.

为了使它工作,我们需要一个配置来增加对babel-loader的声明.它使用正则表达式(/\.jsx?$/)匹配.js.jsx.

考虑到性能我们应该限制在./app目录内进行加载和操作.这种方式不会遍历node_modules目录.另一种方式是建一个排除规则excludenode_modeules排除.我觉得使用include更有用,它看起来是明确的.

下面是相关的配置,可以使Babel工作:

webpack.config.js

...

const common = {
  entry: {
    app: PATHS.app
  },
leanpub-start-insert
  // Add resolve.extensions.
  // '' is needed to allow imports without an extension.
  // Note the .'s before extensions as it will fail to match without!!!
  resolve: {
    extensions: ['', '.js', '.jsx']
  },
leanpub-end-insert
  output: {
    path: PATHS.build,
    filename: 'bundle.js'
  },
  module: {
    loaders: [
      {
        test: /\.css$/,
        loaders: ['style', 'css'],
        include: PATHS.app
      }
leanpub-start-insert
      },
      // Set up jsx. This accepts js too thanks to RegExp
      {
        test: /\.jsx?$/,
        // Enable caching for improved performance during development
        // It uses default OS directory by default. If you need something
        // more custom, pass a path to it. I.e., babel?cacheDirectory=<path>
        loaders: ['babel?cacheDirectory'],
        // Parse only app files! Without this it will go through entire project.
        // In addition to being slow, that will most likely result in an error.
        include: PATHS.app
      }
leanpub-end-insert
    ]
  }
};

...

注意resolve.extensions设置允许参考到没扩展名的JSX文件.但我会明确的使用扩展名,但你可以按你的想法来做.

设置 .babelrc

另外,我们需要一个.babelrc文件.虽然你能通过设置Webpack(例如 babel?presets[]=react,presets[]=es2015),但是它只限于Webpack.这就是为什么我们需要.babelrc的原因.这种思想也适用于其它工具.如ESLint.

Babel 6依赖plugins,有两种类型的插件:语法和变换.前者允许Babel解析附加的语法,而后者应用于转换.这种方式可以把先进的功能转为老版本环境可以理解的语法.

为了便于使用插件.Babel有一个presets概念,每个预置附带一组插件,使你不用分别连接它们,在这个例子里,我们将依赖ES2015和React预置.

npm i babel-preset-es2015 babel-preset-react --save-dev

除此之外,我们将使一些定制功能使项目更方便开发:

  • Property initializers - 例如: renderNote = (note) => { 自动约束这个renderNote方法到到实例
  • Decorators - 例如:@DragDropContext(HTML5Backend).这个注解让我们可以附加功能到类和方法上.
  • Object rest/spread -例如:const {a, b, ...props} = this.props,这个语法让我们简单的从对像中提取属性.

为了更简单的配置这些功能.我创建了一个特定的预置包含它们.

预置是简单的npm模块导出为Babel配置.如果你想跨多个工程共享相同的设置.像这样管理预置很有用.安装:

npm i babel-preset-survivejs-kanban --save-dev

接下来我们需要设置.babelrc文件.

.babelrc

{
  "presets": [
    "es2015",
    "react",
    "survivejs-kanban"
  ]
}

Babel支持明确指定阶段.更清楚的依赖你可能想使用的任何自定义功能.这个文件使你的工程更易维护.更多配置请看 .babelrc options .

定义属于你的Babel Presets

Babel preset是简单引入了其它presets和插件的Node.js的包. preset的想法是拉取它需要的依赖,并通过标准的Node.js接口暴露它.例如我们定义一个preset像:

index.js

module.exports = {
  plugins: [
    require('babel-plugin-syntax-class-properties'),
    require('babel-plugin-syntax-decorators'),
    require('babel-plugin-syntax-object-rest-spread'),

    // You can pass parameters using an array syntax
    [
      require('babel-plugin-transform-regenerator'),
      {
        async: false,
        asyncGenerators: false
      }
    ]
  ]
};

这个例子,我们取得指定的插件到预置.你还可以得到这些插件直接从你的.babelrc通过plugins字段.如果你只需要一两个这样做是方便的,配置开始增长,考虑到提取它们到一个preset中.你也可以定义presets字段,为你的工程引入其它presets.

假设我们命名我们的包为babel-preset-survivejs-kanban,然后安装它作为我们的项目并连接它使用Babel配置.注意babel-preset前缀,它允许我们在多个相似的工程之间共享presets.

Webpack配置使用Babel

可以配置Webpack来使用Babel的transpiled(转译)特性.只要重命名webpack.config.jswebpack.config.babel.js且Webpack会采用Babel设置你的工程.它将遵守.babelrc中的设置.

为了使它工作.你需要安装babel-register到你的工程.Webpack内部依赖interpret来工作.

改用机载器声明

Webpack的机载器声明是比较灵活的.接下来的例子会给你带来灵感.第一个例子显示了怎么通过查询串传递参数:

{
  test: /\.jsx?$/,
  loaders: [
    'babel?cacheDirectory,presets[]=react,presets[]=es2015,presets[]=survivejs-kanban'
  ],
  include: PATHS.app
}

考虑到查询串这种方式是不易读的,使用组合loaderquery字段:

{
  test: /\.jsx?$/,
  loader: 'babel',
  query: {
    cacheDirectory: true,
    presets: ['react', 'es2015', 'survivejs-kanban']
  },
  include: PATHS.app
}

开发第一个React视图

首先,我们需要定义一个App.它是我们程序的核心.它代表了我们应用程序的高级视图和入口.我们开始使用React的基于类的组件定义语法:

app/components/App.jsx

import React from 'react';
import Note from './Note.jsx';

export default class App extends React.Component {
  render() {
    return <Note />;
  }
}

你还可以使用语法import React, {Component} from 'react';react中引入,然后class App extends Component必需引入React因为JSX将语法将转成React.createElement调用.

设置Note

我们还需要定义Note组件.在这个例子中.我们只想显示一些文字.因为这个组件是简单的.我们能使用React的基于函数的组件定义.

app/components/Note.jsx

import React from 'react';

export default () => <div>Learn Webpack</div>;

虽然我们没有直接在代码中引用React.但最好记住最终JSX将转换为调用它.因此如果你移除了imort语句,代码将中断.Babel插件babel-plugin-react-require 将自动生成import语句.如果你不想写imports.

index.jsx中渲染组件

为了保证程序工作,我们将改变我们的inde.js用来渲染这个组件.因为里面有JSX内容,所以我们重命名它为index.jsx.我们将渲染内容使用react-dom包.

app/index.jsx

import './main.css';

import React from 'react';
import ReactDOM from 'react-dom';
import App from './components/App.jsx';

ReactDOM.render(<App />, document.getElementById('app'));

如果你没有使用npm-install-webpack-plugin.记住要安装reactreact-dom到你的工程下面npm i react react-dom -S

尝试运行npm start,在浏览器中转到localhost:8080查看结果.

试着修改你的React组件.能看到Webpack强制刷新.这是我们下一步需要通过热加载解决的.

在继续之前, 删掉component.js文件吧.因为我们不需要它了.

避免直接渲染到document.body,这样做会发生奇怪的问题并且React也会给出一个警告.

激活热加载

注意到我们每次修改好浏览器都会闪一下.这样做意为着我们失去了之前的状态.虽然没关系.但当我们扩大我们的应用时将是痛苦的.有时我们测试时正好需要前一个状态.

babel-plugin-react-transform这里写链接内容让我们用多种手段来使用React.热加载就是其中之一.通过react-transform-hmr使其生效.

当有改变时react-transform-hmr 将一个一个更新React组件而不是强制完全刷新.因为它只是替换方法,它不会捕捉每一个可能的变化.包括改变类的构造函数,这将需要你强制刷新,但大多数情况下它工作是很好的.

babel-preset-react-hmre 这里写链接内容将会让我们安装简单.它有妥当的默认值且减少大量的需要维护配置.安装通过:

npm i babel-preset-react-hmre --save-dev

我们还需要让Babel知道HMR. 首先我们要通过Webpack的配置传递目标环境到Babel.这让我们能控制环境通过.babelrc.在这个例子里我们允许HMR只在开发环境中.如果你想允许一些插件到产品构建,道理是相同的.

一个简单的方式控制.babelrc是设置BABEL_ENV环境变量,作为npm生命周期事件.

webpack.config.js

leanpub-start-insert
process.env.BABEL_ENV = TARGET;
leanpub-end-insert

const common = {
  ...
};

...

此外,我们需要增加我们Babel的配置.包含我们开发过程中的插件.Babel确认值是这样的:

  1. 使用BABEL_ENV,如果设置.
  2. 使用NODE_ENV,如果设置.
  3. 默认是development.

配置BABEL_ENV='start'

.babelrc

{
  "presets": [
    "es2015",
    "react",
    "survivejs-kanban"
leanpub-start-delete
  ]
leanpub-end-delete
leanpub-start-insert
  ],
  "env": {
    "start": {
      "presets": [
        "react-hmre"
      ]
    }
  }
leanpub-end-insert
}

尝试再次执行npm start并修改这个组件.注意到这次没有刷新浏览器.这是一个强大的功能.

总结

你现在应该知道怎么在Webpack中设置React.执加载是使 Webpack 与众不同的特征之一.现在我们有了一个好的开发环境.我们能专注于React的开发.下一章你将学习怎么实现一个小的记事本应用.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值