整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          通過debug搞清楚.vue文件怎么變成.js文件

          通過debug搞清楚.vue文件怎么變成.js文件

          們每天寫的vue代碼都是寫在vue文件中,但是瀏覽器卻只認識htmlcssjs等文件類型。所以這個時候就需要一個工具將vue文件轉換為瀏覽器能夠認識的js文件,想必你第一時間就想到了webpack或者vite。但是webpackvite本身是沒有能力處理vue文件的,其實實際背后生效的是vue-loader和@vitejs/plugin-vue。本文以@vitejs/plugin-vue舉例,通過debug的方式帶你一步一步的搞清楚vue文件是如何編譯為js文件的,看不懂你來打我

          舉個例子

          這個是我的源代碼App.vue文件:

          這個例子很簡單,在setup中定義了msg變量,然后在template中將msg渲染出來。

          下面這個是我從network中找到的編譯后的js文件,已經精簡過了:


          編譯后的js代碼中我們可以看到主要有三部分,想必你也猜到了這三部分剛好對應vue文件的那三塊。

          • _sfc_main對象的setup方法對應vue文件中的<script setup lang="ts">模塊。
          • _sfc_render函數對應vue文件中的<template>模塊。
          • import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";對應vue文件中的<style scoped>模塊。

          debug搞清楚如何將vue文件編譯為js文件

          大家應該都知道,前端代碼運行環境主要有兩個,node端和瀏覽器端,分別對應我們熟悉的編譯時和運行時。瀏覽器明顯是不認識vue文件的,所以vue文件編譯成js這一過程肯定不是在運行時的瀏覽器端。很明顯這一過程是在編譯時的node端。

          要在node端打斷點,我們需要啟動一個debug 終端。這里以vscode舉例,首先我們需要打開終端,然后點擊終端中的+號旁邊的下拉箭頭,在下拉中點擊Javascript Debug Terminal就可以啟動一個debug終端。

          假如vue文件編譯為js文件是一個毛線團,那么他的線頭一定是vite.config.ts文件中使用@vitejs/plugin-vue的地方。通過這個線頭開始debug我們就能夠梳理清楚完整的工作流程。


          vuePlugin函數

          我們給上方圖片的vue函數打了一個斷點,然后在debug終端上面執行yarn dev,我們看到斷點已經停留在了vue函數這里。然后點擊step into,斷點走到了@vitejs/plugin-vue庫中的一個vuePlugin函數中。我們看到vuePlugin函數中的內容代碼大概是這樣的:


          @vitejs/plugin-vue是作為一個plugins插件在vite中使用,vuePlugin函數返回的對象中的buildStarttransform方法就是對應的插件鉤子函數。vite會在對應的時候調用這些插件的鉤子函數,比如當vite服務器啟動時就會調用插件里面的buildStart等函數,當vite解析每個模塊時就會調用transform等函數。更多vite鉤子相關內容查看官網。

          我們這里主要看buildStarttransform兩個鉤子函數,分別是服務器啟動時調用和解析每個模塊時調用。給這兩個鉤子函數打上斷點。


          然后點擊Continue(F5),vite服務啟動后就會走到buildStart鉤子函數中打的斷點。我們可以看到buildStart鉤子函數的代碼是這樣的:


          將鼠標放到options.value.compiler上面我們看到此時options.value.compiler的值為null,所以代碼會走到resolveCompiler函數中,點擊Step Into(F11)走到resolveCompiler函數中。看到resolveCompiler函數代碼如下:


          resolveCompiler函數中調用了tryResolveCompiler函數,在tryResolveCompiler函數中判斷當前項目是否是vue3.x版本,然后將vue/compiler-sfc包返回。所以經過初始化后options.value.compiler的值就是vue的底層庫vue/compiler-sfc,記住這個后面會用

          然后點擊Continue(F5)放掉斷點,在瀏覽器中打開對應的頁面,比如:http://localhost:5173/ 。此時vite將會編譯這個頁面要用到的所有文件,就會走到transform鉤子函數斷點中了。由于解析每個文件都會走到transform鉤子函數中,但是我們只關注App.vue文件是如何解析的,所以為了方便我們直接在transform函數中添加了下面這段代碼,并且刪掉了原來在transform鉤子函數中打的斷點,這樣就只有解析到App.vue文件的時候才會走到斷點中去。


          經過debug我們發現解析App.vue文件時transform函數實際就是執行了transformMain函數,至于transformStyle函數后面講解析style的時候會講:


          transformMain函數

          繼續debug斷點走進transformMain函數,發現transformMain函數中代碼邏輯很清晰。按照順序分別是:

          • 根據源代碼code字符串調用createDescriptor函數生成一個descriptor對象。
          • 調用genScriptCode函數傳入第一步生成的descriptor對象將<script setup>模塊編譯為瀏覽器可執行的js代碼。
          • 調用genTemplateCode函數傳入第一步生成的descriptor對象將<template>模塊編譯為render函數。
          • 調用genStyleCode函數傳入第一步生成的descriptor對象將<style scoped>模塊編譯為類似這樣的import語句,import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";。

          createDescriptor函數

          我們先來看看createDescriptor函數,將斷點走到createDescriptor(filename, code, options)這一行代碼,可以看到傳入的filename就是App.vue的文件路徑,code就是App.vue中我們寫的源代碼。


          debug走進createDescriptor函數,看到createDescriptor函數的代碼如下:


          這個compiler是不是覺得有點熟悉?compiler是調用createDescriptor函數時傳入的第三個參數解構而來,而第三個參數就是options。還記得我們之前在vite啟動時調用了buildStart鉤子函數,然后將vue底層包vue/compiler-sfc賦值給optionscompiler屬性。那這里的compiler.parse其實就是調用的vue/compiler-sfc包暴露出來的parse函數,這是一個vue暴露出來的底層的API,這篇文章我們不會對底層API進行源碼解析,通過查看parse函數的輸入和輸出基本就可以搞清楚parse函數的作用。下面這個是parse函數的類型定義:


          從上面我們可以看到parse函數接收兩個參數,第一個參數為vue文件的源代碼,在我們這里就是App.vue中的code字符串,第二個參數是一些options選項。我們再來看看parse函數的返回值SFCParseResult,主要有類型為SFCDescriptordescriptor屬性需要關注。


          仔細看看SFCDescriptor類型,其中的template屬性就是App.vue文件對應的template標簽中的內容,里面包含了由App.vue文件中的template模塊編譯成的AST抽象語法樹和原始的template中的代碼。


          我們再來看scriptscriptSetup屬性,由于vue文件中可以寫多個script標簽,scriptSetup對應的就是有setupscript標簽,script對應的就是沒有setup對應的script標簽。我們這個場景中只有scriptSetup屬性,里面同樣包含了App.vue中的script模塊中的內容。


          我們再來看看styles屬性,這里的styles屬性是一個數組,是因為我們可以在vue文件中寫多個style模塊,里面同樣包含了App.vue中的style模塊中的內容。


          所以這一步執行createDescriptor函數生成的descriptor對象中主要有三個屬性,template屬性包含了App.vue文件中的template模塊code字符串和AST抽象語法樹scriptSetup屬性包含了App.vue文件中的<script setup>模塊的code字符串,styles屬性包含了App.vue文件中<style>模塊中的code字符串。createDescriptor函數的執行流程圖如下:


          genScriptCode函數

          我們再來看genScriptCode函數是如何將<script setup>模塊編譯成可執行的js代碼,同樣將斷點走到調用genScriptCode函數的地方,genScriptCode函數主要接收我們上一步生成的descriptor對象,調用genScriptCode函數后會將編譯后的script模塊代碼賦值給scriptCode變量。


          將斷點走到genScriptCode函數內部,在genScriptCode函數中主要就是這行代碼: const script=resolveScript(descriptor, options, ssr, customElement);。將第一步生成的descriptor對象作為參數傳給resolveScript函數,返回值就是編譯后的js代碼,genScriptCode函數的代碼簡化后如下:


          我們繼續將斷點走到resolveScript函數內部,發現resolveScript中的代碼其實也很簡單,簡化后的代碼如下:


          這里的options.compiler我們前面第一步的時候已經解釋過了,options.compiler對象實際就是vue底層包vue/compiler-sfc暴露的對象,這里的options.compiler.compileScript()其實就是調用的vue/compiler-sfc包暴露出來的compileScript函數,同樣也是一個vue暴露出來的底層的API,后面我們的分析defineOptions等文章時會去深入分析compileScript函數,這篇文章我們不會去讀compileScript函數的源碼。通過查看compileScript函數的輸入和輸出基本就可以搞清楚compileScript函數的作用。下面這個是compileScript函數的類型定義:



          這個函數的入參是一個SFCDescriptor對象,就是我們第一步調用生成createDescriptor函數生成的descriptor對象,第二個參數是一些options選項。我們再來看返回值SFCScriptBlock類型:


          返回值類型中主要有scriptAstscriptSetupAstcontent這三個屬性,scriptAst為編譯不帶setup屬性的script標簽生成的AST抽象語法樹。scriptSetupAst為編譯帶setup屬性的script標簽生成的AST抽象語法樹,contentvue文件中的script模塊編譯后生成的瀏覽器可執行的js代碼。下面這個是執行vue/compiler-sfccompileScript函數返回結果:


          繼續將斷點走回genScriptCode函數,現在邏輯就很清晰了。這里的script對象就是調用vue/compiler-sfccompileScript函數返回對象,scriptCode就是script對象的content屬性 ,也就是將vue文件中的script模塊經過編譯后生成瀏覽器可直接執行的js代碼code字符串。


          genScriptCode函數的執行流程圖如下:


          genTemplateCode函數

          我們再來看genTemplateCode函數是如何將template模塊編譯成render函數的,同樣將斷點走到調用genTemplateCode函數的地方,genTemplateCode函數主要接收我們上一步生成的descriptor對象,調用genTemplateCode函數后會將編譯后的template模塊代碼賦值給templateCode變量。


          同樣將斷點走到genTemplateCode函數內部,在genTemplateCode函數中主要就是返回transformTemplateInMain函數的返回值,genTemplateCode函數的代碼簡化后如下:


          我們繼續將斷點走進transformTemplateInMain函數,發現這里也主要是調用compile函數,代碼如下:

          同理將斷點走進到compile函數內部,我們看到compile函數的代碼是下面這樣的:

          同樣這里也用到了options.compiler,調用options.compiler.compileTemplate()其實就是調用的vue/compiler-sfc包暴露出來的compileTemplate函數,這也是一個vue暴露出來的底層的API。不過這里和前面不同的是compileTemplate接收的不是descriptor對象,而是一個SFCTemplateCompileOptions類型的對象,所以這里需要調用resolveTemplateCompilerOptions函數將參數轉換成SFCTemplateCompileOptions類型的對象。這篇文章我們不會對底層API進行解析。通過查看compileTemplate函數的輸入和輸出基本就可以搞清楚compileTemplate函數的作用。下面這個是compileTemplate函數的類型定義:

          入參options主要就是需要編譯的template中的源代碼和對應的AST抽象語法樹。我們來看看返回值SFCTemplateCompileResults,這里面的code就是編譯后的render函數字符串。

          genTemplateCode函數的執行流程圖如下:


          genStyleCode函數

          我們再來看最后一個genStyleCode函數,同樣將斷點走到調用genStyleCode的地方。一樣的接收descriptor對象。代碼如下:

          我們將斷點走進genStyleCode函數內部,發現和前面genScriptCodegenTemplateCode函數有點不一樣,下面這個是我簡化后的genStyleCode函數代碼:

          我們前面講過因為vue文件中可能會有多個style標簽,所以descriptor對象的styles屬性是一個數組。遍歷descriptor.styles數組,我們發現for循環內全部都是一堆賦值操作,沒有調用vue/compiler-sfc包暴露出來的任何API。將斷點走到 return stylesCode;,看看stylesCode到底是什么東西?

          通過打印我們發現stylesCode竟然變成了一條import語句,并且import的還是當前App.vue文件,只是多了幾個query分別是:vuetypeindexscopedlang。再來回憶一下前面講的@vitejs/plugin-vuetransform鉤子函數,當vite解析每個模塊時就會調用transform等函數。所以當代碼運行到這行import語句的時候會再次走到transform鉤子函數中。我們再來看看transform鉤子函數的代碼:

          query中有vue字段,并且querytype字段值為style時就會執行transformStyle函數,我們給transformStyle函數打個斷點。當執行上面那條import語句時就會走到斷點中,我們進到transformStyle中看看。

          transformStyle函數的實現我們看著就很熟悉了,和前面處理templatescript一樣都是調用的vue/compiler-sfc包暴露出來的compileStyleAsync函數,這也是一個vue暴露出來的底層的API。同樣我們不會對底層API進行解析。通過查看compileStyleAsync函數的輸入和輸出基本就可以搞清楚compileStyleAsync函數的作用。

          我們先來看看SFCAsyncStyleCompileOptions入參:

          入參主要關注幾個字段,source字段為style標簽中的css原始代碼。scoped字段為style標簽中是否有scoped attribute。id字段為我們在觀察 DOM 結構時看到的 data-v-xxxxx。這個是debug時入參截圖:

          再來看看返回值SFCStyleCompileResults對象,主要就是code屬性,這個是經過編譯后的css字符串,已經加上了data-v-xxxxx

          這個是debugcompileStyleAsync函數返回值的截圖:

          genStyleCode函數的執行流程圖如下:

          transformMain函數簡化后的代碼

          現在我們可以來看transformMain函數簡化后的代碼:

          transformMain函數中的代碼執行主流程,其實就是對應了一個vue文件編譯成js文件的流程。

          首先調用createDescriptor函數將一個vue文件解析為一個descriptor對象。

          然后以descriptor對象為參數調用genScriptCode函數,將vue文件中的<script>模塊代碼編譯成瀏覽器可執行的js代碼code字符串,賦值給scriptCode變量。

          接著以descriptor對象為參數調用genTemplateCode函數,將vue文件中的<template>模塊代碼編譯成render函數code字符串,賦值給templateCode變量。

          然后以descriptor對象為參數調用genStyleCode函數,將vue文件中的<style>模塊代碼編譯成了import語句code字符串,比如:import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";,賦值給stylesCode變量。

          然后將scriptCodetemplateCodestylesCode使用換行符\n拼接起來得到resolvedCode,這個resolvedCode就是一個vue文件編譯成js文件的代碼code字符串。這個是debugresolvedCode變量值的截圖:

          總結

          這篇文章通過debug的方式一步一步的帶你了解vue文件編譯成js文件的完整流程,下面是一個完整的流程圖。如果文字太小看不清,可以將圖片保存下來或者放大看:

          @vitejs/plugin-vue-jsx庫中有個叫transform的鉤子函數,每當vite加載模塊的時候就會觸發這個鉤子函數。所以當import一個vue文件的時候,就會走到@vitejs/plugin-vue-jsx中的transform鉤子函數中,在transform鉤子函數中主要調用了transformMain函數。

          第一次解析這個vue文件時,在transform鉤子函數中主要調用了transformMain函數。在transformMain函數中主要調用了4個函數,分別是:createDescriptorgenScriptCodegenTemplateCodegenStyleCode

          createDescriptor接收的參數為當前vue文件代碼code字符串,返回值為一個descriptor對象。對象中主要有四個屬性templatescriptSetupscriptstyles

          • descriptor.template.ast就是由vue文件中的template模塊生成的AST抽象語法樹。
          • descriptor.template.content就是vue文件中的template模塊的代碼字符串。
          • scriptSetup和script的區別是分別對應的是vue文件中有setup屬性的<script>模塊和無setup屬性的<script>模塊。descriptor.scriptSetup.content就是vue文件中的<script setup>模塊的代碼字符串。

          genScriptCode函數為底層調用vue/compiler-sfccompileScript函數,根據第一步的descriptor對象將vue文件的<script setup>模塊轉換為瀏覽器可直接執行的js代碼。

          genTemplateCode函數為底層調用vue/compiler-sfccompileTemplate函數,根據第一步的descriptor對象將vue文件的<template>模塊轉換為render函數。

          genStyleCode函數為將vue文件的style模塊轉換為import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";樣子的import語句。

          然后使用換行符\ngenScriptCode函數、genTemplateCode函數、genStyleCode函數的返回值拼接起來賦值給變量resolvedCode,這個resolvedCode就是vue文件編譯成js文件的code字符串。

          當瀏覽器執行到import "/src/App.vue?vue&type=style&index=0&scoped=7a7a37b1&lang.css";語句時,觸發了加載模塊操作,再次觸發了@vitejs/plugin-vue-jsx中的transform鉤子函數。此時由于有了type=stylequery,所以在transform函數中會執行transformStyle函數,在transformStyle函數中同樣也是調用vue/compiler-sfccompileStyleAsync函數,根據第一步的descriptor對象將vue文件的<style>模塊轉換為編譯后的css代碼code字符串,至此編譯style部分也講完了。

          avaScript 是語言,而 React 是工具。

          力爭用最簡潔的方式讓你入門 React,前提是你已了解 JavaScript 和 HTML。

          0x01 從 JavaScript 到 React

          一些必須要懂的 JavaScript 概念

          如果你在學習 React 的同時,也在學習 JavaScript,那么這里羅列了一些必須要懂的 JavaScript 概念,來幫助你更好地學習 React。

          • 函數 和 箭頭函數
          • 對象
          • 數組及其常用方法
          • 解構賦值
          • 模版字符串
          • 條件運算符
          • JS 模塊化和導入導出方法

          本文將不會深入講解關于 JavaScript 方面的知識,你無需非常精通 JavaScript 才能學習 React,但以上的一些概念是最適合初學者掌握的 JavaScript 的重要知識。

          當然,你也可以跳過這些基本概念直接進入下面章節的學習,當遇到不理解的問題再回過頭來翻閱這里的概念。

          渲染 UI

          要理解 React 如何工作,首先要搞清楚瀏覽器是如何解釋你的代碼并轉化成 UI 的。

          當用戶訪問一個網頁時,服務器將返回一段 html 代碼到瀏覽器,它可能看起來是這樣的:



          瀏覽器閱讀了 html 代碼,并將它結構化為 DOM。

          什么是 DOM

          DOM 是一個 html 元素的對象化表達,它是銜接你的代碼和 UI 之間的橋梁,它表現為一個父子關系的樹形結構。



          你可以使用 JavaScript 或 DOM 的內置方法來監聽用戶事件,操作 DOM 包括,查詢、插入、更新、刪除界面上特定的元素,DOM 操作不僅允許你定位到特定的元素上,并且允許你修改它的內容及樣式。

          小問答:你可以通過操作 DOM 來修改頁面內容嗎?

          使用 JavaScript 及 DOM 方法來更新 UI

          讓我們一起來嘗試如何使用 JavaScript 及 DOM 方法來添加一個 h1 標簽到你的項目中去。

          打開我們的代碼編輯軟件,然后創建一個新的 index.html 的文件,在文件中加入以下代碼:

          <!-- index.html -->
          <html>
            <body>
              <div></div>
            </body>
          </html>

          然后給定 div 標簽一個特定的 id ,便于后續我們可以定位它。

          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
            </body>
          </html>

          要在 html 文件中編寫 JavaScript 代碼,我們需要添加 script 標簽

          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
              <script type="text/javascript"></script>
            </body>
          </html>

          現在,我們可以使用 DOM 提供的 getElementById 方法來通過標簽的 ID 定位到指定的元素。

          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
          
              <script type="text/javascript">
                const app=document.getElementById('app');
              </script>
            </body>
          </html>

          你可以繼續使用 DOM 的一系列方法來創建一個 h1 標簽元素,h1 元素中可以包含任何你希望展示的文本。

          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
          
              <script type="text/javascript">
                // 定位到 id 為 app 的元素
                const app=document.getElementById('app');
          
                // 創建一個 h1 元素
                const header=document.createElement('h1');
          
                // 創建一個文本節點
                const headerContent=document.createTextNode(
                  'Develop. Preview. Ship.  ',
                );
          
                // 將文本節點添加到 h1 元素中去
                header.appendChild(headerContent);
          
                // 將 h1 元素添加到 id 為 app 的元素中去
                app.appendChild(header);
              </script>
            </body>
          </html>

          至此,你可以打開瀏覽器來預覽一下目前的成果,不出意外的話,你應該可以看到一行使用 h1 標簽的大字,寫道:Develop. Preview. Ship.

          HTML vs DOM

          此時,如果你打開瀏覽器的代碼審查功能,你會注意到在 DOM 中已經包含了剛才創建的 h1 標簽,但源代碼的 html 中卻并沒有。換言之,你所創建的 html 代碼中與實際展示的內容是不同的。



          這是因為 HTML 代碼中展示的是初始化的頁面內容,而 DOM 展示的是更新后的頁面內容,這里尤指你通過 JavaScript 代碼對 HTML 所改變后的內容。

          使用 JavaScript 來更新 DOM,是非常有用的,但也往往比較繁瑣。你寫了如下那么多內容,僅僅用來添加一行 h1 標簽。如果要編寫一個大一些的項目,或者團隊開發,就感覺有些杯水車薪了。

          <!-- index.html -->
          <script type="text/javascript">
            const app=document.getElementById('app');
            const header=document.createElement('h1');
            const headerContent=document.createTextNode('Develop. Preview. Ship.  ');
            header.appendChild(headerContent);
            app.appendChild(header);
          </script>

          以上這個例子中,開發者花了大力氣來“指導”計算機該如何做事,但這似乎并不太友好,或者有沒有更友好的方式讓計算機迅速理解我們希望達到的樣子呢?

          命令式 vs 聲明式編程

          以上就是一個很典型的命令式編程,你一步一步的告訴計算機該如何更新用戶界面。但對于創建用戶界面,更好的方式是使用聲明式,因為那樣可以大大加快開發效率。相較于編寫 DOM 方法,最好有種方法能聲明開發者想要展示的內容(本例中就是那個 h1 標簽以及它包含的文本內容)。

          換句話說就是,命令式編程就像你要吃一個披薩,但你得告訴廚師該如何一步一步做出那個披薩;而聲明式編程就是你告訴廚師你要吃什么樣的披薩,而無需考慮怎么做。

          React 正是那個“懂你”的廚師!

          React:一個聲明式的 UI 庫

          作為一個 React 開發者,你只需告訴 React 你希望展示什么樣的頁面,而它會自己找到方法來處理 DOM 并指導它正確地展示出你所要的效果。

          小問答:你覺得以下哪句話更像聲明式?
          A:我要吃一盤菜,它要先放花生,然后放點雞肉丁,接著炒一下...
          B:來份宮保雞丁


          0x02 快速入門 React

          要想在項目中使用 React,最簡單的方法就是從外部 CDN(如:http://unpkg.com)引入兩個 React 的包:

          • react:React 核心庫
          • react-dom:提供用于操作 DOM 特定方法的庫
          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
          
              <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
              <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
          
              <script type="text/javascript">
                const app=document.getElementById('app');
              </script>
            </body>
          </html>

          這樣就無需使用純 JavaScript 來直接操作 DOM 了,而是使用來自 react-dom 中的 ReactDOM.render() 方法來告訴 React 在 app 標簽中直接渲染 h1 標簽及其文本內容。

          <!-- index.html -->
          <html>
            <body>
              <div id="app"></div>
          
              <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
              <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
          
              <script type="text/javascript">
                const app=document.getElementById('app');
                ReactDOM.render(<h1>Develop. Preview. Ship.  </h1>, app);
              </script>
            </body>
          </html>

          但當你在瀏覽器中運行的時候,它會報一個語法錯誤:

          因為代碼中的 <h1>Develop. Preview. Ship. </h1&gt; 并不是 JavaScript 代碼,而是 JSX。

          什么是 JSX

          JSX 是一種 JS 的語法擴展,它使得你可以用類 HTML 的方式來描述界面。你無需學習 HTML 和 JavaScript 之外的新的符號和語法等,只需要遵守以下三條規則即可:

          • 返回一個單根節點的元素,如:
          <!-- 你可以使用 div 標簽 -->
          <div>
            <h1>Hedy Lamarr's Todos</h1>
            <img 
              src="https://i.imgur.com/yXOvdOSs.jpg" 
              alt="Hedy Lamarr" 
              className="photo"
            />
            <ul>
              ...
            </ul>
          </div>
          
          <!-- 你也可以使用空標簽 -->
          <>
            <h1>Hedy Lamarr's Todos</h1>
            <img 
              src="https://i.imgur.com/yXOvdOSs.jpg" 
              alt="Hedy Lamarr" 
              className="photo"
            />
            <ul>
              ...
            </ul>
          </>
          • 關閉所有標簽
          <!-- 諸如 img 必須自關閉 <img />,而包圍類標簽必須成對出現 <li></li> -->
          <>
            <img 
              src="https://i.imgur.com/yXOvdOSs.jpg" 
              alt="Hedy Lamarr" 
              className="photo"
             />
            <ul>
              <li>Invent new traffic lights</li>
              <li>Rehearse a movie scene</li>
              <li>Improve the spectrum technology</li>
            </ul>
          </>
          • 使用駝峰命名(camalCase)方式
          <!-- 如 stroke-width 必須寫成 strokeWidth,而 class 由于是 react 的關鍵字,因此替換為 className
          <img 
            src="https://i.imgur.com/yXOvdOSs.jpg" 
            alt="Hedy Lamarr" 
            className="photo"
          />

          JSX 并不是開箱即用的,瀏覽器默認情況下是無法解釋 JSX 的,所以你需要一個編譯器(compiler),諸如 Babel,來將 JSX 代碼轉換為普通的瀏覽器能理解的 JavaScript。

          在項目中添加 Babel

          復制粘貼以下腳本到 index.html 文件中:

          <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

          另外,你還需要告訴 Babel 需要轉換哪些代碼,為需要轉換的代碼添加類型 type="text/jsx"

          <html>
            <body>
              <div id="app"></div>
              <script src="https://unpkg.com/react@17/umd/react.development.js"></script>
              <script src="https://unpkg.com/react-dom@17/umd/react-dom.development.js"></script>
              <!-- Babel Script -->
              <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
              <script type="text/jsx">
                const app=document.getElementById('app');
                ReactDOM.render(<h1>Develop. Preview. Ship.  </h1>, app);
              </script>
            </body>
          </html>

          現在可以再次回到瀏覽器中刷新頁面來確認是否能成功展示了。



          使用聲明式的 React,你只編寫了以下代碼:

          <script type="text/jsx">
            const app=document.getElementById("app")
            ReactDOM.render(<h1>Develop. Preview. Ship.  </h1>, app)
          </script>

          而命令式代碼如此前編寫的:

          <script type="text/javascript">
            const app=document.getElementById('app');
            const header=document.createElement('h1');
            const headerContent=document.createTextNode('Develop. Preview. Ship.  ');
            header.appendChild(headerContent);
            app.appendChild(header);
          </script>

          相比較后不難發現,你節省了很多重復冗余的工作。

          這就是 React,一款富含可重用代碼,為你節省時間和提高效率的工具。

          目前,你還無需過多關注究竟 React 用了什么神奇的魔法實現這樣的功能。當然如果你感興趣的話,可以參考 React 的官方文檔中的 UI Tree 和 render 兩個章節。

          React 核心概念

          在真正上手 React 項目之前,還有三個最重要的 React 核心概念需要理解

          • 組件(Components)
          • 參數(Props)
          • 狀態(State)

          在后續的章節中我們將逐一學習以上三個核心概念。


          0x03 使用組件(Components)來建立界面

          一個用戶界面可以被分割成更小的部分,我們稱之為“組件”。它是自包含、可重用的代碼塊,你可以把它想象成樂高玩具,獨立的磚塊可以拼裝成更大的組合結構。如果你要更新界面的某一部分,你可以僅更新特定的組件或“磚塊”。



          模塊化讓你的代碼更具有可維護性,因為你可以輕易地添加、修改、刪除特定的組件而無需改動程序的其他部分。React 組件其實就是用 JavaScript 編寫的,接下來我們將學習如何編寫一個從 JavaScript 原生到 React 的組件。

          創建組件

          在 React 中,組件就是函數,我們在 script 中插入一個 header 方法:

          <script type="text/jsx">
            const app=document.getElementById("app")
            function header() {}
            ReactDOM.render(<h1>Develop. Preview. Ship.  </h1>, app)
          </script>

          組件函數返回一個界面元素(即我們前面所提到過的單根節點的元素),可以使用 JSX 語法,如:

          <script type="text/jsx">
            const app=document.getElementById("app")
          
            function header() {
               return (<h1>Develop. Preview. Ship.  </h1>)
             }
          
            ReactDOM.render(, app)
          </script>

          然后將 header 傳入 ReactDOM.render 的第一個參數中去:

          ReactDOM.render(header, app)
          

          但如果你現在刷新瀏覽器預覽效果的話,將會報錯,因為還需要做兩件事。

          首先,React 組件必須以大寫字母開頭:

          // 首字母大寫
          function Header() {
            return <h1>Develop. Preview. Ship.  </h1>;
          }
          
          ReactDOM.render(Header, app);
          

          其次,你在使用 React 組件時,也需要使用 JSX 的語法格式,將組件名稱用 <> 擴起來:

          function Header() {
            return <h1>Develop. Preview. Ship.  </h1>;
          }
          <br/>ReactDOM.render(<Header />, app);
          

          嵌套組件

          一個應用程序通常包含多個組件,有的甚至是組件嵌套的組件。例如我們來創建一個 HomePage 組件:

          function Header() {
            return <h1>Develop. Preview. Ship.  </h1>;
          }
          
          function HomePage() {
            return <div></div>;
          }
          
          ReactDOM.render(<Header />, app);
          

          然后將 Header 組件放入 HomePage 組件中:

          function Header() {
            return <h1>Develop. Preview. Ship.  </h1>;
          }
          
          function HomePage() {
            return (
              <div>
                {/* 嵌套的 Header 組件 */}
                <Header />
              </div>
            );
          }
          
          ReactDOM.render(<HomePage />, app);
          

          組件樹

          你可以繼續以這種方式嵌套 React 組件,以形成一個更大的組件。



          比如上圖中,你的頂層組件是 HomePage,它下面包含了一個 Header,一個 ARTICLE 和一個 FOOTER。然后 HEADER 組件下又包含了它的子組件等等。

          這樣的模塊化使得你可以在項目的許多其他地方重用組件。


          0x04 參數(Props)與數據展示

          如果你重用 Header 組件,你將顯示相同的內容兩次。

          function Header() {
            return <h1>Develop. Preview. Ship.  </h1>;
          }
          
          function HomePage() {
            return (
              <div><br/>      <Header />
                <Header />
              </div>
            );
          }
          

          但如果你希望在標題中傳入不同的文本,或者你需要從外部源獲取數據再進行文本的設置時,該怎么辦呢?

          普通的 HTML 元素允許你通過設置對應標簽的某些重要屬性來修改其實際的展示內容,比如修改 <img>src 屬性就能修改圖片展示,修改 <a>href 就能改變超文本鏈接的目標地址。

          同樣的,你可以通過傳入某些屬性值來改變 React 的組件,這被稱為參數(Props)



          與 JavaScript 函數類似,你可以設計一個組件接收一些自定義的參數或者屬性來改變組件的行為或展示效果,并且還允許通過父組件傳遞給子組件。

          ?? 注意:React 中,數據流是順著組件數傳遞的。這被稱為單向數據流

          使用參數

          HomePage 組件中,你可以傳入一個自定義的 title 屬性給 Header 組件,就如同你傳入了一個 HTML 屬性一樣。

          // function Header() {
          //   return <h1>Develop. Preview. Ship.  </h1>
          // }
          
          function HomePage() {
            return (
              <div>
                <Header title="Hello React" />
              </div>
            );
          }
          
          // ReactDOM.render(<HomePage />, app)
          

          然后,Header 作為子組件可以接收這些傳入的參數,可在組件函數的第一個入參中獲得。

          function Header(props) {
            return <h1>Develop. Preview. Ship.  </h1>
          }
          

          你可以嘗試打印 props 來查看它具體是什么東西。

          function Header(props) {
              console.log(props) // { title: "Hello React" }
              return <h1>Hello React</h1>
          }
          

          由于 props 是一個 JS 對象,因此你可以使用對象解構來展開獲得對象中的具體鍵值。

          function Header({ title }) {
              console.log(title) // "Hello React"
              return <h1>Hello React</h1>
          }
          

          現在你就能使用 title 變量來替換 h1 標題中的文本了。

          function Header({ title }) {
              console.log(title) // "Hello React"
              return <h1>title</h1>
          }
          

          但當你打開瀏覽器刷新頁面時,你會發現頁面上展示的是標題文本是 title,而不是 title 變量的值。這是因為 React 不能對純文本進行解析,這就需要你額外地對文本展示做一些處理。

          在 JSX 中使用變量

          要在 JSX 中使用你定義的變量,你需要使用花括號 {} ,它允許你在其中編寫 JavaScript 表達式

          function Header({ title }) {
              console.log(title) // "Hello React"
              return <h1>{ title }</h1>
          }
          

          通常,它支持如下幾種方式:

          • 輸出對象屬性 { props.title }
          • 模板字符串 {`Hello ${title}`}
          • 函數返回值 { getTitle() }
          • 三元表達式 { title ? title : "Hello" }

          這樣你就能根據參數輸出不同的標題文本了:

          function Header({ title }) {
            return <h1>{title ? title : 'Hello React!'}</h1>;
          }
          
          function Page() {
            return (
              <div>
                <Header title="Hello JavaScript!" />
                <Header title="Hello World!" />
              </div>
            );
          }
          

          通過列表進行迭代

          通常我們會有一組數據需要展示,它以列表形式呈現,你可以使用數組方法來操作數據,并生成在樣式上統一的不同內容。

          例如,在 HomePage 中添加一組名字,然后依次展示它們。

          function HomePage() {
            const names=['Mike', 'Grace', 'Margaret'];
          
            return (
              <div>
                <Header title="Develop. Preview. Ship.  " />
              </div>
            );
          }
          

          然后你可以使用 Arraymap 方法對數據進行迭代輸出,并使用箭頭函數來將數據映射到每個迭代項目上。

          function HomePage() {
            const names=['Mike', 'Grace', 'Margaret'];
          
            return (
              <div>
                <Header title="Develop. Preview. Ship.  " />
                <ul>
                  {names.map((name)=> (
                    <li>{name}</li>
                  ))}
                </ul>
              </div>
            );
          }
          

          現在如果你打開瀏覽器查看,會看到一個關于缺少 key 屬性的警告。這是因為 React 需要通過 key 屬性來唯一識別數組上的元素來確定最終需要在 DOM 上更新的項目。通常我們會使用 id,但本例子中你可以直接使用 name,因為它們的值也是唯一不同的。

          function HomePage() {
            const names=['Mike', 'Grace', 'Margaret'];
          
            return (
              <div>
                <Header title="Develop. Preview. Ship.  " />
                <ul>
                  {names.map((name)=> (
                    <li key={name}>{name}</li>
                  ))}
                </ul>
              </div>
            );
          }
          

          0x05 使用狀態(State)來增加交互性

          首先,我們看下 React 是如何通過狀態和事件處理來幫助我們增加交互性的。

          我們在 HomePage 組件中添加一個“喜歡”按鈕:

          function HomePage() {
            const names=['Mike', 'Grace', 'Margaret'];
          
            return (
              <div>
                <Header title="Develop. Preview. Ship.  " />
                <ul>
                  {names.map((name)=> (
                    <li key={name}>{name}</li>
                  ))}
                </ul>
          
                <button>Like</button>
              </div>
            );
          }
          

          監聽事件

          要讓按鈕在被點擊的時候做些什么時,你可以在按鈕上添加 onClick 事件屬性:

          function HomePage() {
            // ...
            return (
              <div>
                {/* ... */}
                <button onClick={}>Like</button>
              </div>
            );
          }
          

          在 React 中,屬性名稱都是駝峰命名式的,onClick 是許多事件屬性中的一種,還有一些其他的事件屬性,如:輸入框會有 onChange ,表單會有 onSubmit 等。

          處理事件

          你可以定義一個函數來處理以上一些事件,當它被觸發的時候。事件處理函數可以在返回語句之前定義,如:

          function HomePage() {
            // ...
          
            function handleClick() {
              console.log("I like it.")
            }
          
            return (
              <div>
                {/* ... */}
                <button onClick={}>Like</button>
              </div>
            )
          }
          

          接著你就可以在 onClick 中調用 handleClick 方法了。

          function HomePage() {
            //    ...
            function handleClick() {
              console.log('I like it.');
            }
          
            return (
              <div>
                {/* ... */}
                <button onClick={handleClick}>Like</button>
              </div>
            );
          }
          

          狀態和鉤子

          React 里有一系列鉤子函數(Hooks),你可以利用鉤子函數在組件中創建狀態,你可以把狀態理解為在界面上隨時間或者行為變化的一些邏輯信息,通常情況下是由用戶觸發的。



          你可以通過狀態來存儲和增加用戶點擊喜歡按鈕的次數,在這里我們可以使用 React 的 useState 鉤子函數。

          function HomePage() {
            React.useState();
          }
          

          useState 返回一個數組,你可以使用數組解構來使用它。

          function HomePage() {
            const []=React.useState();
          
            // ...
          }
          

          該數組的第一個值是狀態值,你可以定義為任何變量名稱:

          function HomePage() {
            const [likes]=React.useState();
          
            // ...
          }
          

          該數組的第二個值是狀態修改函數,你可以定義為以 set 為前綴的函數名,如 setLikes

          function HomePage() {
            const [likes, setLikes]=React.useState(); 
            // likes 存儲了喜歡被點擊的次數;setLikes 則是用來修改該次數的函數
          
            // ...
          }
          

          同時,你可以在定義的時候給出 likes 的初始值

          function HomePage() {
            const [likes, setLikes]=React.useState(0);
          }
          

          然后你可以嘗試查看你設置的初始值是否生效

          function HomePage() {
            // ...
            const [likes, setLikes]=React.useState(0);
          
            return (
              // ...
              <button onClick={handleClick}>Like({likes})</button>
            );
          }
          

          最后,你可以在每次按鈕被點擊后調用 setLikes 方法來更新 likes 變量的值。

          function HomePage() {
            // ...
            const [likes, setLikes]=React.useState(0);
          
            function handleClick() {
              setLikes(likes + 1);
            }
          
            return (
              <div>
                {/* ... */}
                <button onClick={handleClick}>Likes ({likes})</button>
              </div>
            );
          }
          

          點擊喜歡按鈕將會調用 handleClick 方法, 然后調用 setLikes 方法將更新后的新值傳入該函數的第一個入參中。這樣 likes 變量的值就變成了新值

          狀態管理

          本章節僅對狀態做了簡單的介紹,舉例了 useState 的用法,你可以在后續的學習中了解到更多的狀態管理和數據流處理的方法,更多的內容可以參考官網的 添加交互性 和 狀態管理 兩個章節進行更深入的學習。

          小問答:請說出參數(Props)和狀態(State)的區別?

          0x06 長路漫漫,繼續前行

          到此為止,你已了解了 React 的三大核心概念:組件、參數和狀態。對這些概念的理解越深刻,對今后開發 React 應用就越有幫助。學習的旅程還很漫長,途中若有困惑可以隨時回看本文,或閱讀以下主題文章進行更深入的學習:

          • Render and Commit (reactjs.org)
          • Referencing Values with Refs (reactjs.org)
          • Managing State (reactjs.org)
          • Passing Data Deeply with Context (reactjs.org)
          • React APIs (reactjs.org)

          React 學習資源

          React 的學習資源層出不窮,你可以在互聯網上搜索 React 來獲取無窮無盡的資源,但在我看來最好的仍是官方提供的《React 文檔》,它涵蓋了所有你需要學習的主題。

          最好的學習方法就是實踐。

          eact 作為前端開發的明星框架,其靈魂之一就是 JSX。今天就來詳細分析一下什么是 JSX,以及如何在開發中高效使用它。作為一名程序員,這些技巧你不可不知!

          1. 什么是 JSX?

          JSX 是 JavaScript XML 的縮寫,它是 React 獨有的一種語法擴展,讓你在 JavaScript 代碼中寫類似 HTML 的標記。這不僅讓代碼可讀性更強,還能直觀地描述 UI 結構。

          示例代碼:

          const element=<h1>Hello, world!</h1>;
          

          2. 渲染虛擬 DOM(元素)

          React 通過將 JSX 轉換為虛擬 DOM,再通過對比虛擬 DOM 和實際 DOM 的差異,來高效地更新 UI。

          示例代碼:

          import React from 'react';
          import ReactDOM from 'react-dom';
          
          const element=<h1>Hello, world!</h1>;
          ReactDOM.render(element, document.getElementById('root'));
          

          代碼解析:

          • React 和 ReactDOM 是 React 核心庫,用于創建組件和渲染 DOM。
          • element 是一個簡單的 JSX 元素。
          • ReactDOM.render 將 element 渲染到實際 DOM 中的 id 為 root 的節點上。

          3. JSX 的使用

          JSX 可以嵌套、包含表達式,還能直接用于條件渲染和數組渲染,靈活又強大。

          嵌套示例:

          const element=(
            <div>
              <h1>Hello, world!</h1>
              <p>This is a paragraph.</p>
            </div>
          );

          包含表達式示例:

          const user={
            firstName: 'Harper',
            lastName: 'Perez'
          };
          
          function formatName(user) {
            return user.firstName + ' ' + user.lastName;
          }
          
          const element=<h1>Hello, {formatName(user)}!</h1>;
          

          4. JSX 的語法規則

          大小寫區分:

          • 小寫字母開頭的 JSX 標簽默認為 HTML 元素,如 <div>。
          • 大寫字母開頭的 JSX 標簽默認為 React 組件,如 <MyComponent>。

          示例代碼:

          const MyComponent=()=> {
            return <h1>Hello, Component!</h1>;
          };
          
          ReactDOM.render(<MyComponent />, document.getElementById('root'));
          

          必須被包裹:

          • 所有 JSX 元素必須被一個根元素包裹。
          • 可以使用空標簽 <> </> 來包裹不想增加額外節點的元素。

          示例代碼:

          const element=(
            <>
              <h1>Hello, world!</h1>
              <p>This is a paragraph.</p>
            </>
          );
          

          表達式用{}包裹:

          • 在 JSX 中使用 JavaScript 表達式必須用 {} 包裹。

          示例代碼:

          const name='Josh Perez';
          const element=<h1>Hello, {name}!</h1>;
          

          總結

          掌握 JSX 是深入學習 React 的起點,它不僅提升代碼可讀性,還能大大提高開發效率。從簡單的標簽嵌套到復雜的表達式嵌套,JSX 讓你在編寫 UI 組件時如魚得水。大家趕緊動手試一試吧!

          #如何自學IT#


          主站蜘蛛池模板: 激情内射亚洲一区二区三区爱妻| 国产综合无码一区二区辣椒| 亚洲国产成人精品久久久国产成人一区二区三区综| 精品视频一区二区三区在线观看| 久久精品无码一区二区三区| 中文字幕日韩一区二区不卡| 国产精品亚洲综合一区在线观看| 一区二区三区影院| 日本一区午夜爱爱| 日本免费电影一区二区| 国产精品电影一区二区三区 | 亚洲线精品一区二区三区影音先锋 | 久久精品国产一区二区| 天堂一区人妻无码| 蜜桃无码一区二区三区| 国产自产在线视频一区| 中文字幕一区二区在线播放| 无码日韩人妻AV一区免费l | 国产日韩精品一区二区在线观看播放 | 亚欧色一区W666天堂| 一区二区三区四区在线视频| 日韩a无吗一区二区三区| 亚洲A∨精品一区二区三区下载| 亚洲天堂一区二区三区| 精品一区二区三区东京热| 日韩一区二区久久久久久| 国产一区二区免费| 久久国产精品视频一区| 亚洲成AV人片一区二区密柚| 亚洲成AV人片一区二区| 一区二区三区精品| 亚洲宅男精品一区在线观看| 一区二区三区午夜视频| 麻豆国产一区二区在线观看 | 福利一区在线视频| 国产成人久久精品麻豆一区| 亚洲日本中文字幕一区二区三区| 一区二区三区福利| 免费无码一区二区三区蜜桃| 日本一区二区免费看| 熟妇人妻AV无码一区二区三区|