国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

ReactNative-HMR原理探索

GT / 1901人閱讀

摘要:原理探索前言在開始本文前,先簡單說下我們在開發(fā)項(xiàng)目中,本地的服務(wù)究竟扮演的是什么樣的角色。這無疑是閹割了一大部分功能綜上,如果僅僅用于切圖,可能不會(huì)有那么多的問題

ReactNative-HMR原理探索 前言

在開始本文前,先簡單說下我們在開發(fā)RN項(xiàng)目中,本地的node服務(wù)究竟扮演的是什么樣的角色。在我們的RN APP中有配置本地開發(fā)的地方,只要我們輸入我們本地的IP和端口號8081就可以開始調(diào)試本地代碼,其實(shí)質(zhì)是APP發(fā)起了一個(gè)請求bundle文件的HTTP請求,而我們的node server在接到request后,開始對本地項(xiàng)目文件進(jìn)行babel,pack,最后返回一個(gè)bundle.js。而本地的node服務(wù)扮演的角色還不止如此,比如啟動(dòng)基礎(chǔ)服務(wù)dev tool,HMR等

什么是HMR

HMR(Hot Module Replacement)模塊熱替換,可以類比成Webpack的Hot Reload。可以讓你在代碼變動(dòng)后不用reload app,代碼直接生效,且當(dāng)前路由棧不會(huì)發(fā)生改變

名詞說明

逆向依賴:如上圖 對于D模塊來說,A,B文件就是D的逆向依賴

淺層依賴:如上圖 對于index.js來說,A,B模塊就是index.js的淺層依賴(直屬依賴),C,D,E跟index沒有直接依賴關(guān)系

實(shí)現(xiàn)原理

先貼上個(gè)人整理的的一個(gè)HMR熱更新的過程

我們來逐步按流程對應(yīng)相應(yīng)的源碼分析

啟動(dòng)Packerage&HMR server run packager server
# react-native/local-cli/server/runServer.js

const serverInstance = http.createServer(app).listen(
   args.port,
   args.host,
   () => {
     attachHMRServer({
       httpServer: serverInstance,
       path: "/hot",
       packagerServer,
     });

     wsProxy = webSocketProxy.attachToServer(serverInstance, "/debugger-proxy");
     ms = messageSocket.attachToServer(serverInstance, "/message");
     webSocketProxy.attachToServer(serverInstance, "/devtools");
     readyCallback();
   }
 );

本地啟動(dòng)在8081啟動(dòng)HTTP服務(wù)的同時(shí),也初始化了本地HMR的服務(wù),這里在初始化的時(shí)候注入了packagerServer,為的是能訂閱packagerServer提供的watchman回調(diào),同時(shí)也為了能拿到packagerServer提供的getDependencies方法,這樣能在HMR內(nèi)部拿到文件的依賴關(guān)系(相互require的關(guān)系)

