React拖动排序组件的实现方法

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文详细阐述了如何在React中实现拖动排序组件,包括对拖放事件的监听、状态管理、列表渲染、性能优化和兼容性处理等关键技术点。React拖动排序组件通过组件化构建用户界面,实现用户通过拖放操作直观地改变列表项顺序,提升用户体验。此外,本项目源码示例可帮助开发者深入理解理论与实践结合,掌握在React项目中应用拖动排序的技巧。 React-react拖动排序组件

1. React拖动排序组件概述

拖动排序组件是前端开发中经常使用的交互组件,它允许用户通过简单的拖放操作来对列表项进行重新排序。在React环境下实现拖动排序组件,需要深入理解拖放事件的机制、状态管理、列表渲染技巧以及性能优化等多个方面的知识。

拖动排序组件的核心功能是提供一个可操作的UI,以实现列表项的排序。为了使这个过程既流畅又高效,开发者需要对React生命周期、事件处理、以及虚拟DOM等概念有充分的掌握。

本章将从拖动排序组件的基本功能和使用场景开始介绍,并为接下来的章节奠定基础。我们将探讨组件设计的基本原则、性能考量以及如何满足跨浏览器的兼容性需求。通过本章内容,读者将获得对于构建高效、可维护和跨浏览器兼容的拖动排序组件的初步理解。

2. 拖放事件监听与处理

2.1 拖放事件的种类和触发时机

2.1.1 拖放事件基本概念

拖放是一种常见的交互方式,允许用户通过鼠标或触摸操作移动界面上的元素。在Web开发中,拖放操作基于一系列的拖放事件来实现,这些事件与用户的操作密切相关。拖放事件包括拖动前的准备、拖动过程中的更新、以及放置操作完成后的结束。常见的拖放事件包括 dragstart drag dragend dragover dragenter dragleave drop 等。

2.1.2 常见拖放事件和应用场景

不同的拖放事件在不同的操作阶段被触发,它们的应用场景也有所差异: - dragstart :当用户开始拖动一个元素时触发。 - drag :在拖动过程中,每隔几百毫秒就会触发一次,可以用来更新拖动元素的外观。 - dragend :当拖动操作结束后触发,无论用户是放置元素还是取消操作。 - dragover :当拖动元素在可放置的目标上移动时触发。 - dragenter :当拖动元素第一次进入目标元素时触发。 - dragleave :当拖动元素离开目标元素时触发。 - drop :当用户在目标元素上释放拖动元素时触发。

2.2 拖放事件的捕获与冒泡机制

2.2.1 事件捕获与冒泡的基本原理

JavaScript事件流包括事件捕获和事件冒泡两个阶段。事件捕获是从外层到内层,先到达目标元素的事件处理器。而事件冒泡则相反,事件从目标元素的处理器开始,向外层元素传播。在拖放事件中,不同的浏览器实现可能会使事件在捕获和冒泡阶段的顺序不同,因此需要正确处理这两种机制以确保拖放操作的正确性和一致性。

2.2.2 在React中处理事件捕获与冒泡

在React中,可以使用事件处理函数来捕获和处理拖放事件。React的事件绑定可以通过 addEventListener 或者在JSX中使用 onDragStart onDrag 等方式绑定。在React组件中处理拖放事件时,可以通过调用 event.stopPropagation() 方法来阻止事件进一步冒泡,或者使用 event.preventDefault() 方法来阻止事件的默认行为。例如:

function handleDragStart(event) {
  // 阻止默认行为
  event.preventDefault();
  // 其他拖动开始时的逻辑...
}

function App() {
  return (
    <div
      onDragStart={handleDragStart}
      draggable="true"
    >
      拖动我!
    </div>
  );
}

2.3 实现自定义拖放逻辑

2.3.1 创建可拖动元素

要创建一个可拖动的元素,你需要为该元素添加 draggable="true" 属性,并为其绑定适当的拖动事件处理函数。下面是一个简单的例子,展示了如何创建一个可拖动的div元素:

class Draggable extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isDragging: false
    };
  }

  handleDragStart = (event) => {
    event.dataTransfer.setData('text/plain', 'This text is being dragged');
    this.setState({ isDragging: true });
  }

  render() {
    return (
      <div 
        draggable="true"
        onDragStart={this.handleDragStart}
        style={{ opacity: this.state.isDragging ? 0.5 : 1 }}
      >
        {this.props.children}
      </div>
    );
  }
}

