我们是袋鼠云数栈 UED 团队,致力于打造优秀的一站式数据中台产品。我们始终保持工匠精神,探索前端道路,为社区积累并传播经验价值。。

本文作者:霁明

一、背景

1、业务背景

业务中会有一些需要实现拖拽的场景,尤其是偏视觉方向以及移动端较多。拖拽在一定程度上能让交互更加便捷,能大大提升用户体验。以业务中心子产品配置功能为例,产品模块通过拖拽来调整顺序,的确会更加方便一些。

2、React DnD 介绍

引用官网介绍:

React DnD 是一组 React 实用程序,可帮助您构建复杂的拖放界面,同时保持组件分离。 它非常适合 Trello 和 Storify 等应用程序,在应用程序的不同部分之间拖动可以传输数据,组件会根据拖放事件更改其外观和应用程序状态。

React-DnD 特点:

  • 使用包裹及注入的方式使组件实现拖拽
  • 可用于构建复杂的拖放界面,同时保持组件分离
  • 采用单向数据流
  • 抹平了不同浏览器平台的差异
  • 可扩展可测试
  • 支持触屏操作

二、使用方式

1、安装

安装 react-dnd, react-dnd-html5-backend

  1. npm install react-dnd react-dnd-html5-backend

2、DndProvider

将需要拖拽的组件使用DndProvider进行包裹

  1. import { DndProvider } from 'react-dnd';
  2. import { HTML5Backend } from 'react-dnd-html5-backend';
  3. import Container from '../components/container';
  4. export default function App() {
  5. return (
  6. <DndProvider backend={HTML5Backend}>
  7. <Container />
  8. </DndProvider>
  9. );
  10. }

看下Container组件,主要是管理数据,并渲染Card列表

  1. function Container() {
  2. // ...
  3. return (
  4. <div style={{ width: 400 }}>
  5. {cards.map((card, index) => (
  6. <Card
  7. key={card.id}
  8. index={index}
  9. id={card.id}
  10. text={card.text}
  11. moveCard={moveCard}
  12. />
  13. ))}
  14. </div>
  15. );
  16. }

3、useDrag和useDrop

接下来看下Card组件,

  1. import { useRef } from 'react';
  2. import { useDrag, useDrop } from 'react-dnd';
  3. import styles from '../styles/home.module.css';
  4. function Card({ id, text, index, moveCard }: ICardProps) {
  5. const ref = useRef<HTMLDivElement>(null);
  6. const [{ handlerId }, drop] = useDrop({
  7. accept: CARD,
  8. collect(monitor) {
  9. return {
  10. handlerId: monitor.getHandlerId(),
  11. };
  12. },
  13. hover(item: IDragItem, monitor) {
  14. if (!ref.current) {
  15. return;
  16. }
  17. const dragIndex = item.index;
  18. const hoverIndex = index;
  19. // ...
  20. // 更新元素的位置
  21. moveCard(dragIndex, hoverIndex);
  22. // ...
  23. },
  24. });
  25. const [{ isDragging }, drag] = useDrag({
  26. type: CARD,
  27. item: { id, index },
  28. collect: (monitor: any) => ({
  29. isDragging: monitor.isDragging(),
  30. }),
  31. });
  32. drag(drop(ref));
  33. const opacity = isDragging ? 0 : 1;
  34. return (
  35. <div
  36. ref={ref}
  37. className={styles.card}
  38. style={{ opacity }}
  39. data-handler-id={handlerId}
  40. >
  41. {text}
  42. </div>
  43. );
  44. }

至此一个简单的拖拽排序列表就实现了,实现的效果类似于React DnD官网的这个示例:https://react-dnd.github.io/react-dnd/examples/sortable/simple,接下来我们来看看实现原理。

三、原理解析

1、总体架构

主要代码代码目录结构

核心代码主要分三个部分:

  • dnd-core:核心逻辑,定义了拖拽接口、管理方式、数据流向
  • backend:抽象出来的后端概念,主要处理DOM事件
  • react-dnd:封装React组件,提供api,相当于接入层

核心实现原理:

dnd-core向backend提供数据的更新方法,backend在拖拽时更新dnd-core中的数据,dnd-core通过react-dnd更新业务组件。

2、DndProvider

