主頁 > 企業開發 > 深入決議React DnD拖拽原理,輕松掌握拖放技巧!

深入決議React DnD拖拽原理,輕松掌握拖放技巧!

2023-06-10 08:37:09 企業開發

我們是袋鼠云數堆疊 UED 團隊,致力于打造優秀的一站式資料中臺產品,我們始終保持工匠精神,探索前端道路,為社區積累并傳播經驗價值,,

本文作者:霽明

一、背景

1、業務背景

業務中會有一些需要實作拖拽的場景,尤其是偏視覺方向以及移動端較多,拖拽在一定程度上能讓互動更加便捷,能大大提升用戶體驗,以業務中心子產品配置功能為例,產品模塊通過拖拽來調整順序,的確會更加方便一些,

file

2、React DnD 介紹

參考官網介紹:
React DnD 是一組 React 實用程式,可幫助您構建復雜的拖放界面,同時保持組件分離, 它非常適合 Trello 和 Storify 等應用程式,在應用程式的不同部分之間拖動可以傳輸資料,組件會根據拖放事件更改其外觀和應用程式狀態,
React-DnD 特點:

  • 使用包裹及注入的方式使組件實作拖拽
  • 可用于構建復雜的拖放界面,同時保持組件分離
  • 采用單向資料流
  • 抹平了不同瀏覽器平臺的差異
  • 可擴展可測驗
  • 支持觸屏操作

二、使用方式

1、安裝

安裝 react-dnd, react-dnd-html5-backend

npm install react-dnd react-dnd-html5-backend

2、DndProvider

將需要拖拽的組件使用DndProvider進行包裹

import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import Container from '../components/container';

export default function App() {
  return (
    <DndProvider backend={HTML5Backend}>
      <Container />
    </DndProvider>
  );
}

看下Container組件,主要是管理資料,并渲染Card串列

function Container() {
  // ...
  return (
    <div style={{ width: 400 }}>
      {cards.map((card, index) => (
        <Card
          key={card.id}
          index={index}
          id={card.id}
          text={card.text}
          moveCard={moveCard}
        />
      ))}
    </div>
  );
}

3、useDrag和useDrop

接下來看下Card組件,

import { useRef } from 'react';
import { useDrag, useDrop } from 'react-dnd';
import styles from '../styles/home.module.css';

function Card({ id, text, index, moveCard }: ICardProps) {
  const ref = useRef<HTMLDivElement>(null);

  const [{ handlerId }, drop] = useDrop({
    accept: CARD,
    collect(monitor) {
      return {
        handlerId: monitor.getHandlerId(),
      };
    },
    hover(item: IDragItem, monitor) {
      if (!ref.current) {
        return;
      }
      const dragIndex = item.index;
      const hoverIndex = index;
      // ...
      // 更新元素的位置
      moveCard(dragIndex, hoverIndex);
      // ...
    },
  });

  const [{ isDragging }, drag] = useDrag({
    type: CARD,
    item: { id, index },
    collect: (monitor: any) => ({
      isDragging: monitor.isDragging(),
    }),
  });

  drag(drop(ref));
  const opacity = isDragging ? 0 : 1;

  return (
    <div
      ref={ref}
      className={styles.card}
      style={{ opacity }}
      data-handler-id={handlerId}
    >
      {text}
    </div>
  );
}

至此一個簡單的拖拽排序串列就實作了,實作的效果類似于React DnD官網的這個示例:https://react-dnd.github.io/react-dnd/examples/sortable/simple,接下來我們來看看實作原理,

三、原理決議

1、總體架構

主要代碼代碼目錄結構

file

核心代碼主要分三個部分:

  • dnd-core:核心邏輯,定義了拖拽介面、管理方式、資料流向
  • backend:抽象出來的后端概念,主要處理DOM事件
  • react-dnd:封裝React組件,提供api,相當于接入層

核心實作原理:
dnd-core向backend提供資料的更新方法,backend在拖拽時更新dnd-core中的資料,dnd-core通過react-dnd更新業務組件,

file

2、DndProvider

先看一下原始碼

/**
 * A React component that provides the React-DnD context
 */
export const DndProvider: FC<DndProviderProps<unknown, unknown>> = memo(
  function DndProvider({ children, ...props }) {
    const [manager, isGlobalInstance] = getDndContextValue(props) // memoized from props
    // ...
    return <DndContext.Provider value=https://www.cnblogs.com/dtux/archive/2023/06/09/{manager}>{children}
  },
)

