使用Sortable.js和React创建重新排序的列表

2020年12月30日11:07:07 发表评论 52 次浏览

本文概述

如今, 有许多库可以处理拖放, 列表排序和重新排序。在jQuery小部件时代, 数十个插件可以轻松地在深度交互和动态结构中转换简单和静态列表。

在香草JavaScript的世界中, 我们拥有著名的Sortable.js。拥有超过20.5k的星星GitHub回购在撰写本文时, 该库背后的社区已经建立了一个稳定的环境, 该环境遍布许多支持:jQuery, Meteor.js, AngularJS, React, Polymer, Vue, Ember, Knockout和任何CSS库, 例如引导程序。

大多数国家也支持现代浏览器, 其中包括适用于非HTML5浏览器的API的后备选项, 使我们能够测试旧版浏览器的行为, 或者使桌面, 移动和旧版浏览器之间的拖放感觉更加一致。

在本教程中, 我们将通过以下方式探索对React的官方支持react-sortablejs包装组件。让我们分析一下Sortable.js如何通过创建, 编辑和删除其中一些项目来组织项目列表。最后, 我们将允许用户在项目列表和网格布置之间切换。

在本教程的最后, 这是我们的示例的外观:

Sortable.js用户界面让用户受益。

可排序用户列表。

设定

在继续之前, 请确保已安装了最新版本的Node.js和npm。我们还需要像素包装以利用创建反应应用工具链。

在你喜欢的文件夹中, 运行以下命令来初始化Yarn管理工具, 然后创建我们的React项目:

yarn init
npx create-react-app sortable-js-app

这足以用到目前为止所需的React文件初始化项目。接下来, 安装以下依赖项:

yarn add axios jquery bootstrap popper.js react-sortablejs styled-components

Axios依赖关系将用于从称为的公共虚假Web服务中检索预取的用户列表。json占位符。这对于CRUD或其他类型的Web应用程序测试非常有用。它为伪造的终结点提供JSON数据, 可用于检查我们的应用程序结构是否正常运行。

需要Bootstrap, jquery和Popper.js才能使Bootstrap框架工作。我们不会为该示例创建任何开箱即用的设计, 因此让此工作留给Bootstrap。

我们为制作了一个自定义演示.
不完全是。点击这里查看.

使用Sortable.js和React创建重新排序的列表1

最后两个依赖项是react-sortablejslib本身, 以及样式化的组件我们将用来推断组件的某些内联样式。

最后, 你需要将文件和文件夹的结构复制到本地示例中。这是项目结构的外观:

Sortable.js文件和文件夹结构。

Sortable.js文件和文件夹结构。

在components文件夹中, 你可能会发现ActionModal.js零件。它负责保持动作模式的逻辑, 在单击列表或网格项之一后, 它将显示编辑和删除选项。

在此示例中, 我们将为用户列表提供完整的CRUD。它使我们可以实时编辑和删除列表中的项目, 并在之后立即刷新它。

的ListItem.js将为清单中的每一项提供结构, 而SimpleList.js是我们的主要组成部分。它是一个将存储其他功能和主要功能以保持示例功能的集成。

建立例子

现在, 让我们从示例开始index.js文件, 位于应用程序的心脏。

打开它, 并将其内容更改为以下内容:

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";

import $ from "jquery";
import Popper from "popper.js";
import "bootstrap/dist/js/bootstrap.bundle.min";
import "bootstrap/dist/css/bootstrap.min.css";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>, document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

在这里, 我们只是导入Bootstrap CSS和JavaScript依赖项, 以便在以后的文件中使用它们时可以看到它们。

你可能还会注意到, 正如VS Code所指出的那样, jQuery和Popper依赖项并未"被使用"。但是, 这里需要它们。否则, 我们将无法使用引导模式等动态功能。

其余部分保持原样, 继续前进到动作模态零件。打开文件并将以下代码放入其中:

import React from "react";

const ActionModal = ({ item, handleDelete }) => {
  return (
    <div className="modal fade" tabIndex="-1" id="actionModal" role="dialog">
      <div className="modal-dialog" role="document">
        <div className="modal-content">
          <div className="modal-header">
            <h5 className="modal-title">Action Modal</h5>
            <button
              type="button"
              className="close"
              data-dismiss="modal"
              aria-label="Close"
            >
              <span aria-hidden="true">&times;</span>
            </button>
          </div>
          <div className="modal-body">
            <p>
              What do you want to do with <b>{item.name}</b>?
            </p>
          </div>
          <div className="modal-footer">
            <button
              type="button"
              data-dismiss="modal"
              className="btn btn-warning"
            >
              Edit item
            </button>
            <button
              type="button"
              className="btn btn-danger"
              data-dismiss="modal"
              onClick={() => handleDelete(item.id)}
            >
              Delete item
            </button>
          </div>
        </div>
      </div>
    </div>
  );
};