先看一下源码

  1. /**
  2. * A React component that provides the React-DnD context
  3. */
  4. export const DndProvider: FC<DndProviderProps<unknown, unknown>> = memo(
  5. function DndProvider({ children, ...props }) {
  6. const [manager, isGlobalInstance] = getDndContextValue(props) // memoized from props
  7. // ...
  8. return <DndContext.Provider value={manager}>{children}</DndContext.Provider>
  9. },
  10. )

从以上代码可以看出,生成了一个manager,并将其放到DndContext.Provider中。先看下DndContext的代码:

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

就是使用 React 的createContext创建的上下文容器组件。

接下来看下这个manager,主要是用来控制拖拽行为,通过Provider让子节点也可以访问。我们看下创建manager的getDndContextValue方法:

  1. import type { BackendFactory, DragDropManager } from 'dnd-core'
  2. import { createDragDropManager } from 'dnd-core'
  3. // ...
  4. function getDndContextValue(props: DndProviderProps<unknown, unknown>) {
  5. if ('manager' in props) {
  6. const manager = { dragDropManager: props.manager }
  7. return [manager, false]
  8. }
  9. const manager = createSingletonDndContext(
  10. props.backend,
  11. props.context,
  12. props.options,
  13. props.debugMode,
  14. )
  15. const isGlobalInstance = !props.context
  16. return [manager, isGlobalInstance]
  17. }
  18. function createSingletonDndContext<BackendContext, BackendOptions>(
  19. backend: BackendFactory,
  20. context: BackendContext = getGlobalContext(),
  21. options: BackendOptions,
  22. debugMode?: boolean,
  23. ) {
  24. const ctx = context as any
  25. if (!ctx[INSTANCE_SYM]) {
  26. ctx[INSTANCE_SYM] = {
  27. dragDropManager: createDragDropManager(
  28. backend,
  29. context,
  30. options,
  31. debugMode,
  32. ),
  33. }
  34. }
  35. return ctx[INSTANCE_SYM]
  36. }

从以上代码可以看出,getDndContextValue方法又调用了createSingletonDndContext方法,并传入了backend、context、options、debugMode这几个属性,然后通过dnd-core中的createDragDropManager来创建manager。

3、DragDropManager

看下createDragDropManager.js中的主要代码

  1. import type { Store } from 'redux'
  2. import { createStore } from 'redux'
  3. // ...
  4. import { reduce } from './reducers/index.js'
  5. export function createDragDropManager(
  6. backendFactory: BackendFactory,
  7. globalContext: unknown = undefined,
  8. backendOptions: unknown = {},
  9. debugMode = false,
  10. ): DragDropManager {
  11. const store = makeStoreInstance(debugMode)
  12. const monitor = new DragDropMonitorImpl(store, new HandlerRegistryImpl(store))
  13. const manager = new DragDropManagerImpl(store, monitor)
  14. const backend = backendFactory(manager, globalContext, backendOptions)
  15. manager.receiveBackend(backend)
  16. return manager
  17. }
  18. function makeStoreInstance(debugMode: boolean): Store<State> {
  19. // ...
  20. return createStore(
  21. reduce,
  22. debugMode &&
  23. reduxDevTools &&
  24. reduxDevTools({
  25. name: 'dnd-core',
  26. instanceId: 'dnd-core',
  27. }),
  28. )
  29. }

可以看到使用了redux的createStore创建了store,并创建了monitor和manager实例,通过backendFactory创建backend后端实例并安装到manager总实例。

