目录

一、VUE3的主要优势

二、创建vue3项目

三、定义数据

1、响应式ref函数

(1)使用ref定义

(2)不使用ref定义

(3)定义复杂数据

2、响应式reactive函数

四、toRefs函数

1.如何取舍ref和reactive

小结:ref和reactive使用步骤 

五、生命周期

六、定义方法

七、监听属性watch和watchEffect

1.watch

(1)单个数据监听

(2)多个数据监听

    1. 多个watch

    2. 数组的形式

(3)复杂数据类型监听

  1. 监听整个数据

  2. 监听某个属性

(4)深度监听

2.watchEffect

(1)watchEffect与watch的区别

(2)watchEffect 的两个属性

八、计算属性computed

1.基本使用

2.v-model绑定

九、父子通信

1.父传子

2.子传父

十、爷孙通信

1.provide()方法

2.inject()方法

十一、ref获取DOM元素、获取组件实例

1.获取DOM元素

2.获取组件实例


一、VUE3的主要优势

vue3 是 vue 的最新版本,它在 vue2 的基础上进行了更新升级,并且引入了 composition API 等新特性。它采用函数式编程方式(hooks),兼容 vue2 的所有写法。

1、性能的提升:

  • 性能提升,首次渲染更快,diff算法更快,内存占用更少,打包体积更小,
  • 更好的Typescript支持(在vue下写TS更方便了)
  • 提供新的写代码方式:Composition API (组合式api)

2、源码的升级

  • 使用Proxy代替defineProperty实现响应式
  • 重写虚拟DOM的实现和Tree-Shaking

二、创建vue3项目

点击下面的链接可以参考

vite搭建完整项目_vite搭建项目_一只大黑洋的博客-CSDN博客

三、定义数据

tips:两种定义数据的方法:ref(简单数据类型或复杂数据类型)和reactive(复杂数据类型)。

1、响应式ref函数

(1)使用ref定义

<template>
  <div @click="clog">点击打印</div>
  <div>{{ '这是ref定义的数据' + title}}</div>
</template>

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

let title = ref('myRef')
const clog = ()=> {
    console.log('需要.value:',title.value)
}
</script>

下图可见:数据改变,视图发生改变,所以使用ref定义数据,数据是响应式的。                

(2)不使用ref定义

<template>
  <div @click="clog">点击打印</div>
  <div>{{ '这是ref定义的数据' + title}}</div>
</template>

<script setup>
import { reactive, toRefs ,ref} from "vue";

let title = ref('myRef')
const clog = ()=> {
    console.log('需要.value:',title.value)
}
</script>

下图可见:数据改变,但视图没发生改变,所以不使用ref定义数据,数据并不是响应式的。

(3)定义复杂数据

<template>
  <div @click="addOne">点击加一</div>
  <div>{{ '这是ref定义的数据' + obj.num}}</div>
</template>

<script setup>
import { reactive } from "vue";

let obj = reactive({num:1})
const addOne = ()=> {
    obj.num++
    console.log(obj.num)
}
</script>

2、响应式reactive函数

tips:reactive只能用于定义复杂数据类型,而且相比ref定义的复杂数据类型可以省去.value

<template>
  <div @click="clog">点击打印</div>
</template>

<script setup>
import { reactive } from "vue";
let obj = reactive({ name: "哈哈" });
const clog = ()=> {
    console.log('无需.value:',obj.name)
}
</script>

四、toRefs函数

tips:toRefs函数与解构赋值效果相同,但不能使用{...obj}的方式,否则数据不会保留响应式的特点,toRefs与reactive配套使用。

<template>
  <div>
    {{ "使用" + name }}
  </div>
</template>

<script setup>
import { reactive, toRefs } from "vue";
let obj = reactive({ name: "哈哈" });
let { name } = toRefs(obj);
</script>
<style lang="scss">
</style>

1.如何取舍ref和reactive

 我们现在有两种定义响应式数据的方式到底该用哪个呢

优先使用ref函数 因为ref 函数 可以处理简单数据类型 也可以处理复杂数据类型 ,常用于简单数据类型定义响应式数据

ref特点: 在代码中获取或修改值时,需要补上.value 但是 template模板中不需要

② reactive 常用与定义复杂数据类型作为响应式数据

reactive特点:在代码中获取或修改值是不需要加.value , 如果明确知道对象中有什么属性就用reactive

小结:ref和reactive使用步骤 

① 从vue框架中导入(ref或reactive)函数

② ref(简单数据类型或复杂数据类型)、reactive(复杂数据类型)

③ ref 会返回一个可变的响应式对象,该对象作为一个响应式的引用维护着它内部的值,这就是 ref 名称的来源。该对象只包含一个名为 value 的 property  注意:在setup函数中需要加.value,在template模板中不需要加value,reactive不管在setup函数内还是模版中使用都不需要加.value。

