
在上一篇内容了解了关于 Vue2.x 到 Vue3 的一个过渡,在最终留下一个关于 ref 的问题,在Vue 2.x 的内容讲解中就讲到关于 ref 的使用 :第二十四篇 ref 访问子组件 ,在该篇中得知道 ref 绑定在标签上就可以获取原生节点,而ref绑定在组件上可以获取到组件对象。通过点击获取input输入框中的数据,可以通过最原始的方式监听input框的数据进而获取,以及使用v-model指令的双向数据绑定特性获取数据,这两种都是可行的,但前面在Vue2.x通过在标签在绑定ref = "mytext",在按钮上绑定点击事件打印 this.refs.mytext 就能获取到数据框的数据了,而在Vue3中使用这种操作行不通,因为this的值是undefined;获取this.refs.mytext就不合理了;

Vue2.x 以new 类和构造函数的方式 而 Vue3是以函数调用的方式,那么如何同ref来获取input数据框的数据呢?
ref()
ref 可以创建一个包装式对象,含有一个响应式属性value;与reactive的区别就是ref没有包装属性value;下面来获取input数据框中的数据:
javascript
import { ref } from 'vue'
const mytext = ref('')
return { mytext }
javascript
<!-- App.vue -->
<template>
<div>
<input type="text" ref='mytext' />
<button @click="handleClick" >add</button>
<ul>
<li v-for="(item,index) in state.list" :key="index" >
{{item}} _ {{index}}
<button @click="handleDel(index)">del</button>
</li>
</ul>
</div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
setup () {
const state = reactive({
text: '',
list: []
})
const mytext = ref('')
const handleClick = () => {
console.log(mytext)
}
const handleDel = (index) => {
console.log('删除索引', index)
state.list.splice(index, 1)
console.log(state.list)
}
return {
state,
mytext,
handleClick,
handleDel
}
}
}
</script>

打印出来一个对象,好复杂啊,有一个value,有点熟悉?是什么呢?打印一下:
javascript
mytext.value

是不是到这就恍然大悟了呢?这不就是原生节点了吗?拿到原生节点之后获取数据不就手到擒来了吗?打印一下:
javascript
mytext.value.value

通过打印mytext.value.value就拿到了,那不就可以讲原来的案例进行调整了嘛,如下:
javascript
<!-- App.vue -->
<template>
<div>
<input type="text" ref='mytext' />
<button @click="handleClick" >add</button>
<ul>
<li v-for="(item,index) in state.list" :key="index" >
{{item}} _ {{index}}
<button @click="handleDel(index)">del</button>
</li>
</ul>
</div>
</template>
<script>
import { reactive, ref } from 'vue'
export default {
setup () {
const state = reactive({
text: '',
list: []
})
const mytext = ref('')
const handleClick = () => {
console.log(mytext.value.value)
state.list.push(mytext.value.value)
mytext.value.value = ''
}
const handleDel = (index) => {
console.log('删除索引', index)
state.list.splice(index, 1)
console.log(state.list)
}
return {
state,
mytext,
handleClick,
handleDel
}
}
}
</script>

以上解决来之前留下的小问题,下面来进一步了解ref的使用!
ref 和 reactive
对于reactive 响应式在上面中讲到了,先来回顾一下reactive的使用:
【 reactive 类似 useState,如果参数是字符串,数字,会报警告,value cannot be made reactive :xxx ,所以应该设置对象,这样可以数据驱动页面;例如:const st = reactive('11') 】
javascript
<template>
<div>
<p>由于参数的设置是对象,使用的是'state.NiceName'而不是'NiceName'</p>
{{ state.NiceName }}
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { reactive } from 'vue'
export default {
setup () {
const state = reactive({
NiceName: 'syan'
})
const handleClick = () => {
console.log(state.NiceName)
}
return {
state,
handleClick
}
}
}
</script>

