Vue 3 响应式核心揭秘:掌握 ref、reactive 与 Provide/Inject 的终极指南

关键点

  • Vue 响应式系统:Vue 3 的响应式 API(refreactive)是构建动态应用的基石,提供高效的数据更新和视图同步。
  • 数据传递机制Provide/Inject 实现跨层级组件通信,简化复杂组件树的数据共享。
  • 应用场景:涵盖单值响应式(ref)、对象响应式(reactive)、跨组件数据传递和状态管理。
  • 常见问题:包括响应式丢失、深层嵌套问题、Provide/Inject 的局限性及性能瓶颈。
  • 优化策略:结合组合式 API、TypeScript 和性能优化,确保高效开发。
  • 最新趋势:Vue 3 的响应式系统结合 Pinia、Vite 和可组合性,成为前端开发的核心。

引言

Vue 3 的组合式 API(Composition API)为前端开发带来了革命性的变化,其核心是强大的响应式系统。通过 refreactive,开发者可以轻松管理单值和复杂对象的响应式状态,而 Provide/Inject 提供了一种优雅的方式来实现跨层级组件通信。这些技术极大地简化了状态管理和组件交互,特别适合构建现代化的单页应用(SPA)。

然而,响应式系统的灵活性也带来了挑战。例如,refreactive 的选择、响应式丢失、深层嵌套对象的处理,以及 Provide/Inject 在大型项目中的局限性,都可能让开发者感到困惑。此外,在最新的前端开发中,Vue 3 的响应式系统与 TypeScript、Pinia 和 Vite 的深度整合,要求开发者掌握更高效的开发实践,以应对复杂项目需求。

本文通过构建一个基于 Vue 3 的任务管理应用,全面探讨 refreactiveProvide/Inject 的使用方法、原理和优化策略。我们将覆盖单值响应式、对象响应式、跨组件数据传递、TypeScript 集成、性能优化和可访问性实践,提供丰富的代码示例和场景分析,帮助开发者打造高效、可维护的 Vue 应用。

通过本项目,您将学习到:

  • 响应式 APIrefreactive 的基本用法、区别和最佳实践。
  • Provide/Inject:实现跨层级组件通信,简化状态共享。
  • TypeScript 集成:为响应式数据添加类型安全,提升代码质量。
  • 性能优化:避免响应式丢失、优化深层嵌套和减少重渲染。
  • 可访问性:确保响应式数据对屏幕阅读器友好。

本文面向有经验的开发者,假设您熟悉 HTML、CSS、JavaScript、Vue 3 和 TypeScript 基础知识。


需求分析

在动手编码之前,我们需要明确任务管理应用的功能需求。一个清晰的需求清单能指导开发过程并帮助我们选择合适的响应式技术和数据传递方式。以下是项目的核心需求:

  1. 响应式数据管理
    • 使用 ref 管理单一状态(如任务计数、输入框值)。
    • 使用 reactive 管理复杂对象(如任务列表、用户设置)。
    • 支持动态更新和视图同步。
  2. 跨组件数据传递
    • 使用 Provide/Inject 在组件树中共享全局配置(如主题、用户权限)。
    • 支持嵌套组件访问共享数据。
  3. TypeScript 集成
    • refreactiveProvide/Inject 添加类型注解。
    • 定义接口和类型,确保类型安全。
  4. 性能优化
    • 避免响应式丢失(如解构 reactive 对象)。
    • 优化深层嵌套对象的响应式性能。
    • 减少不必要的组件重渲染。
  5. 可访问性(a11y)
    • 确保动态数据对屏幕阅读器友好。
    • 提供键盘导航支持。
  6. 手机端适配
    • 响应式布局,适配不同屏幕尺寸。
    • 优化触控交互(如点击、滑动)。
  7. 部署
    • 集成到 Vite 项目,部署到 Vercel。
    • 支持 CDN 加速静态资源加载。

需求背后的意义

