vue props的不规范使用破坏了props的单向数据流动

前言


大家好,我是林三心,用最通俗易懂的话讲最难的知识点是我的座右铭,基础是进阶的前提是我的初心~

当我们传递的props不再是普通的字面量而是对象时,极易写出一些破坏props的单项数据流动的代码

我们以一个简单的todoList做举例,这是App.vue的代码,之后不会做变更

<script setup lang="ts">
import { ref, watch } from 'vue'
import type { Task } from './types'
import TaskCom from './components/Task.vue'

const lists = ref<Task[]>([{
title: 'title1',
done: false,
}])
</script>

<template>
<TaskCom
v-for="(task, index) in lists"
:key="index"
:task="task" />
</template>

注意的点,我们没有接收Task组件的update:task事件,也就是说如果Task组件是受控的,那么它的值应当始终不发生变动,如果它的值不是受控的,也不应当影响到prop传递进去的task,否则就是破坏了props的单项数据流动

最简单的破坏方式

Task.vue组件代码

<script setup lang="ts">
import type { Task } from '../types'

const props = defineProps<{
task: Task
}>()

const emits = defineEmits<{
(e: 'update:task', newTask: Task): void
}>()
</script>

<template>
<div>
<input v-model="task.done" type="checkbox">
<span>{{ task.title }}</span>
</div>
</template>

如果运行起来去更改checkbox的值,你会发现App组件内的lists下面task的done值也进行了变更,Task组件直接修改了props的参数!!!

这种直接使用v-model的还好,因为eslint会给你一个报错,提醒你不会去犯这个错误

