vue3編譯優化之“靜態提升”

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

前言 在上一篇 vue3早已具備拋棄虛擬DOM的能力了文章中講了對於動態節點,vue做的優化是將這些動態節點收集起來,然後當響應式變數修改後進行靶向更新。那麼vue對靜態節點有沒有做什麼優化呢?答案是:當然有,對於靜態節點會進行“靜態提升”。這篇文章我們來看看vue是如何進行靜態提升的。 什麼是靜態 ...


前言

在上一篇 vue3早已具備拋棄虛擬DOM的能力了文章中講了對於動態節點,vue做的優化是將這些動態節點收集起來,然後當響應式變數修改後進行靶向更新。那麼vue對靜態節點有沒有做什麼優化呢?答案是:當然有,對於靜態節點會進行“靜態提升”。這篇文章我們來看看vue是如何進行靜態提升的。

什麼是靜態提升?

我們先來看一個demo,代碼如下:

<template>
  <div>
    <h1>title</h1>
    <p>{{ msg }}</p>
    <button @click="handleChange">change msg</button>
  </div>
</template>

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

const msg = ref("hello");

function handleChange() {
  msg.value = "world";
}
</script>

這個demo代碼很簡單,其中的h1標簽就是我們說的靜態節點,p標簽就是動態節點。點擊button按鈕會將響應式msg變數的值更新,然後會執行render函數將msg變數的最新值"world"渲染到p標簽中。

我們先來看看未開啟靜態提升之前生成的render函數是什麼樣的:

由於在vite項目中啟動的vue都是開啟了靜態提升,所以我們需要在 Vue 3 Template Explorer網站中看看未開啟靜態提升的render函數的樣子(網站URL為: https://template-explorer.vuejs.org/ ),如下圖將hoistStatic這個選項取消勾選即可:
template-explorer

未開啟靜態提升生成的render函數如下:

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("template", null, [
    _createElementVNode("div", null, [
      _createElementVNode("h1", null, "title"),
      _createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
      _createElementVNode("button", { onClick: _ctx.handleChange }, "change msg", 8 /* PROPS */, ["onClick"])
    ])
  ]))
}

每次響應式變數更新後都會執行render函數,每次執行render函數都會執行createElementVNode方法生成h1標簽的虛擬DOM。但是我們這個h1標簽明明就是一個靜態節點,根本就不需要每次執行render函數都去生成一次h1標簽的虛擬DOM。

vue3對此做出的優化就是將“執行createElementVNode方法生成h1標簽虛擬DOM的代碼”提取到render函數外面去,這樣就只有初始化的時候才會去生成一次h1標簽的虛擬DOM,也就是我們這篇文章中要講的“靜態提升”。開啟靜態提升後生成的render函數如下:

import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

const _hoisted_1 = /*#__PURE__*/_createElementVNode("h1", null, "title", -1 /* HOISTED */)

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("template", null, [
    _createElementVNode("div", null, [
      _hoisted_1,
      _createElementVNode("p", null, _toDisplayString(_ctx.msg), 1 /* TEXT */),
      _createElementVNode("button", {
        onClick: _cache[0] || (_cache[0] = (...args) => (_ctx.handleChange && _ctx.handleChange(...args)))
      }, "change msg")
    ])
  ]))
}

從上面可以看到生成h1標簽虛擬DOM的createElementVNode函數被提取到render函數外面去執行了,只有初始化時才會執行一次將生成的虛擬DOM賦值給_hoisted_1變數。在render函數中直接使用_hoisted_1變數即可,無需每次執行render函數都去生成h1標簽的虛擬DOM,這就是我們這篇文章中要講的“靜態提升”。

我們接下來還是一樣的套路通過debug的方式來帶你搞清楚vue是如何實現靜態提升的,註:本文使用的vue版本為3.4.19

如何實現靜態提升

