vue组合式API学习笔记

123 阅读7分钟

区别:

选项式API

  1. 在 vue2.x 项目中使用的就是 选项式API 写法
  2. 优点:易于学习和使用,写代码的位置已经约定好了
  3. 缺点:代码组织性差,相似的逻辑代码不便于复用,逻辑复杂代码多了不好阅读

组合式API

  1. 在 vue3 中使用的就是 组合式API 写法
  2. 优点:功能逻辑复杂繁多情况下,各个功能逻辑代码组织再一起,便于阅读和维护
  3. 缺点:需要有良好的代码组织能力和拆分逻辑能力

学习过程对比选项式API写法。

响应式和计算属性

响应式就是选项式API中的data(),也就是变量声明。

<template>
  <h3>组合式API-简约写法</h3>
  <p>{{ message }}</p>
  <p>{{ userInfo.name }}</p>
  <p>{{ reverse }}</p>
  <p>{{ demo }}</p>
</template>

<script setup>      //这里增加setup
// 为什么每次使用到一个功能,都需要引入?
// Vue3的优势:在打包的时候,Vue2所有内容都会打包,Vue3只把使用到的功能打包
// Vue3的打包会让程序变得更小
//ref:基本类型的响应式数据:String Number Boolean
//reactive:引用类型的响应式数据:Array Object
import { ref, reactive, computed } from "vue"

const message = ref("组合式API 简约 绑定数据")
const userInfo = reactive({
  name: "iwen"
})
const reverse = computed(() => {
  // 注意:message在逻辑里面读取的时候(script),必须通过.value读取到值
  return message.value.split("").reverse().join("")
})

const demo = computed(() => {
  return message.value + "hahah"
})
</script>

事件处理

//选项式API
<template>
  <h3>选项式API</h3>
  <p>{{ count }}</p>
  <button @click="addCountHandle">增加</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    addCountHandle() {
      this.count++
    }
  }
}
</script>
//组合式API
<template>
  <h3>组合式API</h3>
  <p>{{ count }}</p>
  <button @click="addCountHandle">增加</button>
</template>

<script setup>
import { ref } from "vue"

const count = ref(0)                   //将count变量初始化为0
function addCountHandle(e) {            //这里直接写方法,不再用写入methods中。this.count变为count.value
  count.value++
  console.log(e)
}
</script>

侦听器

//选项式API
<template>
  <h3>选项式API</h3>
  <p>{{ count }}</p>
  <button @click="addCountHandle">增加</button>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    addCountHandle() {
      this.count++
    }
  },
  watch: {
    count(newValue, oldValue) {
      console.log(newValue, oldValue);
    }
  }
}
</script>
//组合式API
<template>
  <h3>组合式API</h3>
  <p>{{ count }}</p>
  <button @click="addCountHandle">增加</button>
</template>

<script setup>
import { ref, watch } from "vue"

const count = ref(0);

function addCountHandle() {
  // .value是读取ref数据的
  count.value++
}

// 参数1: 代表侦听的数据
watch(count, (newValue, oldValue) => {
  console.log(newValue, oldValue);
})
</script>

watchEffect

只要你在 watchEffect 函数里用到了响应式变量,这个函数就会在这些变量发生变化时重新运行。

<template>
  <div>
    <p>当前姓名: {{ firstName }}{{ lastName }}</p>
    <button @click="changeFirstNameHandle">改为firstname为纪</button>
  </div>
</template>

<script setup>
import { ref, watchEffect } from 'vue'

const firstName = ref('张')
const lastName = ref('三')

watchEffect(() => {
  console.log(`全名是:${firstName.value}${lastName.value}`)
})

function changeFirstNameHandle() {
  firstName.value = "纪"
}
</script>


控制台输出:
全名是:张三  //程序运行就会执行一次
全名是:张三  //响应式变量发生变化就会再执行一次,
             //不管是 `firstName` 还是 `lastName` 变了,都会重新触发 `watchEffect`。