function App() {
  return (
    <Draggable>
      <p>我是可拖动的元素!</p>
    </Draggable>
  );
}

2.3.2 实现放置逻辑和数据更新

实现放置逻辑需要处理 drop 事件,它在元素被释放到目标位置时触发。在 drop 事件的处理函数中,我们通常需要从事件对象中读取数据,并更新UI或状态以反映新的信息。

handleDrop = (event) => {
  const data = event.dataTransfer.getData('text/plain');
  console.log('Dropped data: ', data);
  // 更新状态或其他逻辑...
}

// 在渲染方法中添加drop事件监听器
render() {
  return (
    <div 
      draggable="true"
      onDragStart={this.handleDragStart}
      onDrop={this.handleDrop}  // 添加drop事件监听器
    >
      {this.props.children}
    </div>
  );
}

在实际应用中,可能需要更复杂的逻辑来处理不同元素之间的拖放关系,以及在放置后对列表或数据集进行操作。例如,拖动排序组件可能需要在放置时调用一个函数来更新列表状态,从而改变元素的位置。

handleDrop = (event) => {
  const data = event.dataTransfer.getData('text/plain');
  const newList = moveItemInArray(this.state.list, data);
  this.setState({ list: newList });
}

moveItemInArray = (list, item) => {
  // 逻辑来移动数组中的项目...
}

通过这些步骤,我们可以实现一个基本的拖放功能,并且能够根据需要扩展以处理更复杂的情况。在下一章节中,我们将深入了解如何使用状态管理来进一步优化拖动排序的实现。

3. 状态管理的实现

在拖动排序组件的开发中,状态管理扮演着至关重要的角色。它不仅关系到组件的渲染效率,还直接影响到用户交互的流畅性。本章将探讨状态管理在拖动排序中的作用,重点分析Redux和React Hooks在状态管理中的应用。

3.1 状态管理在拖动排序中的作用

3.1.1 状态管理基本概念

在React中,组件的状态(state)是驱动组件渲染和行为的核心。状态管理涉及状态的创建、更新、传递和同步。由于拖动排序组件往往涉及复杂的用户交互和动态列表渲染,合理管理状态变得尤为重要。

3.1.2 状态管理与组件渲染的关系

组件的状态变化会触发重新渲染。在拖动排序中,排序逻辑的变更需要实时反映在UI上,这就要求状态更新后能够高效地重新渲染相关组件,避免不必要的性能损耗。因此,选择合适的状态管理方案对于提升用户体验至关重要。

3.2 使用Redux进行状态管理

3.2.1 Redux基本原理和工作流程

Redux是一种流行的JavaScript状态容器,适用于管理复杂的组件状态。它通过单一数据源(store)、不可变数据结构和纯函数(reducer)来实现状态管理。

Redux工作流程
  1. 初始化Store :应用的初始状态被创建,并存储在store中。
  2. 派发Action :当事件发生时,组件会派发一个action到store。
  3. Action触发Reducer :reducer是一个根据当前state和action计算新state的纯函数。
  4. 状态更新 :store接收到新state后更新其内部状态,并通知订阅者。
  5. 组件读取状态 :组件通过connect方法或useSelector Hook从store中读取所需状态,并进行渲染。

3.2.2 结合Redux管理拖动排序状态

在拖动排序组件中,Redux可以用于管理拖动元素的索引、列表顺序等状态。每当用户完成一个拖动动作,组件就会派发一个action来更新状态。

Redux状态管理示例
// store.js
import { createStore } from 'redux';
import { sortReducer } from './reducers';

const store = createStore(sortReducer);

export default store;

// actions.js
export const moveItem = (dragIndex, hoverIndex) => ({
  type: 'MOVE_ITEM',
  dragIndex,
  hoverIndex
});

// reducers.js
const initialState = {
  items: [], // 初始列表数据
};

function sortReducer(state = initialState, action) {
  switch (action.type) {
    case 'MOVE_ITEM':
      // 根据action更新items状态
      // ...
      return {
        ...state,
        items: updatedItems,
      };
    default:
      return state;
  }
}