这些需求覆盖了 Vue 响应式数据传递的核心场景,同时为学习现代前端技术提供了实践机会:

  • 响应式数据:确保数据与视图高效同步,简化开发。
  • 跨组件传递:减少组件耦合,提升代码可维护性。
  • TypeScript 集成:提升代码质量,减少运行时错误。
  • 性能优化:确保应用在复杂场景下保持高效。
  • 可访问性:满足无障碍标准,扩大用户覆盖。
  • 手机端适配:适配移动设备,提升用户体验。

这些需求还为最新的技术趋势提供了实践场景,如 Pinia 的状态管理、Vite 的快速构建和可组合性的普及。


技术栈选择

在实现任务管理应用之前,我们需要选择合适的技术栈。以下是本项目使用的工具和技术,以及选择它们的理由:

  • Vue 3
    核心前端框架,提供组合式 API 和响应式系统,适合构建动态应用。
  • TypeScript
    提供类型安全,增强代码可维护性和 IDE 补全,适合复杂项目。
  • Vite
    构建工具,提供快速的开发服务器和高效的打包能力,符合目前高性能开发趋势。
  • Pinia
    Vue 3 的状态管理库,与响应式 API 深度整合,适合复杂状态管理。
  • Tailwind CSS
    提供灵活的样式解决方案,支持响应式设计。
  • Vercel
    用于部署应用,提供高可用性和全球 CDN 支持。

技术栈优势

  • Vue 3:生态丰富,社区活跃,响应式系统高效。
  • TypeScript:提升代码质量,减少运行时错误。
  • Vite:启动速度快,热更新体验优越。
  • Pinia:轻量、类型友好,适合 Vue 3 项目。
  • Tailwind CSS:简化样式开发,支持响应式设计。
  • Vercel:与 Vue 生态深度集成,部署简单。

项目实现

现在进入核心部分——代码实现。我们将从项目搭建开始,逐步实现响应式数据管理、跨组件数据传递、TypeScript 集成、性能优化和部署。

1. 项目搭建

使用 Vite 创建一个 Vue 3 + TypeScript 项目:

npm create vite@latest task-manager -- --template vue-ts
cd task-manager
npm install
npm run dev

安装必要的依赖:

npm install vue pinia tailwindcss postcss autoprefixer @tanstack/vue-query

初始化 Tailwind CSS:

npx tailwindcss init -p

编辑 tailwind.config.js

/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./index.html",
    "./src/**/*.{vue,js,ts,jsx,tsx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

src/index.css 中引入 Tailwind:

@tailwind base;
@tailwind components;
@tailwind utilities;

2. 组件拆分

我们将应用拆分为以下组件:

  • App:根组件,负责整体布局。
  • TaskList:展示任务列表,支持添加、删除和编辑。
  • TaskInput:处理任务输入,使用 ref 管理输入状态。
  • ThemeSettings:管理全局主题设置,使用 Provide/Inject 共享。
  • AccessibilityPanel:管理可访问性设置。
文件结构
src/
├── components/
│   ├── TaskList.vue
│   ├── TaskInput.vue
│   ├── ThemeSettings.vue
│   └── AccessibilityPanel.vue
├── stores/
│   └── tasks.ts
├── types/
│   └── index.ts
├── App.vue
├── main.ts
└── index.css

3. 响应式数据管理

3.1 使用 ref

src/components/TaskInput.vue

<script setup lang="ts">
import { ref } from 'vue';

const taskName = ref('');
const error = ref('');

const addTask = () => {
  if (!taskName.value.trim()) {
    error.value = '任务名称不能为空';
    return;
  }
  // 触发任务添加逻辑
  error.value = '';
  taskName.value = '';
};
</script>

<template>
  <div class="p-4 bg-white rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4">添加任务</h2>
    <input
      v-model="taskName"
      type="text"
      class="w-full p-2 border rounded-lg mb-2"
      placeholder="输入任务名称"
      aria-label="任务名称"
    />
    <button
      @click="addTask"
      class="px-4 py-2 bg-blue-500 text-white rounded-lg"
    >
      添加
    </button>
    <p v-if="error" class="text-red-500 mt-2">{{ error }}</p>
  </div>
</template>

优点

  • ref 适合管理单一值(如字符串、数字)。
  • 简单直观,易于理解。

避坑

  • 始终通过 .value 访问和修改 ref 值。
  • 避免在模板外解构 ref,否则失去响应式。