從以上代碼可以看出,生成了一個manager,并將其放到DndContext.Provider中,先看下DndContext的代碼:

import { createContext } from 'react'
// ...
export const DndContext = createContext<DndContextType>({
  dragDropManager: undefined,
})

就是使用 React 的createContext創建的背景關系容器組件,

接下來看下這個manager,主要是用來控制拖拽行為,通過Provider讓子節點也可以訪問,我們看下創建manager的getDndContextValue方法:

import type { BackendFactory, DragDropManager } from 'dnd-core'
import { createDragDropManager } from 'dnd-core'
// ...
function getDndContextValue(props: DndProviderProps<unknown, unknown>) {
  if ('manager' in props) {
     const manager = { dragDropManager: props.manager }
     return [manager, false]
  }

   const manager = createSingletonDndContext(
     props.backend,
     props.context,
     props.options,
     props.debugMode,
    )
   const isGlobalInstance = !props.context

   return [manager, isGlobalInstance]
}

function createSingletonDndContext<BackendContext, BackendOptions>(
   backend: BackendFactory,
   context: BackendContext = getGlobalContext(),
   options: BackendOptions,
   debugMode?: boolean,
) {
   const ctx = context as any
   if (!ctx[INSTANCE_SYM]) {
     ctx[INSTANCE_SYM] = {
       dragDropManager: createDragDropManager(
        backend,
	context,
	options,
	debugMode,
       ),
     }
   }
   return ctx[INSTANCE_SYM]
}

從以上代碼可以看出,getDndContextValue方法又呼叫了createSingletonDndContext方法,并傳入了backend、context、options、debugMode這幾個屬性,然后通過dnd-core中的createDragDropManager來創建manager,

3、DragDropManager

看下createDragDropManager.js中的主要代碼

import type { Store } from 'redux'
import { createStore } from 'redux'
// ...
import { reduce } from './reducers/index.js'

export function createDragDropManager(
  backendFactory: BackendFactory,
  globalContext: unknown = undefined,
  backendOptions: unknown = {},
  debugMode = false,
): DragDropManager {
  const store = makeStoreInstance(debugMode)
  const monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store))
  const manager = new DragDropManagerImpl(store, monitor)
  const backend = backendFactory(manager, globalContext, backendOptions)
  manager.receiveBackend(backend)
  return manager
}

function makeStoreInstance(debugMode: boolean): Store<State> {
  // ...
  return createStore(
    reduce,
    debugMode &&
    reduxDevTools &&
    reduxDevTools({
      name: 'dnd-core',
      instanceId: 'dnd-core',
    }),
  )
}

可以看到使用了redux的createStore創建了store,并創建了monitor和manager實體,通過backendFactory創建backend后端實體并安裝到manager總實體,

看一下DragDropManagerImpl的主要代碼

export class DragDropManagerImpl implements DragDropManager {
  private store: Store<State>
  private monitor: DragDropMonitor
  private backend: Backend | undefined
  private isSetUp = false

  public constructor(store: Store<State>, monitor: DragDropMonitor) {
    this.store = store
    this.monitor = monitor
    store.subscribe(this.handleRefCountChange)
   }

   // ...

  public getActions(): DragDropActions {
  /* eslint-disable-next-line @typescript-eslint/no-this-alias */
    const manager = this
    const { dispatch } = this.store

    function bindActionCreator(actionCreator: ActionCreator<any>) {
      return (...args: any[]) => {
        const action = actionCreator.apply(manager, args as any)
        if (typeof action !== 'undefined') {
          dispatch(action)
        }
      }
  }

  const actions = createDragDropActions(this)

  return Object.keys(actions).reduce(
    (boundActions: DragDropActions, key: string) => {
      const action: ActionCreator<any> = (actions as any)[
        key
      ] as ActionCreator<any>
      ;(boundActions as any)[key] = bindActionCreator(action)
        return boundActions
      },
      {} as DragDropActions,
    )
  }

  public dispatch(action: Action<any>): void {
    this.store.dispatch(action)
  }

  private handleRefCountChange = (): void => {
    const shouldSetUp = this.store.getState().refCount > 0
    if (this.backend) {
      if (shouldSetUp && !this.isSetUp) {
	this.backend.setup()
	this.isSetUp = true
      } else if (!shouldSetUp && this.isSetUp) {
	this.backend.teardown()
	this.isSetUp = false
      }
    }
  }
}