这里 ref 就不一样了,ref 可以创建一个包装式对象,含有一个响应式属性value;reactive 通过设置对象,在页面使用的时候就需要 " . " 拿到对象中的属性,那么 ref 与 reactive 的区别在于ref没有包装属性value,来看一下代码会更加清晰一些:
javascript
<template>
<div>
<p>页面显示NiceName是syan,而打印的NiceName则是一个对象</p>
{{ NiceName }}
<button @click="handleClick">点击</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const NiceName = ref('syan')
const handleClick = () => {
console.log(NiceName)
}
return {
NiceName,
handleClick
}
}
</script>

页面显示 NiceName = syan ,而控制台打印的 NiceName 则是一个对象,同样要是 NiceName = syan 则需要打印 NiceName.value ;
javascript
...
const handleClick = () => {
console.log('NiceName:', NiceName)
console.log('NiceName.value:', NiceName.value)
}
...

同样的,通过点击按钮让 NiceName 重新赋值,重新赋值的是 NiceName.value ,这也就是ref 没有包装属性value ;
javascript
...
const handleClick = () => {
NiceName.value = 'SYAN'
console.log('NiceName:', NiceName)
console.log('NiceName.value:', NiceName.value)
}
...
以上就是 ref 与 reactive 两者之间的用法区别,reactive 无法对字符串和简单数据类型进行数据拦截,而 ref 可以,而 ref 不是响应式对象,而 .value 才是响应式对象,而 const state = reactive ({ ... }) ,state 则是一个响应式对象,修改state中的属性都会被拦截进而更新页面。
ref 嵌套在 reactive 中
ref 嵌套在 reactive 中,下面先通过一段代码来看一下:
html
<template>
<div>
age 是 state (reactive) 嵌套的 ref <br>
state ------------ {{ state }} <br>
state.NiceName ------------ {{ state.NiceName }} <br>
state.age ------------ {{ state.age }} <br><br>
<button @click="addAge"> 爆竹声中一岁除 </button>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
export default {
setup () {
const age = ref(18)
const addAge = () => {
age.value++
}
const state = reactive({
NiceName: 'syan',
age
})
return {
state,
addAge
}
}
}
</script>


这样看来好像貌似完全没有必要,为什么要将它嵌套进去?放在reactive中就得通过state.age获取,不能通过age.value获得,嵌入到reactive后没有将age单独导出,而是作为state导出,当然将它导出也不是不可以,这种嵌套做法貌似像多次一举,放在reactive对象中不好吗?你可以这样来理解未就是为了两者之间使用得统一性,分开去使用可能看起来会有点混乱,这样一嵌套就使得两者间有统一性!
函数复用
从前面就可以知道在Vue3中不在使用new的方式,data里面存放数据,methods放置方法,如果你是刚从Vue2转Vue3,你可能看不出这种 ref,reactive 的写法的好处或者它到底好在什么地方?很疑惑,那么带着疑惑往下走。
通过ref来完成一个计数器:

html
<!-- App.vue -->
<template>
<div class="content">
<!-- 计数器 -->
<button @click="handleMinus"> - </button>
<span>{{ count }}</span>
<button @click="handleAdd"> + </button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup () {
const count = ref(0)
const handleMinus = () => {
count.value--
}
const handleAdd = () => {
count.value++
}
return {
count,
handleMinus,
handleAdd
}
}
}
</script>
假使现在这个计数器将被多个页面去进行使用的话,在Vue2中会将这个计数器封装成一个组件,但是Vue3是使用函数式的开发中,不需要封装成组件的,那如何来做?封装成一个新的函数即可,将这个功能抽成一个函数出来。
创建 /hooks/Couter.js 一个 hooks 文件夹来存放抽取出来的函数:
javascript
/* 计数器/hooks/Couter.js */
import { ref } from 'vue'
function useCouter () {
const count = ref(0)
const handleMinus = () => {
count.value--
}
const handleAdd = () => {
count.value++
}
return {
count,
handleMinus,
handleAdd
}
}
export {
useCouter
}
在App.vue中来引入使用,解构return出去:
javascript
<template>
<div class="content">
<!-- 计数器 -->
<button @click="handleMinus"> - </button>
<span>{{ count }}</span>
<button @click="handleAdd"> + </button>
</div>
</template>
<script>
import { useCouter } from './hooks/Counter'
export default {
setup () {
const { count, handleMinus, handleAdd } = useCouter()
return { count, handleMinus, handleAdd }
}
}
</script>

