[转] React Hot Loader 3 beta 升级指南
前言
在用 react-hot-loader v1.3 的时候有些深层组件不会很完美的热更新(可能是我使用有问题)。然后在 react-hot-loader 首页中看到 React Hot Loader 3 is on the horizon,便想换成这个,结果就开启了一周的踩坑之路...
模块依赖
务必升级最新的 React-Hot-Loader v3.0.0-beta.3
这版修复了错误栈无法跟踪到内层组件的问题,否则内部组件报错只能追溯到 AppContainer。
Warning: React.createElement: type should not be null, undefined, boolean, or number. It should be a string (for DOM elements) or a ReactClass (for composite components). Check the render method of `AppContainer`.
Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object. Check the render method of `AppContainer`.

截止 2016-08-08 00:00,依赖模块的版本分别是:
| webpack | ^1.13.1 |
| webpack-dev-server | ^1.14.1 |
| react-hot-loader | ^3.0.0-beta.2 |
| babel-core | ^6.13.2 |
| babel-loader | ^6.2.4 |
注:我没有用到 Redux。
升级方法
因为目前 React Hot Loader 3 还在测试阶段,没有文档,所以需要在 gaearon/react-hot-boilerplate#61 这个 issue 中提到的两个 commit 中查看升级方法:
下面我来总结一下,具体要做哪些改动:
1. 安装 React Hot Loader 3
$ npm install --save-dev react-hot-loader@^3.0.0-beta.2
2. 修改 .babelrc
在 .babelrc 中添加 react-hot-loader/babel 插件
{
"presets": ["es2015", "react"],
"plugins": ["react-hot-loader/babel"]
}
需要注意的一点是,.babelrc 配置不需要再分 dev 环境:
...
"env": {
"development": {
"plugins": ["react-hot-loader/babel"]
}
}
...
因为作者已经在 react-hot-loader 模块中加了 process.env.NODE_ENV 判断,因此它不会在生产环境运行。
3. 修改入口及路由组件
以 React + React-Router 为例,目录结构如下:
singlePageView
├── config
│ ├── App.jsx # 渲染 <Router>
│ ├── Routes.js # routes
│ └── config.js
├── index.html
├── index.jsx # 入口文件
└── views # 单页 views
├── application
│ ├── Home
│ │ └── index.jsx
│ └── Layout
│ ├── Header.jsx
│ ├── Menu.jsx
│ └── index.jsx
└── users
├── Business
└── Employee
└── index.jsx
a) 入口 index.jsx:
// index.jsx
// 增加 AppContainer
import { AppContainer } from 'react-hot-loader'
import React from 'react'
import { render } from 'react-dom'
// <Router> 放在 ./config/App.jsx 中
import App from './config/App'
const appElem = document.querySelector('#app')
// 给原来的 <App /> 包裹一层 AppContainer
render(
<AppContainer>
<App />
</AppContainer>,
appElem
)
if (module.hot) {
// If you use Webpack 2 in ES modules mode, you can
// use <App /> here rather than require() a <NextApp />.
// 如果用 ES 模块模式的 Webpack 2,可以直接用 <App />
module.hot.accept('./config/App', () => {
const NextApp = require('./config/App').default
render(
<AppContainer>
<NextApp />
</AppContainer>,
appElem
)
})
}
b) ./config/App.jsx:
// App.jsx
import React, { Component } from 'react'
import { browserHistory, Router } from 'react-router'
import routes from './Routes'
export default class App extends Component {
render () {
return <Router history={browserHistory} routes={routes} />
}
}
c) ./config/Routes.js
这里我用了 webpack 的 code splitting (require.ensure),因此必须用 routes 的对象形式,而不是 JSX。
// Routes.js
import Layout from '../views/application/Layout'
import Home from '../views/application/Home'
const routes = {
path: '/manage-admin',
component: Layout,
indexRoute: {
component: Home
},
childRoutes: [{
path: 'users/employee',
getComponent (nextState, cb) {
require.ensure([], require => {
const Employee = require('../views/users/Employee')
/**
* 注意:babel 6 不再暴露默认的 `module.exports`
* 可以使用 babel-plugin-add-module-exports 插件
* 或者像下面这样直接使用 Module.default
*/
cb(null, Employee.default)
})
}
}]
}
export default routes
4. 修改 webpack.dev.config.js
因为在 .babelrc 中加上了 react-hot-loader/babel 插件,针对 js/jsx 的 loaders 可以去掉 'react-hot':
// ...
loaders: [{
test: /\.jsx?$/i,
// loaders: ['react-hot', 'babel'],
loaders: ['babel'],
exclude: /(node_modules|bower_components)/
}
// ...
注意点: 在 entry 中要加上 react-hot-loader/patch 这个脚本,而且必须先于页面引用的 JS 文件之前运行。
比如,我一个单页有 vendor.js & entry.js 最先加载的是 vendor,因此必须放在 vendor 最前面引用 react-hot-loader/patch,否则放到 entry 中,是无法进行热更新的。

错误示范:
// ...
entry: {
vendor: ['react', 'react-dom', 'react-router', 'react-tap-event-plugin', 'babel-polyfill'],
'manage-admin': [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
// patch放在这里无效,因为 vendor 最先加载,且包含 react
'react-hot-loader/patch',
'./src/views/manage-admin/index.jsx'
]
},
// ...
正确方法:
// ...
entry: {
// patch 要放在 vendor 最前面
vendor: ['react-hot-loader/patch', 'react', 'react-dom', 'react-router', 'react-tap-event-plugin', 'babel-polyfill'],
'manage-admin': [
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/views/manage-admin/index.jsx'
]
},
// ...
按照 issue 中配置修改到此结束,下面介绍一些解决遗留问题的黑科技
遗留问题
根据 https://github.com/gaearon/react-hot-boilerplate/pull/61 其中 @dferber90 巨巨提出的解决方案整理
1. 避免 react-hot-loader 失效
所有的组件必须用 const 来定义的,避免组件引用被修改,否则会使 react-hot-loader 失效。
2. 避免 react-router 输出报错信息
这个版本 react-router 和 react-hot-loader 3 不太兼容,在每次热更新时 react-router 会报错: Warning: [react-router] You cannot change <Router routes>; it will be ignored
虽然不影响热更新,但有个报错还是很影响开发的。
可以通过引入一个空对象,用 Object.assign 合并 routes 到空对象上,避免「change <Router routes>」:
创建 ./config/referentially-equal-root-route.js
// referentially-equal-root-route.js
export default {}
// Routes.js
// ...
import routeSource from './Routes'
import referenctiallyEqualRootRoute from './referentially-equal-root-route'
const routes = Object.assign(referenctiallyEqualRootRoute, routeSource)
render () { return <Router routes={routes} /> }
// ...
这样修改以后, react-router 的报错便不再出现了。
3. 为异步(Code Splitting)路由组件提供热更新
异步路由组件在修改代码后,看控制台显示热更新完成,但组件却没有变化,除非重新加载一遍这个异步组件(后退前进 或 从别的路由路径切换到这个更新的路由路径),才会更新。
(这个解决方法略微蛋疼)
在 ./config/Routes.js 中我们只要引用任何异步模块:
// ...
getComponent (nextState, cb) {
require.ensure([], require => {
const Employee = require('../views/users/Employee')
cb(null, Employee.default)
})
}
// ...
都需要在 ./config/App.jsx (即 Root 组件) 中 require 一遍:
// ...
if (process.env.NODE_ENV !== 'production') {
// ... 有多少异步模块就 require 多少
require('../views/users/Employee')
}
export default class App extends Component {
render () {
return <Router history={browserHistory} routes={routes} />
}
}
这样才能在开发环境中,对异步模块进行热更新。
(记得在 npm run build 脚本命令中加上 NODE_ENV=production)
最后
上述代码均在开发和生产环境下测试通过,如果有问题,可以在下方 Disqus 评论中问我,或者直接看 https://github.com/gaearon/react-hot-boilerplate/pull/61 里的内容找解决办法。
遗留问题如果没遇到可以不用解决,React Hot Loader 3 正式出来后这些问题应该都不存在了...
[转] React Hot Loader 3 beta 升级指南的更多相关文章
- Webpack2 升级指南和特性摘要(转)
Webpack2 升级指南和特性摘要 resolve.root, resolve.fallback, resolve.modulesDirectories 上述三个选项将被合并为一个标准配置项:res ...
- webpack4升级指南
webpack4升级指南 鉴于图书项目编译速度极慢的情况(项目里面module太多了,编译慢很正常)且最近需求不多(很少出现的空挡期).所以我觉得搞一波webpack升级,看看有没有帮助.webpac ...
- React Native 项目整合 CodePush 全然指南
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/y4x5M0nivSrJaY3X92c/article/details/81976844 作者 | 钱 ...
- babel 7 简单升级指南
babel 7 babel 7 发布两天了,试着对当前项目更新了下,仅此记录分享 主要改动参考 官方博客 官方升级指南 主要升级内容 不再支持放弃维护的 node 版本 0.10.0.12.4.5 使 ...
- webpack4 中的最新 React全家桶实战使用配置指南!
最新React全家桶实战使用配置指南 这篇文档 是吕小明老师结合以往的项目经验 加上自己本身对react webpack redux理解写下的总结文档,总共耗时一周总结下来的,希望能对读者能够有收获, ...
- 企业IT管理员IE11升级指南【17】—— F12 开发者工具
企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...
- 企业IT管理员IE11升级指南【16】—— 使用Compat Inspector快速定位IE兼容性问题
企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...
- 企业IT管理员IE11升级指南【15】—— 代理自动配置脚本
企业IT管理员IE11升级指南 系列: [1]—— Internet Explorer 11增强保护模式 (EPM) 介绍 [2]—— Internet Explorer 11 对Adobe Flas ...
- Ionic2系列-将beta升级到RC1
国庆节前Ionic2发布了RC0版本,已经接近正式版了,前不久Angular2和TypeScript2也已经发布了正式版.详情请参考官方博客: http://blog.ionic.io/announc ...
随机推荐
- 对比Dijakstra和优先队列式分支限界
Dijakstra和分支限界都是基于广度优先搜索,如果说两者都是生成一棵树,那Dijakstra总是找距离树根最近的(属于贪心算法),优先队列式分支限界是在层遍历整棵搜索树的同时剪去达不到最优的树枝. ...
- windowns下excel2013快速生成月报表
作者:邓聪聪 windowns下excel快速生成月报表,省去了手工复制繁琐的过程 Sub AutoCopySheets() Dim i, j As Integer i = 1 j = 11 For ...
- python3+selenium入门08-鼠标事件
使用click()可以模拟鼠标的左键点击事件,现在的web页面中有很多其他的鼠标交互方式,比如鼠标右击.双击.悬停.鼠标拖放等功能.在WebDriver中,将这些关于鼠标操作的方法封装在ActionC ...
- Linux下设置时间
Linux下设置时间 提供两种最根本有效的方式,就是更改时区.这里以更改为国内上海时间例子,其他地方时区同理. 方法一 备份文件 mv /etc/localtime /etc/localtime.ba ...
- 题解-CodeForces835F Roads in the Kingdom
Problem CodeForces-835F 题意:求基环树删去环上任意一边后直径最小值,直径定义为所有点对最近距离的最大值 Solution 首先明确删去环上一点是不会影响树内直径的,所以应当先把 ...
- 请求头缺少 'Access-Control-Allow-Origin'
报错: 火狐上运行,出现报错信息.已拦截跨源请求:同源策略禁止读取位于 https://xxxxxxx 的远程资源.(原因:CORS 头缺少 'Access-Control-Allow-Origin' ...
- Java链接DB2的4种基本类型【转】
原文链接:http://doc.chinaunix.net/java/201002/776480.shtml 第一种:目前IBM一直都没有提供 TYPE 1的JDBC驱动程序. 第二种:类型2驱动:C ...
- NO-CARRIER
自己动手写了创建虚拟接口,删除虚拟接口程序,频繁调用创建删除时,有时将接口up起来时会报错: Name not unique on network 利用ip link命令来查看接口(及其对应的索引) ...
- Go数组和切片定义和初始化
1 前言 切片是动态数组,数组数组是按值赋值,切片是按地址赋值(引用) 2 代码 2.1 数组初始化 func basic_array(){ //var arr2 = [3]int{2,4,6} // ...
- vi快速查找
用vim时,想高亮显示一个单词并查找的方发,将光标移动到所找单词. 1: shift + "*" 向下查找并高亮显示 2: shift + "#" 向上查找 ...