五、生命周期

这些生命周期钩子注册函数只能在 setup() 期间同步使用,因为它们依赖于内部全局状态来定位当前活动实例 (此时正在调用其 setup() 的组件实例)。在没有当前活动实例的情况下调用它们将导致错误。

组件实例上下文也是在生命周期钩子的同步执行期间设置的,因此在生命周期钩子内同步创建的侦听器和计算属性也会在组件卸载时自动删除。

因为 setup 是围绕 beforeCreate 和 created 生命周期钩子运行的,所以不需要显式地定义它们。换句话说,在这些钩子中编写的任何代码都应该直接在 setup 函数中编写。

可以通过在生命周期钩子前面加上 “on” 来访问组件的生命周期钩子。前提需要引入。

<script setup>
import { onMounted } from "vue";

onMounted(()=> {
  console.log('已执行')
})
</script>

六、定义方法

第一种:常规的function写法

<template>
  <div @click="addOne">点击加一</div>
  <div>{{ '这是ref定义的数据' + obj.num}}</div>
</template>

<script setup>
import { reactive } from "vue";

let obj = reactive({num:1})

function addOne() {
  obj.num++
  console.log(obj.num)
}
</script>

第二种:箭头函数

<template>
  <div @click="addOne">点击加一</div>
  <div>{{ '这是ref定义的数据' + obj.num}}</div>
</template>

<script setup>
import { reactive } from "vue";

let obj = reactive({num:1})

const addOne = ()=> {
    obj.num++
    console.log(obj.num)
}
</script>

七、监听属性watch和watchEffect

1.watch

(1)单个数据监听

<template>
  <div @click="numAdd">点击加一</div>
  <div>{{ 'num的值为' + num}}</div>
</template>

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

let num = ref(1)
const numAdd = ()=> {
  num.value++
}
watch(num, (newName, oldName) => {
  console.log("监听num的值", newName,oldName);
});
</script>

(2)多个数据监听

    1. 多个watch
<template>
  <div @click="numAdd">点击num加一</div>
  <div>{{ 'num的值为' + num  }}</div>
  <div @click="num2Add">点击num2加一</div>
  <div>{{ 'num2的值为' + num2 }}</div>
</template>

<script setup>
import { ref , watch} from "vue";
let num = ref(1)
let num2 = ref(2)
const numAdd = ()=> {
  num.value++
}
const num2Add= ()=> {
  num2.value++
}
watch(num, (newName, oldName) => {
  console.log("监听num的值", newName,oldName);
});
watch(num2, (newName, oldName) => {
  console.log("监听num2的值", newName,oldName);
});
</script>
    2. 数组的形式
<template>
  <div @click="numAdd">点击num加一</div>
  <div>{{ 'num的值为' + num  }}</div>
  <div @click="num2Add">点击num2加一</div>
  <div>{{ 'num2的值为' + num2 }}</div>
</template>

<script setup>
import { ref , watch} from "vue";
let num = ref(1)
let num2 = ref(2)
const numAdd = ()=> {
  num.value++
}
const num2Add= ()=> {
  num2.value++
}
watch([num,num2], (newName) => {
  console.log("监听num的值", newName);
});
</script>

(3)复杂数据类型监听

  1. 监听整个数据
<template>
  姓名:{{ peopleInfo.name }},年龄:{{ peopleInfo.age }}
  爱好: {{ peopleInfo.hobby.sing }}
  <button @click="peopleInfo.name ='二蛋'">点击改名字</button>
</template>

<script setup>
import { onMounted, reactive,  ref , watch} from "vue";
let peopleInfo = reactive({name:'瓜皮',age:20,hobby:{sing:'rap'}})
watch(peopleInfo, (newValue) => {
  console.log("peopleInfo变化了", newValue);
});
</script>
  2. 监听某个属性

   tips:要将监听值写成函数返回形式,vue3无法直接监听对象的某个属性变化

<template>
  姓名:{{ peopleInfo.name }},年龄:{{ peopleInfo.age }}
  爱好: {{ peopleInfo.hobby.sing }}
  <button @click="peopleInfo.name ='二蛋'">点击改名字</button>
</template>

<script setup>
import { onMounted, reactive,  ref , watch} from "vue";
let peopleInfo = reactive({name:'瓜皮',age:20,hobby:{sing:'rap'}})
watch(()=>peopleInfo.name, (newValue) => {
  console.log("名字变化了", newValue);
});
</script>

(4)深度监听

<template>
  <div>姓名:{{ peopleInfo.name }}</div>
  <div>年龄:{{ peopleInfo.age }}</div>
  <div>爱好1: {{ peopleInfo.hobby.sing }}</div>
  <div>爱好2: {{ peopleInfo.hobby.run }}</div>
  <button @click="peopleInfo.hobby.run ='长跑'">点击改改变爱好</button>
