toFrankie / blog

种一棵树,最好的时间是十年前。其次,是现在。
22 stars 1 forks source link

从零到一搭建 react 项目系列之(十二) #65

Open toFrankie opened 1 year ago

toFrankie commented 1 year ago

此前讲解 react-router、redux、react-redux、redux-saga 所涉及的内容较多,篇幅也较长。终于可以介绍 HMR 模块热更新。

其实此前已经介绍过了,但今天就结合 React 搭建更友好的热更新效果。

一、HMR 实现

此前是怎么做的呢?

当然这种方式是没有针对使用 React 做优化处理的。

// webpack.config.js
modules.exports = {
  mode: 'develop',
  devServer: {
    // 需要注意的是,要完全启用 HMR,需要 webpack.HotModuleReplacementPlugin
    hot: true
  },
  optimization: {
    // 告知 webpack 使用可读取模块标识符,来帮助更好地调试。开发模式默认开启。简单来说,开启时你看到的是一个具体的模块名称,而不是一个数字 id。
    namedModules: true
  },
  plugins: [
    // 通过它启用 HMR,那么它的接口将被暴露在 module.hot 属性下面。
    new webpack.HotModuleReplacementPlugin()
  ],
  module: {
    rules: [
      {
        // 样式热更新,借助于 style-loader,其实幕后使用了 module.hot.accept
        test: /\.css/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }
}

我们还需在入口文件添加 module.hot.accpet() 方法。

// index.js
import React from 'react'
import { render } from 'react-dom'
import '../styles/style.css'
import Root from './Root'

// 最简单的 React 示例
const rootElem = document.getElementById('app')
render(<Root />, rootElem)

// 通常,先检查 HotModuleReplacementPlugin 暴露的接口是否可访问,然后再开始使用它。
if (module.hot) {
  // accept 方法接受给定的依赖模块的更新,并触发一个回调函数来对这些更新做出响应。
  module.hot.accept('./Root', () => {
    import('./Root.js').then(module => {
      const NextRoot = module.default
      render(<NextRoot/>, rootElem)
    })
  })
}

需要注意的是: 原先的 new webpack.NameModulesPlugin() 在 webpack 4 中已废弃,取代它的是 optimization.nameModules。开发模式下默认开启,生产模式下,默认关闭。关于开启与禁用,最直观的区别如下图。

尝试随便修改一下 Home 组件,看到控制台的输出(如下图),两者区别不同。开启时,能看到具体涉及更新的模块有哪些,而关闭状态则是只能看到一个数字 id。 关闭状态

开启状态

二、HMR 搭配 React 的不足

上面的方式,如果搭配 React 使用的话,其实还不够友好。

用一个最简单的例子说明问题

// 一个非常简单的有状态组件
import React, { Component } from 'react'

class HMRDemo extends Component {
  constructor(props) {
    super(props)
    this.state = {
      count: 0
    }
  }

  render() {
    return (
      <div>
        <h3>HMR Demo Component!</h3>
        <h5>计数器:{this.state.count}</h5>
        <button onClick={() => { this.setState({ count: ++this.state.count }) }}>add</button>
      </div>
    )
  }
}

export default HMRDemo

这时候我们点击按钮,然后组件状态 count 自然变成了 1,这个没问题。接着,我们随意增加一个标签元素,然后页面自然会热更新,但是我们看到,count 又变成了 0,组件状态丢失了,如下图:

我们享受着 HMR 给我们开发带来的便利,但同时我们又不想丢失 Component State 组件状态,怎么做呢?

为了解决这个问题,React Hot Loader 出现了。

三、React Hot Loader

先看一下官方指南的一段原话

React-Hot-Loader is expected to be replaced by React Fast Refresh. Please remove React-Hot-Loader if Fast Refresh is currently supported on your environment.

大概的意思是,react-hot-loader 将会被 React Fast Refresh 取代。如果您当前的环境支持的话,请移除 react-hot-loader。

*至于 React Fast Refresh 是什么?如何使用?这里不展开述说,可以看一下其他人写的一篇文章

继续介绍 react-hot-loader,还需要在上面的基础上,添加以下配置。

  1. 安装依赖包

    $ yarn add --dev react-hot-loader@4.12.19
    # 下面步骤用到
    $ yarn add --dev @hot-loader@react-dom@16.12.0

    @hot-loader/react-dom 是在 react-dom 相同版本的基础上,添加了一些支持热更新的补丁。所以需要安装与 react-dom 一致的版本。

  2. 添加 react-hot-loader/babel.babelrc 配置中。

    // .babelrc
    {
    "plugins": [
    "react-hot-loader/babel"
    ]
    }
  3. 将根组件标记为热导出(hot-exported

    
    import React from 'react'
    import { Provider } from 'react-redux'
    import { hot } from 'react-hot-loader/root'
    import App from './pages/App'
    import store from './store'

const Root = () => { return (

) }

export default hot(Root)

// 温馨提示 // 关于新 API 👉 hot 是位于 '/root' 下面的,但并不是所有的打包工具都支持该新 API。比如 parcel 就不支持。 // 此时 react-hot-loader 就会抛出错误,并要求你使用旧的 API,方法如下: // // import { hot } from 'react-hot-loader' // export default hot(module)(App)


4. 确保在  `react` 和 `react-dom` 之前导入 `react-hot-loader`,两种方式可选择:
* 在入口文件导入 `import react-hot-loader`(要在 React 之前)
* 在 webpack 配置文件的 `entry` 配置 `react-hot-loader/patch`。

```js
// webpack.config.js
{
  entry: [
    'react-hot-loader/patch',
    './src/js/index.js'
  ]
}

需要注意的是:react-hot-loader/patch 一定要写在 entry 的最前面。如果有 babel-polyfill 就写在 babel-polyfill 的后面。

  1. 使用 React Hooks 需要用到 @hot-loader/react-dom。同时,就以上配置重新启动,此时控制台会打印一个 WARNING,如下:

    React-Hot-Loader: react-🔥-dom patch is not detected. React 16.6+ features may not work. (Issue #1227

解决方案之一,另一种可以看下 ISSUE #1227

// webpack.config.js
{
  resolve: {
    alias: {
      'react-dom': '@hot-loader/react-dom'
    }
  }
}

测试一下,分别两次点击 add 后,再添加一个节点元素,发现 Component State 是在我们预期之内的,并没有像之前一样因为热更新而丢失状态。

🎉

四、至此

关于 Hot Module Replacement 模块热替换(热更新)基本就介绍完了。

前面我们提到一个 React Fast Refresh 概念,它由官方维护,稳定性与性能有保障,对 React Hooks 有更完善的支持。官方实现是 react-refresh

后面有时间的话,应该会写一篇关于它的文章。但是,它最低支持版本是 react-dom@16.9+

The end.

uniqie commented 7 months ago

赞👍