3.2 使用 reactive

src/stores/tasks.ts

import { defineStore } from 'pinia';
import { reactive } from 'vue';

interface Task {
  id: number;
  name: string;
  completed: boolean;
}

export const useTaskStore = defineStore('tasks', () => {
  const state = reactive({
    tasks: [] as Task[],
    filter: 'all' as 'all' | 'completed' | 'pending',
  });

  const addTask = (name: string) => {
    state.tasks.push({
      id: Date.now(),
      name,
      completed: false,
    });
  };

  const removeTask = (id: number) => {
    state.tasks = state.tasks.filter(task => task.id !== id);
  };

  const toggleTask = (id: number) => {
    const task = state.tasks.find(task => task.id === id);
    if (task) task.completed = !task.completed;
  };

  return { state, addTask, removeTask, toggleTask };
});

src/components/TaskList.vue

<script setup lang="ts">
import { useTaskStore } from '../stores/tasks';

const store = useTaskStore();
</script>

<template>
  <div class="p-4 bg-white rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4">任务列表</h2>
    <ul>
      <li
        v-for="task in store.state.tasks"
        :key="task.id"
        class="flex items-center justify-between p-2 border-b"
      >
        <span :class="{ 'line-through': task.completed }">{{ task.name }}</span>
        <div>
          <button
            @click="store.toggleTask(task.id)"
            class="px-2 py-1 text-blue-500"
            :aria-label="task.completed ? '标记为未完成' : '标记为已完成'"
          >
            {{ task.completed ? '取消' : '完成' }}
          </button>
          <button
            @click="store.removeTask(task.id)"
            class="px-2 py-1 text-red-500"
            aria-label="删除任务"
          >
            删除
          </button>
        </div>
      </li>
    </ul>
  </div>
</template>

优点

  • reactive 适合管理复杂对象(如数组、嵌套对象)。
  • 自动追踪深层属性变化。

避坑

  • 避免解构 reactive 对象,否则失去响应式:
    // 错误:解构导致失去响应式
    const { tasks } = store.state;
    // 正确:直接使用 store.state.tasks
    
  • 深层嵌套对象需谨慎修改,可能触发不必要重渲染。
3.3 ref vs reactive
  • 选择依据
    • ref:适合单一值或简单状态(如计数器、输入框)。
    • reactive:适合复杂对象或嵌套数据(如表单、列表)。
  • 性能考虑
    • ref.value 访问开销小。
    • reactive 适合深层嵌套,但可能增加追踪开销。

4. Provide/Inject 数据传递

src/App.vue

<script setup lang="ts">
import { provide, ref } from 'vue';
import TaskList from './components/TaskList.vue';
import TaskInput from './components/TaskInput.vue';
import ThemeSettings from './components/ThemeSettings.vue';

interface Theme {
  primaryColor: string;
  darkMode: boolean;
}

const theme = ref<Theme>({
  primaryColor: '#3b82f6',
  darkMode: false,
});

provide('theme', theme);
</script>

<template>
  <div :class="{ 'dark': theme.darkMode }" class="min-h-screen bg-gray-100 dark:bg-gray-900">
    <h1 class="text-3xl font-bold p-4 text-center">任务管理器</h1>
    <div class="grid grid-cols-1 md:grid-cols-2 gap-4 max-w-5xl mx-auto p-4">
      <TaskInput />
      <TaskList />
      <ThemeSettings />
    </div>
  </div>
</template>

src/components/ThemeSettings.vue

<script setup lang="ts">
import { inject, ref } from 'vue';
import type { Ref } from 'vue';

const theme = inject<Ref<{ primaryColor: string; darkMode: boolean }>>('theme');

if (!theme) throw new Error('Theme not provided');

const toggleDarkMode = () => {
  theme.value.darkMode = !theme.value.darkMode;
};
</script>

<template>
  <div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">主题设置</h2>
    <button
      @click="toggleDarkMode"
      class="px-4 py-2 bg-blue-500 text-white rounded-lg"
      :style="{ backgroundColor: theme.primaryColor }"
      aria-label="切换暗黑模式"
    >
      {{ theme.darkMode ? '切换到亮色模式' : '切换到暗黑模式' }}
    </button>
  </div>
