Vue3 中Ref的最佳实践

news2024/10/6 5:32:00
alt

在vue3中如果我们需要获取一个响应式的变量,可以使用ref来定义一个变量。

const name = ref( "" );
name.value = "test"

定义好后,就可以实现修改状态,更新UI的效果了。

在这个基础上,本文主要讨论跨组件时如何管理Ref的状态,以及如何更好地封装Ref的读写。

单向数据流

https://cn.vuejs.org/guide/components/props#one-way-data-flow

所有的 props 都遵循着单向绑定原则,props 因父组件的更新而变化,自然地将新的状态向下流往子组件,而不会逆向传递。这避免了子组件意外修改父组件的状态的情况,不然应用的数据流将很容易变得混乱而难以理解。

举一个例子,我们要实现一个header里面有一个menu的按钮,当点击了会把侧边栏滑进来,然后点击了关闭按钮又隐藏。

<script>
import { ref } from "vue";
import Header from "./components/Header.vue";
import Nav from "./components/Nav.vue";
export default {
  components: {
    Header,
    Nav,
  },
  setup() {
    const isOpen = ref(false);
    const handToggle = () => isOpen.value = !isOpen.value;
    return { isOpen, handToggle };
  },
};
</script>

<template>
  <Header @toggle="handToggle" />
  <Nav :isOpen="isOpen" @toggle="handToggle" />
</template>

header.vue

<script>
export default {
  setup(props, { emit }) {
    const handleMenu = () => {
      emit("toggle");
    };
    return { handleMenu };
  },
};
</script>

<template>
  <header>
    <nav>
      <button @click="handleMenu">menu</button>
    </nav>
  </header>
</template>

nav.vue

<script>
export default {
  props: {
    isOpen: {
      type: Boolean,
      default: false,
    },
  },
  setup(props, { emit }) {
    const handleMenu = () => {
      emit("toggle");
    };
    return { props, handleMenu };
  },
};
</script>

<template>
  <div :class="['nav', { open: props.isOpen }]">
    <a @click="handleMenu">close</a>
  </div>
</template>

整体的效果如下所示:

alt

在点击了Header.vue里面的Menu 后发送一个emit给上层的component,透过上层接收到的toggle事件去修改我们isOpen的value,然后isOpen再透过props 传入Nav.vue里面去控制侧边选单的开起,然后透过侧边选单的close按钮,在发送一个 toggle事件往上去更改 isOpen,然后props 的isOpen也会同步知道被更改,这样就完成了整个的流程!

整个流程其实也遵循了单一数据流的特性。props都是从父级修改的。但是不是感觉把简单的事情复杂化了?增加了一层事件的往返通信来修改props。有没有更方便的做法?其实我们可以直接去掉事件这一层。

<script>
// 其他省略...
export default {
  setup() {
    const isOpen = ref(false);
    const handleOpenMenu = () => isOpen.value = !isOpen.value;
    return { isOpen, handleOpenMenu };
  },
};
</script>

<template>
  <Header :handleOpenMenu="handleOpenMenu" />
  <Nav :isOpen="isOpen" :handleOpenMenu="handleOpenMenu" />
</template>

把修改值的方法传递给子组件去调用,避免了事件的通信。调整后的数据流就简单多了。

alt

单向数据流是一种数据流动的模式,数据从父组件流向子组件,子组件只能通过props从父组件接收数据,而不能直接修改父组件的状态。这种模式使得数据流更加清晰和可预测,有助于维护和管理大型应用。

在react中也有类似的效果"Lifting State Up"(状态提升),当多个组件需要共享同一个状态时,可以将状态提升到它们共同的父组件中。这样,子组件可以通过props从父组件获取状态,并通过回调函数通知父组件更新状态。状态提升有助于避免组件间的直接状态共享,使得状态管理更加集中和一致。

封装

你可能会疑惑,上面子组件调用handleOpenMenu不是直接修改props么?这种不属于直接修改,控制权是在父组件。我们把修改的方式用函数封装起来了,隐藏了中间的实现细节。这就是一种封装。最典型的例子就是react的useState。

import { useState } from  'react' ;

function  MyComponent () {
   const [age, setAge] = useState ( 28 );
   const [name, setName] = useState ( 'Taylor' );
   const [address, setAddress] = useState ( 'Taiwan' );
}

在React中,useState允许开发者创建一个状态变量和一个设置这个状态的函数。这样,开发者可以通过这个函数来修改状态,而不是直接操作状态变量。这种方式简化了状态管理,并且使得状态的修改更加可控。

虽然在小型项目中直接使用.value来修改Vue中的响应式数据非常方便,但在需要传递props或者使用emit进行父子组件通信时,还是需要定义设置函数。随着项目的增大或者时间的推移,可能会出现同时使用.value和设置函数的情况,这可能会让代码变得混乱。所以最好是我们统一封装规范它的使用,我们封装一个useRefState来管理状态。

import { shallowRef } from  "vue" ;