實現靜態提升主要分為兩個階段:

  • transform階段遍歷AST抽象語法樹,將靜態節點找出來進行標記和處理,然後將這些靜態節點塞到根節點的hoists數組中。

  • generate階段遍歷上一步在根節點存的hoists數組,在render函數外去生成存儲靜態節點虛擬DOM的_hoisted_x變數。然後在render函數中使用這些_hoisted_x變數表示這些靜態節點。

transform階段

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

function transform(root, options) {
  // ...省略
  if (options.hoistStatic) {
    hoistStatic(root, context);
  }
  root.hoists = context.hoists;
}

從上面可以看到實現靜態提升是執行了hoistStatic函數,我們給hoistStatic函數打個斷點。讓代碼走進去看看hoistStatic函數是什麼樣的,在我們這個場景中簡化後的代碼如下:

function hoistStatic(root, context) {
  walk(root, context, true);
}

從上面可以看到這裡依然不是具體實現的地方,接著將斷點走進walk函數。在我們這個場景中簡化後的代碼如下:

function walk(node, context, doNotHoistNode = false) {
  const { children } = node;
  for (let i = 0; i < children.length; i++) {
    const child = children[i];
    if (
      child.type === NodeTypes.ELEMENT &&
      child.tagType === ElementTypes.ELEMENT
    ) {
      const constantType = doNotHoistNode
        ? ConstantTypes.NOT_CONSTANT
        : getConstantType(child, context);
      if (constantType > ConstantTypes.NOT_CONSTANT) {
        if (constantType >= ConstantTypes.CAN_HOIST) {
          child.codegenNode.patchFlag = PatchFlags.HOISTED + ` /* HOISTED */`;
          child.codegenNode = context.hoist(child.codegenNode);
          continue;
        }
      }
    }

    if (child.type === NodeTypes.ELEMENT) {
      walk(child, context);
    }
  }
}

我們先在debug終端上面看看傳入的第一個參數node是什麼樣的,如下圖:
root-code

從上面可以看到此時的node為AST抽象語法樹的根節點,樹的結構和template中的代碼剛好對上。外層是div標簽,div標簽下麵有h1、p、button三個標簽。

我們接著來看walk函數,簡化後的walk函數只剩下一個for迴圈遍歷node.children。在for迴圈裡面主要有兩塊if語句:

  • 第一塊if語句的作用是實現靜態提升

  • 第二塊if語句的作用是遞歸遍歷整顆樹。

我們來看第一塊if語句中的條件,如下:

if (
  child.type === NodeTypes.ELEMENT &&
  child.tagType === ElementTypes.ELEMENT
)

在將這塊if語句之前,我們先來瞭解一下這裡的兩個枚舉。NodeTypesElementTypes

NodeTypes枚舉

NodeTypes表示AST抽象語法樹中的所有node節點類型,枚舉值如下:

enum NodeTypes {
  ROOT, // 根節點
  ELEMENT,  // 元素節點,比如:div元素節點、Child組件節點
  TEXT, // 文本節點
  COMMENT,  // 註釋節點
  SIMPLE_EXPRESSION,  // 簡單表達式節點,比如v-if="msg !== 'hello'"中的msg!== 'hello'
  INTERPOLATION,  // 雙大括弧節點,比如{{msg}}
  ATTRIBUTE,  // 屬性節點,比如 title="我是title"
  DIRECTIVE,  // 指令節點,比如 v-if=""
  // ...省略
}

看到這裡有的小伙伴可能有疑問了,為什麼AST抽象語法樹中有這麼多種節點類型呢?

我們來看一個例子你就明白了,如下:

<div v-if="msg !== 'hello'" title="我是title">msg為 {{ msg }}</div>

上面這段代碼轉換成AST抽象語法樹後會生成很多node節點:

  • div對應的是ELEMENT元素節點

  • v-if對應的是DIRECTIVE指令節點

  • v-if中的msg !== 'hello'對應的是SIMPLE_EXPRESSION簡單表達式節點

  • title對應的是ATTRIBUTE屬性節點

  • msg為對應的是ELEMENT元素節點

  • {{ msg }}對應的是INTERPOLATION雙大括弧節點

ElementTypes枚舉

