整合營銷服務商

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

          免費咨詢熱線:

          一個vue頁面復用方案

          問大家一個問題,曾經的你是否也遇到過,一個項目中有好幾個頁面長得基本相同,但又差那么一點,想用 vue extends 繼承它又不能按需繼承html模板部分,恰好 B 頁面需要用的 A 頁面 80% 的模板,剩下的 20% 由 B 頁面自定義,舉個栗子:

          我們假設這是兩個頁面,B頁面比A頁面多了個p標簽,剩余的東西都一樣,難道僅僅是因為這一個 p標簽就要重新寫一份模板嗎?相信大部分伙伴解決方式是把公共部分抽成一個組件來用,這是一個好的做法。沒錯,但是來了,老板讓你在 標題1、標題2下面分別插入一段內容,這會兒你是不是頭大了?難道只能重寫一份了嗎?當然不是,來開始我們的填坑之路~(當你的業務能用插槽或者組件抽離的方式固然更好,以下內容僅針對當你項目達到一定體量,vue老三套難以處理的情況下采用)

          準備工作

          準備以下工具包:

          • node-html-parser: 將html生成dom樹 官網
          npm install --save node-html-parser
          


          思路

          1. 子頁面提供繼承的父頁面的路徑,如下:
          <template extend="./xxx.vue">
          </template>
          


          1. 子頁面需要通過一個自定義標簽(假設是 extend)的方式,來決定如何拓展父頁面,如下就應該是一個替換的操作,它最少應該具備拓展類型 type 與目標節點 target 屬性。
          <template extend="./xxx.vue">
            <div>
              <extend type="replace" target="#div_1">
                <a>通過replace替換掉父頁面下id為div_1的元素 </a>
              </extend>
            </div>
          </template>
          


          最終它生成的應該是除了 id 為 div_1元素被<a>通過replace替換掉父頁面下id為div_1的元素 </a>替換掉之外,剩下的全部和xxx.vue一樣的頁面。

          梳理需求點

          子頁面繼承父頁面既可以完全繼承,也可以通過某種方式以父頁面為基板,對其進行增、刪、改。方便理解,我們先定義一個自定義標簽 extend,子頁面通過該標簽對其繼承的頁面操刀動手術,為了實現一個比較完善的繼承拓展,extend 標簽需要具備以下屬性:

          Extend Attributes

          參數

          說明

          類型

          可選值

          type

          指定擴展類型

          string

          insert(插入)、replace(替換)、remove(移除)、append(向子集追加)

          position

          指定插入的位置(僅在 type 取值 insert 時生效)

          string

          before(目標前)、after(目標后)

          指定插入的位置(僅在 type 取值 append 時生效,用于指定插入成為第幾個子節點)

          number

          -

          target

          指定擴展的目標

          string


          實現需求

          新建一個vue2的項目,項目結構如下:

          我們的繼承拓展通過自定義loader在編譯的時候實現,進入到src/loader/index.js

          const extend = require('./extend');
          module.exports = function (source) {
               // 當前模塊目錄
               const resourcePath = this.resourcePath;
               // 合并
               const result = new extend(source, resourcePath).mergePage();
               // console.log('result :>> ', result);
               // 返回合并后的內容
               this.callback(null, result);
          };
          


          實現繼承拓展主要邏輯代碼:src/loader/extend.js

          const parser = require('node-html-parser');
          const fs = require('fs');
          const pathFile = require('path');
          /**
           * 通過node-html-parser解析頁面文件重組模板
           * @param {String} source 頁面內容
           * @param {String} resourcePath 頁面目錄
           * @returns {String} 重組后的文件內容
           */
          class Extend {
              constructor(source, resourcePath) {
                  this.source = source;
                  this.resourcePath = resourcePath;
              }
              // 合并頁面
              mergePage() {
                  // 通過node-html-parser解析模板文件
                  const pageAst = parser.parse(this.source).removeWhitespace();
                  // 獲取template標簽extend屬性值
                  const extendPath = pageAst.querySelector('template').getAttribute('extend');
                  if (!extendPath) {
                      return pageAst.toString();
                  }
                  // extendPath文件內容
                  const extendContent = fs.readFileSync(pathFile.resolve(pathFile.dirname(this.resourcePath), extendPath), 'utf-8');
                  // extendContent文件解析
                  const extendAst = parser.parse(extendContent).removeWhitespace();
                  // 獲取頁面文件標簽為extend的元素
                  const extendElements = pageAst.querySelectorAll('extend');
          
                  extendElements.forEach((el) => {
                      // 獲取對應屬性值
                      const type = el.getAttribute('type');
                      const target = el.getAttribute('target');
                      const position = parseInt(el.getAttribute('position'));
          
                      // 匹配模板符合target的元素
                      let templateElements = extendAst.querySelectorAll(target);
          
                      // type屬性為insert
                      if (type === 'insert') {
                          templateElements.forEach((tel) => {
                              // 通過position屬性判斷插入位置 默認為after
                              if (position === 'before') {
                                  el.childNodes.forEach((child) => {
                                      tel.insertAdjacentHTML('beforebegin', child.toString());
                                  });
                              } else {
                                  el.childNodes.forEach((child) => {
                                      tel.insertAdjacentHTML('afterend', child.toString());
                                  });
                              }
                          });
                      }
                      // type屬性為append
                      if (type === 'append') {
                          templateElements.forEach((tel) => {
                             const elNodes = el.childNodes;
                             let tlNodes = tel.childNodes;
                             const len = tlNodes.filter((node) => node.nodeType === 1 || node.nodeType === 3).length;
                              // 未傳position屬性或不為數字、大于len、小于0時默認插入到最后
                              if(isNaN(position) || position > len || position <= 0){
                                  elNodes.forEach((child) => {
                                      tel.insertAdjacentHTML('beforeend', child.toString());
                                  });
                              }else {
                                  tlNodes =  [...tlNodes.slice(0, position-1), ...elNodes, ...tlNodes.slice(position-1)]
                                  tel.set_content(tlNodes);
                              }
                          });
                      }
                      // type屬性為replace
                      if (type === 'replace') {
                          templateElements.forEach((tel) => {
                              tel.replaceWith(...el.childNodes);
                          });
                      }
                      // type屬性為remove
                      if (type === 'remove') {
                          templateElements.forEach((tel) => {
                              tel.remove();
                          });
                      }
                  });
                  // 重組文件內容
                  const template = extendAst.querySelector('template').toString();
                  const script = pageAst.querySelector('script').toString();
                  const style = extendAst.querySelector('style').toString() + pageAst.querySelector('style').toString() 
                  return`${template}${script}${style}`
              }
          
          }
          module.exports = Extend;
          


          好的,自定義loader已經編寫完成,在vue.config.js里面配置好我們的loader

          const { defineConfig } = require('@vue/cli-service')
          module.exports = defineConfig({
            configureWebpack: {
              module: {
                rules: [
                  {
                    test: /\.vue$/,
                    use: [
                      {
                        loader: require.resolve('./src/loader'),
                      },
                    ],
                  },
                ],
              },
            },
          })
          
          


          接下來我們嘗試編寫A頁面和B頁面:

          A.vue:

          <template>
            <div class="template">
                <div id="div_1" class="div">父頁面的div_1</div>
                <div id="div_2" class="div">父頁面的div_2</div>
                <div id="div_3" class="div">父頁面的div_3</div>
                <div id="div_4" class="div">父頁面的div_4</div>
                <div id="div_5" class="div">父頁面的div_5</div>
                <div id="div_6" class="div">父頁面的div_6</div>
                <div id="div_7" class="div">父頁面的div_7</div>
                <div id="div_8" class="div">父頁面的div_8</div>
            </div>
          </template>
          <script>
          export default {
            name: 'COM_A',
            props: {
              msg: String
            }
          }
          </script>
          <style scoped>
          .div {
            color: #42b983;
            font-size: 1.5em;
            margin: 0.5em;
            padding: 0.5em;
            border: 2px solid #42b983; 
            border-radius:  0.2em;
          }
          </style>
          


          B.vue:

          <template extend="./A.vue">
            <div>
              <extend type="insert" target="#div_1" position="after">
                <div id="div_child" class="div">子頁面的div_5</div>
              </extend>
              <extend type="append" target="#div_3" position="2">
                <a> 子頁面通過append插入的超鏈接 </a>
              </extend>
            </div>
          </template>
          <script>
          import A from './A.vue'
          export default {
            name: 'COM_B',
            extends: A,//繼承業務邏輯代碼
            props: {
              msg: String
            }
          }
          </script>
          <style scoped>
          #div_child {
            color: #d68924;
            font-size: 1.5em;
            margin: 0.5em;
            padding: 0.5em;
            border: 2px solid #d68924;
          }
          a {
            color: blue;
            font-size: 0.7em;
          }
          </style>
          


          我們在App.vue下引入B.vue

          <template>
            <div id="app">
              <B/>
            </div>
          </template>
          <script>
          import B from './components/B.vue'
          export default {
            name: 'App',
            components: {
              B
            }
          }
          </script>
          <style>
          #app {
            font-family: Avenir, Helvetica, Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            text-align: center;
            color: #2c3e50;
            margin-top: 60px;
          }
          </style>
          


          當我們執行編譯的時候,實際上B.vue的編譯結果如下:

          <template>
            <div class="template">
              <div id="div_1" class="div">父頁面的div_1</div>
              <div id="div_child" class="div">子頁面的div_5</div>
              <div id="div_2" class="div">父頁面的div_2</div>
              <div id="div_3" class="div">
                父頁面的div_3
                <a> 子頁面通過append插入的超鏈接 </a>
              </div>
              <div id="div_4" class="div">父頁面的div_4</div>
              <div id="div_5" class="div">父頁面的div_5</div>
              <div id="div_6" class="div">父頁面的div_6</div>
              <div id="div_7" class="div">父頁面的div_7</div>
              <div id="div_8" class="div">父頁面的div_8</div>
            </div>
          </template>
          <script>
          import A from './A.vue'
          export default {
            name: 'COM_B',
            extends: A,//繼承業務邏輯代碼
            props: {
              msg: String
            }
          }
          </script>
          <style scoped>
          .div {
            color: #42b983;
            font-size: 1.5em;
            margin: 0.5em;
            padding: 0.5em;
            border: 2px solid #42b983;
            border-radius: 0.2em;
          }
          </style>
          <style scoped>
          #div_child {
            color: #d68924;
            font-size: 1.5em;
            margin: 0.5em;
            padding: 0.5em;
            border: 2px solid #d68924;
          }
          
          a {
            color: blue;
            font-size: 0.7em;
          }
          </style>
          


          注意我們在B.vue使用了extends繼承了組件A,這里是為了能復用業務邏輯代碼,最后我們運行代碼,頁面輸出為:

          結語

          在真實的項目當中,我們遇到大量重復的頁面但是又有小區別的頁面,是可以通過這種方式減少我們的代碼量,當然也許有更好的辦法,也希望大伙能提出寶貴的建議。

          最后引用一下 @XivLaw 老哥的評論:有很多人說通過cv就能解決,但是當你的業務有成千上萬個頁面是趨同,并且具有相同的基本功能,當界面需要統一調整或者需要進行ui統一管控的時候,cv就成了你的累贅了。 也有朋友說通過組件化和插槽解決,組件化是一個不錯的方案,但是當成千上萬個趨同的界面存在時,插槽并一定能覆蓋所有的業務定制化。 使不使用這種方式,主要看你的業務。

          直白一點說就是:我現在有一千個頁面幾乎一樣,有的頁面是頭部多一點東西,有的是底部,有的是某個按鈕旁邊多一個按鈕,有的是輸入框之間多個輸入框,ui或者界面或者同時需要添加固定功能,需要調整的時候,這一千個頁面要怎么調?


          作者:小小小小_柏
          鏈接:https://juejin.cn/post/7347973138787467274

          ss怎么引入html

          在HTML中引入CSS的方法主要有四種:

          1. 內聯方式:直接在HTML標簽中的style屬性中添加CSS,即采用行內樣式。例如:<p style="color:red;">這是紅色文字</p>。這種方式的優點是可以直接在HTML文件中看到效果,但缺點是不夠模塊化,不便于復用和維護。

          2. 內嵌樣式:使用<style>標簽在HTML文檔頭部(<head><head>之間)定義CSS樣式。例如:

          <head>

          <style>

          p {

          color: red;

          }

          </style>

          </head>

          <body>

          <p>這是紅色文字</p>

          </body>

          這種方式的優點是可以將CSS樣式與HTML內容分離,提高代碼可讀性,但同樣存在復用和維護的問題。

          3. 鏈接式:使用<link>標簽引入外部CSS樣式表文件。具體操作步驟如下:新建一個HTML文件和一個CSS文件,將新建的CSS文件保存在一個文件夾中,回到HTML文件中,在<title><title>下方添加<link>標簽并設置其屬性為CSS文件的路徑,保存后便實現了引入外部CSS文件。這種方式的優點是可以實現代碼的復用和維護,但需要提前準備好CSS文件。

          4. 導入式:使用@import命令導入外部CSS樣式表。例如:<style type="text/css"> @import"mystyle.css"; </style>。需要注意的是,這里的路徑需要是正確的。這種方式可以在同一個HTML文件中引入多個CSS文件,但瀏覽器對@import的支持程度不同,可能會出現兼容性問題。

          package main
          import (
          "fmt"
          "net/http"
          )
          func main() {
              mux := http.NewServeMux()
              mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
                  // The "/" pattern matches everything, so we need to check
                  // that we're at the root here.
                  if req.URL.Path != "/" {
                      http.NotFound(w, req)
                      return
                  }
                  fmt.Fprintf(w, "Welcome to the home page!")
              })
              http.ListenAndServe("localhost:1234", mux)
              /*
              訪問方式
              http://localhost:1234/ Welcome to the home page!
              */
          }
          package main
          import (
          "fmt"
          "net/http"
          )
          // 設置多個處理器函數
          func handler1(w http.ResponseWriter, r *http.Request) {
              fmt.Fprintf(w, "1 歡迎訪問 www.ydook.com !")
          }
          func handler2(w http.ResponseWriter, r *http.Request) {
              fmt.Fprintf(w, "2 歡迎訪問 www.ydook.com !")
          }
          func handler3(w http.ResponseWriter, r *http.Request) {
              fmt.Fprintf(w, "3 歡迎訪問 www.ydook.com !")
          }
          func main() {
              // 設置多路復用處理函數
              mux := http.NewServeMux()
              mux.HandleFunc("/h1", handler1)
              mux.HandleFunc("/h2", handler2)
              mux.HandleFunc("/h3", handler3)
              // 設置服務器
              server := &http.Server{
                  Addr: "127.0.0.1:8000",
                  Handler: mux,
              }
              // 設置服務器監聽請求端口
              server.ListenAndServe()
          }

          TTP/2有三大特性:頭部壓縮、Server Push、多路復用。前兩個特性意思比較明確, 也好理解, 唯有多路復用不太好理解, 尤其是和HTTP1.1進行對比的時候, 這個問題我想了很長時間,

          也對比了很長時間, 現在把思考的結果分享出來, 希望對大家有幫忙。

          1 先來說說Keep-Alive

          在沒有Keep-Alive前,我們與服務器請求數據的流程是這樣:

          瀏覽器請求//static.mtime.cn/a.js-->解析域名-->HTTP連接-->服務器處理文件-->返回數據-->瀏覽器解析、渲染文件

          瀏覽器請求//static.mtime.cn/b.js-->解析域名-->HTTP連接-->服務器處理文件-->返回數據-->瀏覽器解析、渲染文件

          ...

          ...

          ...

          這樣循環下去,直至全部文件下載完成。

          每次請求都會建立一次HTTP連接, 也就是我們常說的3次握手4次揮手, 這個過程在一次請求過程中占用了相當長的時間, 而且邏輯上是非必需的, 因為不間斷的請求數據, 第一次建立連接是正常的, 以后就占用這個通道, 下載其他文件, 這樣效率多高啊!

          你猜對了, 這就是Keep-Alive。

          Keep-Alive解決的問題

          Keep-Alive解決的核心問題:一定時間內, 同一域名多次請求數據, 只建立一次HTTP請求, 其他請求可復用每一次建立的連接通道, 以達到提高請求效率的問題。這里面所說的一定時間是可以配置的, 不管你用的是Apache還是nginx。

          2 HTTP1.1還是存在效率問題

          如上面所說, 在HTTP1.1中是默認開啟了Keep-Alive, 他解決了多次連接的問題, 但是依然有兩個效率上的問題:

          第一個:串行的文件傳輸。當請求a文件時, b文件只能等待, 等待a連接到服務器、服務器處理文件、服務器返回文件, 這三個步驟。我們假設這三步用時都是1秒, 那么a文件用時為3秒, b文件傳輸完成用時為6秒, 依此類推。

          (注:此項計算有一個前提條件, 就是瀏覽器和服務器是單通道傳輸)

          第二個:連接數過多。我們假設Apache設置了最大并發數為300, 因為瀏覽器限制, 瀏覽器發起的最大請求數為6, 也就是服務器能承載的最高并發為50, 當第51個人訪問時, 就需要等待前面某個請求處理完成。

          3 HTTP/2的多路復用

          HTTP/2的多路復用就是為了解決上述的兩個性能問題, 我們來看一下, 他是如何解決的。

          解決第一個:在HTTP1.1的協議中, 我們傳輸的request和response都是基本于文本的, 這樣就會引發一個問題:所有的數據必須按順序傳輸, 比如需要傳輸:hello world, 只能從h到d一個一個的傳輸, 不能并行傳輸,

          因為接收端并不知道這些字符的順序, 所以并行傳輸在HTTP1.1是不能實現的。

          HTTP/2引入二進制數據幀和流的概念, 其中幀對數據進行順序標識, 這樣瀏覽器收到數據之后, 就可以按照序列對數據進行合并, 而不會出現合并后數據錯亂的情況。

          同樣是因為有了序列, 服務器就可以并行的傳輸數據, 這就是流所做的事情。

          解決第二個問題:HTTP/2對同一域名下所有請求都是基于流, 也就是說同一域名不管訪問多少文件, 也只建立一路連接。同樣Apache的最大連接數為300, 因為有了這個新特性, 最大的并發就可以提升到300, 比原來提升了6倍!

          4 以前我們做的性能優化不適用于HTTP/2

          JS文件的合并。我們現在優化的一個主要方向就是盡量的減少HTTP的請求數, 對我們工程中的代碼, 研發時分模塊開發, 上線時我們會把所有的代碼進行壓縮合并, 合并成一個文件, 這樣不管多少模塊, 都請求一個文件, 減少了HTTP的請求數。

          但是這樣做有一個非常嚴重的問題:文件的緩存。當我們有100個模塊時, 有一個模塊改了東西, 按照之前的方式, 整個文件瀏覽器都需要重新下載, 不能被緩存?,F在我們有了HTTP/2了, 模塊就可以單獨的壓縮上線, 而不影響其他沒有修改的模塊。

          多域名提高瀏覽器的下載速度。之前我們有一個優化就是把css文件和js文件放到2個域名下面, 這樣瀏覽器就可以對這兩個類型的文件進行同時下載, 避免了瀏覽器6個通道的限制, 這樣做的缺點也是明顯的,

          1.DNS的解析時間會變長。

          2.增加了服務器的壓力。有了HTTP/2之后, 根據上面講的原理, 我們就不用這么搞了, 成本會更低。


          主站蜘蛛池模板: 亚洲欧洲专线一区| 国精产品一区一区三区MBA下载| 多人伦精品一区二区三区视频| 精品视频一区二区三区| 无码一区二区三区爆白浆| 国产色综合一区二区三区| 无码少妇一区二区浪潮av| 丝袜美腿一区二区三区| 国产香蕉一区二区精品视频 | 国产小仙女视频一区二区三区| 精品国产一区二区三区免费| 久久无码一区二区三区少妇 | 大香伊人久久精品一区二区| 性色A码一区二区三区天美传媒| 国产亚洲情侣一区二区无码AV| 丝袜美腿高跟呻吟高潮一区| 国产伦精品一区二区三区无广告| 暖暖免费高清日本一区二区三区| 国产一区二区三区乱码网站| 日韩免费一区二区三区在线播放| 亚洲午夜一区二区电影院| 天堂资源中文最新版在线一区 | 亚洲AV无码片一区二区三区| 亚洲国产av一区二区三区丶| 亚洲高清日韩精品第一区 | 亚洲成人一区二区| 亚洲日本一区二区三区在线不卡| 变态调教一区二区三区| 日本一区二区三区精品国产| 精品福利一区二区三区| 国产一在线精品一区在线观看| 国产精品 一区 在线| 国产成人精品一区在线 | 无码精品人妻一区二区三区影院| 色多多免费视频观看区一区| 亚洲一区二区三区乱码A| 国产在线精品一区二区三区不卡| 亚洲一区二区三区在线观看精品中文| 成人国产精品一区二区网站| 日本一区午夜艳熟免费| 无码精品黑人一区二区三区 |