</template>

优点

  • Provide/Inject 简化跨层级数据共享。
  • 支持响应式数据,自动更新子组件。

避坑

  • 确保 provide 在父组件中定义,避免子组件访问失败。
  • inject 提供默认值或错误处理:
    const theme = inject<Ref<Theme>>('theme', ref({ primaryColor: '#3b82f6', darkMode: false }));
    

5. TypeScript 集成

src/types/index.ts

export interface Task {
  id: number;
  name: string;
  completed: boolean;
}

export interface Theme {
  primaryColor: string;
  darkMode: boolean;
}

src/stores/tasks.ts(更新):

import { defineStore } from 'pinia';
import { reactive } from 'vue';
import type { Task } from '../types';

export const useTaskStore = defineStore('tasks', () => {
  const state = reactive<{
    tasks: Task[];
    filter: 'all' | 'completed' | 'pending';
  }>({
    tasks: [],
    filter: 'all',
  });

  const addTask = (name: string) => {
    state.tasks.push({
      id: Date.now(),
      name,
      completed: false,
    });
  };

  const removeTask = (id: number) => {
    state.tasks = state.tasks.filter(task => task.id !== id);
  };

  const toggleTask = (id: number) => {
    const task = state.tasks.find(task => task.id === id);
    if (task) task.completed = !task.completed;
  };

  return { state, addTask, removeTask, toggleTask };
});

src/App.vue(更新):

<script setup lang="ts">
import { provide, ref } from 'vue';
import TaskList from './components/TaskList.vue';
import TaskInput from './components/TaskInput.vue';
import ThemeSettings from './components/ThemeSettings.vue';
import type { Theme } from './types';

const theme = ref<Theme>({
  primaryColor: '#3b82f6',
  darkMode: false,
});

provide('theme', theme);
</script>

避坑

  • refreactive 定义明确的类型。
  • 使用 Ref<T> 包装 ref 类型的注入值。
  • 避免 any 类型,确保类型安全。

6. 性能优化

6.1 避免响应式丢失

问题:解构 reactive 对象导致失去响应式。

错误示例

const { tasks } = store.state; // 失去响应式
tasks.push({ id: 1, name: '错误', completed: false }); // 不会触发更新

正确示例

store.state.tasks.push({ id: 1, name: '正确', completed: false }); // 触发更新

解决方案

  • 直接操作 reactive 对象的属性。
  • 使用 toRefs 解构并保留响应式:
    import { toRefs } from 'vue';
    const { tasks } = toRefs(store.state);
    
6.2 优化深层嵌套

src/stores/tasks.ts(更新):

import { reactive, shallowReactive } from 'vue';

export const useTaskStore = defineStore('tasks', () => {
  const state = shallowReactive({
    tasks: [] as Task[],
    filter: 'all' as 'all' | 'completed' | 'pending',
  });

  const addTask = (name: string) => {
    state.tasks.push(reactive({
      id: Date.now(),
      name,
      completed: false,
    }));
  };

  return { state, addTask, removeTask, toggleTask };
});

优点

  • shallowReactive 仅追踪对象顶层属性,减少深层嵌套的开销。
  • 单独为 tasks 数组元素使用 reactive,确保子对象响应式。

避坑

  • 测试深层嵌套对象的更新是否触发视图。
  • 避免对 shallowReactive 对象进行深层修改。
6.3 减少重渲染

使用 computed 优化复杂计算:

src/components/TaskList.vue(更新):

<script setup lang="ts">
import { useTaskStore } from '../stores/tasks';
import { computed } from 'vue';

const store = useTaskStore();
const filteredTasks = computed(() => {
  if (store.state.filter === 'all') return store.state.tasks;
  return store.state.tasks.filter(task =>
    store.state.filter === 'completed' ? task.completed : !task.completed
  );
});
</script>