export  function  useRefState ( baseState ) {
   const state = shallowRef (baseState);
   const  update = ( newValue ) => {
    state. value = newValue;
  };
  return [state, update];
}

这么一来我们就可以在开发的时候使用跟React 一样的方式来定义状态。

< script  setup > import { useState } from "./composables/useRefState.js" ;
   const [name, setName] = useRefState ( "mike" );
   const [info, setInfo] = useRefState ({
     name : "mike" ,
     age : 12 ,
   
  });
</ script >

< template > 
  < h2 > name: {{ name }} </ h2 > 
  < pre > info: {{ info }} </ pre >

  < input  type = "text"  v-model = "name" />
  
  < button @ click = "setName('jacky')" > set name </ button > 
  < button @ click = "setInfo({ name: 'andy', age: 20 })" > set info </ button > 
</ template >

它的好处是可以减少每次在定义ref的时候都要再写一个set function ,造成code 会很多很杂的问题,而且也不会失去响应式的特性!

在上面的实现中,我们使用的是shallowRef而非ref。

这段内容主要解释了在Vue框架中,refshallowRef的区别以及shallowRef的用途。ref是Vue中的一个响应式API,它将内部值转换为响应式对象。与ref不同,shallowRef内部的值不会自动转换为响应式。只有当你通过.value属性设置值时,这个操作才是响应式的。也就是说,只有第一层的属性是响应式的。

shallowRef常用于大型数据结构的性能优化和与外部状态管理系统的整合。由于shallowRef只在第一层属性上是响应式的,这使得它在处理大型或深层数据结构时,性能更好。因为不需要对每个深层属性都进行响应式处理,减少了性能开销。

当我们使用useRefState之后,就不能直接针对单个属性去set value,需要将原本的物件加上新的值一起写入。

const [info, setInfo] = useState ({
     name : "mike" ,
     age : 12 ,
});

// 把所有属性一起写入
setInfo ({
  ...info,
  address : addr,
});

基本上就跟我直接替换整个object 一样,所以这边就不需要使用ref来增加无谓的效能消耗。

总结

通过本文的探讨,我们可以看到Vue 3的响应式系统为我们提供了强大的工具来管理跨组件的状态。无论是通过事件通信还是直接传递方法来更新状态,Vue都提供了灵活的解决方案。封装useRefState的做法,让我们能够以一种更加接近React的useState的方式来处理Vue中的状态,使得状态管理更加直观和一致。

最后,无论我们选择哪种方式来管理状态,重要的是要确保我们的应用遵循清晰的数据流和一致的设计模式。这样,随着项目的增长和复杂性的增加,我们仍然能够保持代码的可维护性和可扩展性。

本文由 mdnice 多平台发布

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2191196.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于STM32的智能风扇控制系统设计

引言 本项目将基于STM32微控制器设计一个智能风扇控制系统&#xff0c;通过温度传感器实时检测环境温度&#xff0c;并根据预设的温度范围自动调节风扇的转速。该系统展示了STM32的PWM输出、传感器接口以及自动控制应用的实现。 环境准备 1. 硬件设备 STM32F103C8T6 开发板…

Python 语言学习——应用1.1 数字图像处理(第一节,颜色)

目录 1.基础知识 2.实战演示 1.基础知识&#xff1a; 1.图像的表示. 函数表示&#xff1a;图像是二维信号&#xff0c;定义为二维函数f(x,y)&#xff0c;其中&#xff0c;x、y是空间坐标&#xff0c;f(x,y)是点(x,y)的幅值。拓展看&#xff0c;视频&#xff0c;又称动态图像…

SOMEIP_ETS_166: SD_TestFieldUINT8

测试目的&#xff1a; 验证DUT能够通过Getter和Setter方法正确地发送和接收TestFieldUINT8字段的值。 描述 本测试用例旨在确保DUT的ETS能够响应Tester的请求&#xff0c;正确地使用Getter方法获取TestFieldUINT8的值&#xff0c;以及使用Setter方法设置新的值。 测试拓扑&…

QGIS中怎么加载数据(如矢量shp与栅格数据)

最近有不少初学者来问我qgis里怎么加载数据 这个与arcgis中的操作其实也是类似的&#xff0c;也是通过软件的里面&#xff0b;号就行了 下面是我对这个问题的解决思路&#xff1a; 一种是直接把图层文件拖进去&#xff0c;但是这种方法很有局限性&#xff0c;下面我还说明一…

JavaWeb的小结02

第2章-第2节 一、知识点 HttpServletRequest请求对象、HttpServletResponse响应对象、响应内容类型Content-Type、请求转发、重定向、ServletContext对象。 二、目标 深刻理解HttpServletRequest对象的作用。 深刻理解HttpServletResponse对象的作用。 掌握HttpServletRequ…

什么是请求转发?

