Vue3 学习笔记--基础部分

1、介绍

Vue 是一套用于构建用户界面的 渐进式框架

渐进式框架的意思就是,我们可以在项目中一点点的引入和使用 Vue

而不一定要使用 Vue 来开发整个项目

Vue 的本质就是一个 JavaScript 库

1.1 CDN 引入

我们在使用 Vue 时,需要将其添加到项目中

对于初学者,可以使用 CDN 引入

CDN 是一种通过互联网相互连接的电脑网络系统

利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件

发送给用户,来提供高性能、可扩展性及低成本的网络内容传递给用户

引入方式如下:

<script src="https://unpkg.com/vue@next"></script>

1.2 命令式编程和声明式编程

命令式编程关注的是 how to do

声明式编程关注的是 what to do,由框架完成 how 的过程

Vue 就属于声明式编程

Vue.js 的核心是一个允许采用简洁的模板语法来声明式地将数据渲染进 DOM 的系统


2、模板

  <div id="app"></div>

  <template id="my-app">
    <h2>{{message}}</h2>
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: 'Hello World !'
        }
      },
      methods: {
        // 定义方法
      }
    }

    Vue.createApp(App).mount('#app');
  </script>

下面来介绍一下模板中的内容

2.1 template

一个字符串模板,用作 component 实例的标记

模板将会 替换 所挂载元素的 innerHTML

挂载元素的原有内容都将被忽略,除非模板中存在通过插槽分发的内容

分离写法和 Vue2 相同

传入的时候加 # 是因为,如果值以 # 开始,那么它将被用作 querySelector

并且使用 匹配元素 innerHTML 作为模板字符串

2.2 data

类型:Function

该函数返回组件实例的 data 对象

注:data 中返回的对象会被 Vue 的响应式系统劫持

       之后对该对象的修改或者访问都会在劫持中被处理

2.3 methods

类型:{ [ key:string ]:Function }

通常我们会在 methods 里面定义很多方法

这些方法可以被绑定到 template 模板中

我们可以直接使用 this 关键字访问 data 中返回的对象的属性

因为方法中的 this 自动绑定为组件实例

注意:methods 中不能使用箭头函数,理由是箭头函数绑定了父级作用域的上下文

所以 this 将不会按照期望指向组件实例

2.4 Mustache

也称双大括号语法,数据绑定最常见的形式

Mustache 中不仅可以是 data 中的属性,也可以是一个 JavaScript 中的表达式


3、常见指令

3.1 v-once

只渲染元素和组件一次

随后的重新渲染,元素/组件及其所有的子节点将被视为静态内容并跳过

这可以用于优化更新性能

用法如下:

<!-- 单个元素 -->
<span v-once>This will never change: {{msg}}</span>
<!-- 有子元素 -->
<div v-once>
  <h1>comment</h1>
  <p>{{msg}}</p>
</div>
<!-- 组件 -->
<my-component v-once :comment="msg"></my-component>
<!-- `v-for` 指令 -->
<ul>
  <li v-for="i in list" v-once>{{i}}</li>
</ul>

3.2 v-text

更新元素的文本内容

  <span v-text="message"></span>
  <!-- 等价于 -->
  <span>{{message}}</span>

3.3 v-html

更新元素的 innerHTML

  <div id="app"></div>

  <template id="my-app">
    <div>{{message}}</div>
    <div v-html="message"></div>
  </template>
  
  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: '<span style="color:pink;">Hello World !</span>'
        }
      }
    }
    Vue.createApp(App).mount('#app');
  </script>

页面效果如图:

3.4 v-pre

跳过这个元素和它的子元素的编译过程

可以用来显示原始的 Mustache 标签

跳过大量没有指令的节点会加快编译

  <template id="my-app">
    <h2 v-pre>{{message}}</h2>
  </template>

页面效果如图:

3.5 v-cloak

可以隐藏未编译的 Mustache 标签直到组件实例准备完毕

  <style>
    [v-cloak] {
      display: none;
    }
  </style>

  <template id="my-app">
    <h2 v-cloak>{{message}}</h2>
  </template>