<template>
  <div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">任务列表</h2>
    <select
      v-model="store.state.filter"
      class="p-2 border rounded-lg mb-4"
      aria-label="过滤任务"
    >
      <option value="all">全部</option>
      <option value="completed">已完成</option>
      <option value="pending">未完成</option>
    </select>
    <ul>
      <li
        v-for="task in filteredTasks"
        :key="task.id"
        class="flex items-center justify-between p-2 border-b"
      >
        <span :class="{ 'line-through': task.completed }">{{ task.name }}</span>
        <div>
          <button
            @click="store.toggleTask(task.id)"
            class="px-2 py-1 text-blue-500"
            :aria-label="task.completed ? '标记为未完成' : '标记为已完成'"
          >
            {{ task.completed ? '取消' : '完成' }}
          </button>
          <button
            @click="store.removeTask(task.id)"
            class="px-2 py-1 text-red-500"
            aria-label="删除任务"
          >
            删除
          </button>
        </div>
      </li>
    </ul>
  </div>
</template>

避坑

  • 使用 computed 缓存复杂计算,避免重复执行。
  • 确保 key 属性唯一,优化列表渲染。

7. 可访问性(a11y)

src/components/AccessibilityPanel.vue

<script setup lang="ts">
import { inject, ref } from 'vue';
import type { Ref } from 'vue';
import type { Theme } from '../types';

const theme = inject<Ref<Theme>>('theme');
if (!theme) throw new Error('Theme not provided');

const highContrast = ref(false);
</script>

<template>
  <div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">可访问性设置</h2>
    <label class="flex items-center space-x-2">
      <input
        type="checkbox"
        v-model="highContrast"
        class="p-2"
        aria-label="启用高对比度模式"
      />
      <span class="text-gray-900 dark:text-white">高对比度模式</span>
    </label>
    <div :class="{ 'bg-black text-white': highContrast }" class="p-2 mt-4 rounded-lg">
      <p>测试文本:{{ highContrast ? '高对比度' : '正常' }}</p>
    </div>
  </div>
</template>

避坑

  • 为动态元素添加 aria-labelaria-live
  • 测试屏幕阅读器(如 NVDA、VoiceOver)对响应式数据的支持。

8. 手机端适配

使用 Tailwind CSS 优化响应式布局:

src/App.vue(更新):

<template>
  <div :class="{ 'dark': theme.darkMode }" class="min-h-screen bg-gray-100 dark:bg-gray-900 p-2 md:p-4">
    <h1 class="text-2xl md:text-3xl font-bold text-center mb-4">任务管理器</h1>
    <div class="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4 max-w-5xl mx-auto">
      <TaskInput />
      <TaskList />
      <ThemeSettings />
      <AccessibilityPanel />
    </div>
  </div>
</template>

避坑

  • 测试不同屏幕尺寸的布局效果。
  • 确保触控区域足够大(至少 48x48 像素)。

9. 集成 Pinia

src/stores/tasks.ts(已包含 Pinia 实现)。

src/components/TaskInput.vue(更新):

<script setup lang="ts">
import { ref } from 'vue';
import { useTaskStore } from '../stores/tasks';

const taskName = ref('');
const error = ref('');
const store = useTaskStore();

const addTask = () => {
  if (!taskName.value.trim()) {
    error.value = '任务名称不能为空';
    return;
  }
  store.addTask(taskName.value);
  error.value = '';
  taskName.value = '';
};
</script>

优点

  • Pinia 提供模块化状态管理,与 reactive 深度整合。
  • 支持 TypeScript 和 DevTools 调试。

避坑

  • 确保 Pinia 状态与组件同步更新。
  • 避免直接修改 Pinia 状态,使用 action 方法。

10. 部署

10.1 构建项目
npm run build
10.2 部署到 Vercel
  1. 注册 Vercel:访问 Vercel 官网 并创建账号。
  2. 新建项目:选择“New Project”。
  3. 导入仓库:将项目推送至 GitHub 并导入。
  4. 配置构建
    • 构建命令:npm run build
    • 输出目录:dist
  5. 部署:点击“Deploy”.

避坑

  • 确保静态资源路径正确(使用相对路径)。
  • 使用 CDN 加速 Tailwind CSS 和其他资源。

常见问题与解决方案

11.1 响应式丢失

问题:解构 reactive 对象或 ref 值导致响应式失效。

