React DnD如何處理拖拽詳解
正文
React DnD 是一個(gè)專注于數(shù)據(jù)變更的 React 拖拽庫(kù),通俗的將,你拖拽改變的不是頁(yè)面視圖,而是數(shù)據(jù)。React DnD 不提供炫酷的拖動(dòng)體驗(yàn),而是通過(guò)幫助我們管理拖拽中的數(shù)據(jù)變化,再由我們根據(jù)這些數(shù)據(jù)進(jìn)行渲染。我們可能需要寫額外的視圖層來(lái)完成想要的效果,但是這種拖拽管理方式非常的通用,可以在任何場(chǎng)景下使用。初次使用可能感覺并不是那么方便,但是如果場(chǎng)景比較復(fù)雜,或者是需要高度定制,React DnD 一定是首選。
React DnD 的使用說(shuō)明可以參見官方文檔。本文分析 React DnD 的源碼,更深層次的了解這個(gè)庫(kù)。以下的代碼來(lái)源于 react-dnd 14.0.4。
代碼結(jié)構(gòu)
React-DnD 是單個(gè)代碼倉(cāng)庫(kù),但是打了多個(gè)包。這種方式也表示了 React DnD 的三層結(jié)構(gòu)。
___________ ___________ _______________ | | | | | | | | | | | backend-html | | react-dnd | | dnd-core | | | | | | | | backend-touch | |___________| |___________| |_______________|
react-dnd 是 React 版本的 Drag and Drop 的實(shí)現(xiàn)。它定義了 DragSource, DropTarget, DragDropContext 等高階組件,以及 useDrag,useDrop 等 hook。我們可以簡(jiǎn)單的理解為這是一個(gè)接入層。
dnd-core 是整個(gè)拖拽庫(kù)的核心,它實(shí)現(xiàn)了一個(gè)和框架無(wú)關(guān)的拖放管理器,定義了拖放的交互,根據(jù) dnd-core 中定義的規(guī)則,我們完全可以根據(jù)它自己實(shí)現(xiàn)一個(gè) vue-dnd。dnd-core 中使用 redux 做狀態(tài)管理。
backend 是 React DnD 抽象了后端的概念,這里是 DOM 事件轉(zhuǎn)換為 redux action 的地方。如果是 H5 應(yīng)用,backend-html,如果是移動(dòng)端,使用 backend-touch。也支持用戶自定義。
DndProvider
如果想要使用 React DnD,首先需要在外層元素上加一個(gè) DndProvider。
import { HTML5Backend } from 'react-dnd-html5-backend'; import { DndProvider } from 'react-dnd'; <DndProvider backend={HTML5Backend}> <TutorialApp /> </DndProvider>
DndProvider 的本質(zhì)是一個(gè)由 React.createContext 創(chuàng)建一個(gè)上下文的容器(組件),用于控制拖拽的行為,數(shù)據(jù)的共享。DndProvider 的入?yún)⑹且粋€(gè) Backend。Backend 是什么呢?React DnD 將 DOM 事件相關(guān)的代碼獨(dú)立出來(lái),將拖拽事件轉(zhuǎn)換為 React DnD 內(nèi)部的 redux action。由于拖拽發(fā)生在 H5 的時(shí)候是 ondrag,發(fā)生在移動(dòng)設(shè)備的時(shí)候是由 touch 模擬,React DnD 將這部分單獨(dú)抽出來(lái),方便后續(xù)的擴(kuò)展,這部分就叫做 Backend。它是 DnD 在 Dom 層的實(shí)現(xiàn)。
以下是 DndProvider 的核心代碼,通過(guò)入?yún)⑸梢粋€(gè) manager,這個(gè) manager 用于控制拖拽行為。這個(gè) manager 放到 Provider 中,子節(jié)點(diǎn)都可以訪問這個(gè) manager。
export const DndProvider: FC<DndProviderProps<unknown, unknown>> = memo( function DndProvider({ children, ...props }) { const [manager, isGlobalInstance] = getDndContextValue(props) ... return <DndContext.Provider value={manager}>{children}</DndContext.Provider> }, )
DragDropManager
DndProvider 將 DndProvider 放到了 context 中,這個(gè) manager 非常關(guān)鍵,后續(xù)的拖動(dòng)都依賴于 manager,如下是它的創(chuàng)建過(guò)程。
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 }
首先看下 store 的創(chuàng)建過(guò)程,manager 中 store 的創(chuàng)建使用了 redux 的 createStore 方法,store 是用來(lái)以存放應(yīng)用中所有的 state 的。它的第一個(gè)參數(shù) reducer 接收兩個(gè)參數(shù),分別是當(dāng)前的 state 樹和要處理的 action,返回新的 state 樹。
function makeStoreInstance(): Store<State> { return createStore(reduce) }
manager 中的 store 管理著如下 state,每個(gè) state 都有對(duì)應(yīng)的方法進(jìn)行更新。
export interface State { dirtyHandlerIds: DirtyHandlerIdsState dragOffset: DragOffsetState refCount: RefCountState dragOperation: DragOperationState stateId: StateIdState }
標(biāo)準(zhǔn)的 redux 更新數(shù)據(jù)的方法是 dispatch action 的方式。如下是 dragOffset 更新方法,判斷當(dāng)前 action 的類型,從 payload 中獲得需要的參數(shù),然后返回新的 state。
export function reduce( state: State = initialState, action: Action<{ sourceClientOffset: XYCoord clientOffset: XYCoord }>, ): State { const { payload } = action switch (action.type) { case INIT_COORDS: case BEGIN_DRAG: return { initialSourceClientOffset: payload.sourceClientOffset, initialClientOffset: payload.clientOffset, clientOffset: payload.clientOffset, } case HOVER: ... case END_DRAG: case DROP: return initialState default: return state } }
接下來(lái)看 monitor,已知 store 表示的是拖拽過(guò)程中的數(shù)據(jù),那么我們可以根據(jù)這些數(shù)據(jù)計(jì)算出當(dāng)前的一些狀態(tài),比如某個(gè)物體是否可以被拖動(dòng),某個(gè)物體是否正在懸空等等。monitor 提供了一些方法來(lái)訪問這些數(shù)據(jù),不僅如此,monitor 最大的作用是用來(lái)監(jiān)聽這些數(shù)據(jù)的,我們可以為 monitor 添加一些監(jiān)聽器,這樣在數(shù)據(jù)變動(dòng)之后就能及時(shí)響應(yīng)。
如下列出了一些 monitor 中的方法。
export interface DragDropMonitor { subscribeToStateChange( listener: Listener, options?: { handlerIds: Identifier[] | undefined }, ): Unsubscribe subscribeToOffsetChange(listener: Listener): Unsubscribe canDragSource(sourceId: Identifier | undefined): boolean canDropOnTarget(targetId: Identifier | undefined): boolean isDragging(): boolean isDraggingSource(sourceId: Identifier | undefined): boolean getItemType(): Identifier | null getItem(): any getSourceId(): Identifier | null getTargetIds(): Identifier[] getDropResult(): any didDrop(): boolean ... }
subscribeToStateChange 就是添加監(jiān)聽函數(shù)的方法,其原理是使用了 redux 的 subscribe 方法。
public subscribeToStateChange( listener: Listener, options: { handlerIds: string[] | undefined } = { handlerIds: undefined }, ): Unsubscribe { ... return this.store.subscribe(handleChange) }
要注意的是,DragDropMonitor 是一個(gè)全局的 monitor,它監(jiān)聽的范圍是 DndProvider 下所有可拖拽的元素,也就是 monitor 中會(huì)存在多個(gè)對(duì)象,這些拖拽對(duì)象有全局唯一性的 ID 標(biāo)識(shí)(從 0 自增的 ID)。這也是 monitor 中的發(fā)部分方法都需要傳一個(gè) Identifier 的原因。還有一點(diǎn)就是,最好不要存在多個(gè) DndProvider,除非你確定不同 DndProvider 下拖拽元素一定不會(huì)交互。
我們?cè)?DndProvider 傳入了一個(gè)參數(shù) backend,其實(shí)它是個(gè)工廠方法,執(zhí)行之后會(huì)生成真正的 backend。
manager 比較簡(jiǎn)單,它包含了之前生成的 monitor, store, backend,還在初始化的時(shí)候?yàn)?store 添加了一個(gè)監(jiān)聽器。它監(jiān)聽 state 中的 refCount 方法, refCount 表示當(dāng)前標(biāo)記為可拖拽的對(duì)象,如果 refCount 大于 0,初始化 backend,否則,銷毀 backend。
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) } ... 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 } } } }
manager 創(chuàng)建完成,表示此時(shí)我們有了一個(gè) store 來(lái)管理拖拽中的數(shù)據(jù),有了 monitor 來(lái)監(jiān)聽數(shù)據(jù)和控制行為,能通過(guò) manager 進(jìn)行注冊(cè),可以通過(guò) backend 將 Dom 事件轉(zhuǎn)換為 action。接下來(lái)就能使用 useDrag 來(lái)創(chuàng)建一個(gè)真正的可拖拽對(duì)象了。
useDrag
一個(gè)元素想要被拖拽,Hooks 的寫法如下,使用 useDrag 實(shí)現(xiàn)。useDrag 的入?yún)⒑头祷刂悼梢詤⒖?a rel="external nofollow" target="_blank">官方文檔,這里不加贅述。
import { DragPreviewImage, useDrag } from 'react-dnd'; export const Knight: FC = () => { const [{ isDragging }, drag, preview] = useDrag( () => ({ type: ItemTypes.KNIGHT, collect: (monitor) => ({ isDragging: !!monitor.isDragging() }) }), [] ); return ( <> <DragPreviewImage connect={preview} src={knightImage} /> <div ref={drag} > ? </div> </> ); };
在 使用 useDrag 的時(shí)候,我們配置了入?yún)ⅲ且粋€(gè)函數(shù),這個(gè)函數(shù)的返回值就是配置參數(shù),useOptionalFactory 就是使用 useMemo 將這個(gè)方法包了一層,避免重復(fù)調(diào)用。
export function useDrag<DragObject, DropResult, CollectedProps>( specArg: FactoryOrInstance< DragSourceHookSpec<DragObject, DropResult, CollectedProps> >, deps?: unknown[], ): [CollectedProps, ConnectDragSource, ConnectDragPreview] { // 獲得配置參數(shù) const spec = useOptionalFactory(specArg, deps) // 獲得 manager 中的 monitor 的包裝對(duì)象(DragSourceMonitor) const monitor = useDragSourceMonitor<DragObject, DropResult>() // 連接 dom 以及 redux const connector = useDragSourceConnector(spec.options, spec.previewOptions) // 生成唯一 id,封裝 DragSource 對(duì)象 useRegisteredDragSource(spec, monitor, connector) return [ useCollectedProps(spec.collect, monitor, connector), useConnectDragSource(connector), useConnectDragPreview(connector), ] }
原先在 manager 中的 monitor 類型是 DragDropMonitor,看名字就知道,該 monitor 中的方法是結(jié)合了 Drag 和 Drop 兩種行為的,目前只是使用 Drag,因此將 monitor 包裝一下,屏蔽 Drop 的行為。使其類型變?yōu)?DragSourceMonitor。 這就是 useDragSourceMonitor 做的事情,
export function useDragSourceMonitor<O, R>(): DragSourceMonitor<O, R> { const manager = useDragDropManager() return useMemo(() => new DragSourceMonitorImpl(manager), [manager]) }
以上,我們有 Backend 控制 Dom 層級(jí)的行為,Store 和 Monitor 控制數(shù)據(jù)層的變化,那如何讓 Monitor 知道現(xiàn)在要監(jiān)聽到底是哪個(gè)節(jié)點(diǎn),還需要將這兩者連接起來(lái),才能真正的讓 Dom 層和數(shù)據(jù)層保持一致,React DnD 中使用 connector 來(lái)連接著兩者。
useDragSourceConnector 方法中會(huì) new 一個(gè) SourceConnector 的實(shí)例,該實(shí)例會(huì)接受 backend 作為入?yún)ⅲ琒ourceConnector 實(shí)現(xiàn)了 Connector 接口。Connector 中成員變量不多,最重要就是 hooks 對(duì)象,該對(duì)象用于處理 ref 的邏輯。
export interface Connector { // 獲得 ref 指向的 Dom hooks: any // 獲得 dragSource connectTarget: any // dragSource 唯一 Id receiveHandlerId(handlerId: Identifier | null): void // 重新連接 dragSource 和 dom reconnect(): void }
我們?cè)诶又袑?ref 屬性給到了一個(gè) useDrag 的返回值。該返回值其實(shí)就是 hooks 中的 dragSource 方法。
export function useConnectDragSource(connector: SourceConnector) { return useMemo(() => connector.hooks.dragSource(), [connector]) }
從 dragSource 方法可以看出,connector 中將這個(gè) Dom 節(jié)點(diǎn)維護(hù)在了 dragSourceNode 屬性上。
export class SourceConnector implements Connector { // wrapConnectorHooks 判斷 ref 節(jié)點(diǎn)是否是合法的 ReactElement,是的話,執(zhí)行回調(diào)方法 public hooks = wrapConnectorHooks({ dragSource: ( node: Element | ReactElement | Ref<any>, options?: DragSourceOptions, ) => { // dragSourceRef 和 dragSourceNode 賦值 null this.clearDragSource() this.dragSourceOptions = options || null if (isRef(node)) { this.dragSourceRef = node as RefObject<any> } else { this.dragSourceNode = node } this.reconnectDragSource() }, ... }) ... }
獲得節(jié)點(diǎn)后,調(diào)用 this.reconnectDragSource(),該方法中,backend 調(diào)用 connectDragSource 方法為該節(jié)點(diǎn)添加事件監(jiān)聽,后續(xù)會(huì)分析 backend。
private reconnectDragSource() { const dragSource = this.dragSource ... if (didChange) { ... this.dragSourceUnsubscribe = this.backend.connectDragSource( this.handlerId, dragSource, this.dragSourceOptions, ) } }
現(xiàn)在還需要對(duì) Dom 進(jìn)行抽象,生成唯一ID, 封裝為 DragSource 注冊(cè)到 monitor 上。
export function useRegisteredDragSource<O, R, P>( spec: DragSourceHookSpec<O, R, P>, monitor: DragSourceMonitor<O, R>, connector: SourceConnector, ): void { const manager = useDragDropManager() // 生成 DragSource const handler = useDragSource(spec, monitor, connector) const itemType = useDragType(spec) // useLayoutEffect useIsomorphicLayoutEffect( function registerDragSource() { if (itemType != null) { // DragSource 注冊(cè)到 monitor const [handlerId, unregister] = registerSource( itemType, handler, manager, ) // 更新唯一 ID,觸發(fā) reconnect 邏輯 monitor.receiveHandlerId(handlerId) connector.receiveHandlerId(handlerId) return unregister } }, [manager, monitor, connector, handler, itemType], ) }
DragSource 實(shí)現(xiàn)以下幾個(gè)方法,這個(gè)幾個(gè)方法我們使用 useDarg 的時(shí)候可以配置同名函數(shù),這些配置的方法會(huì)被以下方法調(diào)用。
export interface DragSource { beginDrag(monitor: DragDropMonitor, targetId: Identifier): void endDrag(monitor: DragDropMonitor, targetId: Identifier): void canDrag(monitor: DragDropMonitor, targetId: Identifier): boolean isDragging(monitor: DragDropMonitor, targetId: Identifier): boolean }
總結(jié)下 useDarg 做的事情,首先就是支持一些配置參數(shù),這是最基礎(chǔ)的,然后獲得 Provider 中的 managre,對(duì)其中的一些對(duì)象進(jìn)行包裝,屏蔽一些方法,增加一些參數(shù)。最重要的就是創(chuàng)建 connector,在界面加載完畢后,connector 通過(guò) ref 的方式獲得 Dom 節(jié)點(diǎn)的實(shí)例,為該節(jié)點(diǎn)添加拖拽屬性和拖拽事件。同時(shí)根據(jù)配置參數(shù)和 connector 封裝 DragSource 對(duì)象,將其注冊(cè)到 monitor 中。
useDrop 和 useDrag 的流程大同小異,大家可以自己看。
HTML5Backend
之前為 DndProvider 注入的參數(shù) HTML5Backend,其實(shí)是個(gè)工程方法,我們?cè)?DndProvider 除了可以配置 backend 外,還可以配置 backend 的一些參數(shù),當(dāng)然,backend 的實(shí)現(xiàn)不同,傳參也不同。DragDropManager 會(huì)根據(jù)這些參數(shù)初始化真正的 backend。
export const HTML5Backend: BackendFactory = function createBackend( manager: DragDropManager, context?: HTML5BackendContext, options?: HTML5BackendOptions, ): HTML5BackendImpl { return new HTML5BackendImpl(manager, context, options) }
如下是 Backend 需要被實(shí)現(xiàn)的方法。
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 銷毀方法。上文提到過(guò),setup 和 teardown 是在 handleRefCountChange 中執(zhí)行的。React DnD 會(huì)在我們第一個(gè)使用 useDrag 或是 useDrop 的時(shí)候,執(zhí)行 setup 方法,而在它檢測(cè)到?jīng)]有任何地方在使用拖拽功能的時(shí)候,執(zhí)行 teardown 方法。
HTML5BackendImpl 的 setup 方法中執(zhí)行如下方法,target 默認(rèn)狀態(tài)下指的是 window。這里監(jiān)聽了所有的拖拽事件。這是典型的事件委托的方式,統(tǒng)一將拖拽事件的回調(diào)函數(shù)都綁定在 window 上,不僅能提高性能,而且極大的降低了事件銷毀的難度。
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, true) target.addEventListener('drop', this.handleTopDrop as EventListener) target.addEventListener( 'drop', this.handleTopDropCapture as EventListener, true, ) }
HTML5Backend 拖拽的監(jiān)聽函數(shù)就是獲得拖拽事件的對(duì)象,拿到相應(yīng)的參數(shù)。HTML5Backend 通過(guò) Manager 拿到一個(gè) DragDropActions 的實(shí)例,執(zhí)行其中的方法。DragDropActions 本質(zhì)就是根據(jù)參數(shù)將其封裝為一個(gè) action,最終通過(guò) redux 的 dispatch 將 action 分發(fā),改變 store 中的數(shù)據(jù)。
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 方法。該方法用于將某個(gè) Node 節(jié)點(diǎn)轉(zhuǎn)換為可拖拽節(jié)點(diǎn),并且添加監(jiān)聽事件。
HTML5Backend 使用 HTML5 拖放 API 實(shí)現(xiàn)。 首先:為了把一個(gè)元素設(shè)置為可拖放,把 draggable 屬性設(shè)置為 true。然后監(jiān)聽 ondragstart 事件,該事件在用戶開始拖動(dòng)元素時(shí)觸發(fā)。至于 selectstart,不用關(guān)心,是用來(lái)處理一些 IE 特殊情況的。
public connectDragSource( sourceId: string, node: Element, options: any, ): Unsubscribe { ... // 設(shè)置 draggable 屬性 node.setAttribute('draggable', 'true') // 添加 dragstart 監(jiān)聽 node.addEventListener('dragstart', handleDragStart) // 添加 selectstart 監(jiān)聽 node.addEventListener('selectstart', handleSelectStart) ... }
Node 上綁定的 dragstart 事件很簡(jiǎn)單,就是更新了下 sourceId。負(fù)責(zé)的邏輯綁定在了 window 上。
public handleDragStart(e: DragEvent, sourceId: string): void { if (e.defaultPrevented) { return } if (!this.dragStartSourceIds) { this.dragStartSourceIds = [] } this.dragStartSourceIds.unshift(sourceId) }
綜上,HTML5Backend 在初始化的時(shí)候在 window 上綁定拖拽事件的監(jiān)聽函數(shù),處理拖拽中的坐標(biāo)數(shù)據(jù),狀態(tài)數(shù)據(jù),并將其轉(zhuǎn)換為 action 交由上層的 store 處理。完成由 Dom 事件到數(shù)據(jù)的轉(zhuǎn)變。元素上綁定的監(jiān)聽只負(fù)責(zé)更新 sourceId。
TouchBackend
最后簡(jiǎn)單的看下 TouchBackend,與 HTML5Backend 相比,TouchBackend 的使用場(chǎng)景更加廣泛,因?yàn)樗灰蕾囉?H5 的 API,兼容性很好,既能用于瀏覽器端,又能用在移動(dòng)端。
TouchBackend 使用簡(jiǎn)單的事件來(lái)模擬拖放行為。比如在瀏覽器端,使用的是 mousedown,mousemove,mouseup。移動(dòng)端使用 touchstart,touchmove,touchend。
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', }, }
總結(jié)
本文分析了 React-DnD 是如何處理拖拽這一行為的。
首先在設(shè)計(jì)上,React-DnD 使用了分層設(shè)計(jì)的方式,react-dnd 是接入層,它為準(zhǔn)備了高階組件和 Hooks 兩種方式。dnd-core 是核心,它定義了拖拽接口,管理方式,數(shù)據(jù)流向。backend 中將 DOM 事件轉(zhuǎn)換為 redux action 的地方,該層用于屏蔽設(shè)備之間的差異性。
dnd-core 使用了 redux 管理數(shù)據(jù),這些數(shù)據(jù)通過(guò) dispatch action 進(jìn)行修改,使用 monitor 進(jìn)行數(shù)據(jù)的監(jiān)控,使用 connector 連接 dom 和 store。最終拖拽實(shí)現(xiàn)依賴于 backend,為節(jié)點(diǎn)添加了監(jiān)聽事件,然后將事件轉(zhuǎn)化為 action。
整體上看,React-DnD 的核心思路就是將事件轉(zhuǎn)換為數(shù)據(jù),設(shè)計(jì)上參考了 redux 的單一數(shù)據(jù)流的方式(畢竟一個(gè)作者寫的),這樣我們?cè)谔幚硗献У臅r(shí)候就可以關(guān)注于數(shù)據(jù)方面的變化,而不用費(fèi)心去維護(hù)拖拽中的一些中間狀態(tài),更不用自己去添加,移除事件,是非常好的一種設(shè)計(jì)。
以上就是React DnD如何處理拖拽詳解的詳細(xì)內(nèi)容,更多關(guān)于React DnD 拖拽處理的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
在React頁(yè)面重新加載時(shí)保留數(shù)據(jù)的實(shí)現(xiàn)方法總結(jié)
在React頁(yè)面重新加載時(shí)保留數(shù)據(jù),可以通過(guò)多種方法來(lái)實(shí)現(xiàn),常見的方法包括使用瀏覽器的本地存儲(chǔ)(Local Storage 或 Session Storage)、URL參數(shù)、以及服務(wù)器端存儲(chǔ)等,本文給大家總結(jié)了一些具體實(shí)現(xiàn)方法,需要的朋友可以參考下2024-06-06react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景
這篇文章主要介紹了react?事項(xiàng)懶加載的三種方法及使用場(chǎng)景,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-07-07關(guān)于React動(dòng)態(tài)修改元素樣式的三種方式
這篇文章主要介紹了關(guān)于React動(dòng)態(tài)修改元素樣式的三種方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08詳解React setState數(shù)據(jù)更新機(jī)制
這篇文章主要介紹了React setState數(shù)據(jù)更新機(jī)制的相關(guān)資料,幫助大家更好的理解和學(xué)習(xí)使用React框架,感興趣的朋友可以了解下2021-04-04詳解React-Native解決鍵盤遮擋問題(Keyboard遮擋問題)
本篇文章主要介紹了React-Native解決鍵盤遮擋問題(Keyboard遮擋問題),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07react中如何使用定義數(shù)據(jù)并監(jiān)聽其值
這篇文章主要介紹了react中如何使用定義數(shù)據(jù)并監(jiān)聽其值問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01React?組件權(quán)限控制的實(shí)現(xiàn)
本文主要介紹了React?組件權(quán)限控制的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02