export default ActionModal;

在这里, 我们收到两个作为参数的道具:要编辑或删除的项目, 以及处理删除的功能(handleDelete)。这些操作将由主要组件执行。

我们不会收到任何handleEdit例如, 因为你将看到, 此逻辑还将由根组件维护。

组件本身非常简单。它由几个div及其各自的Bootstrap类组成, 并且删除按钮具有适当的删除功能。模态应如下所示:

动作模式。

行动模式在行动。

现在, 让我们进入项目清单零件。这应该是其内容:

import React from "react";
import styled from "styled-components";

const Item = styled.div`
  background-color: #fff6;
  cursor: pointer;
`;

const ListItem = ({ item, prepareAction, isGrid }) => {
  return (
    <Item
      className={isGrid ? "col-md-3 grid-group-item" : "list-group-item"}
      data-toggle="modal"
      data-target="#actionModal"
      id={`person-${item.id}`}
      key={item.id}
      onClick={() => prepareAction(item)}
    >
      <div className="d-flex w-100 justify-content-between">
        <h5 className="mb-1">{item.name}</h5>
        <small>Phone: {item.phone}</small>
      </div>
      <p className="mb-1">{item.email}</p>
      <small>
        More on:{" "}
        <i>
          <a href={`https://${item.website}`}>www.{item.website}</a>
        </i>
      </small>
    </Item>
  );
};

export default ListItem;

这是我们第一次使用样式化的组件。只需一小段代码即可为每个列表项添加背景色和光标样式。

相应地, 该组件将收到三个道具:要显示的相同项目, 将负责准备动作的功能(即, 获取所选项目并将其放入相应的状态值), 以及布尔值(isGrid), 以确定用户是否将视图的布置从列表更改为网格。

最新的布尔参数非常重要, 因为它可以帮助组件的其余部分在有关样式类的列表和网格之间切换。这些类是Bootstrap查看列表和网格的方式。我们还将添加一些自己的CSS来增强整体风格。

还要注意, 代码的这一部分与Sortable.js组件没有直接关系。但是, 这一点很重要, 因为最后它代表了将放置在可排序组件内的单个项目。

要特别注意项目零件。有个警告在里面react-sortablejs说明用户不要使用的文档编号作为列表项的键, 因为排序不起作用。相反, 你应该使用对象的id.

其余代码仅将项目的属性连接到Bootstrap元素。

最后, 我们有简单清单零件。由于它有点长, 我们将其分成较小的部分, 以便于理解。

打开文件, 并在顶部添加以下内容:

import React, { useEffect, useState } from "react";
import { ReactSortable, Swap, Sortable } from "react-sortablejs";
import axios from "axios";

import ListItem from "./ListItem";
import { BASE_URL } from "../constants";
import ActionModal from "./ActionModal";

import "./SimpleList.css";

const initialState = {
  id: "", name: "", phone: "", email: "", website: "", };

Sortable.mount(new Swap());

const SimpleList = () => {
  const [list, setList] = useState([]);
  const [user, setUser] = useState(initialState);
  const [isGrid, setIsGrid] = useState(false);

  const [isEdit, setIsEdit] = useState(false);
  const [isSuccess, setSuccess] = useState(false);
  const [actionItem, setActionItem] = useState(initialState);

};

export default SimpleList;

目前, 我们只是在初始化必要的内容, 例如react-sortablejs组件和Axios。我们正在创建initialState必要时将有助于重置状态的对象。

Sortable对象有助于一些辅助功能, 例如交换。默认情况下, Sortable.js堆叠其列表项, 并将拖动的项放置在你放下的确切位置, 从而相应地重新放置堆的其余部分。

当你声明应交换, 它将切换拖动的项目位置和放置相同项目的位置。

其余代码只是CRUD工作所需的一堆状态变量。你还可以看到导入的constants.js文件, 该文件也应创建并具有以下代码:

export const BASE_URL = `http://jsonplaceholder.typicode.com/users`;

这基本上是用户端点用来初始化我们列表的常量。

现在, 让我们跳到组件功能, 这些功能反过来将有助于我们了解组件逻辑。你必须在状态变量声明之后放置以下代码:

const handleChange = (event) => {
    if (isEdit) {
      setActionItem({
        ...actionItem, [event.target.name]: event.target.value, });
    } else {
      setUser({
        ...user, [event.target.name]: event.target.value, });
    }
  };

  const prepareAction = (item) => {
    setIsEdit(true);
    setActionItem(item);
  };

  const handleSubmit = (event) => {
    event.preventDefault();

    if (isEdit) {
      const copyList = [...list];
      let index = copyList.findIndex((item) => item.id === actionItem.id);

      copyList[index] = actionItem;
      setList(copyList);
    } else {
      setList([...list, user]);
    }

    setSuccess(true);

    reset();
  };

  const handleDelete = (id) => {
    setList(list.filter((item) => item.id !== id));
    reset();
  };

  const handleLayoutChange = () => {
    setIsGrid(!isGrid);
  };

  const reset = () => {
    document.getElementById("list-form").reset();
    setIsEdit(false);
    setUser(initialState);
    setActionItem(initialState);

    setTimeout(function () {
      setSuccess(false);
    }, 5000);
  };

  useEffect(() => {
    axios.get(BASE_URL).then((res) => {
      setList(res.data);
    });
  }, []);