看一下DragDropManagerImpl的主要代码

  1. export class DragDropManagerImpl implements DragDropManager {
  2. private store: Store<State>
  3. private monitor: DragDropMonitor
  4. private backend: Backend | undefined
  5. private isSetUp = false
  6. public constructor(store: Store<State>, monitor: DragDropMonitor) {
  7. this.store = store
  8. this.monitor = monitor
  9. store.subscribe(this.handleRefCountChange)
  10. }
  11. // ...
  12. public getActions(): DragDropActions {
  13. /* eslint-disable-next-line @typescript-eslint/no-this-alias */
  14. const manager = this
  15. const { dispatch } = this.store
  16. function bindActionCreator(actionCreator: ActionCreator<any>) {
  17. return (...args: any[]) => {
  18. const action = actionCreator.apply(manager, args as any)
  19. if (typeof action !== 'undefined') {
  20. dispatch(action)
  21. }
  22. }
  23. }
  24. const actions = createDragDropActions(this)
  25. return Object.keys(actions).reduce(
  26. (boundActions: DragDropActions, key: string) => {
  27. const action: ActionCreator<any> = (actions as any)[
  28. key
  29. ] as ActionCreator<any>
  30. ;(boundActions as any)[key] = bindActionCreator(action)
  31. return boundActions
  32. },
  33. {} as DragDropActions,
  34. )
  35. }
  36. public dispatch(action: Action<any>): void {
  37. this.store.dispatch(action)
  38. }
  39. private handleRefCountChange = (): void => {
  40. const shouldSetUp = this.store.getState().refCount > 0
  41. if (this.backend) {
  42. if (shouldSetUp && !this.isSetUp) {
  43. this.backend.setup()
  44. this.isSetUp = true
  45. } else if (!shouldSetUp && this.isSetUp) {
  46. this.backend.teardown()
  47. this.isSetUp = false
  48. }
  49. }
  50. }
  51. }

先说一下这个handleRefCountChange方法,在构造函数里通过store进行订阅,在第一次使用useDrop或useDrag时会执行setup方法初始化backend,在拖拽源和放置源都被卸载时则会执行teardown销毁backend。

接下来看一下createDragDropActions方法

  1. export function createDragDropActions(
  2. manager: DragDropManager,
  3. ): DragDropActions {
  4. return {
  5. beginDrag: createBeginDrag(manager),
  6. publishDragSource: createPublishDragSource(manager),
  7. hover: createHover(manager),
  8. drop: createDrop(manager),
  9. endDrag: createEndDrag(manager),
  10. }
  11. }

可以看到绑定一些action:

  • beginDrag(开始拖动)
  • publishDragSource(发布当前拖动源)
  • hover(是否经过)
  • drop(落下动作)
  • endDrag(拖拽结束)

manager包含了之前生成的 monitor、store、backend,manager 创建完成,表示此时我们有了一个 store 来管理拖拽中的数据,有了 monitor 来监听数据和控制行为,能通过 manager 进行注册,可以通过 backend 将 DOM 事件转换为 action。接下来便可以注册拖拽源和放置源了。

4、useDrag

  1. /**
  2. * useDragSource hook
  3. * @param sourceSpec The drag source specification (object or function, function preferred)
  4. * @param deps The memoization deps array to use when evaluating spec changes
  5. */
  6. export function useDrag<
  7. DragObject = unknown,
  8. DropResult = unknown,
  9. CollectedProps = unknown,
  10. >(
  11. specArg: FactoryOrInstance<
  12. DragSourceHookSpec<DragObject, DropResult, CollectedProps>
  13. >,
  14. deps?: unknown[],
  15. ): [CollectedProps, ConnectDragSource, ConnectDragPreview] {
  16. const spec = useOptionalFactory(specArg, deps)
  17. invariant(
  18. !(spec as any).begin,
  19. '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)',
  20. )
  21. const monitor = useDragSourceMonitor<DragObject, DropResult>()
  22. const connector = useDragSourceConnector(spec.options, spec.previewOptions)
  23. useRegisteredDragSource(spec, monitor, connector)
  24. return [
  25. useCollectedProps(spec.collect, monitor, connector),
  26. useConnectDragSource(connector),
  27. useConnectDragPreview(connector),
  28. ]
  29. }

可以看到useDrag方法返回了一个包含3个元素的数组,CollectedProps(collect方法返回的对象)、ConnectDragSource(拖拽源连接器)、ConnectDragPreview(拖拽源预览)。

monitor是从前面Provider中的manager中获取的,主要看下connector

  1. export function useDragSourceConnector(
  2. dragSourceOptions: DragSourceOptions | undefined,
  3. dragPreviewOptions: DragPreviewOptions | undefined,
  4. ): SourceConnector {
  5. const manager = useDragDropManager()
  6. const connector = useMemo(
  7. () => new SourceConnector(manager.getBackend()),
  8. [manager],
  9. )
  10. // ...
  11. return connector
  12. }

可以看到connector获取了manager.getBackend后端的数据。

useRegisteredDragSource方法会对拖动源进行注册,会保存拖动源实例,并记录注册的数量。

5、useDrop