</template>

<script setup>
import { onMounted, reactive,  ref , watch} from "vue";
let peopleInfo = reactive({name:'瓜皮',age:20,hobby:{sing:'rap',run:'短跑'}})
watch(
  () => peopleInfo.hobby,
  (newValue) => {
    console.log("爱好变化了", newValue);
  },
  { deep: true,immediate:true } //deep=true开启深度监听  immediate=true立即监听(一般不需要)
);
</script>

2.watchEffect

(1)watchEffect与watch的区别

  • watchEffect 初始化时就会执行(与watch添加immediate属性一样),因此在挂载 DOM 之前尝试访问 DOM 时要小心,watch 跟踪一个或多个特定的响应性属性,并且仅在该属性发生更改时运行。
  • 默认情况下,watch 是惰性的,因此仅当依赖项更改时才会触发。watchEffect 在创建组件后立即运行,然后跟踪依赖关系。
<template>
  <div>姓名:{{ peopleInfo.name }}</div>
  <div>年龄:{{ peopleInfo.age }}</div>
  <div>爱好1: {{ peopleInfo.hobby.sing }}</div>
  <div>爱好2: {{ peopleInfo.hobby.run }}</div>
  <button @click="peopleInfo.hobby.run ='长跑'">点击改改变爱好</button>
</template>

<script setup>
import { reactive,  ref ,  watchEffect} from "vue";
let peopleInfo = reactive({name:'瓜皮',age:20,hobby:{sing:'rap',run:'短跑'}})
watchEffect(()=> {
  console.log('watchEffect监听',peopleInfo.hobby.run)
})
</script>

(2)watchEffect 的两个属性

  1.watchEffect 第一个参数是函数,在函数里又能接收到一个参数就是副作用函数 onInvalidate,可以在下次修改值之前调用,可以在里面执行一些操作。

<template>
  <div>姓名:{{ peopleInfo.name }}</div>
  <div>年龄:{{ peopleInfo.age }}</div>
  <div>爱好1: {{ peopleInfo.hobby.sing }}</div>
  <div>爱好2: {{ peopleInfo.hobby.run }}</div>
  <button @click="peopleInfo.hobby.run ='长跑'">点击改改变爱好</button>
</template>

<script setup>
import { onMounted, reactive,  ref , watch, watchEffect} from "vue";
let peopleInfo = reactive({name:'瓜皮',age:20,hobby:{sing:'rap',run:'短跑'}})
watchEffect((onInvalidate )=> {
  console.log('监听修改值',peopleInfo.hobby.run)
  onInvalidate(()=> {
    console.log('修改前执行')
  })
})
</script>

  2.stopWatcher() 停止监听,减少资源浪费

<template>
  <button @click="stopWatcher">停止监听</button>
  <input type="text" v-model="message">
</template>

<script setup>
import {  ref , watchEffect} from "vue";
let message = ref('')
const stopWatcher = watchEffect(()=> {
  console.log('监听输入值',message.value)
})
</script>

例如:输入到1212时点击停止监听,可以看见watchEffect 就不再监听输入框的值了。

八、计算属性computed

1.基本使用

<template>
  <div>姓名:{{ peopleInfo.name }}</div>
  <div>计算一年薪水:{{ annualAalary }}</div>
</template>

<script setup>
import { computed, reactive } from "vue";
let peopleInfo = reactive({name:'二蛋',salary:1000});
const annualAalary = computed(()=> {
  return peopleInfo.salary * 12
})
</script>

2.v-model绑定

<template>
  <input type="number" v-model="annualAalary">
</template>

<script setup>
import { computed, reactive } from "vue";
let peopleInfo = reactive({name:'二蛋',salary:1000});
const annualAalary = computed(()=> {
  return peopleInfo.salary * 12
})
</script>

九、父子通信

1.父传子

tips:子组件接收数据使用defineProps,传递数据使用defineEmits,两种Api都不需要引入

父组件:和vue2相比就是引入后不需要注册,直接使用,传递的方式是相同的。

<template>
  <div>父组件</div>
  <mySon :money="money"></mySon>
</template>

<script setup>
import { ref } from "vue";
import mySon from '@/components/son.vue'

let money = ref(100);
</script>

子组件:在setup语法糖中,接收父组件传递的数据需要用defineProps来接收。

<template>
  <div>这是子组件</div>
  <div>父组件传过来的零花钱:{{ money }}</div>
</template>

<script setup>
const props = defineProps({
  money: {
    type: Number,
    default: 0,
  },
});
</script>   

2.子传父

父组件:接收子子组件传递一个addMoney方法,写法与vue2相同

<template>
  <div>父组件</div>
  <mySon :money="money" @addMoney="addMoney"></mySon>
</template>

<script setup>
import { ref } from "vue";
import mySon from '@/components/son.vue'

