What is react-hot-loader?
The react – hot – loader is a combination of webpack HotModuleReplacementPlugin plug-in implementation react hot update library, can realize the dynamic thermal retention react condition updates.
Module hot update
Before explaining the React-hot-Loader, you must first understand the HotReplacementPlugin(later HRP). Webpack builds a dependency tree based on the entry file. It can be thought of as a DOM tree. For this dependency tree, HRP injects a Module object into each module, representing some information about the module.
The module object
{
id: string, / / path
loaded: boolean,
exports: boolean,
webpackPolyfill: number,
exports: export,__proto__: {
parents: string[], // Parent reference
children: string[], / / rely onExports of exports I exports of exportsl: not clear,}}Copy the code
Module. hot is the most important part, we can call hot method, we can customize some hot update implementation, such as blocking hot update, or clean some global variables in hot update, you can learn more about module hot replacement, the following two methods we use:
Module.hot method (part only)
I’ll cover only the accept and addStatusHandler methods here
- accept: (dependencies: string | string[], cb: () => void) => void
The dependency tree is similar to the DOM tree. Each hot update, the updated module will send its updated message up like bubbling. When there is an ACCEPT processing method, the bubbling will stop and the ACCEPT processing will be performed. If I reload it, all the states will disappear
module.hot.accept('./App.js', () => {
...dosomething
})
Copy the code
- addStatusHandler: (status => void) => void
Register a function to listen for status changes and control our hot updates based on status
module.hot.addStatusHandler(status= > {
// Respond to the current status......
// Idle: the process is waiting to call check (see below)
// check: The process is checking for updates
// prepare: The process is preparing for an update (for example, downloading the updated module)
// Ready: This update is ready and available
// Dispose: The process is calling its dispose function on the module to be replaced
// apply: The process is calling the ACCEPT handler and reexecuting the self-accepted module
// ABORT: The update is aborted, but the system remains in the previous state
// fail: The update has thrown an exception, and the system state has been corrupted
})
Copy the code
Principle and Implementation
- root.js
In root.js, we get the parent module that references the hot module, that is, the React component that deals with hot updates, and wrap it with HOC
var hot = require("./hot").hot;
let parent;
if (module.hot) {
// Get all module caches required, similar to nodeJS
const cache = require.cache;
if (!module.parents || module.parents.length === 0) {
throw new Error("no parents!!");
}
// Get our component module
parent = cache[module.parents[0]].// Remove the root.js module so that it is reloaded with each invocation
delete cache[module.id];
}
export default hot(parent);
Copy the code
- hot.js
HOC implementation, wrapping our component with HOC, and saving this instance in componentDidMount so that it is updated without reloading, forceUpdate directly
const requireIndirect =
typeof__webpack_require__ ! = ="undefined" ? __webpack_require__ : require;
reactHotLoader.patch(React, ReactDOM); // Make some changes to React and ReactDOM.// Update the queue
const runInRenderQueue = createQueue((cb) = > {
if (ReactDOM.unstable_batchedUpdates) {
ReactDOM.unstable_batchedUpdates(cb);
} else{ cb(); }});// Hot update processing
const makeHotExport = (sourceModule, moduleId) = > {
const updateInstances = (a)= > {
// Get the instance object of the module
const module = hotModule(moduleId);
const deepUpdate = (a)= > {
// forceUpdate for each instance
runInRenderQueue((a)= > {
module.instances.forEach((inst) = > inst.forceUpdate());
});
};
deepUpdate();
};
if (sourceModule.hot) {
// Incorrect parameter passed, but can block hot update bubbling (webpack only)
sourceModule.hot.accept(updateInstances);
if (sourceModule.hot.addStatusHandler) {
if (sourceModule.hot.status() === "idle") {
sourceModule.hot.addStatusHandler((status) = > {
if (status === "apply") {
// Start updating the instance when hot updates are receivedupdateInstances(); }}); }}}};/ / generated HOC
export const hot = (sourceModule) = > {
const moduleId = sourceModule.id || sourceModule.i;
// Save the instance
const module = hotModule(moduleId);
let firstHotRegistered = false;
makeHotExport(sourceModule, moduleId);
return (WrappedComponent) = > {
const Hoc = createHoc(
WrappedComponent,
class Hoc extends React.Component {
componentDidMount() {
// Save our react instance
module.instances.push(this);
}
componentWillUnmount() {
module.instances = module.instances.filter((a) = >a ! = =this);
}
render() {
return <WrappedComponent {. this.props} / >; }}); if (! firstHotRegistered) { firstHotRegistered = true; // Save the module, The following will introduce reactHotLoader. Register (WrappedComponent, WrappedComponent displayName | | WrappedComponent. Name, moduleId); } return Hoc; }; };Copy the code
- reactHotLoader.js
Through the above processing, there is basically a prototype of hot update, but there are still problems
1. Because of the closure, our instance actually points to the original method and forceUpdate will not apply the new code. Even if we manage to make it point to new code, react Tree diff is not the same Component, react will still render again
The solution here is to save the new code for hot updates, create a Proxy on the first load, forceUpdate each time, and let the Proxy find the latest code and execute. This solves the above two problems
const proxies = new Map(a);const types = new Map(a);const resolveType = (type) = > {
if (type["PROXY_KEY"]) {
/ / get the proxy
return proxies.get(type["PROXY_KEY"]);
}
return type;
};
const reactHotLoader = {
register(type, name, id) {
if(! type["PROXY_KEY"]) {
const key = `${id}-${name}`;
// Add a flag to the component
type["PROXY_KEY"] = key;
// Keep the latest component code by key
types.set(key, type);
if(! proxies.get(key)) {// Create a proxy for this component,
proxies.set(
key,
new Proxy(type, {
apply: function (target, thisBinding, args) {
const id = target["PROXY_KEY"];
// Get the latest code
const latestTarget = types.get(id);
returnlatestTarget(... args); }})); }}},// Proxy the React. CreateElement method so that we can find the new module code
patch(React, ReactDOM) {
if(! React.createElement.isPatchd) {const origin = React.createElement;
React.createElement = (type, ... args) = >origin(resolveType(type), ... args); React.createElement.isPatchd =true; }}};export default reactHotLoader;
Copy the code
reference
webpack HotReplacementPlugin
github react-hot-loader
Hot Reloading in React(Dan’s Article)
Hot Reloading in React
The source code