看下useDrop源码

  1. /**
  2. * useDropTarget Hook
  3. * @param spec The drop target specification (object or function, function preferred)
  4. * @param deps The memoization deps array to use when evaluating spec changes
  5. */
  6. export function useDrop<
  7. DragObject = unknown,
  8. DropResult = unknown,
  9. CollectedProps = unknown,
  10. >(
  11. specArg: FactoryOrInstance<
  12. DropTargetHookSpec<DragObject, DropResult, CollectedProps>
  13. >,
  14. deps?: unknown[],
  15. ): [CollectedProps, ConnectDropTarget] {
  16. const spec = useOptionalFactory(specArg, deps)
  17. const monitor = useDropTargetMonitor<DragObject, DropResult>()
  18. const connector = useDropTargetConnector(spec.options)
  19. useRegisteredDropTarget(spec, monitor, connector)
  20. return [
  21. useCollectedProps(spec.collect, monitor, connector),
  22. useConnectDropTarget(connector),
  23. ]
  24. }

useDrop返回了一个包含2个元素的数组,CollectedProps(collect方法返回的对象), ConnectDropTarget(放置源连接器),monitor和connector的获取都和useDrag类似。

6、HTML5Backend

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

一个简单拖拽操作过程,会依次触发拖拽事件: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,看一下其代码实现:

  1. export const HTML5Backend: BackendFactory = function createBackend(
  2. manager: DragDropManager,
  3. context?: HTML5BackendContext,
  4. options?: HTML5BackendOptions,
  5. ): HTML5BackendImpl {
  6. return new HTML5BackendImpl(manager, context, options)
  7. }

可以看到其实是个返回HTML5BackendImpl实例的函数,在创建manager实例时会执行createBackend方法创建真正的backend。

如下是 Backend 需要被实现的方法:

  1. export interface Backend {
  2. setup(): void
  3. teardown(): void
  4. connectDragSource(sourceId: any, node?: any, options?: any): Unsubscribe
  5. connectDragPreview(sourceId: any, node?: any, options?: any): Unsubscribe
  6. connectDropTarget(targetId: any, node?: any, options?: any): Unsubscribe
  7. profile(): Record<string, number>
  8. }

setup 是 backend 的初始化方法,teardown 是 backend 销毁方法。connectDragSource方法将元素转换为可拖拽元素,并添加监听事件。connectDropTarget方法会给元素添加监听事件,connectDragPreview方法会将preview元素保存以供监听函数使用,profile方法用于返回一些简要的统计信息。

以上这几个方法都在HTML5BackendImpl中,我们先看一下setup方法:

  1. public setup(): void {
  2. const root = this.rootElement as RootNode | undefined
  3. if (root === undefined) {
  4. return
  5. }
  6. if (root.__isReactDndBackendSetUp) {
  7. throw new Error('Cannot have two HTML5 backends at the same time.')
  8. }
  9. root.__isReactDndBackendSetUp = true
  10. this.addEventListeners(root)
  11. }

root默认是windows,通过addEventListeners方法把监听事件都绑定到windows上,这提高了性能也降低了事件销毁的难度。

看下addEventListeners方法:

  1. private addEventListeners(target: Node) {
  2. if (!target.addEventListener) {
  3. return
  4. }
  5. target.addEventListener(
  6. 'dragstart',
  7. this.handleTopDragStart as EventListener,
  8. )
  9. target.addEventListener('dragstart', this.handleTopDragStartCapture, true)
  10. target.addEventListener('dragend', this.handleTopDragEndCapture, true)
  11. target.addEventListener(
  12. 'dragenter',
  13. this.handleTopDragEnter as EventListener,
  14. )
  15. target.addEventListener(
  16. 'dragenter',
  17. this.handleTopDragEnterCapture as EventListener,
  18. true,
  19. )
  20. target.addEventListener(
  21. 'dragleave',
  22. this.handleTopDragLeaveCapture as EventListener,
  23. true,
  24. )
  25. target.addEventListener('dragover', this.handleTopDragOver as EventListener)
  26. target.addEventListener(
  27. 'dragover',
  28. this.handleTopDragOverCapture as EventListener,
  29. true,
  30. )
  31. target.addEventListener('drop', this.handleTopDrop as EventListener)
  32. target.addEventListener(
  33. 'drop',
  34. this.handleTopDropCapture as EventListener,
  35. true,
  36. )
  37. }