div元素節點、Child組件節點都是NodeTypes.ELEMENT元素節點,那麼如何區分是不是組件節點呢?就需要使用ElementTypes枚舉來區分了,如下:

enum ElementTypes {
  ELEMENT,  // html元素
  COMPONENT,  // 組件
  SLOT, // 插槽
  TEMPLATE, // 內置template元素
}

現在來看第一塊if條件,你應該很容易看得懂了:

if (
  child.type === NodeTypes.ELEMENT &&
  child.tagType === ElementTypes.ELEMENT
)

如果當前節點是html元素節點,那麼就滿足if條件。

當前的node節點是最外層的div節點,當然滿足這個if條件。

接著將斷點走進if條件內,第一行代碼如下:

const constantType = doNotHoistNode
  ? ConstantTypes.NOT_CONSTANT
  : getConstantType(child, context);

在搞清楚這行代碼之前先來瞭解一下ConstantTypes枚舉

ConstantTypes枚舉

我們來看看ConstantTypes枚舉,如下:

enum ConstantTypes {
  NOT_CONSTANT = 0, // 不是常量
  CAN_SKIP_PATCH, // 跳過patch函數
  CAN_HOIST,  // 可以靜態提升
  CAN_STRINGIFY,  // 可以預字元串化
}

ConstantTypes枚舉的作用就是用來標記靜態節點的4種等級狀態,高等級的狀態擁有低等級狀態的所有能力。比如:
NOT_CONSTANT:表示當前節點不是靜態節點。比如下麵這個p標簽使用了msg響應式變數:

<p>{{ msg }}</p>

const msg = ref("hello");

CAN_SKIP_PATCH:表示當前節點在重新執行render函數時可以跳過patch函數。比如下麵這個p標簽雖然使用了變數name,但是name是一個常量值。所以這個p標簽其實是一個靜態節點,但是由於使用了name變數,所以不能提升到render函數外面去。

<p>{{ name }}</p>
const name = "name";

CAN_HOIST:表示當前靜態節點可以被靜態提升,當然每次執行render函數時也無需執行patch函數。demo如下:

<h1>title</h1>

CAN_STRINGIFY:表示當前靜態節點可以被預字元串化,下一篇文章會專門講預字元串化
從debug終端中可以看到此時doNotHoistNode變數的值為true,所以constantType變數的值為ConstantTypes.NOT_CONSTANT
getConstantType函數的作用是根據當前節點以及其子節點拿到靜態節點的constantType

我們接著來看後面的代碼,如下:

if (constantType > ConstantTypes.NOT_CONSTANT) {
  if (constantType >= ConstantTypes.CAN_HOIST) {
    child.codegenNode.patchFlag = PatchFlags.HOISTED + ` /* HOISTED */`;
    child.codegenNode = context.hoist(child.codegenNode);
    continue;
  }
}

前面我們已經講過了,當前div節點的constantType的值為ConstantTypes.NOT_CONSTANT,所以這個if語句條件不通過。

我們接著看walk函數中的最後一塊代碼,如下:

if (child.type === NodeTypes.ELEMENT) {
  walk(child, context);
}

前面我們已經講過了,當前child節點是div標簽,所以當然滿足這個if條件。將子節點div作為參數,遞歸調用walk函數。

我們再次將斷點走進walk函數,和上一次執行walk函數不同的是,上一次walk函數的參數為root根節點,這一次參數是div節點。

同樣的在walk函數內先使用for迴圈遍歷div節點的子節點,我們先來看第一個子節點h1標簽,也就是需要靜態提升的節點。很明顯h1標簽是滿足第一個if條件語句的:

if (
  child.type === NodeTypes.ELEMENT &&
  child.tagType === ElementTypes.ELEMENT
)

在debug終端中來看看h1標簽的constantType的值,如下:
constantType

從上圖中可以看到h1標簽的constantType值為3,也就是ConstantTypes.CAN_STRINGIFY。表明h1標簽是最高等級的預字元串,當然也能靜態提升

h1標簽的constantType當然就能滿足下麵這個if條件:

