記錄--手寫一個 v-tooltip 指令

来源:https://www.cnblogs.com/smileZAZ/archive/2023/11/17/17839481.html
-Advertisement-
Play Games

這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 日常開發中,我們經常遇到過tooltip這種需求。文字溢出、產品文案、描述說明等等,每次都需要寫一大串代碼,那麼有沒有一種簡單的方式呢,這回我們用指令來試試。 功能特性 支持tooltip樣式自定義 支持tooltip內容自定義 動 ...


這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

前言

日常開發中,我們經常遇到過tooltip這種需求。文字溢出、產品文案、描述說明等等,每次都需要寫一大串代碼,那麼有沒有一種簡單的方式呢,這回我們用指令來試試。

功能特性

  • 支持tooltip樣式自定義
  • 支持tooltip內容自定義
  • 動態更新tooltip內容
  • 文字省略自動出提示
  • 支持彈窗位置自定義和偏移

功能實現

vue3中,指令也是擁有著對應的生命周期。

 我們這裡需要使用的是 mountedupdatedunmounted鉤子。

import { DirectiveBinding } from 'vue'
export default {
  mounted(el: HTMLElement, binding: DirectiveBinding) {
  	
  },
  updated(el: HTMLElement, binding: DirectiveBinding) {
    
  },
  unmounted(el: HTMLElement) {

  }
}

在元素掛載完成之後,我們需要完成上述指令的功能。

什麼時候可用?

首先我們需要考慮的是tooltip什麼時候可用?

  • 元素是省略元素
  • 手動開啟時,我們需要啟用tooltip,比如描述或者產品文案等等。

如果是省略元素,我們需要先判斷元素是否存在省略,一般通過這種方式判斷:

function isOverflow(el: SpecialHTMLElement) {
  if (el.scrollWidth > el.offsetWidth || el.scrollHeight > el.clientHeight) {
    return true
  }
  return false
}
// element plus 採用如下方式判斷,相容 firefox
function isOverflow(el: SpecialHTMLElement){
  const range = document.createRange()
  range.setStart(el, 0)
  range.setEnd(el, el.childNodes.length)
  const rangeWidth = range.getBoundingClientRect().width
  const padding =
    (Number.parseInt(getComputedStyle(el)['paddingLeft'], 10) || 0) +
    (Number.parseInt(getComputedStyle(el)['paddingRight'], 10) || 0)
  if (
    rangeWidth + padding > el.offsetWidth ||
    el.scrollWidth > el.offsetWidth
  ) {
    return true
  }
  return false
}
我們也需要考慮手動開啟這種情況,一般使用一個特殊的CSS屬性開啟。
const enable = el.getAttribute('enableTooltip')

內容構造和位置計算

tooltip開啟之後,我們需要構造它的內容和動態計算tooltip的位置,比如元素髮生縮放和滾動。

構造tooltip內容的話,我們採用一個vue組件,然後通過動態組件方式,將其掛載為tooltip的內容。

<template>
  <div
    ref="tooltipRef"
    class="__CUSTOM_TOOLTIP_ITEM_CONTENT__"
    :class="arrow"
    @mouseover="mouseOver"
    @mouseleave="mouseLeave"
    v-html="content"
  ></div>
</template>

<script lang="ts" setup>
  import type { TimeoutHTMLElement } from './tooltip'
  defineProps({
    content: {
      type: String,
      default: '',
    },
    arrow: {
      type: String,
      default: '',
    },
  })
  const tooltipRef = ref()
  let parent: TimeoutHTMLElement
  onMounted(() => {
    parent = tooltipRef.value.parentElement
  })
  function mouseOver() {
    clearTimeout(parent.__hide_timeout__)
    parent.setAttribute('data-show', 'true')
    parent.style.visibility = 'visible'
  }
  function mouseLeave() {
    parent.setAttribute('data-show', 'false')
    parent.style.visibility = 'hidden'
  }
