[转] React Hot Loader 3 beta 升级指南

react

在用 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 中查看升级方法:

  • Update TodoMVC example to React Hot Loader 3
  • Bump to 3.0.0-beta.0

下面我来总结一下,具体要做哪些改动:

1. 安装 React Hot Loader 3

Bash

$ npm install --save-dev react-hot-loader@^3.0.0-beta.2

2. 修改 .babelrc

在 .babelrc 中添加 react-hot-loader/babel 插件

JSON

{

"presets": ["es2015", "react"],

"plugins": ["react-hot-loader/babel"]

}

需要注意的一点是,.babelrc 配置不需要再分 dev 环境:

JSON

...

"env": {

"development": {

"plugins": ["react-hot-loader/babel"]

}

}

...

因为作者已经在 react-hot-loader 模块中加了 process.env.NODE_ENV 判断,因此它不会在生产环境运行。

3. 修改入口及路由组件

以 React + React-Router 为例,目录结构如下:

Bash

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:

React 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:

React 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。

JavaScript

// 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':

JavaScript

// ...

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 中,是无法进行热更新的。

错误示范:

JavaScript

// ...

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'

]

},

// ...

正确方法:

JavaScript

// ...

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

JavaScript

// referentially-equal-root-route.js

export default {}

React JSX

// 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 中我们只要引用任何异步模块:

JavaScript

// ...

getComponent (nextState, cb) {

require.ensure([], require => {

const Employee = require('../views/users/Employee')

cb(null, Employee.default)

})

}

// ...

都需要在 ./config/App.jsx (即 Root 组件) 中 require 一遍:

React JSX

// ...

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 升级指南 的全部内容, 来源链接: utcz.com/z/383223.html

回到顶部