#react-native/local-cli/server/util/attachHMRServer.js
// 略微簡化下代碼
function attachHMRServer({httpServer, path, packagerServer}) {
    
    ...
    
    const WebSocketServer = require("ws").Server;
     const wss = new WebSocketServer({
       server: httpServer,
       path: path,
     });
     wss.on("connection", ws => {
     ...

   getDependencies(params.platform, params.bundleEntry)
     .then((arg) => {
       client = {
         ...
       };
   packagerServer.setHMRFileChangeListener((filename, stat) => {
        
        ...
        
         client.ws.send(JSON.stringify({type: "update-start"}));
         stat.then(() => {
           return packagerServer.getShallowDependencies({
             entryFile: filename,
             platform: client.platform,
             dev: true,
             hot: true,
           })
             .then(deps => {
               if (!client) {
                 return [];
               }


               const oldDependencies = client.shallowDependencies[filename];
               // 分析當(dāng)前文件的require關(guān)系是否與之前一致,如果require關(guān)系有變動(dòng),需要重新對文件的dependence進(jìn)行分析
               if (arrayEquals(deps, oldDependencies)) {
                 return packagerServer.getDependencies({
                   platform: client.platform,
                   dev: true,
                   hot: true,
                   entryFile: filename,
                   recursive: true,
                 }).then(response => {
                   const module = packagerServer.getModuleForPath(filename);

                   return response.copy({dependencies: [module]});
                 });
               }
               return getDependencies(client.platform, client.bundleEntry)
                 .then(({
                   dependenciesCache: depsCache,
                   dependenciesModulesCache: depsModulesCache,
                   shallowDependencies: shallowDeps,
                   inverseDependenciesCache: inverseDepsCache,
                   resolutionResponse,
                 }) => {
                   if (!client) {
                     return {};
                   }

               return packagerServer.buildBundleForHMR({
                 entryFile: client.bundleEntry,
                 platform: client.platform,
                 resolutionResponse,
               }, packagerHost, httpServerAddress.port);
             })
             .then(bundle => {
               if (!client || !bundle || bundle.isEmpty()) {
                 return;
               }

               return JSON.stringify({
                 type: "update",
                 body: {
                   modules: bundle.getModulesIdsAndCode(),
                   inverseDependencies: client.inverseDependenciesCache,
                   sourceURLs: bundle.getSourceURLs(),
                   sourceMappingURLs: bundle.getSourceMappingURLs(),
                 },
               });
             })
            .then(update => {
               client.ws.send(update);
             });
           }
         ).then(() => {
           client.ws.send(JSON.stringify({type: "update-done"}));
         });
       });


       client.ws.on("close", () => disconnect());
     })
}

RN最舒服的地方就是命名規(guī)范,基本看到函數(shù)名就能知道他的職能,我們來看上面這段代碼,attachHMRServer這個(gè)總共做了以下幾件事:

起一個(gè)socket服務(wù),這樣在監(jiān)聽到文件變動(dòng)的時(shí)候能夠?qū)⑻幚硗甑腸ode通過socket層扔給App端

訂閱packager server提供fileChange方法

拿到packager server提供的getDependence方法,對變動(dòng)文件進(jìn)行簡單的依賴分析。如果說發(fā)現(xiàn)變動(dòng)文件A之前require了B,C文件,但是這次只require了B文件,oldDependencies!==currentDep(這里HMRServer為了優(yōu)化性能,對淺層依賴關(guān)系,逆向依賴關(guān)系,依賴緩存時(shí)間都做了cache),那么HMR server會(huì)讓Packager Server重新梳理一遍項(xiàng)目文件的依賴關(guān)系(因?yàn)榭赡艽嬖谠鰟h文件的可能),同時(shí)對它局部維護(hù)的一些cache Map做更新

HMRClient 注冊

我們已經(jīng)看到了socket的發(fā)送方,那么必定存在一個(gè)接收方,也就是這里要講的HMRClient,首先先來看這邊注冊函數(shù)

#react-native/Libraries/BatchedBridge/BatchedBridge.js

const MessageQueue = require("MessageQueue");

const BatchedBridge = new MessageQueue(
  () => global.__fbBatchedBridgeConfig,
  serializeNativeParams
);

const Systrace = require("Systrace");
const JSTimersExecution = require("JSTimersExecution");

BatchedBridge.registerCallableModule("Systrace", Systrace);
BatchedBridge.registerCallableModule("JSTimersExecution", JSTimersExecution);
BatchedBridge.registerCallableModule("HeapCapture", require("HeapCapture"));

if (__DEV__) {
  BatchedBridge.registerCallableModule("HMRClient", require("HMRClient"));
}

這邊就是HMRClient注冊階段,貼這段代碼其實(shí)是因?yàn)榘l(fā)現(xiàn)RN里的JS->Native,Native->JS通信是通過MQ(MessageQueue)實(shí)現(xiàn)的,而追溯到最里層發(fā)現(xiàn)竟然是一套setTimeout,setImmediate的異步隊(duì)列...扯遠(yuǎn)了,有空的話,可以專門分享一下。

HMRClient
    #react-native/Libraries/Utilities/HMRClient.js
    
    activeWS.onmessage = ({ data }) => {
            
            ...

          modules.forEach(({ id, code }, i) => {
                
                ...
            
            const injectFunction = typeof global.nativeInjectHMRUpdate === "function"
              ? global.nativeInjectHMRUpdate
              : eval;

            code = [
              "__accept(",
              `${id},`,
              "function(global,require,module,exports){",
              `${code}`,
              "
},",
              `${JSON.stringify(inverseDependencies)}`,
              ");",
            ].join("");

            injectFunction(code, sourceURLs[i]);
          });
      }
    };