先說一下這個handleRefCountChange方法,在建構式里通過store進行訂閱,在第一次使用useDrop或useDrag時會執行setup方法初始化backend,在拖拽源和放置源都被卸載時則會執行teardown銷毀backend,

接下來看一下createDragDropActions方法

export function createDragDropActions(
  manager: DragDropManager,
): DragDropActions {
  return {
    beginDrag: createBeginDrag(manager),
    publishDragSource: createPublishDragSource(manager),
    hover: createHover(manager),
    drop: createDrop(manager),
    endDrag: createEndDrag(manager),
  }
}

可以看到系結一些action:

  • beginDrag(開始拖動)
  • publishDragSource(發布當前拖動源)
  • hover(是否經過)
  • drop(落下動作)
  • endDrag(拖拽結束)

manager包含了之前生成的 monitor、store、backend,manager 創建完成,表示此時我們有了一個 store 來管理拖拽中的資料,有了 monitor 來監聽資料和控制行為,能通過 manager 進行注冊,可以通過 backend 將 DOM 事件轉換為 action,接下來便可以注冊拖拽源和放置源了,

4、useDrag

/**
 * useDragSource hook
 * @param sourceSpec The drag source specification (object or function, function preferred)
 * @param deps The memoization deps array to use when evaluating spec changes
 */
export function useDrag<
  DragObject = unknown,
  DropResult = unknown,
  CollectedProps = unknown,
>(
  specArg: FactoryOrInstance<
    DragSourceHookSpec<DragObject, DropResult, CollectedProps>
  >,
  deps?: unknown[],
): [CollectedProps, ConnectDragSource, ConnectDragPreview] {
  const spec = useOptionalFactory(specArg, deps)
  invariant(
    !(spec as any).begin,
    'useDrag::spec.begin was deprecated in v14. Replace spec.begin() with spec.item(). (see more here - https://react-dnd.github.io/react-dnd/docs/api/use-drag)',
  )

  const monitor = useDragSourceMonitor<DragObject, DropResult>()
  const connector = useDragSourceConnector(spec.options, spec.previewOptions)
  useRegisteredDragSource(spec, monitor, connector)

  return [
    useCollectedProps(spec.collect, monitor, connector),
    useConnectDragSource(connector),
    useConnectDragPreview(connector),
  ]
}

可以看到useDrag方法回傳了一個包含3個元素的陣列,CollectedProps(collect方法回傳的物件)、ConnectDragSource(拖拽源連接器)、ConnectDragPreview(拖拽源預覽),

monitor是從前面Provider中的manager中獲取的,主要看下connector

export function useDragSourceConnector(
  dragSourceOptions: DragSourceOptions | undefined,
  dragPreviewOptions: DragPreviewOptions | undefined,
): SourceConnector {
  const manager = useDragDropManager()
  const connector = useMemo(
    () => new SourceConnector(manager.getBackend()),
    [manager],
  )
  // ...
  return connector
}

可以看到connector獲取了manager.getBackend后端的資料,

useRegisteredDragSource方法會對拖動源進行注冊,會保存拖動源實體,并記錄注冊的數量,

5、useDrop

看下useDrop原始碼

/**
 * useDropTarget Hook
 * @param spec The drop target specification (object or function, function preferred)
 * @param deps The memoization deps array to use when evaluating spec changes
 */
export function useDrop<
  DragObject = unknown,
  DropResult = unknown,
  CollectedProps = unknown,
>(
  specArg: FactoryOrInstance<
    DropTargetHookSpec<DragObject, DropResult, CollectedProps>
  >,
  deps?: unknown[],
): [CollectedProps, ConnectDropTarget] {
  const spec = useOptionalFactory(specArg, deps)
  const monitor = useDropTargetMonitor<DragObject, DropResult>()
  const connector = useDropTargetConnector(spec.options)
  useRegisteredDropTarget(spec, monitor, connector)

  return [
    useCollectedProps(spec.collect, monitor, connector),
    useConnectDropTarget(connector),
  ]
}

useDrop回傳了一個包含2個元素的陣列,CollectedProps(collect方法回傳的物件), ConnectDropTarget(放置源連接器),monitor和connector的獲取都和useDrag類似,

6、HTML5Backend