以上代码中监听了一些拖拽事件,这些监听函数会获得拖拽事件的对象、拿到相应的参数,并执行相应的action方法。HTML5Backend 通过 manager 拿到一个 DragDropActions 的实例,执行其中的方法。DragDropActions 本质就是根据参数将其封装为一个 action,最终通过 redux 的 dispatch 将 action 分发,改变 store 中的数据。

  1. export interface DragDropActions {
  2. beginDrag(
  3. sourceIds?: Identifier[],
  4. options?: any,
  5. ): Action<BeginDragPayload> | undefined
  6. publishDragSource(): SentinelAction | undefined
  7. hover(targetIds: Identifier[], options?: any): Action<HoverPayload>
  8. drop(options?: any): void
  9. endDrag(): SentinelAction
  10. }

最后我们再看下connectDragSource方法:

  1. public connectDragSource(
  2. sourceId: string,
  3. node: Element,
  4. options: any,
  5. ): Unsubscribe {
  6. // ...
  7. node.setAttribute('draggable', 'true')
  8. node.addEventListener('dragstart', handleDragStart)
  9. node.addEventListener('selectstart', handleSelectStart)
  10. return (): void => {
  11. // ...
  12. node.removeEventListener('dragstart', handleDragStart)
  13. node.removeEventListener('selectstart', handleSelectStart)
  14. node.setAttribute('draggable', 'false')
  15. }
  16. }

可以看到主要是把节点的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中对事件的定义:

  1. const eventNames: Record<ListenerType, EventName> = {
  2. [ListenerType.mouse]: {
  3. start: 'mousedown',
  4. move: 'mousemove',
  5. end: 'mouseup',
  6. contextmenu: 'contextmenu',
  7. },
  8. [ListenerType.touch]: {
  9. start: 'touchstart',
  10. move: 'touchmove',
  11. end: 'touchend',
  12. },
  13. [ListenerType.keyboard]: {
  14. keydown: 'keydown',
  15. },
  16. }

8、主要拖拽过程

四、总结

React-DnD 采用了分层设计,react-dnd充当接入层,dnd-core实现拖拽接口、定义拖拽行为、管理数据流向,backend将DOM事件通过redux action转换为数据。

使用可插入的方式引入backend,使拖拽的实现可扩展且更加灵活。

使用了单向数据流,在拖拽时不用处理中间状态,不用额外对DOM事件进行处理,只需专注于数据的变化。

React-DnD对backend的实现方式、数据的管理方式,以及整体的设计都值得借鉴。

五、参考链接:


最后

欢迎关注【袋鼠云数栈UED团队】~

袋鼠云数栈UED团队持续为广大开发者分享技术成果,相继参与开源了欢迎star

深入解析React DnD拖拽原理,轻松掌握拖放技巧!的更多相关文章

  1. js拖拽原理和碰撞原理

    拖拽的原理onmousedown 选择元素onmousemove 移动元素onmouseup 释放元素 1:如果拖拽的时候有文字:被选中,会产生问题原因:当鼠标按下的时如果页面中有文字或者图片被选中的 ...

  2. React 实现拖拽功能

    实现效果:(可戳 https://codepen.io/wenr/pen/EGEQxp 查看) 因为工作中会用到 JIRA 所以想实现一下相似的功能,顺便学习一下 H5 的拖拽.不支持拖拽改变顺序,感 ...

  3. JS鼠标的拖拽原理

    拖拽功能主要是用在让用户做一些自定义的动作,比如拖动排序,弹出框拖动移动等等,效果还是蛮不错的.下面讲解一下拖拽的原理,希望可以帮助到有需要的朋友! 一.拖拽的流程动作①鼠标按下②鼠标移动③鼠标松开 ...

  4. react 可拖拽改变位置和大小的弹窗

    一 目标 最近,项目上需要一个可以弹出一个可以移动位置和改变大小的窗口,来显示一下对当前页面的一个辅助内容 二 思路 1.之前写过一个antd modal的可移动弹窗但是毕竟不如自己写的更定制化,比如 ...

  5. Jquery拖拽原理

    /* onmousedown : 选择元素 onmousemove : 移动元素 onmouseup : 释放元素 */ 查看Demo:拖拽图片 function drag(obj) { obj.on ...

  6. JS拖拽原理

    实现拖拽效果主要跟鼠标的三个事件有关: onmousedown : 选择要拖拽的元素 onmousemove : 移动元素 onmouseup : 释放元素 三个事件的关系: obj.onmoused ...

  7. Android4.0 Launcher拖拽原理分析

    在Android4.0源码自带的Launcher中,拖拽是由DragController进行控制的. 1) 先来看看类之间的继承关系 2)再来看看Launcher拖拽流程的时序图   1.基本流程: ...

  8. javascript拖拽原理与简单实现方法[demo]

    美国人有一句常用的俗语—“Re-inventing the Wheel”,从字面上来解释就是“重新发明轮子”.可是轮子早已问世,再要去发明岂非劳而无功? 产品经理发下需求,实施者再到网上搜索代码,也许 ...

  9. js拖拽原理及简单实现(渣渣自学)

    第一步 首先简单分析下需求吧,我们就是想实现鼠标拖拽带颜色的方块时,让方块停留在鼠标松开的位置,需要计算的就是拖拽前的坐标和拖拽后的坐标,鼠标移动后相对于原位置的偏移量=目标元素的偏移量,根据这个等式 ...

  10. React.js实现原生js拖拽效果及思考

    一.起因&思路 不知不觉,已经好几天没写博客了...近来除了研究React,还做了公司官网... 一直想写一个原生js拖拽效果,又加上近来学react学得比较嗨.所以就用react来实现这个拖 ...