if (constantType > ConstantTypes.NOT_CONSTANT) {
  if (constantType >= ConstantTypes.CAN_HOIST) {
    child.codegenNode.patchFlag = PatchFlags.HOISTED + ` /* HOISTED */`;
    child.codegenNode = context.hoist(child.codegenNode);
    continue;
  }
}

值得一提的是上面代碼中的codegenNode屬性就是用於生成對應node節點的render函數。

然後以codegenNode屬性作為參數執行context.hoist函數,將其返回值賦值給節點的codegenNode屬性。如下:

child.codegenNode = context.hoist(child.codegenNode);

上面這行代碼的作用其實就是將原本生成render函數的codegenNode屬性替換成用於靜態提升的codegenNode屬性。

context.hoist方法

將斷點走進context.hoist方法,簡化後的代碼如下:

function hoist(exp) {
  context.hoists.push(exp);
  const identifier = createSimpleExpression(
    `_hoisted_${context.hoists.length}`,
    false,
    exp.loc,
    ConstantTypes.CAN_HOIST
  );
  identifier.hoisted = exp;
  return identifier;
}

我們先在debug終端看看傳入的codegenNode屬性。如下圖:
before-codegenNode

從上圖中可以看到此時的codegenNode屬性對應的就是h1標簽,codegenNode.children對應的就是h1標簽的title文本節點。codegenNode屬性的作用就是用於生成h1標簽的render函數。

hoist函數中首先執行 context.hoists.push(exp)將h1標簽的codegenNode屬性push到context.hoists數組中。context.hoists是一個數組,數組中存的是AST抽象語法樹中所有需要被靜態提升的所有node節點的codegenNode屬性。

接著就是執行createSimpleExpression函數生成一個新的codegenNode屬性,我們來看傳入的第一個參數:

`_hoisted_${context.hoists.length}`

由於這裡處理的是第一個需要靜態提升的靜態節點,所以第一個參數的值_hoisted_1。如果處理的是第二個需要靜態提升的靜態節點,其值為_hoisted_2,依次類推。

接著將斷點走進createSimpleExpression函數中,代碼如下:

function createSimpleExpression(
  content,
  isStatic = false,
  loc = locStub,
  constType = ConstantTypes.NOT_CONSTANT
) {
  return {
    type: NodeTypes.SIMPLE_EXPRESSION,
    loc,
    content,
    isStatic,
    constType: isStatic ? ConstantTypes.CAN_STRINGIFY : constType,
  };
}

這個函數的作用很簡單,根據傳入的內容生成一個簡單表達式節點。我們這裡傳入的內容就是_hoisted_1

表達式節點我們前面講過了,比如:v-if="msg !== 'hello'"中的msg!== 'hello'就是一個簡單的表達式。

同理上面的_hoisted_1表示的是使用了一個變數名為_hoisted_1的表達式。

我們在debug終端上面看看hoist函數返回值,也就是h1標簽新的codegenNode屬性。如下圖:
after-codegenNode

此時的codegenNode屬性已經變成了一個簡單表達式節點,表達式的內容為:_hoisted_1。後續執行generate生成render函數時,在render函數中h1標簽就變成了表達式:_hoisted_1

最後再執行transform函數中的root.hoists = context.hoists,將context上下文中存的hoists屬性數組賦值給根節點的hoists屬性數組,後面在generate生成render函數時會用。

至此transform階段已經完成了,主要做了兩件事:

  • 將h1靜態節點找出來,將該節點生成render函數的codegenNode屬性push到根節點的hoists屬性數組中,後面generate生成render函數時會用。

  • 將上一步h1靜態節點的codegenNode屬性替換為一個簡單表達式,表達式為:_hoisted_1

generate階段

generate階段主要分為兩部分:

  • 將原本render函數內調用createElementVNode生成h1標簽虛擬DOM的代碼,提到render函數外面去執行,賦值給全局變數_hoisted_1

  • 在render函數內直接使用_hoisted_1變數即可。

如下圖:
generate