export default sortReducer;

// DragSortComponent.jsx
import React from 'react';
import { connect } from 'react-redux';
import { moveItem } from './actions';

const DragSortComponent = ({ items, moveItem }) => {
  // 渲染列表,处理拖动逻辑
  // ...
  return (
    // React元素
  );
};

const mapStateToProps = (state) => ({
  items: state.items,
});

const mapDispatchToProps = {
  moveItem,
};

export default connect(mapStateToProps, mapDispatchToProps)(DragSortComponent);

3.3 使用React Hooks优化状态管理

3.3.1 Hooks在函数组件中的应用

随着React Hooks的推出,函数组件也能进行复杂的状态管理。Hooks如 useState useReducer 提供了简洁的状态管理方式。

3.3.2 使用useState和useReducer简化状态逻辑

useState的使用

useState Hook允许我们在函数组件中添加状态。每次状态更新时,组件都会重新渲染。

useReducer的使用

useReducer Hook类似于Redux中的reducer,它更适合处理复杂的状态逻辑。

示例:使用useState和useReducer管理拖动排序状态
// DragSortHookComponent.jsx
import React, { useState, useReducer } from 'react';

function dragReducer(state, action) {
  // 根据action类型更新状态
  // ...
}

function DragSortHookComponent() {
  const [items, setItems] = useState(initialItems);
  const [state, dispatch] = useReducer(dragReducer, { items });

  // 绑定事件处理函数,触发dispatch更新状态
  // ...

  return (
    // React元素,根据state渲染列表
  );
}

export default DragSortHookComponent;

在本章中,我们详细介绍了状态管理在拖动排序组件中的作用,分析了Redux和React Hooks各自的优势和适用场景。下一章将围绕动态列表渲染技巧展开讨论,揭示如何高效地实现列表更新和优化渲染性能。

4. 动态列表渲染技巧

在构建复杂的用户界面时,动态列表渲染是一项常见但又非常关键的功能。它涉及在用户界面上显示一系列动态数据,如列表、表格、卡片等,并且这些数据会随着用户交互或数据更新而变化。在本章中,我们将深入探讨实现高效动态列表渲染的技巧,特别是在React环境下。

4.1 列表渲染的基本原则

列表渲染在Web应用中无处不在,从简单的数据列表到复杂的网格系统。在React中,列表通常通过 map 方法遍历数组来实现。高效地渲染列表能够极大提升用户体验,同时减少不必要的资源消耗。

4.1.1 列表渲染与性能优化

React虚拟DOM机制允许开发者不必关心DOM的具体操作,框架会自动比较新旧虚拟DOM的差异,并以此更新真实的DOM。但在列表渲染时,不恰当的操作可能导致性能瓶颈。具体来说,当列表数据更新时,如果列表的每一项都重新渲染,那么即使是无变化的元素,也可能会被不必要地更新,造成性能浪费。

性能优化的一个重要策略是使用React的 key 属性。它帮助React识别哪些元素发生了变化,比如被添加或被删除,从而只对变化的部分进行操作。

4.1.2 React虚拟DOM机制与列表渲染

React虚拟DOM机制是高效列表渲染的关键。它通过一个简单的算法将虚拟DOM树中的变化高效地应用到真实DOM中。当开发者调用 setState forceUpdate 方法时,React首先会创建一个新的虚拟DOM树,然后将其与上一次的虚拟DOM树进行比较,计算出最小的差异集,最后将这些差异应用到真实DOM上。

4.1.3 使用key属性优化列表更新

在列表渲染中使用 key 属性是一个优化手段,可以为每个列表项提供一个稳定的唯一标识。这样,React可以利用这个标识来匹配旧树和新树中的对应元素,减少不必要的DOM操作。

示例代码:

class ListOfItems extends React.Component {
  render() {
    return (
      <ul>
        {this.props.items.map((item) => (
          <li key={item.id}>
            {item.text}
          </li>
        ))}
      </ul>
    );
  }
}

在这个示例中,每个 <li> 元素都被赋予了一个 key 属性,它来自列表项对象的 id 属性。这有助于React在列表更新时高效地识别哪些元素发生了变化。