只有在编译结束之后 h2 里面的内容才会显示出来

3.6 v-bind

动态地绑定一个或多个 attribute,或一个组件 prop 到表达式

缩写为 :

用法如下:

<!-- 绑定 attribute -->
<img v-bind:src="imageSrc" />

<!-- 动态 attribute 名 -->
<button v-bind:[key]="value"></button>

<!-- 缩写 -->
<img :src="imageSrc" />

<!-- 动态 attribute 名缩写 -->
<button :[key]="value"></button>

<!-- 内联字符串拼接 -->
<img :src="'/path/to/images/' + fileName" />

<!-- class 绑定 -->
<div :class="{ red: isRed }"></div>
<div :class="[classA, classB]"></div>
<div :class="[classA, { classB: isB, classC: isC }]">
  <!-- style 绑定 -->
  <div :style="{ fontSize: size + 'px' }"></div>
  <div :style="[styleObjectA, styleObjectB]"></div>

  <!-- 绑定一个全是 attribute 的对象 -->
  <div v-bind="{ id: someProp, 'other-attr': otherProp }"></div>

  <!-- prop 绑定。"prop" 必须在 my-component 声明 -->
  <my-component :prop="someThing"></my-component>

  <!-- 通过 $props 将父组件的 props 一起传给子组件 -->
  <child-component v-bind="$props"></child-component>

  <!-- XLink -->
  <svg><a :xlink:special="foo"></a></svg>
</div>

3.6.1 绑定 HTML Class

对象语法

我们可以传给 v-bind:class 一个对象,以动态地切换 class

<div :class="{ active: isActive }"></div>

所以上面 active 这个 class 是否存在 取决于 isActive 的值是否为 true

此外,v-bind:class 也可以与普通的 class 属性共存

如下:

<div
  class="static"
  :class="{ active: isActive, 'text-danger': hasError }"
></div>
data() {
  return {
    isActive: true,
    hasError: false
  }
}

渲染结果为:

<div class="static active"></div>

其中 static 就是静态绑定的 class,后面两个 class 则是动态绑定的

注:我们可以看到 text-danger 外面加了引号,短横线拼接的字符串就需要加引号

数组语法

我们可以把一个数组传给 v-bind:class,以应用一个 class 列表

<div :class="[activeClass, errorClass]"></div>
data() {
  return {
    activeClass: 'active',
    errorClass: 'text-danger'
  }
}

渲染结果为:

<div class="active text-danger"></div>

3.6.2 绑定内联样式

对象语法

直接上代码

<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>

也可以像下面这样写:

<div :style="{ color: activeColor, 'font-size': fontSize + 'px' }"></div>

css 的属性名可以用驼峰式和短横线分割来命名,值得注意的是后者要加引号

data() {
  return {
    activeColor: 'red',
    fontSize: 30
  }
}

我们也可以把上面两个样式合并到一个对象中,使模板更加清晰