生成render函數外面的_hoisted_1變數

經過transform階段的處理,根節點的hoists屬性數組中存了所有需要靜態提升的靜態節點。我們先來看如何處理這些靜態節點,生成h1標簽對應的_hoisted_1變數的。代碼如下:

genHoists(ast.hoists, context);

將根節點的hoists屬性數組傳入給genHoists函數,將斷點走進genHoists函數,在我們這個場景中簡化後的代碼如下:

function genHoists(hoists, context) {
  const { push, newline } = context;
  newline();
  for (let i = 0; i < hoists.length; i++) {
    const exp = hoists[i];
    if (exp) {
      push(`const _hoisted_${i + 1} = ${``}`);
      genNode(exp, context);
      newline();
    }
  }
}

generate部分的代碼會在後面文章中逐行分析,這篇文章就不細看到每個函數了。簡單解釋一下genHoists函數中使用到的那些方法的作用。

  • context.code屬性:此時的render函數字元串,可以在debug終端看一下執行每個函數後render函數字元串是什麼樣的。

  • newline方法:向當前的render函數字元串中插入換行符。

  • push方法:向當前的render函數字元串中插入字元串code。

  • genNode函數:在transform階段給會每個node節點生成codegenNode屬性,在genNode函數中會使用codegenNode屬性生成對應node節點的render函數代碼。

在剛剛進入genHoists函數,我們在debug終端使用context.code看看此時的render函數字元串是什麼樣的,如下圖:
before-genHoists

從上圖中可以看到此時的render函數code字元串只有一行import vue的代碼。

然後執行newline方法向render函數code字元串中插入一個換行符。

接著遍歷在transform階段收集的需要靜態提升的節點集合,也就是hoists數組。在debug終端來看看這個hoists數組,如下圖:
hoists

從上圖中可以看到在hoists數組中只有一個h1標簽需要靜態提升。

在for迴圈中會先執行一句push方法,如下:

push(`const _hoisted_${i + 1} = ${``}`)

這行代碼的意思是插入一個名為_hoisted_1的const變數,此時該變數的值還是空字元串。在debug終端使用context.code看看執行push方法後的render函數字元串是什麼樣的,如下圖:
const

從上圖中可以看到_hoisted_1全局變數的定義已經生成了,值還沒生成。

接著就是執行genNode(exp, context)函數生成_hoisted_1全局變數的值,同理在debug終端看看執行genNode函數後的render函數字元串是什麼樣的,如下圖:
const-value

從上面可以看到render函數外面已經定義了一個_hoisted_1變數,變數的值為調用createElementVNode生成h1標簽虛擬DOM。

生成render函數中return的內容

generate中同樣也是調用genNode函數生成render函數中return的內容,代碼如下:

genNode(ast.codegenNode, context);

這裡傳入的參數ast.codegenNode是根節點的codegenNode屬性,在genNode函數中會從根節點開始遞歸遍歷整顆AST抽象語法樹,為每個節點生成自己的createElementVNode函數,執行createElementVNode函數會生成這些節點的虛擬DOM。

我們先來看看傳入的第一個參數ast.codegenNode,也就是根節點的codegenNode屬性。如下圖:
ast-codegenNode

從上圖中可以看到靜態節點h1標簽已經變成了一個名為_hoisted_1的變數,而使用了msg變數的動態節點依然還是p標簽。

我們再來看看執行這個genNode函數之前render函數字元串是什麼樣的,如下圖:
before-return

從上圖中可以看到此時的render函數字元串還沒生成return中的內容。

執行genNode函數後,來看看此時的render函數字元串是什麼樣的,如下圖:
after-return

從上圖中可以看到,在生成的render函數中h1標簽靜態節點已經變成了_hoisted_1變數,_hoisted_1變數中存的是靜態節點h1的虛擬DOM,所以每次頁面更新重新執行render函數時就不會每次都去生成一遍靜態節點h1的虛擬DOM。

總結

整個靜態提升的流程圖如下:
full-progress