通过以上的这种形式可以将其中的内容抽取出来,那么在先前的 Vue2 当中,我们是不敢随意的使用这种方式,需要担心我们的这个this指向问题,所以在此反过来看这个ref,reactive看起来好像比之前的复杂了,但所承担的就是它可以把可以将其中的一些业务抽取出来做成函数,即自定义钩子hooks;
那么下面如果在父组件和子组件当中同时去引入使用这个计数器,它们之间是否会相互进行影响呢?
创建 /components/ComChild.vue 组件,在App.vue 中引入使用:
html
<!-- ComChild.vue -->
<template>
<div style="border:1px solid red">
<p>子组件</p>
useCouter.count - {{ count }}
</div>
</template>
<script>
import { useCouter } from '../hooks/Counter'
export default {
setup () {
const { count, handleMinus, handleAdd } = useCouter()
return { count, handleMinus, handleAdd }
}
}
</script>
html
<!-- App.vue -->
<template>
<div class="content">
<!-- 计数器 -->
<button @click="handleMinus"> - </button>
<span>{{ count }}</span>
<button @click="handleAdd"> + </button>
<child></child>
</div>
</template>
<script>
import { useCouter } from './hooks/Counter'
import child from './components/ComChild'
export default {
components: {
child
},
setup () {
const { count, handleMinus, handleAdd } = useCouter()
return { count, handleMinus, handleAdd }
}
}
</script>
测试:通过操作父组件中引入的计数器,然后观察子组件中对于的数据是否会受到影响?


不是同一个模块吗?两者之间没有产生影响,可能在这里有的小白预期会产生影响,这样一来它们不就能够进行通信了嘛,这个自定义hooks每一次的调用它都会创建一份新的,所以在不同组件调用同一个的时候都不会相互影响。那么到这里就已然知道这个可以将里面的业务单独抽取出来做成一个hooks,同时引入到项目当中又好像一个孤岛,那么可以进行将它作为自己的一个状态然后再导出去,整齐划一:
javascript
{{state.count}}
...
setup () {
const { count, handleMinus, handleAdd } = useCouter()
// return { count, handleMinus, handleAdd }
const state = reactive({
count
})
return {
state,
handleMinus,
handleAdd
}
}
}
</script>
如果它们之间会相互影响,那么在不同组件当中为了去共用,现在反而会相互影响,那么自定义hooks不就没有这个可复用性了吗?所以它们之间不会相互影响的;那么以上就是从ref,以及ref 和 reactive 的用法区别,以及之间的嵌套混合使用以及所衍生的自定义hooks。
toRefs
ref 和 reactive 两者都可以管理状态,变成响应式的状态。在使用 reactive 的时候在页面需要通过 "." 某个状态,如上述中的 state.count ;想要的效果就是直接使用 count ,不想 state.count感觉很麻烦很难受,那么这里可以将 state 去进行展开,在展开之前需要先通过 toRefs()方法;下面来进行测试一下:
javascript
<template>
<div>
<!-- {{ state.count }} -->
{{count}}
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
setup () {
const state = reactive({
count: 0
})
return {
// state
...toRefs(state)
}
}
}
</script>

测试效果并没有任何问题,可以看到页面中可以通过 {{ count }} 来显示数据状态;那么本篇也到了尾声了,通过讲 reactive 和 refs 和所衍生的内容了解到了 Vue2 和 Vue3 在使用上的一些差别,如果对于 Vue2 转 Vue3 仍感兴趣的可以关注,后续会继续分享,感谢大家的支持!!!