HMRClient做的事就很簡單了,接到socket傳入的String,直接eval運(yùn)行,這邊的code形如下圖

我們可以看到這邊是一個(gè)__accept函數(shù)在接受這個(gè)變更后的HMR bundle

真正的熱更新過程
#react-native/packager/react-packager/src/Resolver/polyfills/require.js

  const accept = function(id, factory, inverseDependencies) {
      //在當(dāng)前模塊映射表里查找,如果找的到將其Code進(jìn)行替換,并執(zhí)行,若沒有,重新進(jìn)行聲明
    const mod = modules[id];

    if (!mod) {
        //重新申明
      define(id, factory);
      return true; // new modules don"t need to be accepted
    }

    const {hot} = mod;
    if (!hot) {
      console.warn(
        "Cannot accept module because Hot Module Replacement " +
        "API was not installed."
      );
      return false;
    }

    // replace and initialize factory
    if (factory) {
      mod.factory = factory;
    }
    mod.hasError = false;
    mod.isInitialized = false;
    //真正進(jìn)行熱替換的地方
    require(id);

    //當(dāng)前模塊熱更新后需要執(zhí)行的回調(diào),一般用來解決循環(huán)引用
    if (hot.acceptCallback) {
      hot.acceptCallback();
      return true;
    } else {
      // need to have inverseDependencies to bubble up accept
      if (!inverseDependencies) {
        throw new Error("Undefined `inverseDependencies`");
      }

        //將當(dāng)前moduleId的逆向依賴傳入,熱更新他的逆向依賴,遞歸執(zhí)行
      return acceptAll(inverseDependencies[id], inverseDependencies);
    }
  };

  global.__accept = accept;

這邊的代碼就不進(jìn)行刪減了,accept函數(shù)接受三個(gè)參數(shù),moduleId,factory,inverseDependencies。

moduleId:需要熱更新的ID,對于每個(gè)模塊,都會(huì)被賦予一個(gè)模塊ID,RN 30之前的版本使用的是filePath作為key,而后使用的是一個(gè)遞增的整型

factory:babel后實(shí)際的需要熱替換的code

inverseDependencies:當(dāng)前所有的逆向依賴Map

簡單來說accept做的事情就是判斷變動(dòng)當(dāng)前模塊是新加的需要define,還是說直接更新內(nèi)存里已存在的module,同時(shí)沿著他的逆向依賴樹,全部load一遍,一直到最頂級的AppResigterElement,這樣熱替換的過程就完成了,形如下圖

那么問題就來了,react的View展現(xiàn)對state是強(qiáng)依賴的,重新load一遍,state不會(huì)丟失么,實(shí)際上在load的過程中,RN把老的ref傳入了,所以繼承了之前的state

講到這還略過了最重要的一點(diǎn),為什么說我這邊熱替換了內(nèi)存中module,并執(zhí)行了一遍,我的App就能拿到這個(gè)更新后的代碼,我們依舊拿代碼來說

#react-native/packager/react-packager/src/Resolver/polyfills/require.js

global.require = require;
global.__d = define;

const modules = Object.create(null);

function define(moduleId, factory) {
  if (moduleId in modules) {
    // prevent repeated calls to `global.nativeRequire` to overwrite modules
    // that are already loaded
    return;
  }
  modules[moduleId] = {
    factory,
    hasError: false,
    isInitialized: false,
    exports: undefined,
  };
  if (__DEV__) {
    // HMR
    modules[moduleId].hot = createHotReloadingObject();

    // DEBUGGABLE MODULES NAMES
    // avoid unnecessary parameter in prod
    const verboseName = modules[moduleId].verboseName = arguments[2];
    verboseNamesToModuleIds[verboseName] = moduleId;
  }
}

function require(moduleId) {
  const module = __DEV__
    ? modules[moduleId] || modules[verboseNamesToModuleIds[moduleId]]
    : modules[moduleId];
  return module && module.isInitialized
    ? module.exports
    : guardedLoadModule(moduleId, module);
}

RN復(fù)寫了require,這樣所有模塊其實(shí)拿到的是這里

HMR存在的問題