Unexpected mutation of "task" prop.eslint[vue/no-mutating-props](https://eslint.vuejs.org/rules/no-mutating-props.html)

使用toRef进行破坏

比较常见的是使用toRef对props的值进行解构,进而规避掉了eslint的错误提醒

也会达到直接修改了props的效果

<script setup lang="ts">
//...

const task = toRef(props, 'task');

// 或者

const { task } = toRefs(props)
</script>

所以使用toRef或者toRefs对props的解构并不是一个很好的行为

不当的使用vueuse的useVmodel

useVmodel对于字面量的值做双向绑定可以做到受控的状态绑定,但是对于对象而言跟使用toRef是一样的结果

<script setup lang="ts">
//...
const task = useVModel(props, 'task', emits)
</script>

这种情况下,依然会直接修改到props的值

不破坏props数据单向流动的使用方法

处理成受控组件

每一次更改都使用emit事件进行发送

<script setup lang="ts">
// ...
function handleChange(e: any) {
emits('update:task', {
...props.task,
done: e.target.checked,
});

e.target.checked = props.task.done;
}
</script>

<template>
<div>
<input :checked="task.done" type="checkbox" @change="handleChange" />
<span>{{ task.title }}</span>
</div>
</template>

需要手动赋值e.target.checked,是因为input的checked属性不是一个受控值

摘自mdn

checked

一个布尔属性,表示该复选框是否被默认选中(当页面加载时)。它不表示这个复选框当前是否被选中:如果复选框的状态被改变,这个内容属性不反映这个变化。此时,只有 `HTMLInputElement`[1] 的 IDL 属性 checked 会更新。

此时,点击checked的值是不会发生变更的,props的值也不会受到影响,除非父组件确定接收这个变更

如果App.vue对子组件发出的变更事件作出响应

<script setup lang="ts">
// ...
function updateTask(task: Task, index: number) {
lists.value[index] = task;
}
</script>

<template>
<TaskCom
v-for="(task, index) in lists"
:key="index"
:task="task"
@update:task="(val) => updateTask(val, index)"
/>
</template>

这样checked的点击效果又恢复成正常了,而且是完全受控的

当然也可以使用vueuse的usevmodel发送事件,就不如上面那么的直观

<script setup lang="ts">// ...const task = useVModel(props, 'task', emits);function handleChange(e: any) {  task.value = {    ...props.task,    done: e.target.checked,  };  e.target.checked = props.task.done;}</script>

处理成非受控组件

<script setup lang="ts">
// ...
const task = ref({ ...props.task });

/** 处理props值同步 */
watch(
() => props.task,
() => {
task.value.done = props.task.done;
task.value.title = props.task.title;
},
{ deep: true }
);

/** 处理事件发送 */
watch(
() => task,
() => {
emits('update:task', {...task.value});
},
{ deep: true }
);
</script>

<template>
<div>
<input type="checkbox" v-model="task.done" />
<span>{{ task.title }}</span>
</div>
</template>

直接使用useVmodel或者watchEffect(()=>{task.value=props.task})都很容易出现死循环,所以建议一般还是处理成受控的组件

Demo

Vue props单向数据流动 - StackBlitz[2]

结论

在 Vue 中,props 是单向数据流,即组件只能从父组件接收 props,并不能直接修改它们。这是为了保持代码的可维护性和可预测性。

然而,当我们将对象作为 props 传递给子组件时,容易写出一些破坏单向数据流的代码。比如,直接使用 v-model 绑定对象的属性,使用 toRef 或 toRefs 对 props 进行解构等等,这些行为都有可能直接修改父组件传递进来的 props,导致数据流的混乱和代码的难以维护。

在实际开发中,我们应该遵循 Vue 的设计原则,保持 props 的单向数据流动,而不是试图修改它们。为了解决这个问题,我们可以将 props 转换为受控组件,通过 emit 事件将更改的值传递给父组件进行响应,从而保持单向数据流的原则。

以一个 todoList 的例子,我们演示了一些破坏 props 单向数据流的代码和如何通过转换成受控组件避免这些问题的方法。虽然在实际开发中我们可能会使用其他方法来解决这个问题,但遵循单向数据流的原则对于代码的可维护性和可预测性都是非常重要的。

作者:成路

链接:https://juejin.cn/post/7225231707825045563

来源:稀土掘金

结语

我是林三心

  • 一个待过小型toG型外包公司、大型外包公司、小公司、潜力型创业公司、大公司的作死型前端选手;
  • 一个偏前端的全干工程师;
  • 一个不正经的掘金作者;
  • 逗比的B站up主;
  • 不帅的小红书博主;
  • 喜欢打铁的篮球菜鸟;
  • 喜欢历史的乏味少年;
  • 喜欢rap的五音不全弱鸡如果你想一起学习前端,一起摸鱼,一起研究简历优化,一起研究面试进步,一起交流历史音乐篮球rap,可以来俺的摸鱼学习群哈哈,点这个,有7000多名前端小伙伴在等着一起学习哦 --> 

相关推荐

  • 15篇MyBatis-Plus系列集合篇「值得收藏学习」
  • 总结了十个Vue3超级实用但是很冷门的API
  • 11月24日,OC城市行·深圳站「操作系统与AI技术应用实践沙龙」邀你参与!
  • 99%的程序员容易忽视的“系统”健康问题
  • 纯CSS实现炫酷文本时钟
  • 和小伙伴们仔细梳理一下 Spring 国际化吧!从用法到源码!
  • 在农村,谁家孩子在华为、腾讯、阿里、字节上班,亲朋乡邻羡慕不已,年薪百万
  • 全新升级!Supabase 与 Next.js 14 的完美融合
  • 2024年不容错过的网站开发技术新趋势
  • 揭秘Java中的瑞士军刀——ArrayList源码解析
  • 轻量级文件快递柜,像拿快递一样取文件
  • 微软230页报告,像素级评估GPT-4前沿科研能力:潜力无限速速上车!
  • 起底OpenAI「国王」Ilya:师从Hinton,为了他,马斯克与谷歌创始人彻底决裂
  • OpenAI 505员工联名逼宫请奥特曼回归,Ilya痛悔赶走CEO!威胁董事会立即解散,否则集体跳槽微软
  • GPT-4V医学执照考试成绩超过大部分医学生,AI加入临床还有多远?
  • 脑科学如何启发AI,这场论坛带你全面探索「大脑网络与智能计算融合方式」
  • 奖学金18万/年,香港科技大学(广州)数据科学与分析方向招收全奖博士生
  • 用检索增强生成让大模型更强大,这里有个手把手的Python实现
  • Sam Altman要加入微软,推动者Ilya却后悔了,超500名员工请辞逼宫董事会
  • 一些RLHF的平替汇总