let money = ref(100);
const addMoney = ()=> {
  
}
</script>

子组件:所有的子传父方法名都要在defineEmits中注册才能使用。

<template>
  <div>这是子组件</div>
  <div>父组件传过来的零花钱:{{ money }}</div>
  <div @click="forMoney">点击找父亲要1000元</div>
</template>

<script setup>
const props = defineProps({
  money: {
    type: Number,
    default: 0,
  },
});
//所有的子传父方法名都要在defineEmits中注册
const emits = defineEmits(['addMoney'])
const forMoney = ()=> {
    emits('addMoney', 1000) //调用
}
</script>   

十、爷孙通信

        只有两层组件嵌套的情况下props是没有问题的,如果多层组件嵌套,还使用props一层一层去传递,就会导致组件的复用性变得很差而且数据看起来杂乱,好像 props 并不是一个好的选择了,然而是有解决办法的,在vue3中使用的provide和inject来解决多层组件嵌套传值的问题。

                     

1.provide()方法

tips:provide 接受两个参数。第一个参数需要是一个独一无二的标识(不允许和组件内部的变量重名)(测试了一下同名也没有报错也能接收到),第二个参数就是准备传递的值。

(1)爷爷组件直接使用 provide(名称,值/函数) 方法提供(发布)一个数据或者是方法。

<template>
  <div @click="numAdd">爷爷组件</div>
  <son></son>
</template>

<script setup>
import { provide, ref } from "vue";
import son from '@/components/son.vue'
let num = ref(1)
const numAdd = ()=> {
  num.value++
}
provide('transferNum',num)
provide('transferNumAdd',numAdd)
</script>

2.inject()方法

(2)孙子组件使用inject(名称,值/函数)方法来“接收”到发布的数据或者方法

<template>
    <div>这是孙组件:{{ num }}</div>
    <button @click="addNum">孙组件使用父组件方法</button>
  </template>
  
  <script setup>
  import { inject, ref, } from "vue";
  let num = inject('transferNum')
  let addNum = inject('transferNumAdd',()=>{})
  </script>
  <style lang="scss" scoped>
  
  </style>

重点: inject 还可以允许你有个兜底的行为。意思就是:假设这个儿子组件在别的地方也需要复用,但是它的爷爷组件或者它压根就没有爷爷组件,那么第二个参数将作为 suibian 的默认值。

//爷爷组件
<template>
  <div @click="numAdd">爷爷组件</div>
  <son></son>
</template>

<script setup>
import { provide, ref } from "vue";
import son from '@/components/son.vue'
let num = ref(1)
const numAdd = ()=> {
  num.value++
}
// 注释发布方法,模拟孙子组件的上层没有爷组件传递数据来触发“兜底”行为
// provide('transferNum',num) 
// provide('transferNumAdd',numAdd)  
</script>

//孙子组件
<template>
   <div>这是孙组件:{{ num }}</div>
   <button @click="addNum">孙组件使用父组件方法</button>
</template>
  
<script setup>
import { inject, ref, } from "vue";
let num = inject('transferNum','爷爷组件没给东西由我来兜底')
let addNum = inject('transferNumAdd',()=>{console.log('兜底函数')})
</script>

如图:

十一、ref获取DOM元素、获取组件实例

1.获取DOM元素

<template>
  <div>父组件</div>
  <div @click="getRef">点击获取ref实例</div>
  <input ref="isInput" type="text" placeholder="请输入" />
</template>

<script setup>
import { onMounted, ref, } from "vue";
import mySon from "@/components/son.vue";

const isInput = ref(null); //创建一个存放ref元素的容器
const getRef = () => {
  console.log(isInput.value);
};
</script>

2.获取组件实例

tips:在子组件中必须通过defineExpose({value,function}) 方法将其暴露出去,直接掉用会报错。

父组件:定义一个存放ref的容器,再去获取其身上的属性。

<template>
  <div>父组件</div>
  <div @click="getRef">点击获取ref实例</div>
  <mySon ref="mySonRef"></mySon>
  <div>ref拿到子组件中的数据:{{ message }}</div>
</template>

<script setup>
import { ref, } from "vue";
import mySon from "@/components/son.vue";

let message = ref('')
const mySonRef = ref(null) //创建一个存放ref元素的容器 
const getRef = () => {
  mySonRef.value.sonMethed()
  message.value = mySonRef.value.message
};
</script>

子组件:通过defineExpose({value,function})方法暴露出想要暴露的值。

<template>
  <div>这是子组件</div>
</template>

<script setup>
import { ref, } from "vue";
let message = ref('子组件的数据')
const sonMethed = ()=> {
    console.log('调用了子组件的方法')
}
defineExpose({sonMethed,message})  // 在setup语法糖中 ref获取实例上必须通过此函数暴露出去
</script>

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