Vue3中那些冷门但实用的方法

在日常的Vue 3开发中,大家往往对 refreactivewatchcomputed 等基础API信手拈来。然而,Vue 3的生态中其实隐藏着不少"宝藏"方法。这些API虽然相对冷门,但在特定的业务场景下,它们能极大地优化代码结构、提升应用性能,甚至解决一些令人头疼的痛点。

无脑使用ref和reactive?看看shallowRef和shallowReactive

只有一层的监听,对象内部所有属性的变化不会触发更新。

例如:接口查到列表数据并展示在页面上,逻辑是这样写的:

js 复制代码
const list = ref([])
async function getList() {
    const res = await apiGetData() // 调用接口获取数据
    list.value = res?.list || []
}

我们发现,针对list的改动,都是对list.value整体进行赋值,因此,我们可以使用shallowRef替换ref,这样Vue就不会去监听里面的每一层属性,从而提升性能。

js 复制代码
const list = shallowRef([])


async function getList() {
    list.value = await apiGetList() // 调用接口获取数据
}

对reactive的处理也是类似:

js 复制代码
const userObj = shallowReactive({
    user: {
        name: '',
        age: ''
    },
    loginFlag: false
})


async function getUserData() {
   const res = await apiUserList() // 调用接口获取数据
    userObj.user = res.user
    userObj.loginFlag = true
}

在这里,我们的 userObj.user是统一更新的,因此可以使用shallowReactive来提升性能。

js 复制代码
// 执行这个方法,数据变了,页面没变
function addItem() {
  let len = list.value.length
  list.value.push({
    label: `测试数据${len + 1}`,
    value: `${len + 1}`
  })
}


// 执行这个方法,数据变了,页面没变
function addAge() {
  userObj.user.age = userObj.user.age + 1
}


// 执行这个方法,数据变了,页面同步变化,并且上面两个方法执行后导致的数据与页面不同步,会在此时同步更新
function triggerLogin() {
  userObj.loginFlag = !userObj.loginFlag
}

不需要被响应:markRaw

js 复制代码
import { reactive, markRaw } from 'vue' 
import * as echarts from 'echarts'
const rawChart = markRaw(echarts.init(document.getElementById('chart')))

原理是给markRaw的数据添加上__v_skip标记。

js 复制代码
function markRaw(value) {
  if (Object.isExtensible(value)) {
    def(value, "__v_skip", true);
  }
  return value;
}

在创建响应式对象的时候,会忽略带标记的数据:

js 复制代码
function getTargetType(value) {
  return value["__v_skip"] || !Object.isExtensible(value) ? 0 /* INVALID */ : targetTypeMap(toRawType(value));
}

function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
  if (!shared.isObject(target)) {
    {
      warn(`value cannot be made reactive: ${String(target)}`);
    }
    return target;
  }
  if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
    return target;
  }
  const existingProxy = proxyMap.get(target);
  if (existingProxy) {
    return existingProxy;
  }
  const targetType = getTargetType(target);
  if (targetType === 0 /* INVALID */) {
    return target;
  }
  const proxy = new Proxy(
    target,
    targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
  );
  proxyMap.set(target, proxy);
  return proxy;
}

不想使用pinia?直接使用provide和inject进行跨组件通信

使用方式就和状态管理工具mobx类似,

根组件定义provide:

js 复制代码
const flag = ref(false)
provide('finished', flag)

子组件访问:

js 复制代码
const status = inject('finished'); // 访问父组件的flag,子组件的

子组件修改这个共享变量,修改后每一个用到的地方都会同步修改并且更新页面。

js 复制代码
const status = inject('finished')


function triggerFinished() {
  status.value = !finished.value
}

Vue组件内部的数据转换:toRefs、toRef、toRaw

js 复制代码
// 假设这里有A-G七科成绩,需要展示出来
const score1 = ref({
  A: 100,
  B: 99,
  C: 98,
  D: 97,
  E: 96,
  F: 95,
  G: 94
})


const score2 = reactive({
  A: 100,
  B: 99,
  C: 98,
  D: 97,
  E: 96,
  F: 95,
  G: 94
})

我们只能通过score1.value.Ascore2.A去访问,如果数据很多或者层级很深,我们访问元素属性的链路就会特别长。

html 复制代码
<div>A:{{ score1.A }} </div>
<div>B:{{ score1.B }} </div>
<div>C:{{ score1.C }} </div>
<div>D:{{ score1.D }} </div>
<div>E:{{ score1.E }} </div>
<div>F:{{ score1.F }} </div>
<div>G:{{ score1.G }} </div>


<div> </div>