4.1.4 如何合理地使用key属性

在为列表项分配 key 属性时,应当注意以下几点: - key 属性应该是稳定的,即在列表渲染过程中,相同的元素应始终拥有相同的 key 。 - key 不应该使用数组索引,除非你的列表不会重新排序。因为索引作为 key 在元素位置变动时会导致性能问题。 - 如果列表项没有稳定的ID,你可能需要添加额外的属性或逻辑来确保 key 的唯一性。

4.1.5 React虚拟DOM机制与列表渲染的关系

如前所述,React的虚拟DOM机制是列表性能优化的核心。合理的 key 使用不仅有助于优化更新效率,还可以减少因更新导致的渲染波动,提升用户体验。开发者应当深入理解虚拟DOM的工作原理,以便更有效地进行性能优化。

4.2 使用key属性优化列表更新

在React中, key 不仅是一个标识符,它还扮演了优化React虚拟DOM算法性能的重要角色。在列表渲染中合理使用 key 属性,能够极大提升性能。

4.2.1 key属性的作用和要求

key 属性是React中用于追踪列表中元素身份的一个特殊属性。它的存在可以让React在进行列表元素的更新时,更加高效地识别哪些元素发生了变化。

当一个元素的 key 发生变化时,React会将其视为一个新的元素,并且会重新创建这个元素,以及之后的所有元素。如果一个元素的 key 未发生变化,React则会保留该元素,仅更新其内容。

4.2.2 如何合理地使用key属性

合理地使用 key 属性,关键是要保证它对于列表中每个元素来说都是唯一且稳定的。以下是关于 key 的一些实用建议:

  • 避免使用索引作为key。 当列表元素更新时,如果使用索引作为 key ,元素的 key 可能会随着元素在列表中的位置变化而变化,导致不必要的元素重新创建和删除,从而影响性能。
  • 使用稳定且唯一的ID。 如果每个列表项都有一个唯一的ID属性,这是一个理想的 key 。这样无论列表如何变化, key 都保持不变。
  • 没有唯一ID时,创建一个。 如果列表项没有唯一的属性或ID,开发者需要创建一个。比如,可以在列表项对象中添加一个自增的 id 属性,或者使用列表项内容生成一个哈希值作为 key

示例:

// 假设我们有一个待办事项列表,每个任务都有一个title和一个id。
class TodoList extends React.Component {
  render() {
    return (
      <ul>
        {this.props.tasks.map((task) => (
          <li key={task.id}>
            {task.title}
          </li>
        ))}
      </ul>
    );
  }
}

在这个例子中,每个 <li> 元素都有一个与之对应的 task.id 作为 key ,保证了列表项的稳定性。

4.3 实现动态增删改查功能

动态列表渲染的常见需求是实现增、删、改、查功能。开发者需要在React组件中编写相应的事件处理函数来响应用户的操作。

4.3.1 事件处理函数实现增删改查

添加元素: 用户点击添加按钮时,触发一个函数来添加新元素到数据列表中。

class ListWithCRUD extends React.Component {
  state = { items: [] };

  handleAddItem = (text) => {
    const newItem = { id: Date.now(), text };
    this.setState((prevState) => ({
      items: [...prevState.items, newItem],
    }));
  };

  render() {
    const { items } = this.state;
    return (
      <div>
        {items.map((item) => (
          <Item key={item.id} {...item} />
        ))}
        <AddItemForm onAddItem={this.handleAddItem} />
      </div>
    );
  }
}

在这个例子中, handleAddItem 函数在用户提交表单时被调用,并将新项目添加到状态中。

删除元素: 用户点击删除按钮时,触发一个函数来从数据列表中移除对应的元素。

handleRemoveItem = (id) => {
  this.setState((prevState) => ({
    items: prevState.items.filter((item) => item.id !== id),
  }));
}

这个函数通过过滤掉具有指定 id 的项目来删除一个列表项。

修改元素: 用户编辑列表项并提交更改时,触发一个函数来更新列表项内容。

handleUpdateItem = (id, newText) => {
  this.setState((prevState) => ({
    items: prevState.items.map((item) =>
      item.id === id ? { ...item, text: newText } : item
    ),
  }));
}

