新知識get,vue3是如何實現在style中使用響應式變數?

来源:https://www.cnblogs.com/heavenYJJ/p/18289087
-Advertisement-
Play Games

前言 vue2的時候想必大家有遇到需要在style模塊中訪問script模塊中的響應式變數,為此我們不得不使用css變數去實現。現在vue3已經內置了這個功能啦,可以在style中使用v-bind指令綁定script模塊中的響應式變數,這篇文章我們來講講vue是如何實現在style中使用script ...


前言

vue2的時候想必大家有遇到需要在style模塊中訪問script模塊中的響應式變數,為此我們不得不使用css變數去實現。現在vue3已經內置了這個功能啦,可以在style中使用v-bind指令綁定script模塊中的響應式變數,這篇文章我們來講講vue是如何實現在style中使用script模塊中的響應式變數。註:本文中使用的vue版本為3.4.19

關註公眾號:【前端歐陽】,給自己一個進階vue的機會

看個demo

我們來看個簡單的demo,index.vue文件代碼如下:

<template>
  <div>
    <p>222</p>
    <span class="block">hello world</span>
  </div>
</template>

<script setup lang="ts">
import { ref } from "vue";

const primaryColor = ref("red");
</script>

<style scoped>
.block {
  color: v-bind(primaryColor);
}
</style>

我們在script模塊中定義了一個響應式變數primaryColor,並且在style中使用v-bind指令將primaryColor變數綁定到color樣式上面。

我們在瀏覽器的network面板中來看看編譯後的js文件,如下圖:
index-vue

從上圖中可以看到在network面板中編譯後的index.vue文件有兩個,並且第二個裡面有一些query參數,其中的type=style就表示當前文件的內容對應的是style模塊。第一個index.vue對應的是template和script模塊中的內容。

我們來看看第一個index.vue,如下圖:
setup

從上圖中可以看到setup函數是script模塊編譯後的內容,在setup函數中多了一個_useCssVars函數,從名字你應該猜到了,這個函數的作用是和css變數有關係。彆著急,我們接下來會詳細去講_useCssVars函數。

我們再來看看第二個index.vue,如下圖:
style

從上圖中可以看到這個index.vue確實對應的是style模塊中的內容,並且原本的color: v-bind(primaryColor);已經變成了color: var(--c845efc6-primaryColor);

很明顯瀏覽器是不認識v-bind(primaryColor);指令的,所以經過編譯後就變成了瀏覽器認識的css變數var(--c845efc6-primaryColor);

我們接著在elements面板中來看看此時class值為block的span元素,如下圖:
elements

從上圖中可以看到color的值為css變數var(--c845efc6-primaryColor),這個我們前面講過。不同的是這裡從父級元素div中繼承過來一個--c845efc6-primaryColor: red;

這個就是聲明一個名為--c845efc6-primaryColor的css變數,變數的值為red

還記得我們在script模塊中定義的響應式變數primaryColor嗎?他的值就是red

所以這個span元素最終color渲染出來的值就是red

接下來我們將通過debug的方式帶你搞清楚在style中是如何將指令v-bind(primaryColor)編譯成css變數var(--c845efc6-primaryColor),以及_useCssVars函數是如何生成聲明值為red的css變數--c845efc6-primaryColor

doCompileStyle函數

在前面的文章中我們講過了style模塊實際是由doCompileStyle函數函數處理的,具體如何調用到doCompileStyle函數可以查看我之前的文章: 掉了兩根頭髮後,我悟了!vue3的scoped原來是這樣避免樣式污染

我們需要給doCompileStyle函數打個斷點,doCompileStyle函數的代碼位置在:node_modules/@vue/compiler-sfc/dist/compiler-sfc.cjs.js

還是一樣的套路啟動一個debug終端。這裡以vscode舉例,打開終端然後點擊終端中的+號旁邊的下拉箭頭,在下拉中點擊Javascript Debug Terminal就可以啟動一個debug終端。
debug-terminal

在debug終端執行yarn dev,在瀏覽器中打開對應的頁面,比如:http://localhost:5173/ 。