<div>A:{{ score2.A }} </div>
<div>B:{{ score2.B }} </div>
<div>C:{{ score2.C }} </div>
<div>D:{{ score2.D }} </div>
<div>E:{{ score2.E }} </div>
<div>F:{{ score2.F }} </div>
<div>G:{{ score2.G }} </div>

我们可以使用toRefs去把用refreactive定义的对象进行解构,会生成一个普通对象,普通对象的每一个属性都是响应式对象。把这些对象的属性单独生成响应式对象。

js 复制代码
const { A, B, C, D, E, F, G } = toRefs(score1.value)
const { A, B, C, D, E, F, G } = toRefs(unref(score1))
const { A, B, C, D, E, F, G } = toRefs((score2))

A.value = 90 // 同步发生变化

如果我们不需要处理所有的属性,可以使用toRef进行单个属性处理

js 复制代码
const GG = toRef(score1.value, 'G')
const GG = toRef(unref(score1), 'G')
const GG = toRef(score2, 'G')

GG.value = 80 // 同步发生变化

使用场景:某些场景下可以当computed用

js 复制代码
const GG = computed(() => score2.G)

但是我们一般不会把computed放在v-model里,为了确保触发数据变化的来源唯一。

这种情况下,就适合使用toRef了,如果对象层次非常深,可读性更好。

html 复制代码
<a-input v-model="GG"></a-input>
<a-input v-model="score2.G"></a-input>

toRaw:把响应式对象转成原始对象

js 复制代码
const originData = toRaw(score1.value)
const originData = toRaw(unref(score1))
const originData = toRaw(score2)


originData.A++ // 原始数据改变,数据会变,但是页面不更新,等到下次有触发页面更新是,会将此次改动同步,实际上我们不会这样操作,只是演示一下这样操作的后果,减少出现难以理解的问题。

toRaw 的作用:我们知道,Vue3是通过proxy对数据进行的劫持,因此对proxy对象的读取(get)、修改(set)、删除(deleteProperty)等操作都会触发一些响应式逻辑。处理大量数据或者复杂操作时,使用toRaw处理后,我们可以直接访问原始对象,避免不必要的响应式开销,从而提升性能。

事实上,大部分的场景是使用cloneDeep方法把对象复制一份,进行处理,避免出现隐式操作不小心改了响应式对象。

h函数

h函数处理dom:

js 复制代码
const style = { 'text-align': 'left', 'text-overflow': 'ellipsis', 'white-space': 'nowrap', overflow: 'hidden' };
customRender: ({ record }) => {
  return h('div', { style }, record.nd);
}

h函数处理组件:

js 复制代码
customRender: ({ record }) => {
  if (!Reflect.has(record, 'pendingStatus')) {
    record.pendingStatus = false
  }
  return h(Switch, {
    checked: record.status == '1',
    checkedChildren: '已启用',
    unCheckedChildren: '已停用',
    loading: record.pendingStatus,
    onChange(checked, e) {
      e.stopPropagation() // 直接点击按钮的时候避免触发选中事件
      record.pendingStatus = true;
      const newStatus = checked ? '1' : '0';
      changeStatus({
        status: newStatus,
        id: record.id
      }).then(() => {
        record.status = newStatus;
      }).finally(() => {
        record.pendingStatus = false;
      });
    },
  });
}

嵌套处理:

html 复制代码
<a-button @click.stop="handlePreview(text)">
  <Icon icon="bi:eye" />
  <span>{{ text.length }}</span>
</a-button>
js 复制代码
let h1 = h(Icon, {
  icon: 'bi:eye'
})
let h2 = h('span', {}, record[item.zdm].length)
return h(Button, { onClick: () => handlePreview(record[item.zdm]) }, [h1, h2])
相关推荐
qq_349523261 小时前
分析原型到表的过程
前端
1 小时前
Pinia 全局状态管理
前端
M ? A1 小时前
Vue 转 React | VuReact 实时监听开发指南
前端·vue.js·后端·react.js·面试·开源·vureact
陆枫Larry1 小时前
uni-app 小程序:滚动联动透明导航栏的实现
前端
半兽先生1 小时前
vue高性能下拉组件 支持上万数据不卡顿
前端·javascript·vue.js
invicinble1 小时前
前端框架使用vue-cli( 第二层:工程配置层--路由页面配置)
javascript·vue.js·前端框架
懂懂tty1 小时前
Vue3 架构
前端·vue.js
invicinble1 小时前
前端框架使用vue-cli( 第二层:工程配置层--总览)
前端·vue.js·前端框架
哆啦A梦15882 小时前
01, 前端vue3框架的快速搭建以及项目工程的讲解
前端·vue3·springboot