查询元素: 提供搜索框让用户输入搜索条件,触发过滤函数来显示符合搜索条件的列表项。

handleSearch = (searchText) => {
  this.setState((prevState) => ({
    items: prevState.items.filter((item) =>
      item.text.toLowerCase().includes(searchText.toLowerCase())
    ),
  }));
}

4.3.2 动态列表状态同步更新

为了确保列表状态与UI的同步更新,开发者需要合理管理组件状态。在React中,任何状态的更新都应该通过 setState useState 钩子来处理,以确保组件的重新渲染。

例如,在上文的 handleAddItem handleRemoveItem 函数中,通过调用 setState 来更新组件状态,从而触发UI的重新渲染。同样,通过 useState 钩子可以实现类似的效果。

示例代码:

// 使用useState实现列表的增加功能
const [items, setItems] = useState([]);

const handleAddItem = (item) => {
  setItems([...items, item]);
};

在实现动态增删改查功能时,开发者需要注意保持数据的不可变性。更新状态时,应创建新的数组或对象,而不是直接修改现有状态。这有助于避免一些常见的bug,并且确保React可以正确地追踪和渲染组件。

通过这些方法,我们可以实现列表的动态更新,并且确保UI能够与数据状态同步更新,提供流畅的用户体验。

5. 性能优化方法

5.1 性能优化的重要性

性能优化是任何软件产品开发周期中不可或缺的一部分,尤其在前端开发中,用户可直接感知到界面的流畅度和交互的响应速度,因此性能优化显得尤为重要。本小节将介绍性能优化的常见表现形式以及性能优化的一些基本原则。

5.1.1 性能瓶颈的常见表现

在前端项目中,性能瓶颈可能会表现为以下几种情况:

  • 用户界面响应缓慢,存在明显的卡顿现象。
  • 高频率地触发重绘和回流操作,导致浏览器CPU占用率高。
  • 内存使用量异常,特别是在复杂交互或长列表渲染时。
  • 频繁的DOM操作导致页面重排重绘,降低用户交互体验。

5.1.2 性能优化的基本原则

要达到良好的性能优化效果,可以遵循以下基本原则:

  • 避免不必要的计算 :在组件更新过程中,应避免执行重复且不必要的计算。
  • 减少重绘和回流 :尽量减少对DOM的操作,使用CSS3动画替代JavaScript动画。
  • 优化内存使用 :合理管理内存,避免内存泄漏。
  • 分批处理数据 :在处理大量数据时,使用分页或虚拟滚动技术。
  • 懒加载 :对于非首屏的内容,使用懒加载技术延时加载,减少初次加载的资源。

5.2 React组件的性能优化策略

在React中,性能优化可以从多个层面进行,包括利用React自身提供的生命周期方法和高阶组件。

5.2.1 shouldComponentUpdate生命周期方法

shouldComponentUpdate 生命周期方法允许开发者手动控制组件是否重新渲染。它会在接收到新的props或state时被调用。根据返回值(true或false),React将决定是否继续更新组件。

class MyComponent extends React.Component {
  shouldComponentUpdate(nextProps, nextState) {
    // 示例逻辑,只在特定条件时允许更新
    return nextProps.value !== this.props.value;
  }
}

5.2.2 使用PureComponent或React.memo

React提供了一个名为 PureComponent 的抽象组件类,它通过浅层比较props和state来实现shouldComponentUpdate的默认行为,减少不必要的渲染。类似的, React.memo 是一个高阶组件,用于函数组件,作用类似。

import React, { memo } from 'react';

const MyComponent = memo(function MyComponent(props) {
  // 组件的渲染逻辑
});

5.3 拖动排序组件性能优化实例

当涉及到拖动排序功能时,性能优化同样重要。以下是一些实现性能优化的实例。

5.3.1 优化拖动响应速度

拖动响应速度优化可以通过以下方法实现:

  • 防止默认事件 :在拖动时,可以禁止默认的滚动和缩放行为。
  • 使用requestAnimationFrame :在拖动事件处理中使用 requestAnimationFrame 以获得更加平滑的动画效果。

5.3.2 减少不必要的DOM操作