HTML5Backend使用了HTML5 拖放 API,先了解下HTML拖拽事件:

file

一個簡單拖拽操作程序,會依次觸發拖拽事件:dragstart -> drag -> dragenter -> dragover (-> dragleave) -> drop -> dragend,

drag事件會在dragstar觸發后持續觸發,直至drop,

dragleave事件會在拖拽元素離開一個可釋放目標時觸發,

接下來介紹一下HTML5Backend,是React DnD 主要支持的后端,使用HTML5 拖放 API,它會截取拖動的 DOM 節點并將其用作開箱即用的“拖動預覽”,React DnD 中以可插入的方式實作 HTML5 拖放支持,可以根據觸摸事件、滑鼠事件或其他完全不同的事件撰寫不同的實作,這種可插入的實作在 React DnD 中稱為后端,官網提供了HTML5Backend和TouchBackend,分別用來支持web端和移動端,

后端擔任與 React 的合成事件系統類似的角色:它們抽象出瀏覽器差異并處理原生DOM 事件,盡管有相似之處,但 React DnD 后端并不依賴于 React 或其合成事件系統,在后臺,后端所做的就是將 DOM 事件轉換為 React DnD 可以處理的內部 Redux 操作,

前面給DndProvider傳遞的HTML5backend,看一下其代碼實作:

export const HTML5Backend: BackendFactory = function createBackend(
  manager: DragDropManager,
  context?: HTML5BackendContext,
  options?: HTML5BackendOptions,
): HTML5BackendImpl {
  return new HTML5BackendImpl(manager, context, options)
}

可以看到其實是個回傳HTML5BackendImpl實體的函式,在創建manager實體時會執行createBackend方法創建真正的backend,

如下是 Backend 需要被實作的方法:

export interface Backend {
  setup(): void
  teardown(): void
  connectDragSource(sourceId: any, node?: any, options?: any): Unsubscribe
  connectDragPreview(sourceId: any, node?: any, options?: any): Unsubscribe
  connectDropTarget(targetId: any, node?: any, options?: any): Unsubscribe
  profile(): Record<string, number>
}

setup 是 backend 的初始化方法,teardown 是 backend 銷毀方法,connectDragSource方法將元素轉換為可拖拽元素,并添加監聽事件,connectDropTarget方法會給元素添加監聽事件,connectDragPreview方法會將preview元素保存以供監聽函式使用,profile方法用于回傳一些簡要的統計資訊,

以上這幾個方法都在HTML5BackendImpl中,我們先看一下setup方法:

public setup(): void {
  const root = this.rootElement as RootNode | undefined
  if (root === undefined) {
    return
  }

  if (root.__isReactDndBackendSetUp) {
    throw new Error('Cannot have two HTML5 backends at the same time.')
  }
  root.__isReactDndBackendSetUp = true
  this.addEventListeners(root)
}

root默認是windows,通過addEventListeners方法把監聽事件都系結到windows上,這提高了性能也降低了事件銷毀的難度,

看下addEventListeners方法:

private addEventListeners(target: Node) {
  if (!target.addEventListener) {
    return
  }
  target.addEventListener(
    'dragstart',
    this.handleTopDragStart as EventListener,
  )
  target.addEventListener('dragstart', this.handleTopDragStartCapture, true)
  target.addEventListener('dragend', this.handleTopDragEndCapture, true)
  target.addEventListener(
    'dragenter',
    this.handleTopDragEnter as EventListener,
  )
  target.addEventListener(
    'dragenter',
    this.handleTopDragEnterCapture as EventListener,
    true,
  )
  target.addEventListener(
    'dragleave',
    this.handleTopDragLeaveCapture as EventListener,
    true,
  )
  target.addEventListener('dragover', this.handleTopDragOver as EventListener)
  target.addEventListener(
    'dragover',
    this.handleTopDragOverCapture as EventListener,
    true,
  )
  target.addEventListener('drop', this.handleTopDrop as EventListener)
  target.addEventListener(
    'drop',
    this.handleTopDropCapture as EventListener,
    true,
  )
}

以上代碼中監聽了一些拖拽事件,這些監聽函式會獲得拖拽事件的物件、拿到相應的引數,并執行相應的action方法,HTML5Backend 通過 manager 拿到一個 DragDropActions 的實體,執行其中的方法,DragDropActions 本質就是根據引數將其封裝為一個 action,最終通過 redux 的 dispatch 將 action 分發,改變 store 中的資料,