</script>
<style scoped lang="scss">
  $radius: 8px;
  @mixin arrow {
    position: absolute;
    border-style: solid;
    border-width: $radius;
    width: 0;
    height: 0;
    content: '';
  }

  .__CUSTOM_TOOLTIP_ITEM_CONTENT__ {
    position: absolute;
    border-radius: 4px;
    padding: 10px;
    width: 100%;
    max-width: 260px;
    font-size: 12px;
    color: #fff;
    background: rgb(45 46 50 / 80%);
    line-height: 18px;

    &.top::before {
      @include arrow;

      top: $radius * (-2);
      left: calc(50% - #{$radius});
      border-color: transparent transparent rgb(45 46 50 / 80%) transparent;
    }

    &.top-start::before .top-start::before {
      @include arrow;

      top: $radius * (-2);
      left: $radius;
      border-color: transparent transparent rgb(45 46 50 / 80%) transparent;
    }

    &.top-end::before &.top-end::before {
      @include arrow;

      top: $radius * (-2);
      left: calc(100% - #{$radius * 3});
      border-color: transparent transparent rgb(45 46 50 / 80%) transparent;
    }
  }
</style>
此外我們也可以通過slot方式自定義提示內容。當然也可以通過屬性查詢[slot='content']節點,取出其中的innerHTML,但是這種在更新時需要特殊處理。
function parseSlot(vNode) {
  const content = vNode.children.find(i => {
    return i?.data?.slot === 'content'
  })
  const app = createApp({
    functional: true,
    props: {
      render: Function
    },
    render() {
      return this.render()
    }
  })
	const el = document.createElement('div')
  app.mount(el)
  return el?.innerHTML
}
tooltip位置計算和自動更新,這裡我們使用@floating-ui/dom庫。
const __tooltip_el__ = document.createElement('div')
__tooltip_el__.className = '__CUSTOM_TOOLTIP__'
document.body.appendChild(__tooltip_el__)
function createEle() {
  const tooltip = document.createElement('div')
  tooltip.className = '__CUSTOM_TOOLTIP_ITEM__'
  tooltip.style['zIndex'] = '9999'
  tooltip.style['position'] = 'absolute'
  __tooltip_el__.appendChild(tooltip)
  return tooltip
}
function initTooltip(el: SpecialHTMLElement, binding: DirectiveBinding) {
  const tooltip = createEle()
  el.__float_tooltip__ = tooltip as unknown as TimeoutHTMLElement
  createTooltip(el, binding)
  autoUpdate(el, tooltip, () => updatePosition(el), {
    animationFrame: false,
    ancestorResize: false,
    elementResize: false,
    ancestorScroll: true,
  })
}
function createTooltip(el: SpecialHTMLElement, binding: DirectiveBinding) {
  const tooltip = el.__float_tooltip__ as HTMLElement
  const { width } = el.getBoundingClientRect()
  tooltip.style['minWidth'] = width + 'px'
  const arrow = el.getAttribute('arrow')
  // eslint-disable-next-line vue/one-component-per-file
  const app = createApp(tooltipVue, {
    arrow: arrow,
    content: binding.value !== void 0 ? binding.value : el.oldVNode,
  })
  app.mount(tooltip)
  el.__float_app__ = app
}
function updatePosition(el: SpecialHTMLElement) {
  const tooltip = el.__float_tooltip__
  const middlewares = []
  const visible = tooltip?.style?.visibility
  if (visible !== 'hidden' && visible) {
    const placement = el?.getAttribute('placement') || 'bottom'
    let offsetY =
      el?.getAttribute('offsetY') || el?.getAttribute('offset-y') || 5
    let offsetX = el?.getAttribute('offsetX') || el?.getAttribute('offset-x')
    const offsetXY = el?.getAttribute('offset')
    if (offsetXY !== null) {
      offsetX = offsetXY
      offsetY = offsetXY
    }
    if (offsetX || offsetY) {
      middlewares.push(
        offset({
          mainAxis: Number(offsetY),
          crossAxis: Number(offsetX),
        })
      )
    }

    computePosition(el, tooltip, {
      placement: placement as Placement,
      strategy: 'absolute',
      middleware: middlewares,
    }).then(({ x, y }) => {
      Object.assign(tooltip.style, {
        top: `${y}px`,
        left: `${x}px`,
      })
    })
  }
}

用戶交互

在構造好tooltip之後,我們需要添加用戶交互行為事件,比如用戶移入目標元素,顯示tooltip,移除目標元素,隱藏tooltip。這裡我們加上hide-delay,即延遲隱藏,在設置offset時特別有用,同時也支持添加show-delay,延遲顯示。

function attachEvent(el: HTMLElement) {
  el?.addEventListener?.('mouseover', mouseOver)
  el?.addEventListener?.('mouseleave', mouseLeave)
}

function mouseOver(evt: MouseEvent) {
  const el = evt.currentTarget as SpecialHTMLElement
  const tooltip = el?.__float_tooltip__
  clearTimeout(tooltip?.__hide_timeout__)
  if (tooltip) {
    tooltip.style.visibility = 'visible'
    tooltip.setAttribute('data-show', 'true')
    updatePosition(el)
  }
}

function mouseLeave(evt: MouseEvent) {
  const el = evt.currentTarget as SpecialHTMLElement
  const tooltip = el?.__float_tooltip__
  const isShow = tooltip?.getAttribute?.('data-show')
  const delay = el.getAttribute('hide-delay') || 100
  clearTimeout(tooltip?.__hide_timeout__)
  if (tooltip) {
    if (delay) {
      tooltip.__hide_timeout__ = setTimeout(() => {
        if (isShow === 'true') {
          tooltip.style.visibility = 'hidden'
        }
      }, +delay)
    } else {
      if (isShow === 'true') {
        tooltip.style.visibility = 'hidden'
      }
    }
  }
}

內容更新

我們tooltip的內容並不總是一成不變的,所以我們需要支持內容更新,這個可以在updated鉤子中完成內容更新。

既然我們支持了指令傳值和slot方式,所以我們需要考慮三點:

  • 指令值變化
  • slot內容變化
  • 開啟和關閉

對於slot內容變化監測,我們可以對比新舊slot內容,內容不同則觸發更新。

{
  updated(el: SpecialHTMLElement, binding: DirectiveBinding, vNode: VNode) {
    if (binding.value !== binding.oldValue) {
      updated(el, binding)
    } else {
      const enable = el.getAttribute('enableTooltip')
      if (enable !== el.oldEnable) {
        mounted(el, binding, vNode)
      } else {
        const newVNode = parseSlot(vNode)
        if (el.oldVNode !== newVNode) {
          el.oldVNode = newVNode
          updated(el, binding)
        }
      }
    }
  },
}
function updated(el: SpecialHTMLElement, binding: DirectiveBinding) {
  el?.__float_app__?.unmount?.()
  el.__float_app__ = null
  createTooltip(el, binding)
}

銷毀tooltip

最後,在元素銷毀或者tooltip關閉的的時候,我們需要把相應的事件等進行銷毀。

function unmounted(el: SpecialHTMLElement) {
  removeEvent(el)
  const tooltip = el?.__float_tooltip__
  if (tooltip) {
    __tooltip_el__.removeChild(tooltip)
    el?.__float_app__?.unmount?.()
    el.__float_app__ = null
    el.__float_tooltip__ = null
  }
}

function removeEvent(el: HTMLElement) {
  el?.removeEventListener?.('mouseover', mouseOver)
  el?.removeEventListener?.('mouseleave', mouseLeave)
}

完整代碼

import { DirectiveBinding, VNode, App } from 'vue'
import {
  computePosition,
  autoUpdate,
  offset,
  Placement,
} from '@floating-ui/dom'

import tooltipVue from './CustomTooltip.vue'

export type TimeoutHTMLElement = HTMLElement & {
  __hide_timeout__: NodeJS.Timeout
}
export type SpecialHTMLElement =
  | HTMLElement & {
      __float_tooltip__: TimeoutHTMLElement | null
    } & {
      __float_app__: App | null
    } & {
      oldEnable: string | null
    } & {
      oldVNode: string
    }

// tooltip 容器
const __tooltip_el__ = document.createElement('div')
__tooltip_el__.className = '__CUSTOM_TOOLTIP__'
document.body.appendChild(__tooltip_el__)
// 判斷是否溢出
function isOverflow(el: SpecialHTMLElement) {
  if (el.scrollWidth > el.offsetWidth || el.scrollHeight > el.clientHeight) {
    return true
  }
  return false
}
// 清除 slot
function emptySlot(el: SpecialHTMLElement) {
  const slot = el.querySelector("[slot='content']")
  if (slot) {
    el.removeChild(slot)
  }
  return slot?.innerHTML
}
// 卸載
function unmounted(el: SpecialHTMLElement) {
  removeEvent(el)
  const tooltip = el?.__float_tooltip__
  if (tooltip) {
    __tooltip_el__.removeChild(tooltip)
    el?.__float_app__?.unmount?.()
    el.__float_app__ = null
    el.__float_tooltip__ = null
  }
}
// 移除事件
function removeEvent(el: SpecialHTMLElement) {
  el?.removeEventListener?.('mouseover', mouseOver)
  el?.removeEventListener?.('mouseleave', mouseLeave)
}
// 添加事件
function attachEvent(el: SpecialHTMLElement) {
  el?.addEventListener?.('mouseover', mouseOver)
  el?.addEventListener?.('mouseleave', mouseLeave)
}
// 滑鼠懸浮
function mouseOver(evt: MouseEvent) {
  const el = evt.currentTarget as SpecialHTMLElement
  const tooltip = el?.__float_tooltip__
  clearTimeout(tooltip?.__hide_timeout__)
  if (tooltip) {
    tooltip.style.visibility = 'visible'
    tooltip.setAttribute('data-show', 'true')
    updatePosition(el)
  }
}
// 滑鼠移出
function mouseLeave(evt: MouseEvent) {
  const el = evt.currentTarget as SpecialHTMLElement
  const tooltip = el?.__float_tooltip__
  const isShow = tooltip?.getAttribute?.('data-show')
  const delay = el.getAttribute('hide-delay') || 100
  clearTimeout(tooltip?.__hide_timeout__)
  if (tooltip) {
    if (delay) {
      tooltip.__hide_timeout__ = setTimeout(() => {
        if (isShow === 'true') {
          tooltip.style.visibility = 'hidden'
        }
      }, +delay)
    } else {
      if (isShow === 'true') {
        tooltip.style.visibility = 'hidden'
      }
    }
  }
}
// 掛載tooltip
function mounted(
  el: SpecialHTMLElement,
  binding: DirectiveBinding,
  vNode: VNode
) {
  const overflow = isOverflow(el)
	// 手動啟用tooltip
  const enable = el.getAttribute('enableTooltip')
  el.oldEnable = enable
  if (binding.value === void 0 && vNode) {
    el.oldVNode = parseSlot(vNode)
  }
  emptySlot(el)
  // 顯示延遲
  const delay = el.getAttribute('show-delay') || 100
  if (overflow || enable === 'true') {
    if (delay) {
      setTimeout(() => {
        initTooltip(el, binding)
        attachEvent(el)
      }, +delay)
    } else {
      initTooltip(el, binding)
      attachEvent(el)
    }
  } else {
    unmounted(el)
  }
}
// 更新tooltip 只更新內容
function updated(el: SpecialHTMLElement, binding: DirectiveBinding) {
  el?.__float_app__?.unmount?.()
  el.__float_app__ = null
  createTooltip(el, binding)
}
// 創建元素工廠
function createEle() {
  const tooltip = document.createElement('div')
  tooltip.className = '__CUSTOM_TOOLTIP_ITEM__'
  tooltip.style['zIndex'] = '9999'
  tooltip.style['position'] = 'absolute'
  __tooltip_el__.appendChild(tooltip)
  return tooltip
}
// 初始化tooltip:創建和計算位置
function initTooltip(el: SpecialHTMLElement, binding: DirectiveBinding) {
  const tooltip = createEle()
  el.__float_tooltip__ = tooltip as unknown as TimeoutHTMLElement
  createTooltip(el, binding)
  autoUpdate(el, tooltip, () => updatePosition(el), {
    animationFrame: false,
    ancestorResize: false,
    elementResize: false,
    ancestorScroll: true,
  })
}
// 創建tooltip
function createTooltip(el: SpecialHTMLElement, binding: DirectiveBinding) {
  const tooltip = el.__float_tooltip__ as HTMLElement
  const { width } = el.getBoundingClientRect()
  tooltip.style['minWidth'] = width + 'px'
  const arrow = el.getAttribute('arrow')
  // eslint-disable-next-line vue/one-component-per-file
  const app = createApp(tooltipVue, {
    arrow: arrow,
    content: binding.value !== void 0 ? binding.value : el.oldVNode,
  })
  app.mount(tooltip)
  el.__float_app__ = app
}
// 更新tooltip位置
function updatePosition(el: SpecialHTMLElement) {
  const tooltip = el.__float_tooltip__
  const middlewares = []
  const visible = tooltip?.style?.visibility
  if (visible !== 'hidden' && visible) {
    const placement = el?.getAttribute('placement') || 'bottom'
    let offsetY =
      el?.getAttribute('offsetY') || el?.getAttribute('offset-y') || 5
    let offsetX = el?.getAttribute('offsetX') || el?.getAttribute('offset-x')
    const offsetXY = el?.getAttribute('offset')
    if (offsetXY !== null) {
      offsetX = offsetXY
      offsetY = offsetXY
    }
    if (offsetX || offsetY) {
      middlewares.push(
        offset({
          mainAxis: Number(offsetY),
          crossAxis: Number(offsetX),
        })
      )
    }

    computePosition(el, tooltip, {
      placement: placement as Placement,
      strategy: 'absolute',
      middleware: middlewares,
    }).then(({ x, y }) => {
      Object.assign(tooltip.style, {
        top: `${y}px`,
        left: `${x}px`,
      })
    })
  }
}
// 解析slot
function parseSlot(vNode: VNode) {
  const content = (vNode.children as VNode[]).find?.((i: VNode) => {
    return i?.props?.slot === 'content'
  })
  // eslint-disable-next-line vue/one-component-per-file
  const app = createApp(
    {
      functional: true,
      props: {
        render: Function,
      },
      render() {
        return this.render()
      },
    },
    // eslint-disable-next-line vue/one-component-per-file
    {
      render: () => {
        return content
      },
    }
  )
  const el = document.createElement('div')
  app.mount(el)
  return el?.innerHTML
}

export default {
  mounted(el: SpecialHTMLElement, binding: DirectiveBinding, vNode: VNode) {
    mounted(el, binding, vNode)
  },
  updated(el: SpecialHTMLElement, binding: DirectiveBinding, vNode: VNode) {
    if (binding.value !== binding.oldValue) {
      updated(el, binding)
    } else {
      const enable = el.getAttribute('enableTooltip')
      if (enable !== el.oldEnable) {
        mounted(el, binding, vNode)
      } else {
        const newVNode = parseSlot(vNode)
        if (el.oldVNode !== newVNode) {
          el.oldVNode = newVNode
          updated(el, binding)
        }
      }
    }
  },
  unmounted(el: SpecialHTMLElement) {
    unmounted(el)
  },
}

示例

<div v-tooltip='hello world' enableTooltip='true'>tooltip</div>

<div v-tooltip enableTooltip='true'>
  tooltip
	<div slot='content'>
    <div>this is a tooltip</div>
    <button>confirm</button>
  </div>
</div>

總結

在經過二次封裝之後,我們只需要v-tooltip這樣簡便的操作,即可達到tooltip的作用,簡化了傳統的書寫流程,對於一些特殊tooltip內容,我們可以通過slot方式,定製化更多的提示內容。

本文轉載於:

https://juejin.cn/post/7177384845968932901

如果對您有所幫助,歡迎您點個關註,我會定時更新技術文檔,大家一起討論學習,一起進步。

 


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

-Advertisement-
Play Games
更多相關文章
  • 這是我打的第一次比賽,主打的pwn方向,紀念我的成長 需求:一定的linux系統的命令指令知識,基礎的彙編代碼,配置好了的虛擬機(打pwn建議是ubuntu),pwntools的使用,python的使用,ROPgadget的使用 每次把文件拖入IDA前記得用Exeinfope進行檢查一下,看是x86 ...
  • 一、簡介 1.1 Mongodb MongoDb是一個基於分散式文件存儲的資料庫,官方地址 https://www.mongodb.com/ 1.2 資料庫 資料庫(DataBase)是按照數據結構來組織、存儲和管理數據的應用程式。 作用:資料庫的主要作用就是管理數據,對數據進行增(c)、刪(d)、 ...
  • SELECT 關鍵字 SQL的SELECT語句用於從資料庫中選擇數據。SELECT語句的基本語法如下: SELECT column1, column2, ... FROM table_name; 其中,column1, column2,等是您要從表中選擇的欄位名稱,而table_name是您要選擇數 ...
  • 使用Redis Enterprise與Oracle共同用作企業級緩存或副本資料庫,Oracle與Redis Enterprise合作,解決了使用Oracle時的問題。Redis Enterprise提供實時性能、降低成本、解除數據限制,並與Oracle協同工作,用作記憶體資料庫或緩存,提升性能和現代化... ...
  • 近日,2023金融街論壇年會在北京成功舉辦。由華為雲GaussDB參與支持的工商銀行傳統集中式資料庫平滑遷移解決方案和華夏銀行借記卡系統分散式資料庫改造創新研究與實踐,均獲得“十佳卓越實踐獎”。 ...
  • 在剛剛過去的北京Doris Summit Asia 2023,玖章算術技術副總裁陳長城受邀參加並做了《NineData面向Doris實時數倉集成的技術實踐》報告。 ...
  • Kubernetes 是用於編排容器化應用程式的雲原生系統。最初由 Google 創建,如今由 Cloud Native Computing Foundation(CNCF)維護更新。 Kubernetes 是市面上最受歡迎的集群管理解決方案之一。它自動化容器化應用程式的部署、擴展和管理,允許管理和 ...
  • 可以少去理解一些不必要的概念,而多去思考為什麼會有這樣的東西,它解決了什麼問題,或者它的運行機制是什麼? React 1. React 起源和發展(是什麼?) React 是用於構建用戶界面的 JavaScript 庫,起源於 Facebook 的內部項目,該公司對市場上所有 JavaScript ...
一周排行
    -Advertisement-
    Play Games
  • 當使用Autofac處理一個介面有多個實現的情況時,通常會使用鍵(key)進行區分或者通過IIndex索引註入,也可以通過IEnumerable集合獲取所有實例,以下是一個具體的例子,演示如何在Autofac中註冊多個實現,並通過構造函數註入獲取指定實現。 首先,確保你已經安裝了Autofac Nu ...
  • 本篇將分享Prometheus+Grafana的監控平臺搭建,並監控之前文章所搭建的主機&服務,分享日常使用的一些使用經驗本篇將配置常用服務的監控與面板配置:包括 MySQL,MongoDB,CLickHouse,Redis,RabbitMQ,Linux,Windows,Nginx,站點訪問監控,已... ...
  • 使用Aspirate可以將Aspire程式部署到Kubernetes 集群 工具安裝 dotnet tool install -g aspirate --prerelease 註意:Aspirate 正在開發中,該軟體包將作為預覽版進行版本控制,--prelease 選項將獲得最新的預覽版。 容器註 ...
  • 前言 本文要說的這種開發模式,這種模式並不是只有blazor支持,js中有一樣的方案next.js nuxt.js;blazor還有很多其它內容,本文近關註漸進式開發模式。 是的,前後端是主流,不過以下情況也許前後端分離並不是最好的選擇: 小公司,人員不多,利潤不高,創業階段能省則省 個人開發者,接 ...
  • 在.NET中,Microsoft.Extensions.Logging是一個靈活的日誌庫,它允許你將日誌信息記錄到各種不同的目標,包括資料庫。在這個示例中,我將詳細介紹如何使用Microsoft.Extensions.Logging將日誌保存到MySQL資料庫。我們將使用Entity Framewo ...
  • chatgpt介面開發筆記3: 語音識別介面 1.文本轉語音 1、瞭解介面參數 介面地址: POST https://api.openai.com/v1/audio/speech 下麵是介面文檔描述內容: 參數: { "model": "tts-1", "input": "你好,我是饒坤,我是ter ...
  • 前面兩篇文章主要是介紹瞭如何解決高併發情況下資源爭奪的問題。但是現實的應用場景中除了要解決資源爭奪問題,高併發的情況還需要解決更多問題,比如快速處理業務數據等, 本篇文章簡要羅列一下與之相關的更多技術細節。 1、非同步編程:使用async和await關鍵字進行非同步編程,這可以避免阻塞線程,提高程式的響 ...
  • 大家好,我是棧長。 Nacos 2.3.0 前幾天正式發佈了,新增了不少實用性的新功能,真是史上最強版本。 Nacos 2.3.0 還真是一個比較重要的大版本,因為它涉及了太多重大更新,今天棧長給大家來解讀下。 Nacos 先掃個盲: Nacos 一個用於構建雲原生應用的動態服務發現、配置管理和服務 ...
  • IDEA的遠程開發功能,可以將本地的編譯、構建、調試、運行等工作都放在遠程伺服器上執行,而本地僅運行客戶端軟體進行常規的開發操作即可,舊版本IDEA目前不支持該功能.,本例使用的是IDEA2023.2.5版本 下麵介紹如何在IDEA中設置遠程連接伺服器開發環境並結合Cpolar內網穿透工具實現無公網 ...
  • 本文解釋為啥會有響應式編程,為什麼它在開發者中不太受歡迎,以及引入 Java 虛擬線程後它可能最終會消失。 命令式風格編程一直深受開發者喜愛,如 if-then-else、while 迴圈、函數和代碼塊等結構使代碼易理解、調試,異常易追蹤。然而,像所有好的東西一樣,通常也有問題。這種編程風格導致線程 ...