请求转发 解释 请求转发,将前端发送的请求转发到别的资源 别的资源是指: servlet,页面 即: 请求转发,可以将请求转发值另外一个servlet;也可以是将请求转发至页面 1、 请求转发演示 1.1 请求转发跳转页面 实战: 之前注册练习,修改: 实现注册完跳转到登录页面 1.2 请求转发…

【Matlab案例】imageJ + matlab 实现物体轨迹追踪及路径彩色上色

我们经常看到一些文献中对细胞或者粒子的运动轨迹进行上色&#xff0c;不同的颜色对应着不同的时间。一纯色的轨迹实现起来很方便&#xff0c;彩色的轨迹如何实现呢&#xff1f;本文使用imageJ获取轨迹数据&#xff0c;使用matlab对轨迹进行上色。结果如下&#xff1a; 1. im…

Java | Leetcode Java题解之第457题环形数组是否存在循环

题目&#xff1a; 题解&#xff1a; class Solution {public boolean circularArrayLoop(int[] nums) {int n nums.length;for (int i 0; i < n; i) {if (nums[i] 0) {continue;}int slow i, fast next(nums, i);// 判断非零且方向相同while (nums[slow] * nums[fast]…

Python爬虫(二)--http基本原理(Python Crawler (2) Basic Principles of HTTP)

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:Linux运维老纪的首页…

TriLite完成A轮扩展融资:加速AR微型投影仪技术创新与市场拓展

近日,全球领先的AR微型投影仪开发商TriLite宣布成功完成A轮扩展融资,将A轮融资总额提升至超过2000万欧元。这一轮融资不仅彰显了资本市场对TriLite技术实力和市场潜力的高度认可,更为其后续在AR微型投影仪领域的技术研发、产品迭代以及市场拓展提供了坚实的资金保障。以下是…

力扣刷题 | 两数之和

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 给定一个整数数组 nums 和…

C++ | Leetcode C++题解之第456题132模式

题目&#xff1a; 题解&#xff1a; class Solution { public:bool find132pattern(vector<int>& nums) {int n nums.size();vector<int> candidate_i {nums[0]};vector<int> candidate_j {nums[0]};for (int k 1; k < n; k) {auto it_i upper_…

Leetcode—416. 分割等和子集【中等】

2024每日刷题&#xff08;172&#xff09; Leetcode—416. 分割等和子集 C实现代码 class Solution { public:bool canPartition(vector<int>& nums) {int sum accumulate(nums.begin(), nums.end(), 0);if(sum % 2) {return false;}int m nums.size();int subSu…

自动售卖柜目标检测数据集 4880张 商品数据集 voc yolo

自动售货机商品检测数据集 名称 自动售货机商品检测数据集 (Automatic Vending Machine Product Detection Dataset) 规模 图像数量&#xff1a;4880张图像。类别&#xff1a;30种不同的商品类别。 数据划分 训练集 (Train)&#xff1a;通常占总数据的80%左右&#xff0c;…

【AI知识点】二项分布(Binomial Distribution)

二项分布&#xff08;Binomial Distribution&#xff09; 是概率论和统计学中描述独立重复的伯努利试验中成功次数的离散概率分布。它是基于多次独立的伯努利试验的扩展&#xff0c;用于描述在 n n n 次试验中发生成功的次数。 1. 二项分布的定义 二项分布用于描述在 n n n…

利用GPU进行训练

文章目录 一、GPU训练模型二、对比使用gpu和cpu进行训练所花费的时间三、GPU训练模型的第二种表达方式 一、GPU训练模型 GPU只能够训练三种变量&#xff0c;分别是&#xff1a; 网络模型 数据&#xff08;输入&#xff0c;标注targets&#xff09; 损失函数 使用方式是.cuda…

精品WordPress主题/响应式个人博客主题Kratos

Kratos 是一款专注于用户阅读体验的响应式 WordPress 主题&#xff0c;整体布局简洁大方&#xff0c;针对资源加载进行了优化。 Kratos主题基于Bootstrap和Font Awesome的WordPress一个干净&#xff0c;简单且响应迅速的博客主题&#xff0c;Vtrois创建和维护&#xff0c; 主…

rockylinux9安装软件报错

1、rocky linux9再安装软件的时候报错&#xff1a; [rootClient119 yum.repos.d]# yum -y install epel-release [rootClient119 yum.repos.d]# yum -y install libcgroup Extra Packages for Enterprise Linux 9 - x86_64 …

【MySQL 09】表的内外连接

目录 1.内连接 创建表&#xff08;案例准备&#xff09; 案例&#xff1a; 2.外连接 2.1左外连接 案例&#xff1a; 2.2右外连接 案例&#xff1a; 1.内连接 内连接实际上就是利用where子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开发过…

什么是pip? -- Python 包管理工具

前言 不同的编程语言通常都有自己的包管理工具&#xff0c;这些工具旨在简化项目的依赖管理、构建过程和开发效率&#xff0c;同时促进代码的复用和共享。每个包管理工具都有其独特的特点和优势&#xff0c;开发者可以根据自己的编程语言和项目需求选择合适的包管理工具。 pip是…