此時斷點將停留在doCompileStyle函數中,在我們這個場景中doCompileStyle函數簡化後的代碼如下:

import postcss from "postcss";

function doCompileStyle(options) {
  const {
    filename,
    id,
    postcssOptions,
    postcssPlugins,
  } = options;
  const source = options.source;
  const shortId = id.replace(/^data-v-/, "");

  const plugins = (postcssPlugins || []).slice();
  plugins.unshift(cssVarsPlugin({ id: shortId, isProd }));

  const postCSSOptions = {
    ...postcssOptions,
    to: filename,
    from: filename,
  };
  let result;
  try {
    result = postcss(plugins).process(source, postCSSOptions);
    return result.then((result) => ({
      code: result.css || "",
      // ...省略
    }));
  } catch (e: any) {
    errors.push(e);
  }
}

在前面的文章掉了兩根頭髮後,我悟了!vue3的scoped原來是這樣避免樣式污染中我們講過了,這裡id的值為使用了scoped後給html增加的自定義屬性data-v-x,每個vue文件生成的x都是不一樣的。在doCompileStyle函數中使用id.replace方法拿到x賦值給變數shortId

接著就是定義一個plugins插件數組,並且將cssVarsPlugin函數的返回結果push進去。

這裡cssVarsPlugin函數就是返回了一個自定義的postcss插件。

最後就是執行result = postcss(plugins).process(source, postCSSOptions)拿到經過postcss轉換編譯器處理後的css。

可能有的小伙伴對postcss不夠熟悉,我們這裡來簡單介紹一下。

postcss 是 css 的 transpiler(轉換編譯器,簡稱轉譯器),它對於 css 就像 babel 對於 js 一樣,能夠做 css 代碼的分析和轉換。同時,它也提供了插件機制來做自定義的轉換。

在我們這裡主要就是用到了postcss提供的插件機制來完成css scoped的自定義轉換,調用postcss的時候我們傳入了source,他的值是style模塊中的css代碼。並且傳入的plugins插件數組中有個cssVarsPlugin插件,這個自定義插件就是vue寫的用於處理在css中使用v-bind指令。

在執行postcss對css代碼進行轉換之前我們在debug終端來看看此時的css代碼是什麼樣的,如下圖:

source

從上圖中可以看到此時的options.source中還是v-bind(primaryColor)指令。

cssVarsPlugin插件

cssVarsPlugin插件在我們這個場景中簡化後的代碼如下:

