Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
toFrankie opened this issue Feb 25, 2023 · 1 comment
Open

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

toFrankie opened this issue Feb 25, 2023 · 1 comment
Labels
2020 2020 年撰写 React 与 React 相关的文章

Comments

@toFrankie
Copy link
Owner

toFrankie commented Feb 25, 2023

此前讲解 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 一致的版本。

  1. 添加 react-hot-loader/babel.babelrc 配置中。
// .babelrc
{
  "plugins": [
    "react-hot-loader/babel"
  ]
}
  1. 将根组件标记为热导出(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 (
    <Provider store={store}>
      <App />
    </Provider>
  )
}

export default hot(Root)

// 温馨提示
// 关于新 API 👉 hot 是位于 '/root' 下面的,但并不是所有的打包工具都支持该新 API。比如 parcel 就不支持。
// 此时 react-hot-loader 就会抛出错误,并要求你使用旧的 API,方法如下:
// 
// import { hot } from 'react-hot-loader'
// export default hot(module)(App)
  1. 确保在 reactreact-dom 之前导入 react-hot-loader,两种方式可选择:
  • 在入口文件导入 import react-hot-loader(要在 React 之前)
  • 在 webpack 配置文件的 entry 配置 react-hot-loader/patch
// 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.

@toFrankie toFrankie added the React 与 React 相关的文章 label Feb 25, 2023
@toFrankie toFrankie added the 2020 2020 年撰写 label Apr 26, 2023
@uniqie
Copy link

uniqie commented Apr 17, 2024

赞👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2020 2020 年撰写 React 与 React 相关的文章
Projects
None yet
Development

No branches or pull requests

2 participants