拖动排序过程中,减少DOM操作的频率可显著提高性能:

  • 批量更新 :在拖动结束后再进行DOM更新,而不是每次移动时都进行DOM操作。
  • 虚拟DOM :React的虚拟DOM机制在处理复杂的DOM更新时能够有效减少实际DOM操作的次数。

在本章节的后续部分,我们将深入探讨React中的性能优化技术,并通过实际案例来展示如何将这些原则和技术应用于拖动排序组件中。通过减少不必要的渲染和DOM操作,提升组件的运行效率,最终达到优化用户交互体验的目的。

6. 跨浏览器兼容性解决方案

6.1 浏览器兼容性问题分析

6.1.1 浏览器兼容性问题的产生

在构建React拖动排序组件时,开发者可能面临多种浏览器兼容性问题。这些兼容性问题可能源自浏览器的不同版本,不同的渲染引擎(如Blink, Gecko, WebKit等),以及浏览器对Web标准的支持程度差异。此外,老旧的浏览器可能无法支持现代JavaScript语法,或者对拖放API有不同的实现和行为。开发者在编写代码时,需要充分考虑到这些差异性,以确保组件能够在各种环境下稳定运行。

6.1.2 拖放事件的浏览器兼容性对比

例如,拖放API在不同的浏览器中可能有不同的前缀或者API差异。一些浏览器支持 ondragstart 事件,而其他浏览器则可能要求使用 ondragstart="" 的形式。此外,一些浏览器对拖放事件的支持是有限的,或者在某些特定条件下才支持拖放功能。为了提高兼容性,开发者可能需要编写额外的检测代码,以确保在不同浏览器中均能正确触发事件。

6.2 兼容性问题的解决方案

6.2.1 polyfill技术的应用

为了提高兼容性,开发者可以使用polyfill技术。Polyfill是一段代码,它能够把一些新的JavaScript特性或API引入到旧的浏览器环境中。例如, core-js 就是一个流行的JavaScript库,它提供了对最新ECMAScript标准的兼容实现。开发者可以通过引入polyfill库来为旧浏览器提供支持新JavaScript特性的能力,从而解决兼容性问题。

6.2.2 为不同浏览器提供定制化功能

另一种方法是为不同的浏览器提供定制化的功能实现。这通常意味着需要对代码进行条件检测,根据浏览器类型或版本提供不同的功能实现。例如,对于那些不支持标准拖放API的浏览器,开发者可以考虑使用鼠标事件模拟拖放行为,虽然这可能会增加开发的复杂度和代码量。此外,通过使用PostCSS或Babel等工具,可以将新语法转译为旧语法,从而使代码在不支持新特性的浏览器中也能运行。

6.3 兼容性测试与维护

6.3.1 自动化测试工具的选择和使用

为了确保组件在各种浏览器中都能正常工作,进行彻底的兼容性测试是必不可少的。开发者可以使用像Selenium、Nightwatch.js、Cypress等自动化测试工具,它们能够帮助开发者编写脚本,自动在不同的浏览器和版本中运行测试。自动化测试不仅可以提高测试效率,还可以持续监控新版本浏览器中可能出现的兼容性问题。

6.3.2 持续集成(CI)与兼容性维护策略

结合持续集成(Continuous Integration, CI)工具,可以进一步提高兼容性维护的效率。将测试流程集成到开发流程中,每当代码更新后,CI工具会自动运行测试套件,并提供反馈。这不仅有助于开发者快速发现和修复兼容性问题,还可以为最终用户提供更加稳定的软件体验。对于拖动排序组件,这可能包括定期检查最新的浏览器版本,确保兼容性,并更新polyfills以适应新的环境。

通过上述的分析,我们可以看到,要实现跨浏览器的兼容性解决方案,需要从代码层面、测试层面以及长期的维护策略上全面考虑。这样的策略不仅为现有用户提供稳定的体验,也为未来的浏览器和环境变更做好了准备。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本文详细阐述了如何在React中实现拖动排序组件,包括对拖放事件的监听、状态管理、列表渲染、性能优化和兼容性处理等关键技术点。React拖动排序组件通过组件化构建用户界面,实现用户通过拖放操作直观地改变列表项顺序,提升用户体验。此外,本项目源码示例可帮助开发者深入理解理论与实践结合,掌握在React项目中应用拖动排序的技巧。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值