export interface DragDropActions {
  beginDrag(
    sourceIds?: Identifier[],
    options?: any,
  ): Action<BeginDragPayload> | undefined
    publishDragSource(): SentinelAction | undefined
    hover(targetIds: Identifier[], options?: any): Action<HoverPayload>
    drop(options?: any): void
    endDrag(): SentinelAction
}

最后我們再看下connectDragSource方法:

public connectDragSource(
  sourceId: string,
  node: Element,
  options: any,
): Unsubscribe {
  // ...
  node.setAttribute('draggable', 'true')
  node.addEventListener('dragstart', handleDragStart)
  node.addEventListener('selectstart', handleSelectStart)

  return (): void => {
    // ...
    node.removeEventListener('dragstart', handleDragStart)
    node.removeEventListener('selectstart', handleSelectStart)
    node.setAttribute('draggable', 'false')
  }
}

可以看到主要是把節點的draggable屬性設定為true,并添加監聽事件,回傳一個Unsubscribe函式用于執行銷毀,

綜上,HTML5Backend 在初始化的時候在 window 物件上系結拖拽事件的監聽函式,拖拽事件觸發時執行對應action,更新 store 中的資料,完成由 Dom 事件到資料的轉變,

7、TouchBackend

HTML5 后端不支持觸摸事件,因此它不適用于平板電腦和移動設備,可以使用react-dnd-touch-backend來支持觸摸設備,簡單看下ToucheBackend,

ToucheBackend主要是為了支持移動端,也支持web端,在web端可以使用 mousedown、mousemove、mouseup,在移動端則使用 touchstart、touchmove、touchend,下面是ToucheBackend中對事件的定義:

const eventNames: Record<ListenerType, EventName> = {
  [ListenerType.mouse]: {
    start: 'mousedown',
    move: 'mousemove',
    end: 'mouseup',
    contextmenu: 'contextmenu',
  },
  [ListenerType.touch]: {
    start: 'touchstart',
    move: 'touchmove',
    end: 'touchend',
  },
  [ListenerType.keyboard]: {
    keydown: 'keydown',
  },
}

8、主要拖拽程序

file

四、總結

React-DnD 采用了分層設計,react-dnd充當接入層,dnd-core實作拖拽介面、定義拖拽行為、管理資料流向,backend將DOM事件通過redux action轉換為資料,

使用可插入的方式引入backend,使拖拽的實作可擴展且更加靈活,

使用了單向資料流,在拖拽時不用處理中間狀態,不用額外對DOM事件進行處理,只需專注于資料的變化,
React-DnD對backend的實作方式、資料的管理方式,以及整體的設計都值得借鑒,

五、參考鏈接:

  • https://github.com/react-dnd/react-dnd/
  • https://react-dnd.github.io/react-dnd
  • https://developer.mozilla.org/zh-CN/docs/Web/API/HTML_Drag_and_Drop_API
  • https://zhuanlan.zhihu.com/p/429986799
  • https://juejin.cn/post/6885511137236877325

最后

歡迎關注【袋鼠云數堆疊UED團隊】~
袋鼠云數堆疊UED團隊持續為廣大開發者分享技術成果,相繼參與開源了歡迎star

  • 大資料分布式任務調度系統——Taier
  • 輕量級的 Web IDE UI 框架——Molecule
  • 針對大資料領域的 SQL Parser 專案——dt-sql-parser
  • 袋鼠云數堆疊前端團隊代碼評審工程實踐檔案——code-review-practices
  • 一個速度更快、配置更靈活、使用更簡單的模塊打包器——ko

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/554817.html

標籤:其他

上一篇:前端常見的字符編碼方案

下一篇:返回列表