const vBindRE = /v-bind\s*\(/g;
const cssVarsPlugin = (opts) => {
  const { id, isProd } = opts;
  return {
    postcssPlugin: "vue-sfc-vars",
    Declaration(decl) {
      const value = decl.value;
      if (vBindRE.test(value)) {
        vBindRE.lastIndex = 0;
        let transformed = "";
        let lastIndex = 0;
        let match;
        while ((match = vBindRE.exec(value))) {
          const start = match.index + match[0].length;
          const end = lexBinding(value, start);
          if (end !== null) {
            const variable = normalizeExpression(value.slice(start, end));
            transformed +=
              value.slice(lastIndex, match.index) +
              `var(--${genVarName(id, variable, isProd)})`;
            lastIndex = end + 1;
          }
        }
        decl.value = transformed + value.slice(lastIndex);
      }
    },
  };
};

這裡的id就是我們在doCompileStyle函數中傳過來的shortId,每個vue文件對應的shortId值都是不同的。

這裡使用到了Declaration鉤子函數,css中每個具體的樣式都會觸發這個Declaration鉤子函數。

Declaration鉤子函數打個斷點,當post-css處理到color: v-bind(primaryColor);時就會走到這個斷點中。如下圖:
before-decl

將字元串v-bind(primaryColor)賦值給變數value,接著執行if (vBindRE.test(value))vBindRE是一個正則表達式,這裡的意思是當前css的值是使用了v-bind指令才走到if語句裡面。

接著就是執行while ((match = vBindRE.exec(value)))進行正則表達式匹配,如果value的值符合vBindRE正則表達式,也就是value的值是v-bind綁定的,那麼就走到while迴圈裡面去。

看到這裡有的小伙伴會問了,這裡使用if就可以了,為什麼還要使用while迴圈呢?

答案是css的值可能是多個v-bind指令組成的,比如border: v-bind(borderWidth) solid v-bind(primaryColor);。這裡的css值就由兩個v-bind組成,分別是v-bind(borderWidth)v-bind(primaryColor);

為了處理上面這種多個v-bind指令組成的css值,所以就需要使用while迴圈搭配exec方法。正則表達式使用了global標誌位的時候,js的RegExp 對象是有狀態的,它們會將上次成功匹配後的位置記錄在 lastIndex 屬性中。使用此特性,exec() 可用來對單個字元串中的多次匹配結果進行逐條的遍歷。

在debug終端來看看此時的match數組是什麼樣的,如下圖:
match

從上圖中可以看到match[0]的值是正則表達式匹配的字元串,在我們這裡匹配的字元串是v-bind(match.index的值為匹配到的字元位於原始字元串的基於 0 的索引值。

看到這裡有的小伙伴可能對match.index的值有點不理解,我舉個簡單的例子你一下就明白了。

還是以v-bind(borderWidth) solid v-bind(primaryColor)為例,這個字元串就是原始字元串,第一次在while迴圈中正則表達式匹配到第一個bind,此時的match.index的值為0,也就是第一個v在原始字元串的位置。第二次在while迴圈中會基於第一次的位置接著向後找,會匹配到第二個v-bind指令,此時的match.index的值同樣也是基於原始字元串的位置,也就是第二個v-bind中的v的位置,值為26。

在while迴圈中使用const start = match.index + match[0].lengthstart變數賦值,match.index的值是v-bind中的v的位置。match[0]是正則匹配到的字元串 v-bind(。所以這個start的位置就是v-bind(primaryColor)primaryColor變數的開始位置,也就是p所在的位置。

接著就是執行lexBinding函數拿到v-bind(primaryColor)primaryColor變數的結束位置,賦值給變數end。在我們這個場景中簡化後的lexBinding函數代碼如下:

function lexBinding(content: string, start: number) {
  for (let i = start; i < content.length; i++) {
    const char = content.charAt(i);
    if (char === `)`) {
      return i;
    }
  }
  return null;
}

簡化後的lexBinding函數也很簡單,使用for迴圈遍歷v-bind(primaryColor)字元串,如果發現字元串)就說明找到了primaryColor變數的結束位置。

接著來看拿到end變數後的代碼,會執行const variable = normalizeExpression(value.slice(start, end))。這裡先執行了value.slice(start, end)根據start開始位置和end結束位置提取出v-bind指令綁定的變數,接著normalizeExpression函數對其進行trim去除空格。

在我們這個場景中簡化後的normalizeExpression函數代碼如下:

function normalizeExpression(exp) {
  exp = exp.trim();
  return exp;
}

將從v-bind指令中提取出來的變數賦值給variable變數,接著執行字元串拼接拿到由v-bind指令轉換成的css變數,代碼如下:

transformed +=
  value.slice(lastIndex, match.index) +
  `var(--${genVarName(id, variable, isProd)})`;

這裡的value是css變數值v-bind(primaryColor),在我們這裡lastIndex的值為0,match.index的值也是0,所以value.slice(lastIndex, match.index)拿到的值也是空字元串。

接著來看後面這部分,使用字元串拼接得到:var(--變數)。這個看著就很熟悉了,他就是一個css變數。變數名是調用genVarName函數生成的,genVarName函數代碼如下:

import hash from "hash-sum";
function genVarName(id, raw, isProd) {
  if (isProd) {
    return hash(id + raw);
  } else {
    return `${id}-${getEscapedCssVarName(raw)}`;
  }
}

這個id是根據當前vue組件路徑生成的,每個vue組件生成的id都不同。這個raw也就是綁定的響應式變數,在這裡是primaryColorisProd表示當前是不是生產環境。

如果是生產環境就根據id和變數名使用哈希演算法生成一個加密的字元串。

如果是開發環境就使用字元串拼接將id和變數名primaryColor拼接起來得到一個css變數。getEscapedCssVarName函數的代碼也很簡單,是對變數中的特殊字元進行轉義,以便在 CSS 變數名中使用。代碼如下:

const cssVarNameEscapeSymbolsRE = /[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g;
function getEscapedCssVarName(key: string) {
  return key.replace(cssVarNameEscapeSymbolsRE, (s) => `\\${s}`);
}

這也就是為什麼不同組件的primaryColor生成的css變數名稱不會衝突的原因了,因為在生成的css變數前面拼接了一個id,每個vue組件生成的id值都不同。

拿到轉換成css變數的css值後,並且將其賦值給變數transformed。接著就是執行lastIndex = end + 1,在我們這裡lastIndex就指向了字元串的末尾。

最後就是執行decl.value = transformed + value.slice(lastIndex);v-bind指令替換成css變數,由於lastIndex是指向了字元串的末尾,所以value.slice(lastIndex)的值也是一個空字元串。

所以在我們這裡實際是執行了decl.value = transformed,執行完這句話後color的值就由v-bind(primaryColor)轉換成了var(--c845efc6-primaryColor)

生成useCssVars函數

前面我們講過了編譯後的setup函數中多了一個useCssVars函數,實際在我們的源代碼中是沒有這個useCssVars函數的。接下來我們來看看編譯時處理script模塊時是如何生成useCssVars函數的。

在之前的 為什麼defineProps巨集函數不需要從vue中import導入?文章中我們講過了vue的script模塊中的代碼是由compileScript函數處理的,當然你沒看過那篇文章也不影響這篇文章的閱讀。

compileScript函數打個斷點,在我們這個場景中簡化後的compileScript函數代碼如下:

function compileScript(sfc, options) {
  const ctx = new ScriptCompileContext(sfc, options);
  const startOffset = ctx.startOffset;

  ctx.s.prependLeft(
    startOffset,
    `
${genCssVarsCode(sfc.cssVars, ctx.bindingMetadata, scopeId, !!options.isProd)}
`
  );
}

首先調用ScriptCompileContext類new了一個ctx上下文對象,我們這裡來介紹一下需要使用到的ctx上下文對象中的兩個方法:ctx.s.toStringctx.s.prependLeft

  • ctx.s.toString:返回此時由script模塊編譯成的js代碼。

  • ctx.s.prependLeft:給編譯後的js代碼在指定index的前面插入字元串。

ctx.s.prependLeft方法打個斷點,在debug終端使用ctx.s.toString方法來看看此時由script模塊編譯成的js代碼是什麼樣的,如下圖:
before-append

從上圖中可以看到此時生成的js代碼code字元串只有一條import語句和定義primaryColor變數。

由於篇幅有限我們就不深入到genCssVarsCode函數了,這個genCssVarsCode函數會生成useCssVars函數的調用。我們在debug終端來看看生成的code代碼字元串是什麼樣的,如下圖:
genCssVarsCode

從上圖中可以看到genCssVarsCode函數生成了一個useCssVars函數。

執行ctx.s.prependLeft函數後會將生成的useCssVars函數插入到生成的js code代碼字元串的前面,我們在debug終端來看看,如下圖:
after-append

從上圖中可以看到此時的js code代碼字元串中已經有了一個useCssVars函數了。

執行useCssVars函數

前面我們講過了編譯時經過cssVarsPlugin這個post-css插件處理後,v-bind(primaryColor)指令就會編譯成了css變數var(--c845efc6-primaryColor)。這裡只是使用css變數值的地方,那麼這個css變數的值又是在哪裡定義的呢?答案是在useCssVars函數中。

在開始我們講過了編譯後的setup函數中多了一個useCssVars函數,所以我們給useCssVars函數打個斷點,刷新瀏覽器此時代碼就會走到斷點中了。如下圖:
useCssVars

從上圖中可以看到執行useCssVars函數時傳入了一個回調函數作為參數,這個回調函數返回了一個對象。

將斷點走進useCssVars函數,在我們這個場景中簡化後的useCssVars函數代碼如下:

function useCssVars(getter) {
  const instance = getCurrentInstance();

  const setVars = () => {
    const vars = getter(instance.proxy);
    setVarsOnVNode(instance.subTree, vars);
  };

  watchPostEffect(setVars);
}

useCssVars函數中先調用getCurrentInstance函數拿到當前的vue實例,然後將setVars函數作為參數傳入去執行watchPostEffect函數。

這個watchPostEffect函數大家應該知道,他是watchEffect() 使用 flush: 'post' 選項時的別名。

為什麼需要使用 flush: 'post'呢?

答案是需要在setVars回調函數中需要去操作DOM,所以才需要使用 flush: 'post'讓回調函數在組件渲染完成之後去執行。

setVars函數打個斷點,組件渲染完成後斷點將會走進setVars函數中。

首先會執行getter函數,將返回值賦值給變數vars。前面我們講過了這個getter函數是調用useCssVars函數時傳入的回調函數,代碼如下:

_useCssVars((_ctx) => ({
  "c845efc6-primaryColor": primaryColor.value
}))

在這個回調函數中會返回一個對象,對象的key為c845efc6-primaryColor,這個key就是css變數var(--c845efc6-primaryColor)括弧中的內容。

對象的值是ref變數primaryColor的值,由於這個代碼是在watchPostEffect的回調函數中執行的,所以這裡的ref變數primaryColor也被作為依賴進行收集了。當primaryColor變數的值變化時,setVars函數也將再次執行。這也就是為什麼在style中可以使用v-bind指令綁定一個響應式變數,並且當響應式變數的值變化時樣式也會同步更新。

接著就是執行setVarsOnVNode(instance.subTree, vars)函數,傳入的第一個參數為instance.subTree。他的值是當前vue組件根元素的虛擬DOM,也就是根元素div的虛擬DOM。第二個參數為useCssVars傳入的回調函數返回的對象,這是一個css變數組成的對象。

接著將斷點走進setVarsOnVNode函數,在我們這個場景中簡化後的代碼如下:

function setVarsOnVNode(vnode: VNode, vars) {
  setVarsOnNode(vnode.el, vars);
}

setVarsOnVNode函數中是調用了setVarsOnNode函數,不同的是傳入的第一個參數不再是虛擬DOM。而是vnode.el虛擬DOM對應的真實DOM,也就是根節點div。

將斷點走進setVarsOnNode函數,在我們這個場景中簡化後的setVarsOnNode函數代碼如下:

function setVarsOnNode(el: Node, vars) {
  if (el.nodeType === 1) {
    const style = el.style;
    for (const key in vars) {
      style.setProperty(`--${key}`, vars[key]);
    }
  }
}

setVarsOnNode函數中先使用if語句判斷el.nodeType === 1,這個的意思是判斷當前節點類型是不是一個元素節點,比如<p><div>。如果是就走進if語句裡面,使用el.style拿到根節點的style樣式。

這裡的vars是css變數組成的對象,遍歷這個對象。對象的key為css變數名稱,對象的value為css變數的值。

接著就是遍歷css變數組成的對象,使用style.setProperty方法給根節點div增加內聯樣式,也就是--c845efc6-primaryColor: red;span元素由於是根節點div的子節點,所以他也繼承了樣式--c845efc6-primaryColor: red;

由於span元素的color經過編譯後已經變成了css變數var(--c845efc6-primaryColor),並且從根節點繼承過來css變數--c845efc6-primaryColor的值為red,所以最終span元素的color值為red

總結

下麵這個是我總結的流程圖,如下(搭配流程圖後面的文字解釋一起服用效果最佳):
full-progress

編譯階段script模塊是由compileScript函數處理的,compileScript函數會去執行一個genCssVarsCode函數。這個函數會返回一個useCssVars函數的調用。然後在compileScript函數中會調用ctx.s.prependLeft方法將生成的useCssVars函數插入到編譯後的setup函數中。

編譯階段style模塊是由doCompileStyle函數處理的,在doCompileStyle函數中會調用postcss對css樣式進行處理。vue自定義了一個名為cssVarsPluginpostcss插件,插件中有個Declaration鉤子函數,css中每個具體的樣式都會觸發這個Declaration鉤子函數。

Declaration鉤子函數中使用正則表達式去匹配當前css值是不是v-bind綁定的,如果是就將匹配到的v-bind綁定的變數提取出來賦值給變數variable。還有一個id變數,他是根據當前vue組件的路徑生成的加密字元串。使用字元串拼接就可以得到var(--${id}-${variable}),他就是由v-bind編譯後生成的css變數。最終生成的css變數類似這樣:var(--c845efc6-primaryColor)

運行時階段初始化的時候會去執行setup函數,由於在編譯階段setup函數中插入了一個useCssVars函數。使用在運行時階段初始化時useCssVars函數會被執行。

useCssVars函數中執行了watchPostEffect函數,他是watchEffect() 使用 flush: 'post' 選項時的別名。

由於我們需要在回調中操作DOM,所以才需要使用flush: 'post',讓回調函數在組件渲染之後去執行。由於在回調函數中會去讀取v-bind綁定的響應式變數,所以每次綁定的響應式變數值變化後都會再次執行調用watchPostEffect傳入的回調函數,以此讓響應式變數綁定的樣式保存更新。

watchPostEffect傳入的回調函數中會通過當前vue組件實例拿到真實DOM的根節點,然後遍歷css變數組成的對象,將這些css變數逐個在根節點上面定義,類似這樣:--c845efc6-primaryColor: red;。由於css可以繼承,所以子節點都繼承了這個css定義。

我們的<span>標簽在編譯階段由color: v-bind(primaryColor);編譯成了css變數color: var(--c845efc6-primaryColor)。並且在運行時由於useCssVars函數的作用在根節點生成了css變數的定義--c845efc6-primaryColor: red;。由於css繼承,所以span標簽也繼承了這個css變數的定義,所以span標簽渲染到頁面上的color值最終為red

關註公眾號:【前端歐陽】,給自己一個進階vue的機會


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • UINavigationController 是 iOS 中用於管理視圖控制器層次結構的一個重要組件,通常用於實現基於堆棧的導航。它提供了一種用戶界面,允許用戶在視圖控制器之間進行層次化的導航,例如從列表視圖到詳細視圖。 UINavigationController 的主要功能 管理視圖控制器堆棧: ...
  • UITabBarController 是 iOS 中用於管理和顯示選項卡界面的一個視圖控制器。它允許用戶在多個視圖控制器之間進行切換,每個視圖控制器對應一個選項卡。 主要功能 管理多個視圖控制器: UITabBarController 管理一個視圖控制器數組,每個視圖控制器對應一個選項卡。 顯示選項 ...
  • 在MVC模型中,V指view,負責用戶界面的顯示、處理用戶輸入,並將輸入傳遞給控制器。C是指ViewController,充當模型和視圖之間的中介。控制器接收用戶輸入,處理用戶請求,並將結果傳遞給視圖以更新顯示。本文詳細介紹在iOS開發中UIView與UIViewController的生命周期。 U ...
  • ‍ 寫在開頭 點贊 + 收藏 學會 理解 forEach JavaScript 的forEach方法是一種流行的數組迭代工具。它為每個數組元素執行一次提供的函數。但是,與傳統的for 和 while迴圈不同,forEach它被設計為對每個元素執行該函數,沒有內置機制來提前停止或中 ...
  • 本文介紹了NodeJS中流(Stream)的概念、類型和應用。流通過將數據分成小塊進行處理,優化了記憶體使用和數據處理效率。文章涵蓋了四種基本流類型:可讀流、可寫流、雙工流和轉換流,並通過實例代碼演示瞭如何使用流進行高效的數據傳輸和處理。 ...
  • title: Nuxt框架中內置組件詳解及使用指南(三) date: 2024/7/8 updated: 2024/7/8 author: cmdragon excerpt: 摘要:“Nuxt 3框架中與組件的深度使用教程,包括如何使用這兩個組件進行頁面導航和載入指示的自定義配置與實戰示例。” ca ...
  • NodeJS是一個基於V8引擎和libuv的JavaScript運行時,適用於輕量級和高效的數據密集型Web應用。其單線程、非阻塞IO模型依賴事件迴圈和線程池管理非同步任務。使用NodeJS開發需避免阻塞主線程,正確處理事件和錯誤。 ...
  • zustand 和 jotai 是當下比較流行的react狀態管理庫。其都有著輕量、方便使用,和react hooks能夠很好的搭配,並且性能方面,對比React自身提供的context要好得多,因此被很多開發小伙伴所喜愛。 更有意思的是,這兩個庫的作者是同一個人,同時他還開源了另外一個狀態庫 va ...
一周排行
    -Advertisement-
    Play Games
  • 通過WPF的按鈕、文本輸入框實現了一個簡單的SpinBox數字輸入用戶組件並可以通過數據綁定數值和步長。本文中介紹了通過Xaml代碼實現自定義組件的佈局,依賴屬性的定義和使用等知識點。 ...
  • 以前,我看到一個朋友在對一個系統做初始化的時候,通過一組魔幻般的按鍵,調出來一個隱藏的系統設置界面,這個界面在常規的菜單或者工具欄是看不到的,因為它是一個後臺設置的關鍵界面,不公開,同時避免常規用戶的誤操作,它是作為一個超級管理員的入口功能,這個是很不錯的思路。其實Winform做這樣的處理也是很容... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他的程式每次關閉時就會自動崩潰,一直找不到原因讓我幫忙看一下怎麼回事,這位朋友應該是第二次找我了,分析了下 dump 還是挺經典的,拿出來給大家分享一下吧。 二:WinDbg 分析 1. 為什麼會崩潰 找崩潰原因比較簡單,用 !analyze -v 命 ...
  • 在一些報表模塊中,需要我們根據用戶操作的名稱,來動態根據人員姓名,更新報表的簽名圖片,也就是電子手寫簽名效果,本篇隨筆介紹一下使用FastReport報表動態更新人員簽名圖片。 ...
  • 最新內容優先發佈於個人博客:小虎技術分享站,隨後逐步搬運到博客園。 創作不易,如果覺得有用請在Github上為博主點亮一顆小星星吧! 博主開始學習編程於11年前,年少時還只會使用cin 和cout ,給單片機點點燈。那時候,類似async/await 和future/promise 模型的認知還不是 ...
  • 之前在阿裡雲ECS 99元/年的活動實例上搭建了一個測試用的MINIO服務,以前都是直接當基礎設施來使用的,這次準備自己學一下S3相容API相關的對象存儲開發,因此有了這個小工具。目前僅包含上傳功能,後續計劃開發一個類似圖床的對象存儲應用。 ...
  • 目錄簡介快速入門安裝 NuGet 包實體類User資料庫類DbFactory增刪改查InsertSelectUpdateDelete總結 簡介 NPoco 是 PetaPoco 的一個分支,具有一些額外的功能,截至現在 github 星數 839。NPoco 中文資料沒多少,我是被博客園群友推薦的, ...
  • 前言 前面使用 Admin.Core 的代碼生成器生成了通用代碼生成器的基礎模塊 分組,模板,項目,項目模型,項目欄位的基礎功能,本篇繼續完善,實現最核心的模板生成功能,並提供生成預覽及代碼文件壓縮下載 準備 首先清楚幾個模塊的關係,如何使用,簡單畫一個流程圖 前面完成了基礎的模板組,模板管理,項目 ...
  • 假設需要實現一個圖標和文本結合的按鈕 ,普通做法是 直接重寫該按鈕的模板; 如果想作為通用的呢? 兩種做法: 附加屬性 自定義控制項 推薦使用附加屬性的形式 第一種:附加屬性 創建Button的附加屬性 ButtonExtensions 1 public static class ButtonExte ...
  • 在C#中,委托是一種引用類型的數據類型,允許我們封裝方法的引用。通過使用委托,我們可以將方法作為參數傳遞給其他方法,或者將多個方法組合在一起,從而實現更靈活的編程模式。委托類似於函數指針,但提供了類型安全和垃圾回收等現代語言特性。 基本概念 定義委托 定義委托需要指定它所代表的方法的原型,包括返回類 ...