React實現(xiàn)卡片拖拽效果流程詳解
前提摘要:
學習宋一瑋 React 新版本 + 函數(shù)組件 &Hooks 優(yōu)先 開篇就是函數(shù)組件+Hooks 實現(xiàn)的效果如下: 學到第11篇了 照葫蘆畫瓢,不過老師在講解的過程中沒有考慮拖拽目標項邊界問題,我稍微處理了下這樣就實現(xiàn)拖拽流暢了
下面就是主要的代碼了,實現(xiàn)拖拽(src/App.js):
核心在于標記當前項,來源項,目標項,并且在拖拽完成時對數(shù)據(jù)處理,更新每一組數(shù)據(jù)(useState);
/** @jsxImportSource @emotion/react */ // 上面代碼是使用emotion的關(guān)鍵CSS-in-JS import React, { useEffect, useState, useRef } from "react"; import { css } from "@emotion/react"; import "./App.css"; const MINUTE = 60 * 1000; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; const UPDATE_INTERVAL = MINUTE; // const ongoingList = [{ title: "進行任務(wù)", status: "2022-11-09 15:29" }]; // const doneList = [{ title: "完成任務(wù)", status: "2022-11-09 15:59" }]; const KanbanBoard = ({ children }) => ( <main css={css` flex: 10; display: flex; flex-direction: row; gap: 1rem; margin: 0 1rem 1rem; `} > {children} </main> ); const KanbanColumn = ({ children, className, title, setIsDragSource = () => { }, setIsDragTarget = () => { }, onDrop, }) => { const combinedClassName = `kanban-column ${className}`; return ( <section className={combinedClassName} onDragStart={() => setIsDragSource(true)} onDragOver={(evt) => { evt.preventDefault(); evt.dataTransfer.dropEffect = "move"; setIsDragTarget(true); }} onDragLeave={(evt) => { evt.preventDefault(); evt.dataTransfer.dropEffect = "none"; setIsDragTarget(false); }} onDrop={(evt) => { evt.preventDefault(); onDrop && onDrop(evt); }} onDragEnd={(evt) => { evt.preventDefault(); setIsDragSource(false); setIsDragTarget(false); }} > <h2>{title}</h2> <ul>{children}</ul> </section> ); }; const KanbanCard = ({ title, status, onDragStart }) => { const [displayTime, setDisplayTime] = useState(status); useEffect(() => { const updateDisplayTime = () => { const timePassed = new Date() - new Date(status); let relativeTime = "剛剛"; if (MINUTE <= timePassed && timePassed < HOUR) { relativeTime = `${Math.ceil(timePassed / MINUTE)} 分鐘前`; } else if (HOUR <= timePassed && timePassed < DAY) { relativeTime = `${Math.ceil(timePassed / HOUR)} 小時前`; } else if (DAY <= timePassed) { relativeTime = `${Math.ceil(timePassed / DAY)} 天前`; } setDisplayTime(relativeTime); }; const intervalId = setInterval(updateDisplayTime, UPDATE_INTERVAL); updateDisplayTime(); return function cleanup() { clearInterval(intervalId); }; }, [status]); const handleDragStart = (evt) => { evt.dataTransfer.effectAllowed = "move"; evt.dataTransfer.setData("text/plain", title); onDragStart && onDragStart(evt); }; return ( <li className="kanban-card" draggable onDragStart={handleDragStart}> <div className="card-title">{title}</div> <div className="card-status">{displayTime}</div> </li> ); }; const AddKanbanCard = ({ onSubmit }) => { const [title, setTitle] = useState(""); const handleChange = (evt) => { setTitle(evt.target.value); }; const handleKeyDown = (evt) => { if (evt.key === "Enter") onSubmit(title); }; const inputElem = useRef(null); useEffect(() => { inputElem.current.focus(); }); return ( <li className="kanban-card"> <h4>添加新卡片</h4> <div className="card-title"> <input ref={inputElem} type="text" value={title} onChange={handleChange} onKeyDown={handleKeyDown} ></input> </div> </li> ); }; const DATE_STORE_KEY = "kanban_data_store"; const COLUMN_KEY_TODO = "todo"; const COLUMN_KEY_ONGONING = "ongoing"; const COLUMN_KEY_DONE = "done"; function App() { const [todoList, setTodoList] = useState([ { title: "開發(fā)任務(wù)-1", status: "2022-05-22 18:15" }, ]); const [ongoingList, setOngoingList] = useState([ { title: "進行任務(wù)-1", status: "2022-08-22 18:15" }, ]); const [doneList, setDoneList] = useState([ { title: "完成任務(wù)-1", status: "2022-10-22 18:15" }, ]); const [showAdd, setShowAdd] = useState(false); const handleAdd = (evt) => { setShowAdd(true); }; const handleSubmit = (title) => { // todoList.unshift({title,status:new Date().toDateString()}); setTodoList((current) => [{ title, status: new Date() + " " }, ...current]); setShowAdd(false); }; const handleSaveAll = () => { const data = JSON.stringify({ todoList, ongoingList, doneList, }); window.localStorage.setItem(DATE_STORE_KEY, data); }; useEffect(() => { const data = window.localStorage.getItem(DATE_STORE_KEY); setTimeout(() => { if (data) { const kanbanColumnData = JSON.parse(data); setTodoList(kanbanColumnData.todoList); } }, 1000); }); const [draggedItem, setDraggedItem] = useState(null); const [dragSource, setDragSource] = useState(null); const [dragTarget, setDragTarget] = useState(null); const handleDrop = (evt) => { if (!draggedItem || !dragSource || !dragTarget || dragSource === dragTarget) { return; } const updaters = { [COLUMN_KEY_TODO]: setTodoList, [COLUMN_KEY_ONGONING]: setOngoingList, [COLUMN_KEY_DONE]: setDoneList }; if (dragSource) { updaters[dragSource]((currentStat) => { return currentStat.filter((item) => !Object.is(item, draggedItem)); }); } if (dragTarget) { updaters[dragTarget]((currentStat) => { if (currentStat.length > 0) { return [draggedItem, ...currentStat] } else { return [draggedItem] } }) } }; return ( <div className="App"> <header className="App-header"> <h1> 我的看板<button onClick={handleSaveAll}>保存所有卡片</button>{" "} </h1> </header> <KanbanBoard> <KanbanColumn className="column-todo" title={ <> 待處理 <button disabled={showAdd} onClick={handleAdd}> ⊕添加新卡片 </button>{" "} </> } setIsDragSource={(isSrc) => setDragSource(isSrc ? COLUMN_KEY_TODO : null) } setIsDragTarget={(isTarget) => setDragTarget(isTarget ? COLUMN_KEY_TODO : null) } onDrop={handleDrop} > {/* <h2> 待處理 <button disabled={showAdd} onClick={handleAdd}> ⊕添加新卡片 </button>{" "} </h2> */} {/* <ul> */} {showAdd && <AddKanbanCard onSubmit={handleSubmit} />} {todoList && todoList.map((item) => ( <KanbanCard {...item} key={item.title} onDragStart={() => setDraggedItem(item)} /> ))} {/* </ul> */} </KanbanColumn> <KanbanColumn className="column-ongoing" title={"進行中"} setIsDragSource={(isSrc) => setDragSource(isSrc ? COLUMN_KEY_ONGONING : null) } setIsDragTarget={(isTarget) => setDragTarget(isTarget ? COLUMN_KEY_ONGONING : null) } onDrop={handleDrop}> {/* <h2>進行中</h2> <ul> */} {ongoingList && ongoingList.map((item) => ( <KanbanCard {...item} key={item.title} onDragStart={() => setDraggedItem(item)} /> ))} {/* </ul> */} </KanbanColumn> <KanbanColumn className="column-done" title={"已處理"} setIsDragSource={(isSrc) => setDragSource(isSrc ? COLUMN_KEY_DONE : null) } setIsDragTarget={(isTarget) => setDragTarget(isTarget ? COLUMN_KEY_DONE : null) } onDrop={handleDrop}> {/* <h2>已處理</h2> <ul> */} {doneList && doneList.map((item) => ( <KanbanCard {...item} key={item.title} onDragStart={() => setDraggedItem(item)} /> ))} {/* </ul> */} </KanbanColumn> </KanbanBoard> </div> ); } export default App;
這時拖拽基本完成,此時有一個bug,就是待處理添加新卡片的時候,拖拽之后的數(shù)據(jù)出現(xiàn)混亂?。∪缦滤荆?/p>
首先問題定位,移動的來源項出現(xiàn)了問題,看代碼之后發(fā)現(xiàn)拖拽處理來源項沒有問題,那一定是那塊調(diào)用更新todaList出現(xiàn)了問題,問題定位在useEffect,使用時如果useEffect的第二個參數(shù)不傳就在組件所有更新都執(zhí)行(即任何時候),傳個空數(shù)組僅在掛載和卸載的時候執(zhí)行,或者傳個你想要去進行更新時候去執(zhí)行(默認情況下,effect 將在每輪渲染結(jié)束后執(zhí)行,但你可以選擇讓它 在只有某些值改變的時候 才執(zhí)行。)
更改代碼如下:
useEffect(() => { const data = window.localStorage.getItem(DATE_STORE_KEY); setTimeout(() => { if (data) { const kanbanColumnData = JSON.parse(data); setTodoList(kanbanColumnData.todoList); } }, 1000); },[]);
useEffect新增空數(shù)組效果展示:
學習一個新的框架總是會進行對比,
一:React的單項數(shù)據(jù)流和Vue中的雙向綁定有什么區(qū)別?
在我看了Vue 雙向綁定其實是語法糖罷了,其原理其實是Object.defineProperty()對數(shù)據(jù)進行劫持,監(jiān)聽到變化就去對數(shù)據(jù)進行更改;
而React 中的單項數(shù)據(jù)流做到了對原有數(shù)據(jù)的保護,你不能去直接去對Props進行更改,而是在需要賦值給State,然后再SetState中去進行更改,當然Hooks中提供了useState方法,使得開發(fā)者更加方便的去對數(shù)據(jù)進行處理和更改(我可太喜歡Hooks了很省事?。。?/p>
二:JSX 是什么?
學習React的時候,寫組件的時候?qū)戫撁嬖厥怯肑SX來寫的,即Render里面是用JSX來實現(xiàn)的,渲染之后其實質(zhì)是React.createElement,他只是語法糖 實現(xiàn)React組件的一部分而已,對比Vue中Template,(我更喜歡Vue的實現(xiàn),更符合開發(fā)者)不過Vue也可用JSX來實現(xiàn);
三:函數(shù)式組件(Hooks)與類組件(Class)優(yōu)缺點?
宋一瑋老師的數(shù)據(jù)表明函數(shù)式比類組件使用更多,并且函數(shù)組件基本上涵蓋了類組件的功能點,除了(只有類組件才能成為錯誤邊界)
從React官網(wǎng)中開局也是用類組件來領(lǐng)進門的,我是看完了官網(wǎng)的基礎(chǔ)才來學習課程的才覺得學習沒有那么吃力反而能加深理解;(老師的反其道而行可能不太適合初學者)
四:CSS可否想JS一樣應(yīng)用在組件中?
可以的,使用emotion來應(yīng)用到JSX中(主要代碼中有使用),不過CSS中傳入JS數(shù)據(jù)確實很方便但是在運行emotion時會創(chuàng)建大量的<style>
標簽,有可能影響頁面性能。
CSS-in-JS 技術(shù)能幫我們做到樣式隔離、提升組件樣式的可維護性、可復(fù)用性。
五:常用的hooks——useState,useEffect、useRef
到此這篇關(guān)于React實現(xiàn)卡片拖拽效果流程詳解的文章就介紹到這了,更多相關(guān)React卡片拖拽內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React?component.forceUpdate()強制重新渲染方式
這篇文章主要介紹了React?component.forceUpdate()強制重新渲染方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-10-10react-native 封裝視頻播放器react-native-video的使用
本文主要介紹了react-native 封裝視頻播放器react-native-video的使用,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-01-01react-draggable實現(xiàn)拖拽功能實例詳解
這篇文章主要給大家介紹了關(guān)于react-draggable實現(xiàn)拖拽功能的相關(guān)資料,React-Draggable一個使元素可拖動的簡單組件,文中通過代碼示例介紹的非常詳細,需要的朋友可以參考下2023-08-08React 項目遷移 Webpack Babel7的實現(xiàn)
這篇文章主要介紹了React 項目遷移 Webpack Babel7的實現(xiàn),小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-09-09