zoukankan      html  css  js  c++  java
  • [转] 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

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

    gaearon said

    错误示范:

    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 正式出来后这些问题应该都不存在了...

  • 相关阅读:
    HDU_1068_Girls and Boys_二分图匹配
    Bringing Native Performance to Electron
    BIM开发引挈
    旷视科技 -- Face++ 世界最大的人脸识别技术平台
    freedownloadmanager 下载工具,代替 讯雷
    两种 AuthorizationSchemes 在 ASP.NET Core 2
    layout 的应用
    递归获取一个树层次结构(合同清单)的部分选择项(计量合同清单),以及递归删除部分清单
    Nest + typeorm
    http://nancyfx.org + ASPNETCORE
  • 原文地址:https://www.cnblogs.com/chris-oil/p/8454611.html
Copyright © 2011-2022 走看看