随机推荐

  1. 汽车制造工艺 2.5D 可视化组态监控 | 图扑软件

    前言 随着世界经济的不断发展,汽车作为一个如今随处可见的物体,从大体上概括是由四大部分组成:发动机.底盘.车身.电气系统.看似简单的几个名词组件,其内部却是由无数的细小零件构成,一辆汽车更是由上万个微 ...

  2. 六位一体Serverless化应用,帮你摆脱服务器的烦恼

    ​ 随着互联网技术的飞速发展,越来越多的应用横空出世,是以不可避免带来了大量的服务器需求.大部分的开发者都选择购买或者租用服务器,然而这样也带来了诸多的烦恼. 1.硬件成本高昂 购买服务器费用昂贵,除 ...

  3. Exchangis搭建安装

    项目简介 Exchangis是一个轻量级的.高扩展性的数据交换平台,支持对结构化及无结构化的异构数据源之间的数据传输,在应用层上具有数据权限管控.节点服务高可用和多租户资源隔离等业务特性,而在数据层上 ...

  4. jinjia2基本用法

    前言这几年一直在it行业里摸爬滚打,一路走来,不少总结了一些python行业里的高频面试,看到大部分初入行的新鲜血液,还在为各样的面试题答案或收录有各种困难问题 于是乎,我自己开发了一款面试宝典,希望 ...

  5. python入门教程之二十邮件操作

    SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式. python的smtplib提供了一 ...

  6. 1.springsecurity基于内存和数据库的认证

    1.总结: 昨天主要是使用security实现了基于内存的认证和基于数据库的认证(实际项目中使用): 在security的项目中,必须配置WebSecurityConfigurerAdaptor的实现 ...

  7. 【Visual Leak Detector】库的 22 个 API 使用说明

    说明 使用 VLD 内存泄漏检测工具辅助开发时整理的学习笔记.本篇主要介绍 VLD 库提供的 22 个外部接口.同系列文章目录可见 <内存泄漏检测工具>目录 目录 说明 1. 头文件简介 ...

  8. Ffmpeg 视频压缩的几个关键参数

    Ffmpeg的视频操作官网文档:https://ffmpeg.org/ffmpeg-filters.html#Video-Filters 视频压缩用到的参数主要为以下几个: 文件路径:-i 输入文件的 ...

  9. [C++基础入门] 8、结构体

    文章目录 8 结构体 8.1 结构体基本概念 8.2 结构体定义和使用 8.3 结构体数组 8.4 结构体指针 8.5 结构体嵌套结构体 8.6 结构体做函数参数 8.7 结构体中 const使用场景 ...

  10. 深度学习04-(Tensorflow简介、图与会话、张量基本操作、Tensorboard可视化、综合案例:线性回归)

    深度学习04-Tensorflow 深度学习04-(Tensorflow) Tensorflow概述 Tensorflow简介 什么是Tensorflow Tensorflow的特点 Tensorfl ...