watchEffectwatch 的区别

对比项watchEffectwatch
执行时机立即执行一次依赖变化时执行
依赖收集方式自动收集(访问了哪些响应式数据)手动指定要监听的变量
使用场景快速响应、调试、简单副作用精准控制依赖、旧值新值比较

停止监听

watchEffect 返回一个停止函数:

const stop = watchEffect(() =&gt; {
  console.log('运行中...')
})
​
// 停止监听
stop()

副作用清理:

目前看不懂,用到再说,看官网文档。

watchEffect(async (onCleanup) => {
  const { response, cancel } = doAsyncWork(newId)
  // 如果 `id` 变化,则调用 `cancel`,
  // 如果之前的请求未完成,则取消该请求
  onCleanup(cancel)
  data.value = await response
})

什么是副作用(Side Effect)?

副作用是指:函数在运行过程中,除了返回值之外,还对外部产生了影响的行为。

这些“影响”可能包括:

  • 修改了外部的变量
  • 操作了 DOM
  • 发起了网络请求(AJAX / fetch)
  • 设置了定时器(setTimeout / setInterval
  • 打印日志(console.log
  • 本地存储(localStorage

🎯 举个例子

没有副作用的函数(纯函数):

function add(a, b) {
  return a + b
}

这个函数:

  • 不依赖外部变量
  • 不改变外部状态
  • 每次调用都返回相同结果 ✅ 所以它是纯函数,没有副作用。

有副作用的函数:

let total = 0function addToTotal(num) {
  total += num
}

这个函数:

  • 修改了外部变量 total
  • 改变了“外部世界”的状态 ❌ 所以它有副作用

在 Vue 中的副作用是?

在 Vue 中,副作用通常是那些“对界面或系统产生实际影响的操作”,比如:

watchEffect(() =&gt; {
  console.log(count.value) // ✅ 副作用:打印日志
})

或者:

watchEffect(() =&gt; {
  fetch(`/api/data?id=${id.value}`) // ✅ 副作用:发起请求
})

这些副作用会在响应式数据发生变化时重新执行。

💡为什么要特别处理副作用?

副作用本身不可避免,但要管理好它们,因为它们:

  • 可能导致数据不一致
  • 在组件销毁后还在运行(比如异步请求没取消)
  • 影响性能或用户体验

Vue 3 提供 watchEffectwatch,就是帮我们更安全、更智能地处理副作用

生命周期

image.png //选项式API

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  methods: {
    addCountHandle() {
      this.count++
    }
  },
  created() {
  console.log("创建之后");
  },
mounted() {
  console.log("渲染之后1");
  },
// 第二个生命周期函数覆盖了第一个,只显示2。所有代码都要放在一个生命周期函数里面,会使代码太过臃肿。
mounted() {
  console.log("渲染之后2");
  }  
}
</script>  
//组合式API
<template>
  <h3>组合式API</h3>
  <p>{{ count }}</p>
  <button @click="addCountHandle">增加</button>
</template>

<script setup>
import { ref, watch ,onMounted} from "vue"   //引入onMounted

const count = ref(0);

function addCountHandle() {
  // .value是读取ref数据的
  count.value++
}
//每个生命周期函数方法,都可以独立的处理一个业务。
onMounted(() => {
  console.log("组件渲染之后1");
});
//这里不会覆盖,1和2都会显示。
onMounted(() => {                          
  console.log("组件渲染之后2");
});
</script>

模板引用

获取真实的DOM对象的。 当然可以!以下是图片中的文字内容:

虽然 Vue 的声明性渲染模型为你抽象了大部分对 DOM 的直接操作,但在某些情况下,我们仍然需要直接访问底层 DOM 元素。要实现这一点,我们可以使用特殊的refattribute,组合式API的实现更为简洁。

//选项式API
<template>
  <h3>选项式API</h3>
  <p ref="message">选项式API-模板引用</p>
</template>

<script>
export default {
  mounted() {
    this.$refs.message.innerHTML = "选项式API-模板引用-修改"
  }
}
</script>
<template>
  <h3>组合式API</h3>
  <p ref="message">组合式API-模板引用</p>
</template>

<script setup>
import { ref, onMounted } from "vue"

const message = ref(null)            //初始化为null,必须和模板里面ref同名  

// 不能放在最外层,因为在DOM还没有渲染的时候,这里的代码就已经执行
// 必须保证DOM渲染完成之后在去读取DOM,所以innerHTML要放在onMounted里面。
onMounted(() => {
  message.value.innerHTML = "组合式API-模板引用-修改"
})
</script>

Props

父传子
选项式API:

<template>
  <h3>选项式API-Parent</h3>
  <Child title = "传递数据"/>
</template>

<script>
import Child from "./Child.vue"
export default {
  components: {
    Child
  }
}
</script>
<template>
  <h3>选项式API-Child</h3>
  <p>{{ title }}</p>
</template>

<script>
export default {
  props: {
    title: {
      type: String,
      default: ""
    }
  }
}
</script>

组合式API:

<template>                                
  <h3>组合式API-Parent</h3> 
  <Child title = "传递数据" :age = "age"/>            //组合式API引入组件不需要三步走了,现在只需要二步。
</template>

<script setup>
import Child from "./Child.vue"
import ref from "vue"

const age = ref(20)
</script>
<template>
  <h3>组合式API-Child</h3>
  <p>{{ title }}</p>
  <p>{{ age }}</p>
</template>

<script setup>
const props = defineProps({           //使用definProps
  title: {
    type: String,
    default: ""
  },
  age:{
    type:Number,
    default:0
})

consolo.log(props.title)
consolo.log(props.age)

</script>

事件

子传父
组件之间传递数据,除了props可以做到,还可以使用自定义事件$emit
选项式API:

<template>
  <h3>选项式API-Parent</h3>
  <Child title="传递数据" @onSomeEvent="getMessageHandler"/>
  <p>{{ message }}</p>
</template>

<script>
import Child from "./Child.vue"
export default {
  data() {
    return {
      message: ""
    }
  },
  components: {
    Child
  },
  methods: {
    getMessageHandler(data) {
      this.message = data;
    }
  }
}
</script>
<template>
  <h3>选项式API-Child</h3>
  <p>{{ title }}</p>
  <button @click="sendMessageHandler">发送数据</button>
</template>

<script>
export default {
  data() {
    return {
      message: "Child数据"
    }
  },
  methods: {
    sendMessageHandler() {
      this.$emit("onSomeEvent", this.message)
    }
  }
}
</script>

组合式API

<template>
  <h3>组合式API-Parent</h3>
  <Child  @onSomeEvent="getMessageHandler"/>
  <p>{{ message }}</p>
</template>

<script setup>
import Child from "./Child.vue"
import { ref } from "vue"

const message = ref("")

function getMessageHandler(data){
  message.value = data
}
</script>
<template>
  <h3>组合式API-Child</h3>
  <p>{{ title }}</p>
  <p>{{ age }}</p>
  <button @click="sendMessageHandler">发送数据</button>
</template>

<script setup>
import { ref } from "vue"

const emit = defineEmits(["onSomeEvent"])  //需要使用defineEmits定义事件

function senMessageHandler(){
	emit("onSomeEvent", message.value)       //再用返回的emit发送数据给父组件
}

</script>

自定义指令基础

选项式API:

<template>
  <h3>选项式API-自定义指令</h3>
  <p v-author>学习前端</p>               //网页显示会加上-itbaizh
  <p v-author>大家好,学习前端课程</p>    //网页显示会加上-itbaizh
</template>

<script>
export default {
  // 自定义指令
  directives: {                        
    author: {
      mounted(element) {
        element.innerHTML = element.innerHTML + "-itbaizh"
      }
    }
  }
}
</script>

组合式API

<template>
  <h3>组合式API</h3>
  <p v-author>学习前端</p>            //网页显示会加上-itbaizh
</template>

<script setup>
const vAuthor = {                   //需要使用vAuthor关键字
  mounted: (element) => {
    element.innerHTML = element.innerHTML + "-itbaizhan"
  }
}
</script>

全局和局部自定义指令

自定义指令是区分全局和局部注册,在全局注册,可以在任意组件中使用,局部注册,只在当前组件中使用。
上节里面的那种只能在当前组件中使用,就是局部注册。
全局注册(代码放在main.js中):

//main.js
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

// 全局自定义指令
app.directive("iblue", {
  mounted(element){
    element.style.color = "blue"
  }
})

app.mount('#app')
//app.vue  其它所有给件都可以这样使用v-blue自定义指令
<template>
  <DirectiveScoped />
  <p v-blue>大家好,我是蓝色的</p>
</template>

<script setup>
import DirectiveScoped from "./components/DirectiveScoped.vue"
</script>

自定义指令的钩子函数

自定义指令有很多钩子函数,我们可以理解为是自定义指令的生命周期函数,在不同的情况下会自动调用。

<template>
  <h3>自定义指令的钩子函数</h3>
  <p v-red v-if="flag">{{ message }}</p>
  <button @click="updateHandler">修改内容</button>
  <button @click="delHandler">删除元素</button>
</template>

<script setup>
import { ref } from "vue"

const message = ref("红色效果")
const flag = ref(true)

function updateHandler(){
  message.value = "修改 红色效果"
}

function delHandler(){
  flag.value = false
}

const vRed = {
        //在绑定元素的attribute前
        //或事件监听器应用前调用
        //比生命周期函数少了一个beforeCreate()函数
  created(el, binding, vnode, prevVnode) {
    console.log("created");
  },
       //在元素被插入到DOM前调用
  beforeMount(el, binding, vnode, prevVnode) {
    console.log("beforeMount");
  },
       //在绑定元素的父组件
       //及他自己的所有子节点都挂载完成后调用
  mounted(el, binding, vnode, prevVnode) {
    console.log("mounted");
  },
       //绑定元素的父组件更新前调用
  beforeUpdate(el, binding, vnode, prevVnode) {
    console.log("beforeUpdate");
  },
      //在绑定元素的父组件
      //及他自己的所有子节点都更新后调用
  updated(el, binding, vnode, prevVnode) {
    console.log("updated");
  },
      //绑定元素的父组件卸载前调用
  beforeUnmount(el, binding, vnode, prevVnode) {
    console.log("beforeUnmount");
  }, 
     //绑定元素的父组件卸载后调用
  unmounted(el, binding, vnode, prevVnode){
  	console.log("unmounted")
  }
}
</script>

自定义指令的钩子函数

image.png
常用的参数是elbinding的value、arg值,其它基本用不上。
el就是html元素本身。

<template>
  <h3>自定义指令钩子函数参数</h3>
  <p v-myShow:foo="flag">{{ message }}</p>            //binding的value值就是其中flag的值,arg就是foo
  <button @click="updateHandler">切换</button>            
</template>

<script setup>
import { ref } from "vue"

const message = ref("模拟v-show指令")
const flag = ref(true)                               //根据是true还是flase来切换是否显示

function updateHandler(){
  flag.value = flag.value === true ? false : true
}

// 模拟v-show的指令
const vMyShow = {
  // mounted(el, binding){
  //   binding.value === true ? el.style.display = "block" : el.style.display = "none"
  // },
  updated(el, binding){                    //这里要写在updated中,不然点切换按钮没反应
    binding.value === true ? el.style.display = "block" : el.style.display = "none"
  }
}
</script>