由于其原理是逆向load其依賴樹,如果說項(xiàng)目的技術(shù)方法破壞了其樹狀依賴結(jié)構(gòu),那么HMR也沒法生效。例如通過global掛載包裝了AppResigter這樣的方法。

由于Ctrl+s會(huì)立即觸發(fā)watchMan的回調(diào),導(dǎo)致可能代碼改了一半就走進(jìn)了HMR的邏輯,在transfrom Code或者require的時(shí)候就直接紅屏了

由于其HMR原理是逆向執(zhí)行依賴樹,如果項(xiàng)目中存在文件循環(huán)引用,也會(huì)導(dǎo)致棧溢出,可以通過文件增加module.hot.accept這樣的方法解決,但是如果項(xiàng)目公用方法存在這樣的問題,就只能強(qiáng)行把HMR的逆向加載這塊代碼注釋了。這無疑是閹割了HMR一大部分功能

綜上,HMR如果僅僅用于切圖,可能不會(huì)有那么多的問題

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/94378.html

相關(guān)文章

  • React原理探索- @providesModule 模塊系統(tǒng)

    摘要:原理探索模塊系統(tǒng)是什么拋出組件化的概念后,對于開發(fā)者而言,為了提高代碼的可讀性與結(jié)構(gòu)性,通過文件目錄結(jié)構(gòu)去闡述組件嵌套關(guān)系無疑是一個(gè)很好的辦法,但是目錄級別的加深,同時(shí)讓的文件路徑讓人頭疼。 React原理探索- @providesModule 模塊系統(tǒng) @providesModule是什么 react拋出組件化的概念后,對于開發(fā)者而言,為了提高代碼的可讀性與結(jié)構(gòu)性,通過文件目錄結(jié)構(gòu)去...

    My_Oh_My 評論0 收藏0
  • #yyds干貨盤點(diǎn)#探索RocketMQ的DefaultMQPullConsumer的原理及源碼分析

    摘要:與相比最大的區(qū)別是,消費(fèi)哪些隊(duì)列的消息,從哪個(gè)位移開始消費(fèi),以及何時(shí)提交消費(fèi)位移都是由程序自己的控制的。下面來介紹一下的內(nèi)部原理。最后將對象集合返回給調(diào)用者。向發(fā)送請求獲取參數(shù)對應(yīng)的信息和配置信息,即對象。 前提介紹在RocketMQ中一般有兩種獲取消息的方式,一個(gè)是拉(pull,消費(fèi)者主動(dòng)去broker拉取)...

    不知名網(wǎng)友 評論0 收藏0
  • JavaScript尾遞歸優(yōu)化探索

    摘要:原文地址尾調(diào)優(yōu)化在知道尾遞歸之前,我們要直到什么是尾調(diào)用優(yōu)化,因?yàn)槲舱{(diào)用優(yōu)化是尾遞歸的基礎(chǔ)。尾遞歸優(yōu)化,就是利用尾調(diào)用優(yōu)化的原理,對遞歸進(jìn)行優(yōu)化。所以當(dāng)我們使用尾遞歸進(jìn)行優(yōu)化的時(shí)候,依舊發(fā)生了棧溢出的錯(cuò)誤。 原文地址:https://github.com/HolyZheng/... 尾調(diào)優(yōu)化 在知道尾遞歸之前,我們要直到什么是尾調(diào)用優(yōu)化,因?yàn)槲舱{(diào)用優(yōu)化是尾遞歸的基礎(chǔ)。尾調(diào)用就是:在函...

    sean 評論0 收藏0
  • 利用RSA對前后端加密的探索

    摘要:項(xiàng)目地址前后端交互時(shí)為了保證信息安全可使用方式加密信息,在數(shù)據(jù)量大的時(shí)候可采用結(jié)合方式。由于加密和解密使用同樣規(guī)則簡稱密鑰,這被稱為對稱加密算法。從那時(shí)直到現(xiàn)在,算法一直是最廣為使用的非對稱加密算法。 RSA-JS-PHP 項(xiàng)目地址rsa-js-php 前后端交互時(shí)為了保證信息安全可使用RSA方式加密信息,在數(shù)據(jù)量大的時(shí)候可采用DES+RSA結(jié)合方式。DEMO演示地址 一點(diǎn)歷史 1...

    banana_pi 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<