標籤雲
其他(160732) Python(38219) JavaScript(25491) Java(18216) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7241) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4589) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1957) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1880) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • IEEE1588PTP在數字化變電站時鐘同步方面的應用

    IEEE1588ptp在數字化變電站時鐘同步方面的應用 京準電子科技官微——ahjzsz 一、電力系統時間同步基本概況 隨著對IEC 61850標準研究的不斷深入,國內外學者提出基于IEC61850通信標準體系建設數字化變電站的發展思路。數字化變電站與常規變電站的顯著區別在于程序層傳統的電流/電壓互 ......

    uj5u.com 2020-09-10 03:51:52 more
  • HTTP request smuggling CL.TE

    CL.TE 簡介 前端通過Content-Length處理請求,通過反向代理或者負載均衡將請求轉發到后端,后端Transfer-Encoding優先級較高,以TE處理請求造成安全問題。 檢測 發送如下資料包 POST / HTTP/1.1 Host: ac391f7e1e9af821806e890 ......

    uj5u.com 2020-09-10 03:52:11 more
  • 網路滲透資料大全單——漏洞庫篇

    網路滲透資料大全單——漏洞庫篇漏洞庫 NVD ——美國國家漏洞庫 →http://nvd.nist.gov/。 CERT ——美國國家應急回應中心 →https://www.us-cert.gov/ OSVDB ——開源漏洞庫 →http://osvdb.org Bugtraq ——賽門鐵克 →ht ......

    uj5u.com 2020-09-10 03:52:15 more
  • 京準講述NTP時鐘服務器應用及原理

    京準講述NTP時鐘服務器應用及原理京準講述NTP時鐘服務器應用及原理 安徽京準電子科技官微——ahjzsz 北斗授時原理 授時是指接識訓通過某種方式獲得本地時間與北斗標準時間的鐘差,然后調整本地時鐘使時差控制在一定的精度范圍內。 衛星導航系統通常由三部分組成:導航授時衛星、地面檢測校正維護系統和用戶 ......

    uj5u.com 2020-09-10 03:52:25 more
  • 利用北斗衛星系統設計NTP網路時間服務器

    利用北斗衛星系統設計NTP網路時間服務器 利用北斗衛星系統設計NTP網路時間服務器 安徽京準電子科技官微——ahjzsz 概述 NTP網路時間服務器是一款支持NTP和SNTP網路時間同步協議,高精度、大容量、高品質的高科技時鐘產品。 NTP網路時間服務器設備采用冗余架構設計,高精度時鐘直接來源于北斗 ......

    uj5u.com 2020-09-10 03:52:35 more
  • 詳細解讀電力系統各種對時方式

    詳細解讀電力系統各種對時方式 詳細解讀電力系統各種對時方式 安徽京準電子科技官微——ahjzsz,更多資料請添加VX 衛星同步時鐘是我京準公司開發研制的應用衛星授時時技術的標準時間顯示和發送的裝置,該裝置以M國全球定位系統(GLOBAL POSITIONING SYSTEM,縮寫為GPS)或者我國北 ......

    uj5u.com 2020-09-10 03:52:45 more
  • 如何保證外包團隊接入企業內網安全

    不管企業規模的大小,只要企業想省錢,那么企業的某些服務就一定會采用外包的形式,然而看似美好又經濟的策略,其實也有不好的一面。下面我通過安全的角度來聊聊使用外包團的安全隱患問題。 先看看什么服務會使用外包的,最常見的就是話務/客服這種需要大量重復性、無技術性的服務,或者是一些銷售外包、特殊的職能外包等 ......

    uj5u.com 2020-09-10 03:52:57 more
  • PHP漏洞之【整型數字型SQL注入】

    0x01 什么是SQL注入 SQL是一種注入攻擊,通過前端帶入后端資料庫進行惡意的SQL陳述句查詢。 0x02 SQL整型注入原理 SQL注入一般發生在動態網站URL地址里,當然也會發生在其它地發,如登錄框等等也會存在注入,只要是和資料庫打交道的地方都有可能存在。 如這里http://192.168. ......

    uj5u.com 2020-09-10 03:55:40 more
  • [GXYCTF2019]禁止套娃

    git泄露獲取原始碼 使用GET傳參,引數為exp 經過三層過濾執行 第一層過濾偽協議,第二層過濾帶引數的函式,第三層過濾一些函式 preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'] (?R)參考當前正則運算式,相當于匹配函式里的引數 因此傳遞 ......

    uj5u.com 2020-09-10 03:56:07 more
  • 等保2.0實施流程

    流程 結論 ......

    uj5u.com 2020-09-10 03:56:16 more
