V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
rizon
V2EX  ›  程序员

写了一个 [排序只需要修改一个对象数据] 的模式

  •  
  •   rizon ·
    othorizon · 334 天前 · 1259 次点击
    这是一个创建于 334 天前的主题,其中的信息可能已经有所发展或是发生改变。

    废话:
    最近在对 notelive.cc 这个网站做一个新版本(不会替代这个网站,只是做一个新项目),然后遇到了目录树的排序老问题。

    正题:
    出于降低数据库写压力,简化初始化数据结构,以及我就是不喜欢排序要修改两个对象的个人原因等,因此做了这个排序模型。 实现效果:

    1. 数据结构初始化不需要包含排序信息(个人业务需要,默认按照时间排序,本 demo 里按照 id 排序)
    2. 排序时只需要修改拖动的对象的数据,不需要操作目标位置的对象。

    有什么好的优化建议,也欢迎贴出来。

    Edit single-sort

    "use client";
    import { useRef, useState } from "react";
    
    interface Item {
      id: number;
      orderTime: number | undefined;
      pre: number | null | undefined;
    }
    let init: Item[] = [
      {
        id: 1,
        orderTime: undefined,
        pre: undefined,
      },
      {
        id: 4,
        orderTime: undefined,
        pre: undefined,
      },
      {
        id: 2,
        orderTime: undefined,
        pre: 3,
      },
      {
        id: 3,
        orderTime: undefined,
        pre: undefined,
      },
    ];
    export default function SortPage() {
      const source = useRef<HTMLInputElement>(null);
      const target = useRef<HTMLInputElement>(null);
      const [list, setList] = useState<Item[]>(init);
    
      const doSort = () => {
        if (!source.current || !source.current.value) {
          setList(reSort(list));
          return;
        }
    
        const sourceItem = list.find(
          (item) => item.id == parseInt(source.current!.value)
        );
        if (!sourceItem) {
          alert("not fount sourceItem");
          return;
        }
        const targetItem = target.current!.value
          ? list.find((item) => item.id == parseInt(target.current!.value))
          : null;
    
        // 只需要修改 sourceItem 的值,不用处理 targetItem
        if (!targetItem) {
          sourceItem.orderTime = Date.now();
          sourceItem.pre = -1;
        } else {
          sourceItem.orderTime = Date.now();
          sourceItem.pre = targetItem.id;
        }
        setList(reSort(list));
      };
      return (
        <div>
          <div>
            <div>
              移动的元素: <input className="border" ref={source} type="text" />
            </div>
            <div>
              目标元素的后面(留空首位):{" "}
              <input className="border" ref={target} type="text" />
            </div>
            <div>
              <button className="bg-gray-400 p-2" onClick={doSort}>
                移动
              </button>
            </div>
          </div>
          <div className="mt-5 flex items-center justify-start gap-x-2 px-3">
            {list.map((item) => {
              return (
                <div className="bg-blue-100 p-2 " key={item.id}>
                  <p>id: {item.id}</p>
                  <p>orderTime: {item.orderTime}</p>
                  <p>pre: {item.pre}</p>
                </div>
              );
            })}
          </div>
        </div>
      );
    }
    
    function reSort(originList: Item[]) {
      const list = [...originList];
      const sortById = (pre: Item, next: Item) => {
        // id 从小到大排序
        if (pre.id > next.id) {
          return 1;
        } else if (pre.id < next.id) {
          return -1;
        }
        return 0;
      };
      const sortByOrderTime = (pre: Item, next: Item) => {
        let preOrderTime = pre.orderTime || 0;
        let nextOrderTime = next.orderTime || 0;
        // orderTime 从小到大排序
        if (preOrderTime > nextOrderTime) {
          return 1;
        } else if (preOrderTime < nextOrderTime) {
          return -1;
        }
        return 0;
      };
    
      list.sort(sortById).sort(sortByOrderTime);
    
      const checkList = [...list];
    
      // 按照 orderTime 从旧到新,每个元素都执行一次 pre 位置调整处理,如果两个元素的 pre 相同,orderTime 最晚的则会插入到旧数据的前面
      for (let i = 0; i < checkList.length; i++) {
        const checkItem = checkList[i];
        if (checkItem.pre) {
          moveElementAfter(list, checkItem.id, checkItem.pre);
        }
      }
    
      function moveElementAfter(
        array: Item[],
        sourceId: number,
        targetItemId: number
      ) {
        const sourceIdx = array.findIndex((e) => e.id === sourceId);
        if (sourceIdx === -1) {
          return;
        }
        const sourceItem = array[sourceIdx];
    
        const indexToInsert = list.findIndex((e) => e.id === targetItemId);
        array.splice(sourceIdx, 1);
        if (indexToInsert > -1) {
          array.splice(indexToInsert + 1, 0, sourceItem); // 在目标元素后面插入
        } else {
          array.unshift(sourceItem); // 如果找不到元素 b ,则将元素 a 放在数组首位
        }
      }
    
      return list;
    }
    
    

    文末: 如果大家对 notelive.cc 这个网站有什么建议,或对正在内测的新笔记产品有兴趣,或单纯想要唠唠嗑,可以加微信群哈。

    notelive

    第 1 条附言  ·  333 天前
    行不通。已放弃,最后做的的方案就是每改变一个元素就全 order 排序了。考虑到每个子文件夹下的数量还好,就这么简单的搞了。
    其实还有考虑单独存储排序数据,后来想想算了。
    7 条回复    2023-12-10 19:30:07 +08:00
    rizon
        1
    rizon  
    OP
       334 天前
    擦 有问题,有些场景不对。忽略吧
    renmu
        2
    renmu  
       334 天前 via Android
    只要取到目标前后对象的 sort 值,然后将这这两个值和的一半写入目标就可以了
    zhy0216
        3
    zhy0216  
       334 天前 via Android
    @renmu 最后会有精度问题的
    renmu
        4
    renmu  
       334 天前 via Android
    @zhy0216 你先算一下多少次能达到精度问题,这是一个很小概率的事情,就算有,完全可以做个额外处理
    zhy0216
        5
    zhy0216  
       334 天前 via Android
    @renmu 只要有存在的可能就要额外处理 感觉不够好而已
    rizon
        6
    rizon  
    OP
       334 天前
    @renmu #2
    @zhy0216 #3 以前也想过这个办法,两个元素之间留数字的空间,这样每次排序只改变一个的值。
    但是时间久了还要执行一次刷库操作,不然总有用完的时候,或者就想说的精度问题
    rizon
        7
    rizon  
    OP
       334 天前
    @rizon #6 而且这个方案,要求第一次写入的数据就要有顺序。
    我其实核心想实现的事,如果未做排序,就不产生大量的排序数据,按创建时间降序排序即可。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2717 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 12:49 · PVG 20:49 · LAX 04:49 · JFK 07:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.