这里有几点要点。

首先, handleChange是可以确保更新用户(或行动项目(如果是编辑过程), 则只要用户在相应的表单字段中输入内容即可。每当用户点击某个项目时, 我们都会将其视为编辑/删除该项目的意图。因此, 我们需要两个不同的对象来保存创建和用户编辑的值。

还有其他方法可以做到这一点。例如, 你可以以模式显示编辑表单, 但是为了简单起见, 我们将坚持使用相同的表单。

的prepareAction功能将设置isEditboolean为true, 因为这是一种编辑操作。还将当前项目(作为参数发送)设置为行动项目州。该对象将有助于在表单字段中显示适当的值。

的handleSubmit负责表单提交过程。如果他们要编辑, 请创建当前用户列表的副本(我们不想弄乱他们的值), 找到当前所选项目的索引, 然后将其元素替换为当前行动项目。由于将通过更改来确保使用更改后的值正确更新handleChange功能。

但是, 如果是正在创建的新用户, 我们将其添加到列表中。确保每次创建/编辑后始终重置对象状态, 因为新动作可能会进一步发生。

重置函数依次调用表单重置功能, 并重新初始化所有React状态的值。在这里, 要特别注意成功属性, 该属性包含布尔值, 用于确定警报部分是否应可见。操作完成后, 本部分将显示一条成功消息, 以使用户知道应用程序中发生了什么。

的handleDelete只是对用户列表执行删除操作, 然后再次重置状态值。同时, handleLayoutChange函数切换定义该布局是列表还是网格的布尔变量。

最后, 我们有useEffect挂钩, 它将在React组件渲染后执行。它的工作是呼叫用户的外部端点, 并在展览之前向我们的列表提供一些数据。

让我们用返回内容:

return (
  <>
    {isSuccess ? (
      <div
        className="alert alert-success alert-dismissible fade show"
        role="alert"
      >
        <strong>Success!</strong>
        <button
          type="button"
          className="close"
          data-dismiss="alert"
          aria-label="Close"
        >
          <span aria-hidden="true">&times;</span>
        </button>
      </div>
    ) : (
      ""
    )}

    <form onSubmit={handleSubmit} id="list-form">
      <div className="form-row">
        <div className="col">
          <label htmlFor="name">Name:</label>
          <input
            type="text"
            required
            className="form-control"
            id="name"
            name="name"
            defaultValue={actionItem.name}
            placeholder="Type your name..."
            onChange={handleChange}
          />
        </div>
        <div className="col">
          <label htmlFor="phone">Phone:</label>
          <input
            type="text"
            required
            className="form-control"
            id="phone"
            name="phone"
            defaultValue={actionItem.phone}
            placeholder="Type your phone..."
            onChange={handleChange}
          />
        </div>
      </div>

      <div className="form-row mt-2">
        <div className="col">
          <label htmlFor="email">Email:</label>
          <input
            type="text"
            required
            className="form-control"
            id="email"
            name="email"
            defaultValue={actionItem.email}
            placeholder="Type your email..."
            onChange={handleChange}
          />
        </div>
      </div>

      <div className="form-row mt-2">
        <div className="col">
          <label htmlFor="website">Your Website:</label>
          <input
            type="text"
            required
            className="form-control"
            id="website"
            name="website"
            defaultValue={actionItem.website}
            placeholder="Type your website..."
            onChange={handleChange}
          />
        </div>
      </div>

      <button type="submit" className="btn btn-primary mb-4 mt-2">
        Save
      </button>

      <button
        type="button"
        className="btn btn-secondary mb-4 ml-2 mt-2"
        onClick={reset}
      >
        Clear
      </button>
    </form>

    <hr className="mb-4" />

    <div className="mb-3">
      <label className="switch ">
        <input type="checkbox" onChange={handleLayoutChange} />
        <span className="slider"></span>
      </label>
      <span className="ml-2">Toggle Grid</span>
    </div>

    <ReactSortable
      swap
      id={isGrid ? "people-grid" : "people-list"}
      className={isGrid ? "row" : "list-group"}
      chosenClass="chosen-list"
      list={list}
      setList={setList}
      animation={150}
    >
      {isGrid
        ? parseToGrid(list).map((array) =>
            array.map((item) => (
              <ListItem
                isGrid={isGrid}
                prepareAction={prepareAction}
                item={item}
                key={item.id}
              />
            ))
          )
        : list.map((item) => (
            <ListItem
              isGrid={isGrid}
              prepareAction={prepareAction}
              item={item}
              key={item.id}
            />
          ))}
    </ReactSortable>

    <ActionModal item={actionItem} handleDelete={handleDelete} />
  </>
);

如你所见, 这是一个很长的代码清单, 因为页面上显示了很多组件。显然, 你可以将其分解为更多组件, 并根据需要进行更改。

开头显示了警报部分, 当用户成功执行操作时, 我们将在其中显示Bootstrap警报消息。

下方是带有单个输入字段的表单, 最后是一个提交按钮。请注意, 每个字段的默认值都直接连接到行动项目道具。

然后, 我们得到一个拨动开关, 它将允许用户选择其项目的列表布置。它是手工制作的, 因此我们需要一些CSS才能使其样式起作用。

的ReactSortable组件是主要组件。它负责包装整个拖放列表。当它是网格或列表时, 我们还应该应用不同的规则, 并提供列表和设置列表可以满足要求的值。

列表的布置与网格有点不同。网格是由一个数组数组组成的-在我们的例子中是一个用户数组数组。同时, 列表只是一个数组。

因此, 我们首先要检查配置是否为网格, 以便我们调用适当的逻辑。的parseToGrid函数可以通过根据参数将项目数组转换为单独的数组列表来帮助实现这一点。

这里是内容, 必须放在代码的末尾:

function parseToGrid(array, cols = 4) {
  let [...arr] = array;
  var res = [];
  while (arr.length) {
    res.push(arr.splice(0, cols));
  }
  return res;
}

最后, 我们将动作模态零件。重要的是要记住, 它不会显示在前面, 因为单击按钮会触发此类操作。

为了推断出我们之前提到的自定义样式, 这是SimpleList.css文件:

.chosen-list {
  background-color: teal !important;
  color: white;
}

.grid-group-item {
  padding: 0.75rem 1.25rem;
  border: 1px solid rgba(0, 0, 0, 0.125);
}

.switch {
  position: relative;
  display: inline-block;
  width: 60px;
  height: 34px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
  -webkit-transition: 0.4s;
  transition: 0.4s;
  border-radius: 4px;
}

.slider:before {
  position: absolute;
  content: "";
  height: 26px;
  width: 26px;
  left: 4px;
  bottom: 4px;
  background-color: white;
  -webkit-transition: 0.4s;
  transition: 0.4s;
}

input:checked + .slider {
  background-color: #2196f3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196f3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(26px);
  -ms-transform: translateX(26px);
  transform: translateX(26px);
}

我们列表难题的最后一小部分属于App组件。在那里, 你必须导入简单清单组件就是这样的:

import React from "react";
import SimpleList from "./components/SimpleList";

const App = () => {
  return (
    <div className="container">
      <div className="px-3 py-3 pt-md-5 pb-md-4 mx-auto text-center">
        <h1 className="display-4">SortableJS</h1>
        <p className="lead">
          Let's test together the amazing features of this library along with
          React.
        </p>
      </div>
      <SimpleList />
    </div>
  );
};

export default App;

此代码内容大部分与静态Bootstrap内容有关。列表组件将直接在底部导入。

总结

现在, 该测试所有内容了。继续进行CRUD操作, 拖放操作以及在一个视图位置和另一个视图位置之间切换的开关。

一个好的练习是通过更改样式或向网格添加新级别来制作自己的自定义配置。你甚至可以允许用户决定要在网格系统中显示多少列。

我不能过分强调两者的官方文档的必要性SortableJS和react-sortablejs。如果你正在寻找其他库和框架的支持, 请在Sortable.js文档的末尾找到参考链接以及社区制作的一些比较视频。

你也可以找到此示例的源代码这里.

全面了解生产React应用

调试React应用程序可能很困难, 尤其是当用户遇到难以重现的问题时。如果你有兴趣监视和跟踪Redux状态, 自动显示JavaScript错误以及跟踪缓慢的网络请求和组件加载时间,

尝试notlogy

.

使用Sortable.js和React创建重新排序的列表2
LogRocket仪表板免费试用横幅

日志火箭就像Web应用程序的DVR, 实际上记录了React应用程序中发生的一切。你可以汇总并报告问题发生时应用程序所处的状态, 而不用猜测为什么会发生问题。 notlogy还监视你的应用程序的性能, 并使用客户端CPU负载, 客户端内存使用情况等指标进行报告。

notlogy Redux中间件软件包在你的用户会话中增加了一层可见性。 notlogy记录Redux存储中的所有操作和状态。

现代化如何调试React应用程序-免费开始监控.

一盏木

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: