1、自定义指令不能放到已经封装过的组件上
错误:
- 控制台警告 Runtime directive used on component with non-element root node. The directives will not function as intended.
原因:
-
意思是自定义指令不能放到组件上,而是要放到自有的元素上,也就是
这里用到的v-show
不能放在自定义组件上,而是放在原来就有的标签上,所以这里套了一层div-
比如:
-
<el-color-picker v-show="isShow"></el-color-picker> // 错误
-
-
v-loading是一个由Element-plus插件里创造的自定义的指令,它不能直接用el-dialog这个种封装过的组件
-
比如:
-
<el-dialog v-loading="loading"></el-dialog> // 报错
-
解决:
-
外面套一层不是自定义组件的东东就可以,我这里套了一层
div
,你也可以嵌套一层template
(v-if的情况)-
比如:
-
<div v-show="isShow"> <el-color-picker></el-color-picker> </div> 或者(新版本好像会报警告'v-show' directives cannot be put on <template> tags) <template v-if="isShow"> <el-color-picker></el-color-picker> </template>
-
-
将v-loading使用在内部
-
比如:
-
<el-dialog> <div v-loading="loading"></div> </el-dialog> // 报错
-
2、setup语法糖写法,watch和defineExpose中函数必须声明在前边
错误:
- setPosition3d.vue:210 Uncaught (in promise) ReferenceError: Cannot access ‘XXX’ before initialization
原因:
-
setup语法糖加载顺序类似于js,先声明后引用
-
比如:
-
defineExpose({ initData // ref.value.initData()使用的时候报错 }); const initData = () => {...} const add = ref(true) watch(add, () => { add && addData() // 报错 }) const addData = () => {...}
-
解决:
-
声明提前
-
比如:
-
defineExpose({ initData }); const initData = () => {...} const add = ref(true) const addData = () => {...} watch(add, () => { add && addData() })
-
3、ref、toRef、toRefs、toRaw 方法应用
3.1、toRef()
-
toRef() 函数基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然。
-
toRef() 函数一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性。
<template>
<div class="demo">
<div>姓名--state.name:{{state.name}}</div>
<div>姓名2--nameRef:{{nameRef}}</div>
<div>年龄:{{state.age}}</div>
</div>
</template>
<script>
import { reactive, toRef } from 'vue'
export default {
name: 'reactivedemo',
setup () {
// 响应式对象
const state = reactive({
name: '太凉',
age: 18
})
// 通过toRef创建一个Ref响应式
const nameRef = toRef(state, 'name')
// 过3秒后改变 两者 保持引用关系
setTimeout(() => {
// update1:改变name属性
state.name = '冰箱太凉'
}, 3000)
// 过6秒后改变 两者 保持引用关系
setTimeout(() => {
// update1:改变name属性
nameRef.value = '我就是冰箱太凉'
}, 6000)
return {
nameRef,
state
}
}
}
</script>
3.2、toRefs()
- toRefs() 函数将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。
<template>
<div class="demo">
<h3>state 方式 不推荐的方式绑定</h3>
<div>姓名--state.name:{{state.name}}</div>
<div>年龄--state.age:{{state.age}}</div>
</div>
<div class="demo">
<h3>toRefs之后的 方式 推荐这种方式,return需要{...toRefs(state)}</h3>
<div>姓名--name:{{name}}</div>
<div>年龄--age:{{age}}</div>
</div>
</template>
<script>
import { reactive, toRefs } from 'vue'
export default {
name: 'reactivedemo',
setup () {
// 响应式对象
const state = reactive({
name: '太凉',
age: 18
})
// 通过toRefs创建一个响应式对象属性的Ref
const toRefsValue = toRefs(state)
// 过3秒后改变 两者保持引用关系
setTimeout(() => {
state.name = '冰箱太凉'
state.age = '30'
}, 3000)
// 过6秒后改变 两者保持引用关系
setTimeout(() => {
toRefsValue.name.value = '我就是宇宙小超人'
toRefsValue.age.value = '101'
}, 6000)
return {
// 不建议使用这种方式,可以用下面的方式直接替换
state,
// 最佳方式:这里是结构 将 name的ref,age的ref结构到对象根下面
...toRefsValue
}
}
}
</script>
- 我们使用 reactive创建的对象,如果想在模板中使用,就必须得使用 xxx.xxx的形式;
- 如果大量用到的话还是很麻烦的,但是使用 es6解构以后,会失去响应式。
- 那么toRefs的作用就体现在这,利用toRefs可以将一个响应式 reactive 对象的所有原始属性转换为响应式的ref属性。
注意:reactive封装的响应式对象,不要通过解构的方式return,这是不具有响应式的。可以通过 toRefs 处理,然后再解构返回,这样才具有响应式
const data = reactive({
a: 12;
b: {
c: 12;
}
})
const { a, b } = ...toRefs(data)
/* 使用 */
{{ a }}
{{ b }}
3.3、toRaw()
toRaw() 函数可以返回由 reactive()
、readonly()
、shallowReactive()
或者 shallowReadonly()
创建的代理对应的原始对象(将响应式对象转化为普通对象)。
这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用。
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
3.4、ref、toRef、toRefs 异同点
-
ref、toRef、toRefs 函数都可以将某个对象中的属性变成响应式数据
-
ref 函数的本质是拷贝,修改响应式数据,不会影响到原始数据(引用数据类型会有影响),视图会更新
-
toRef、toRefs 函数和传入的数据形成引用关系,修改 toRef 会影响这些数据,但是不会更新视图
- 作用:把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个 ref
- toRef 函数一次仅能设置一个数据,接收两个参数,第一个参数是哪个对象,第二个参数是对象的哪个属性
- toRefs 函数接收一个对象作为参数,它会遍历对象身上的所有属性,然后挨个调用 toRef 执行。用于批量设置多个数据为响应式
- 作用:把一个响应式对象转换成普通对象,该普通对象的每个属性都是一个 ref
-
ref 数据会引起监听行为,而 toRef 不会
3.5、ref
-
如果将一个对象赋值给 ref,那么这个对象将通过 reactive() 转为具有深层次响应式的对象。这也意味着如果对象中包含了嵌套的 ref,它们将被深层地解包。
-
若要避免这种深层次的转换,请使用 shallowRef() 来替代。
-
相较于reactive包裹对象完成响应式,ref更具优势,因为当改变reactive中保存的对象的内存地址时(也就是换一个全新的对象),由于vue3的响应式是通过创建代理对象,在代理对象的set方法中放入该实例的渲染函数的方法构建的,而替换整个对象,而不是修改对象内部的属性值,其并不会调用set方法,所以页面并不会响应式,但是如果使用ref包裹对象,那么此对象仅仅是ref对象的一个value属性,所以更改内存地址时,也仅仅是修改ref对象的value属性,依然会调用代理对象的set方法,完成响应式dom更新
另一个需要注意的点是,如果 ref 是文本插值的最终计算值 (即 {{ }}
标签),那么它将被解包,因此以下内容将渲染为 1
:
{{ object.id }}
该特性仅仅是文本插值的一个便利特性,等价于 {{ object.id.value }}
。
4、使用reactive定义响应式数据进行列表赋值时,视图没有更新
问题:
原因:
-
原因就是reactive函数会返回一个
Proxy
包装的对象,所以当我们这样直接赋值时:(看下面例子) -
<script setup> import { reactive } from "vue"; let userInfo = reactive([{name:'Eula'}]) console.log(userInfo) // Proxy(Array) 打印出来是一个Proxy对象 当然具备响应式 // 直接后端数据进行赋值 userInfo = [{name:'优菈'}] console.log(userInfo) // [{name:'优菈'}] 可以看出 就是打印出了一个普通的数组 所以不具备响应式 </script>
这样赋值的话,就会把Proxy对象给覆盖掉,从而无法触发对应的set和get,最终就会导致丢失掉响应性了;
上面的代码 reactive([{name:‘Eula’}]) 创建了一个响应式数组,返回一个Proxy包装的对象由userInfo变量进行存放,但是后面我又把一个普通的数组(也就是后端返回的数据)赋值给userInfo,注意这时userInfo这个变量存放的已经是一个普通的数组了,当然也就不具备响应式了;
所以:对于reactive创建的响应式数据应该避免直接使用=号进行赋值;会覆盖响应式;
解决:
- 再封装一层数据,即定义属性名,在后期赋值的时候,对此属性进行直接赋值
<script setup>
import { reactive, ref } from "vue";
// 定义响应式
let list1 = reactive({myRenderList:[]});
// 请求的数据
let newList1 = [
{ name: "Eula", age: "18", isActive: false },
{ name: "Umbra", age: "17", isActive: false },
]
// 更改数据
const setList1 = () => {
list1.myRenderList = newList1
}
</script>
- 使用数组的
splice
来直接更改原数组
-
还是用reactive来定义响应式数据,只不过改数据的方式变了,使用数组的原生方法splice()来更改原数组,不是直接覆盖所以并不会影响响应式;
-
splice有三个参数时,可以对数组进行修改, 第一项是起始索引, 第二项是长度, 第三项是新插入的元素,可以有多个;
-
下面的代码是把之前数组的每一项删除掉,然后插入新的数据newList1,使用…扩展符进行展开每一项;
-
list1.splice(0,list1.length,...newList1)
-
-
当然,
push()
方法也是可以触发响应式的,只不过只能在后面新增数据。还有pop,shift,unshift等方法(用的不多) -
对象格式可以使用
.
的方式-
<script setup> const ruleForm = reactive({}) for (const key in data) { // if (Object.hasOwnProperty.call(ruleForm, key)) { ruleForm[key] = data[key]; // } } </script>
-
- 复杂数据类型也可以使用
ref
进行定义,而且数据都是响应式的;原理就有点像第一种方式,重新包装了一层value;每次使用的时候都要写.value
;
5、elementplus的elpagination组件报错
错误:
- 控制台警告:[el-input] [API] label is about to be deprecated in version 2.8.0, please use aria-label instead.
原因:
-
el-pagination组件内部的jumper在使用el-input时,使用了label属性,而不是最新的aria-label,从而导致开发环境会报警告:[el-input] [API] label is about to be deprecated in version 2.8.0, please use aria-label instead.
由于这里是生产环境,所以没有警告输出
解决:
- 等待elementplus版本更新