解决方案

  • 使用 toRefs 解构 reactive 对象:
    import { toRefs } from 'vue';
    const { tasks } = toRefs(store.state);
    
  • 避免在非响应式上下文中修改 ref
    // 错误
    const name = taskName.value;
    name = '新值'; // 不会触发更新
    // 正确
    taskName.value = '新值';
    

11.2 Provide/Inject 局限性

问题:子组件无法访问父组件的 provide

解决方案

  • 确保 provide 在组件树的上层:
    // App.vue
    provide('key', value);
    
  • inject 提供默认值:
    const value = inject('key', defaultValue);
    

11.3 深层嵌套性能

问题reactive 深层嵌套对象触发过多重渲染。

解决方案

  • 使用 shallowReactive 减少追踪开销:
    const state = shallowReactive({ nested: { a: 1 } });
    
  • 结合 computed 缓存复杂计算:
    const filtered = computed(() => state.tasks.filter(t => t.completed));
    

11.4 TypeScript 错误

问题:类型推断失败或 any 滥用。

解决方案

  • 定义明确的接口:
    interface Task {
      id: number;
      name: string;
      completed: boolean;
    }
    
  • refinject 添加类型:
    const theme = ref<Theme>({ primaryColor: '#3b82f6', darkMode: false });
    const injected = inject<Ref<Theme>>('theme');
    

练习:添加任务过滤器

为巩固所学,设计一个练习:为任务管理应用添加动态过滤器。

需求

  • 支持按关键字搜索任务。
  • 动态更新任务列表。
  • 使用 refcomputed 实现响应式过滤。

实现步骤

  1. 添加搜索输入

src/components/TaskList.vue(更新):

<script setup lang="ts">
import { useTaskStore } from '../stores/tasks';
import { ref, computed } from 'vue';

const store = useTaskStore();
const searchQuery = ref('');

const filteredTasks = computed(() => {
  const query = searchQuery.value.toLowerCase();
  return store.state.tasks.filter(task =>
    task.name.toLowerCase().includes(query)
  );
});
</script>

<template>
  <div class="p-4 bg-white dark:bg-gray-800 rounded-lg shadow">
    <h2 class="text-xl font-bold mb-4 text-gray-900 dark:text-white">任务列表</h2>
    <input
      v-model="searchQuery"
      type="text"
      class="w-full p-2 border rounded-lg mb-4"
      placeholder="搜索任务"
      aria-label="搜索任务"
    />
    <ul>
      <li
        v-for="task in filteredTasks"
        :key="task.id"
        class="flex items-center justify-between p-2 border-b"
      >
        <span :class="{ 'line-through': task.completed }">{{ task.name }}</span>
        <div>
          <button
            @click="store.toggleTask(task.id)"
            class="px-2 py-1 text-blue-500"
            :aria-label="task.completed ? '标记为未完成' : '标记为已完成'"
          >
            {{ task.completed ? '取消' : '完成' }}
          </button>
          <button
            @click="store.removeTask(task.id)"
            class="px-2 py-1 text-red-500"
            aria-label="删除任务"
          >
            删除
          </button>
        </div>
      </li>
    </ul>
  </div>
</template>

练习目标

通过此练习,您将学会结合 refcomputed 实现动态响应式过滤,增强应用交互性。


注意事项

  • 响应式选择:根据数据类型选择 refreactive
  • Provide/Inject:明确注入键名,避免冲突。
  • TypeScript:定义清晰的类型接口,避免 any
  • 性能优化:使用 shallowReactivecomputed 减少开销。
  • 学习建议:参考 Vue 3 文档Pinia 文档Vite 文档.

结语

通过这个任务管理应用项目,您完整体验了 Vue 3 的 refreactiveProvide/Inject 的使用流程,掌握了响应式数据管理、跨组件数据传递、TypeScript 集成和性能优化的关键技术。这些技能将帮助您构建高效、可维护的 Vue 应用,满足目前前端开发需求。

Vue 的响应式系统将进一步融入 Pinia、Vite 和可组合性趋势。希望您继续探索高级功能,如自定义 hooks、服务器端渲染和多模态交互,打造创新的用户体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

EndingCoder

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值