整個流程主要分為兩個階段:

  • transform階段中:

    • 將h1靜態節點找出來,將靜態節點的codegenNode屬性push到根節點的hoists屬性數組中。

    • 將h1靜態節點的codegenNode屬性替換為一個簡單表達式節點,表達式為:_hoisted_1

  • generate階段中:

    • 在render函數外面生成一個名為_hoisted_1的全局變數,這個變數中存的是h1標簽的虛擬DOM。

    • 在render函數內直接使用_hoisted_1變數就可以表示這個h1標簽。

關註(圖1)公眾號:【前端歐陽】,解鎖我更多vue原理文章。
加我(圖2)微信回覆「666」,免費領取歐陽研究vue源碼過程中收集的源碼資料,歐陽寫文章有時也會參考這些資料。同時讓你的朋友圈多一位對vue有深入理解的人。
公眾號微信


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

-Advertisement-
Play Games
更多相關文章
  • 說來慚愧,作為差不多10年的開發者,第一次嘗試提審,結果卻收穫來了蘋果無休止的等待 我從4月24日替身,後續到現在沒有任何回饋,只告訴你 other 原因拒絕, 請問蘋果是只針對中國開發者,還是所有開發者? 如果一個賬戶按年計費,一次等待按照一個月起算,耽誤多少開發者的時間和金錢。 我知道蘋果一向傲 ...
  • 有時候,我們需要在網頁判斷用戶是否處與非活躍狀態,如果用戶長時間沒有在頁面上進行任何操作,我們則判定該用戶是非活躍的。 在 javascript 中我們可以通過監聽某些滑鼠或鍵盤相關的事件來判定用戶是否在活躍中。 ...
  • 一、執行上下文 簡單的來說,執行上下文是一種對Javascript代碼執行環境的抽象概念,也就是說只要有Javascript代碼運行,那麼它就一定是運行在執行上下文中 執行上下文的類型分為三種: 全局執行上下文:只有一個,瀏覽器中的全局對象就是 window對象,this 指向這個全局對象 函數執行 ...
  • 最近項目中需要用到js庫來渲染pdf文件,調研後發現無論是reach-pdf.js或者是svelte-pdf.js都是在pdf.js基礎上做了些許精簡,反而功能還不如原始的pdf.js來得全面。但是原始的庫幾乎沒有像樣的代碼示例,而能搜索到的大多數代碼不少都是十幾年前的了,在這個過程中踩了不少坑,做 ...
  • 一、是什麼 webpack proxy,即webpack提供的代理服務 基本行為就是接收客戶端發送的請求後轉發給其他伺服器 其目的是為了便於開發者在開發模式下解決跨域問題(瀏覽器安全策略限制) 想要實現代理首先需要一個中間伺服器,webpack中提供伺服器的工具為webpack-dev-server ...
  • 前言 在家沒事的時候刷抖音玩,抖音首頁的視頻怎麼刷也刷不完,經常不知不覺的一刷就到半夜了 不禁感嘆道 "垃圾抖音,費我時間,毀我青春" 這是我的 模仿抖音 系列文章的第二篇,本文將一步步實現抖音首頁 視頻無限滑動 的效果,乾貨滿滿 第一篇:200行代碼實現類似Swiper.js的輪播組件 第 ...
  • 一、錯誤類型 任何一個框架,對於錯誤的處理都是一種必備的能力 在Vue 中,則是定義了一套對應的錯誤處理規則給到使用者,且在源代碼級別,對部分必要的過程做了一定的錯誤處理。 主要的錯誤來源包括: 後端介面錯誤 代碼中本身邏輯錯誤 二、如何處理 後端介面錯誤 通過axios的interceptor實現 ...
  • 目錄VUE-局部使用快速入門常用指令v-forv-bindv-if & v-showv-onv-modelvue生命周期AxiosVue案例 VUE-局部使用 Vue 是一款用於構建用戶界面的漸進式的JavaScript框架。 (官方:https://cn.vuejs.org/) 快速入門 準備 準 ...
一周排行
    -Advertisement-
    Play Games
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...