<div :style="styleObject"></div>
data() {
  return {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
}

数组语法

v-bind:style 的数组语法可以将多个样式对象应用到同一个元素上:

<div :style="[baseStyles, overridingStyles]"></div>

3.7 v-on

用于监听 DOM 事件

缩写为 @

基本用法:

<a v-on:click="doSomething"> ... </a>

如果希望一个元素绑定多个事件,可以传入一个对象 

修饰符:

  • .stop - 调用 event.stopPropagation()
  • .prevent - 调用 event.preventDefault()
  • .capture - 添加事件侦听器时使用 capture 模式
  • .self - 只有当事件是从侦听器绑定的元素本身触发时才触发回调
  • .{ keyAlias } - 仅当事件是从特定键触发时才触发回调
  • .once - 只触发一次回调
  • .left - 只当点击鼠标左键时触发
  • .right - 只当点击鼠标右键时触发
  • .middle - 只当点击鼠标中键时触发
  • .passive - { passive: true } 模式添加侦听器

v-on 的参数传递问题 ?


4、条件渲染

4.1 基本用法

<h1 v-if="awesome">Vue is awesome!</h1>
<!-- v-else-if="" -->
<h1 v-else>Oh no 😢</h1>

条件为 true 时才会渲染

4.2 在 template 元素上使用

因为 v-if 是一个指令,所以必须将它添加到一个元素上

但是如果想切换多个元素呢

<template v-if="ok">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

4.3 v-show

用于条件性展示元素的另一种方式

<h1 v-show="ok">Hello!</h1>

4.4 v-show 和 v-if 的区别

首先,在用法上的区别

v-show 是不支持和 template 一起使用

v-show 不可以和 v-else 一起使用

其次,本质的区别是

v-show 元素无论是否需要显示到浏览器上,它的 DOM 实际都是有渲染的

只是通过 CSS 的 display 属性来进行切换

v-if 条件为 false 时,其对应的元素不会被渲染到 DOM 中

在实际开发中,应该如何进行选择呢?

如果我们的元素需要在显示和隐藏之间频繁的切换,那么使用 v-show
如果不会频繁的发生切换,那么使用 v-if

5、列表渲染

5.1 v-for 的基本使用

  <div id="app"></div>

  <template id="my-app">
    <h2>电影列表</h2>
    <!-- 遍历数组 -->
    <ul>
      <li v-for="(movie,index) in movies">{{index+1}}.{{movie}}</li>
    </ul>
    <!-- 遍历对象 -->
    <h2>个人信息</h2>
    <ul>
      <!-- 第一个参数为值,第二个参数为键名,第三个参数为索引 -->
      <li v-for="(value,key,index) in info">{{index+1}}.{{key}} : {{value}}</li>
    </ul>
    <h2>数字</h2>
    <!-- 遍历数字 -->
    <ul>
      <li v-for="item in 5">{{item}}</li>
    </ul>
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          // 数组
          movies: ["夏洛特烦恼","星际穿越","肖申克的救赎","当幸福来敲门"],
          // 对象
          info: {
            name: "big_orange",
            age: 23,
            sex: "男"
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>

页面效果如图:

5.2 在 <template> 中使用 v-for

  <div id="app"></div>

  <template id="my-app">
    <ul>
      <template v-for="(value,key) in info">
        <li>{{key}}</li>
        <li>{{value}}</li>
        <li></li>
      </template>
    </ul>
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          // 对象
          info: {
            name: "big_orange",
            age: 23,
            sex: "男"
          }
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>

页面效果如图:

5.3 数组更新检测

变更方法

Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会 触发视图更新

这些被包裹过的方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替换数组

某些方法不会变更原始数组,而总是返回一个新数组

比如  filter()  concat()  slice()

5.4 v-for 中 key 的作用

高效的更新虚拟 DOM

感觉还是没完全搞懂 ?

5.4.1 认识 VNode

VNode 的全称是 Virtual Node,也就是虚拟节点
事实上,无论是组件还是元素,它们最终在 Vue 中表示出来的都是一个又一个 VNode
VNode 本质是一个 JavaScript 对象
它描述了应该怎样去创建真实的 DOM 结点
<div class="title" style="font-size: 30px; color: pink;">Hello World</div>

对应的 VNode 为:

const vnode = {
      type: "div",
      props: {
        class: "title",
        style: {
          "font-size": "30px",
          color: "red",
        },
      },
      children: "Hello World",
    }

5.4.2 插入案例

现在有一个列表,里面存放着 A B C D E G

我们现在需要在中间插入一个 F

那么 Vue 内部对列表是如何进行更新的呢

有 key,使用 patchKeyedChildren 方法

没有 key,使用 patchUnkeyedChildren 方法

5.4.3 没有 key 的 diff 算法

过程大致是,取新旧 VNode 中列表长度最短的值进行遍历

对于上图来说就是取旧 VNode 列表的长度

然后挨个比较,如果值相同就下一个,如果不同就用新值覆盖旧值,节点还是继续复用

最后新的多就增加节点,旧的多就删除节点

5.4.4 有 key 的 diff 算法

这个感觉还是要看源码会清晰一些

先从头部开始比较,如果两个节点的 key 不一样的话,就跳出循环

对于上图来说就是 a 和 a 比较,b 和 b 比较,c 和 f 比较之后跳出循环

然后从尾部开始比较,如果两个节点的 key 不一样的话,就跳出循环

新节点比较多的情况下,就用空节点与其比较,然后挂载新节点

旧节点比较多的情况下,移除旧节点

如果新旧节点一样多,但中间不一样,且是无序的

它就会尽量去利用旧的节点,去找跟新的节点 key 一样的,达到复用的一个效果

旧的里面有的,新的没有,就做删除操作

旧的里面没有的,新的里面有,就做新增操作

其余的就移动位置


6、计算属性

在模板中可以通过插值语法显示 data 中的数据

但是在某些情况下,我们需要对数据进行一些转化后再显示

或者需要将多个数据结合起来进行显示

我们在模板中使用表达式,可以很方便的实现上面的需求

但是 在模板中放入太多的逻辑会让模板过重,难以维护

我们有两种方法可以将逻辑抽离出去

  • methods
  • computed

6.1 案例实现

为了对比这两者的区别,我们来看三个案例

案例一:我们有两个变量:firstName 和 lastName,希望它们拼接之后在界面上显示
案例二:我们有一个分数:score
              当 score 大于 60 的时候,在界面上显示及格
              当 score 小于 60 的时候,在界面上显示不及格
案例三:文字反转

6.1.1 模板语法实现

  <template id="my-app">
    <h2>{{firstName + lastName}}</h2>
    <h2>{{score >= 60 ? "及格":"不及格"}}</h2>
    <h2>{{message.split("").reverse().join("")}}</h2>
  </template>
data() {
  return {
    message: 'Hello World !',
    firstName: 'coder',
    lastName: 'why',
    score: 89
  }
}

页面效果如图:

可以很明显的看到缺点:

  • 模板中存在大量复杂的逻辑,不便于维护
  • 当有多次一样的逻辑时,存在重复的代码
  • 多次使用时,逻辑需要重复执行,没有缓存

6.1.2 methods 实现

  <template id="my-app">
    <h2>{{getFullName()}}</h2>
    <h2>{{getResult()}}</h2>
    <h2>{{getReverseMessage()}}</h2>
  </template>
methods: {
  getFullName() {
    return this.firstName + " " + this.lastName;
  },
  getResult() {
    return this.score >= 60 ? "及格":"不及格";
  },
  getReverseMessage() {
    return this.message.split("").reverse().join("");
  }
}

这种方法也是有缺点的:

  • 我们想在模板上呈现的是一个结果,但是其实是方法的调用
  • 多次使用时,需要重复调用方法,没有缓存

6.1.3 computed 实现

  <template id="my-app">
    <h2>{{fullName}}</h2>
    <h2>{{result}}</h2>
    <h2>{{reverseMessage}}</h2>
  </template>
computed: {
  fullName() {
    return this.firstName + " " + this.lastName;
  },
  result() {
    return this.score >= 60 ? "及格":"不及格";
  },
  reverseMessage() {
    return this.message.split("").reverse().join("");
  }
}

计算属性看起来像是一个函数,但是我们不需要在使用的时候加 ()

直观上看起来也很简洁,并且计算属性是有缓存的

在上面的案例实现方案中,我们会发现计算属性和 methods 的实现看起来差别不是很大

但区别还是很大的,主要表现在 methods 的每一次调用都需要重新去执行相应的函数

就好比计算 1 + 1 = 2

methods 每次都要重新算,而 computed 在执行一次运算之后就可以将结果缓存起来

下次再算的时候可以直接返回结果

6.2 计算属性的缓存

计算属性会基于它们的 依赖关系 进行缓存

在数据不发生变化时,计算属性是不需要重新计算的

但是如果 依赖的数据发生变化,在使用时,计算属性依然会 重新进行计算

6.3 计算属性的 setter 和 getter

计算属性在大多数情况下,只需要一个 getter 方法即可

对于上述的 fullName 函数,我们有一个较为完整的写法

如下:

computed: {
  // fullName 的 getter 方法
  // fullName: function() {
  // return this.firstName + " " + this.lastName;
  // }
  // 计算属性的完整写法
  fullName: {
    get: function() {
      return this.firstName + " " + this.lastName;
    },
    set: function(newValue) {
      // 修改计算属性的值时,就会触发 set 函数,执行一些自定义的操作
    }
  }
}

总而言之就是,对于计算属性我们有两种写法

但是一般情况,我们只会传入一个 getter,而不是一个包含 setter 和 getter 的对象

对于传入了什么,Vue 内部会有一个逻辑判断


7、侦听器

在代码逻辑中监听数据的变化

  <div id="app"></div>

  <template id="my-app">
    <h2>{{question}}</h2>
    <button @click="btnClick">点我</button>
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          question: 'Hello World !'
        }
      },
      // question 侦听的是 data 中的属性
      // newValue 是变化之后的新值
      // oldValue 是变化之前的旧值
      watch: {
        question(newValue,oldValue) {
          // 可以在这里将侦听到的值打印出来
          console.log(newValue,oldValue)
        }
      },
      // 通过 methods 改变 question 中的值
      methods: {
        btnClick() {
          return this.question = "New World !"
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>

页面效果如图:

点击之后,我们可以在控制台看到输出

表示已经监听到了变化

默认情况下,我们的侦听器只会侦听到数据本身的改变

就好比我现在的数据是一个对象,对象里面的某个值发生改变是侦听不到的

只有当整个对象被重新赋值时才能侦听到

  <div id="app"></div>

  <template id="my-app">
    <h2>{{info.name}}</h2>
    <button @click="changInfoName()">点我</button>
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          info: {
            name: "why",
            age: 18
          }
        }
      },
      watch: {
        // 传入一个对象
        // info(newInfo,oldInfo) {
        //   console.log("newValue:",newInfo,"oldValue:",oldInfo);
        // }
        // 这种写法是下面 handler 的语法糖形式
        info: {
          handler: function(newInfo,oldInfo) {
            console.log("newValue:",newInfo,"oldValue:",oldInfo);
          },
          // 深度侦听
          deep: true,
          // 立即执行,不管数据有没有发生改变,先侦听一次
          immediate: true
        },
        // 还有一种写法
        // 如果我们只想侦听对象中的某一个值的话,不使用深度侦听的办法

        // "info.name": function(newName,oldName) {
        // console.log(newName,oldName)
        }
      },
      methods: {
        changInfoName() {
          this.info.name = "orange"
        }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>

上诉代码中,info 里面的 name 发生改变是侦听不到的

为了侦听到数据内部的改变,我们可以使用深度侦听

即设置 deep 的值为 true

这个时候我们就可以对内部数据进行监听

还有一种监听的方式就是使用 $watch 的 API ?


8、v-model

在表单控件或者组件上创建双向绑定

8.1 v-model 的本质

v-model 的内部其实是包含两个操作的

  <div id="app"></div>

  <template id="my-app">
    <h2>{{message}}</h2>
    <!-- 1、v-bind 绑定 value  2、监听 input 事件,更新 message 的值 -->
    <!-- <input type="text" :value="message" @input="inputChange"> -->
    <!-- 语法糖形式 -->
    <input type="text" v-model="message">
  </template>

  <script src="../vue.js"></script>
  <script>
    const App = {
      template: '#my-app',
      data() {
        return {
          message: 'Hello World !'
        }
      },
      methods: {
        // inputChange(event) {
        //   this.message = event.target.value;
        // }
      },
    }

    Vue.createApp(App).mount('#app');
  </script>

8.2 基础用法

 <!-- 1.绑定 textarea -->
 <label for="intro">
   自我介绍
   <textarea name="intro" id="intro" cols="30" rows="10" v-model="intro"></textarea>
 </label>
 <h2>intro: {{intro}}</h2>

 <!-- 2.checkbox -->
 <!-- 2.1.单选框 -->
 <label for="agree">
   <input id="agree" type="checkbox" v-model="isAgree"> 同意协议
 </label>
 <h2>isAgree: {{isAgree}}</h2>

 <!-- 2.2.多选框 -->
 <span>你的爱好: </span>
 <label for="basketball">
   <input id="basketball" type="checkbox" v-model="hobbies" value="basketball"> 篮球
 </label>
 <label for="football">
   <input id="football" type="checkbox" v-model="hobbies" value="football"> 足球
 </label>
 <label for="tennis">
   <input id="tennis" type="checkbox" v-model="hobbies" value="tennis"> 网球
 </label>
 <h2>hobbies: {{hobbies}}</h2>

 <!-- 3.radio -->
 <span>你的性别: </span>
 <label for="male">
   <input id="male" type="radio" v-model="gender" value="male">男
 </label>
 <label for="female">
   <input id="female" type="radio" v-model="gender" value="female">女
 </label>
 <h2>gender: {{gender}}</h2>

 <!-- 4.select -->
 <span>喜欢的水果: </span>
 <select v-model="fruit" multiple size="2">
   <option value="apple">苹果</option>
   <option value="orange">橘子</option>
   <option value="banana">香蕉</option>
 </select>
 <h2>fruit: {{fruit}}</h2>
data() {
   return {
     intro: "Hello World",
     isAgree: false,
     hobbies: ["basketball"],
     gender: "",
     fruit: "orange"
   }
 }

8.3 修饰符

8.3.1 .lazy

<!-- 在 change 时而非 input 时更新 -->
<input v-model.lazy="msg" />

比如说,我们在文本框中输入信息,不想实时同步

想在回车之后再同步数据,可以给 v-model 添加 lazy 修饰符

8.3.2 .number

如果想自动将用户的输入值转为数值类型,可以给 v-model 添加 number 修饰符

<input v-model.number="age" type="number" />

8.3.3 .trim

如果要自动过滤用户输入的首尾空白字符,可以给 v-model 添加 trim 修饰符

<input v-model.trim="msg" />

9、组件基础

前面我们在 createApp 函数中传入了一个对象 App
这个对象其实本质上就是一个组件,也是我们应用程序的 根组件
组件的注册有两种类型:
  • 全局注册
  • 局部注册

9.1 注册全局组件

  <div id="app"></div>

  <template id="my-app">
    <h2>{{message}}</h2>

    <!-- 使用组件 -->
    <my-component></my-component>
  </template>

  <!-- 组件模板 -->
  <template id="my-component">
    <h2>我是组件</h2>
    <p>我是组件内容</p>
  </template>

  <script src="../vue.js"></script>
  <script>

    const App = {
      template: '#my-app',
      data() {
        return {
          message: 'Hello World !'
        }
      }
    }

    const app = Vue.createApp(App);

    // 注册全局组件
    app.component('my-component',{
      template: '#my-component',
      data() {
        return {
          message: "我是组件"
        }
      }
    })

    app.mount('#app')
  </script>

9.2 注册局部组件

  <div id="app"></div>

  <template id="my-app">
    <h2>{{message}}</h2>
    <component-a></component-a>
  </template>

  <!-- 组件模板 -->
  <template id="component-a">
    <h2>我是组件</h2>
    <p>我是组件内容</p>
  </template>

  <script src="../vue.js"></script>
  <script>

    // 定义一个组件
    const ComponentA = {
      template: '#component-a'
    }

    const App = {
      template: '#my-app',
      // 局部注册,只能在 App 里面使用
      components: {
        // key: value
        // 组件名称:组件对象
        ComponentA
      },
      data() {
        return {
          message: 'Hello World !'
        }
      }
    }

    Vue.createApp(App).mount('#app');
  </script>

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值