最新发布
  • 深入決議React DnD拖拽原理,輕松掌握拖放技巧!

    >我們是[袋鼠云數堆疊 UED 團隊](http://ued.dtstack.cn/),致力于打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社區積累并傳播經驗價值。。 >本文作者:霽明 # 一、背景 ## 1、業務背景 業務中會有一些需要實作拖拽的場景,尤其是偏視覺方向以及移動端 ......

    uj5u.com 2023-06-10 08:37:09 more
  • 前端常見的字符編碼方案

    在前端開發中,最常見的字符編碼方案是 UTF-8。UTF-8是一種可變長度的 Unicode 編碼方案,可以表示幾乎所有的字符,并且與 ASCII 兼容。由于互聯網的廣泛應用和多語言的支持,UTF-8成為了前端開發中的首選字符編碼方案。 使用UTF-8編碼的好處: 1. 多語言支持 :UTF-8可以 ......

    uj5u.com 2023-06-10 08:36:58 more
  • Map

    ## 簡介 Map -- JS內置物件 Map物件保存鍵值對 -- 能夠記住鍵的原始插入順序 -- 任何值(資料型別包括物件)都可以作為一個鍵或一個值 Map物件是鍵值對的集合 -- Map中的一個鍵只能出現一次 Map物件按鍵值對迭代 -- for...of回圈在每次迭代后會回傳一個形式為[key ......

    uj5u.com 2023-06-10 08:35:47 more
  • JavaScript語法學習

    # JS語法學習 **Javascript:客戶端的腳本語言** ## **1. JavaScript資料型別** ![](https://img2023.cnblogs.com/blog/3008601/202306/3008601-20230607170622855-1334758269.png ......

    uj5u.com 2023-06-10 08:35:25 more
  • 前端常見的字符編碼方案

    在前端開發中,最常見的字符編碼方案是 UTF-8。UTF-8是一種可變長度的 Unicode 編碼方案,可以表示幾乎所有的字符,并且與 ASCII 兼容。由于互聯網的廣泛應用和多語言的支持,UTF-8成為了前端開發中的首選字符編碼方案。 使用UTF-8編碼的好處: 1. 多語言支持 :UTF-8可以 ......

    uj5u.com 2023-06-10 08:35:20 more
  • 深入決議React DnD拖拽原理,輕松掌握拖放技巧!

    >我們是[袋鼠云數堆疊 UED 團隊](http://ued.dtstack.cn/),致力于打造優秀的一站式資料中臺產品。我們始終保持工匠精神,探索前端道路,為社區積累并傳播經驗價值。。 >本文作者:霽明 # 一、背景 ## 1、業務背景 業務中會有一些需要實作拖拽的場景,尤其是偏視覺方向以及移動端 ......

    uj5u.com 2023-06-10 08:34:49 more
  • Map

    ## 簡介 Map -- JS內置物件 Map物件保存鍵值對 -- 能夠記住鍵的原始插入順序 -- 任何值(資料型別包括物件)都可以作為一個鍵或一個值 Map物件是鍵值對的集合 -- Map中的一個鍵只能出現一次 Map物件按鍵值對迭代 -- for...of回圈在每次迭代后會回傳一個形式為[key ......

    uj5u.com 2023-06-09 08:39:29 more
  • Electron-ChatGPT桌面端ChatGPT實體|electron25+vue3聊天AI模板E

    基于electron25+vite4+vue3仿制chatgpt客戶端聊天模板ElectronChatGPT。 electron-chatgpt 使用最新桌面端技術Electron25.x結合Vite4.x全家桶技術開發跨端模仿ChatGPT智能聊天程式模板。支持經典+分欄兩種布局、暗黑+明亮主題模 ......

    uj5u.com 2023-06-09 08:11:31 more
  • 記一次BootCDN被黑產掛馬導致站點跳轉博彩網站的問題

    近期發現公司某些站點出現偶爾跳轉博彩網站的現象,經過排查發現該現象為供應鏈投毒攻擊,BootCDN上的靜態資源無一例外均被污染, 當外站引入BootCDN的靜態資源時,如果請求攜帶的Referer頭為指定值(涉及公司隱私不便透露),User-Agent頭為手機瀏覽器UA,觸發惡意代碼注入。 例如:h ......

    uj5u.com 2023-06-09 08:05:55 more
  • VUE | Element組件庫的 el-collapse 標簽的用法

    Collapse 折疊面板:通過折疊面板收納內容區域。 1. 基礎用法 可以折疊展開多個面板,面板之間互不影響。 示例代碼 <el-collapse v-model="activeNames" @change="handleChange"> <el-collapse-item title="一致性 ......

    uj5u.com 2023-06-09 08:05:38 more