Vue2中watch与Vue3中watch对比和踩坑

news2024/11/18 3:03:59

上一节说到了 computed计算属性对比 ,虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

Vue2 watch用法

 Vue2 中的 watch 是一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个 property。

Vue2 存在两种监听方式,分别是简单监听和复杂监听

简单监听:监听的是一个回调函数,当监听的值发生改变时,才会执行监听动作。

<template>
  <h2>当前求和值为:{{ sum }}</h2>
  <button @click="sum++">点击加1</button>
</template>

<script>

export default {
  name: "TestComponent",
  data() {
    return {
      sum:1
    }
  },
  watch:{
    sum(newValue, oldValue) {
      console.log('sum的值变化了',newValue, oldValue);
    }
  },
};
</script>

上面的是一个最简单的监听动作,只有在点击按钮 sum 的值变化之后,监听器 watch 才会触发。同时,我们还可以将这个方法放到 methods 中,通过方法名的方式在 watch 中实现监听效果

  watch:{
    sum:'sumAdd'
  },
  methods: {
    sumAdd(newValue, oldValue) {
       console.log('sum的值变化了',newValue, oldValue);
    }
  },

深度监听:监听的是一个包含选项的对象。除了包含简单监听的功能之外,还包含深度监听、初始化监听等。

首先,我们可以通过对象形式来实现简单监听的效果,还是按照上面的例子,例如:

// 其余代码一致
watch:{
  sum:{
    handler(newValue, oldValue) {
      console.log('sum的值变化了',newValue, oldValue);
    }
  }
},

通过对象形式实现深度监听 -- deep:true 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深,也就是说即使监听的是一个对象形式的数据,只要对象内部属性发生变化,都能被监听到。

watch:{
  sum:{
    handler(newValue, oldValue) {
      console.log('sum的值变化了',newValue, oldValue);
    },
    deep:true
  }
},

通过对象形式实现初始化监听 -- immediate:true 该回调将会在侦听开始之后被立即调用,也就是说在组件初始化时,就会监听一次,在数据改变之后继续监听

watch:{
  sum:{
    handler(newValue, oldValue) {
      console.log('sum的值变化了',newValue, oldValue);
    },
    immediate:true
  }
},

完整的对象监听:深度监听+初始化监听

watch:{
  sum:{
    handler(newValue, oldValue) {
      console.log('sum的值变化了',newValue, oldValue);
    },
    deep: true,
    immediate:true
  }
},

在Vue3 中使用 Vue2 的watch

 和 在 Vue3 中使用 Vue2 的computed 计算属性一样,直接使用 watch 配置项即可。

<template>
  <h2>当前求和值为:{{ sum }}</h2>
  <button @click="sum++">点击加1</button>
</template>

<script>
import { ref } from "vue";

export default {
  name: "TestComponent",
  watch: {
    sum: {
      handler(newValue, oldValue) {
        console.log("sum的值变化了", newValue, oldValue);
      },
      deep: true,
      immediate: true,
    },
  },

  setup() {
    let sum = ref(1);

    return {
      sum,
    };
  },
};
</script>

当页面第一次渲染时,监听器就执行了一次,这对应的是  -- immediate: true

点击按钮之后,页面渲染,同时监听器也会同步触发。

Vue3 中 watch的基本使用 

和 computed 一样,组合式api在使用时,需要先引入,再使用。

Vue3 中的 watch 是一个函数,接收三个参数,

  1. 第一个参数是需要被监听的数据( 单个数据,数组格式的多个数据),
  2. 第二个参数是回调函数,用来处理监听之后的动作
  3. 第三个参数则是监听配置项( 深度监听、初始化监听 )。

但是和 computed 不一样的是 在 setup 中定义的监听器不需要使用变量接收且 return 返回的,因为 监听是一种行为,而计算属性则是一个值。 

<template>
  <h2>当前求和值为:{{ sum }}</h2>
  <button @click="sum++">点击加1</button>
</template>

<script>
//组合式api需要先引入再使用
import { ref ,watch} from "vue";

export default {
  name: "TestComponent",

  setup() {
    let sum = ref(1);
    
    // 不用接收,不用返回,因为监听是动作,计算属性、响应式数据、函数都是值
    watch(sum, (newValue, oldValue) => {
      console.log("sum的值变化了", newValue, oldValue);
    })

    return {
      sum,
    };
  },
};
</script>

Vue3 中 watch 的复杂使用方式

上面说的Vue3 中 watch 的简单使用方式,其实就是监听单个 ref 定义的响应式数据。但是 Vue3 中的 watch 可以分为好几种情况:

情况一:通过 watch 监听 ref 定义的单个基础类型响应式数据,也就是上面的例子

情况二:通过 watch 监听 ref 定义的多个基础类型响应式数据,例如

<template>
  <h2>当前求和值为:{{ sum }}</h2>
  <button @click="sum++">点击加1</button>

  <br>

  <h2>当前msg值为:{{ msg }}</h2>
  <button @click="msg += '!'">点击加!</button>
</template>

<script>
import { ref ,watch} from "vue";

export default {
  name: "TestComponent",

  setup() {
    let sum = ref(1);
    let msg = ref('你好啊')

    watch(sum, (newValue, oldValue) => {
      console.log("sum的值变化了", newValue, oldValue);
    })

    watch(msg, (newValue, oldValue) => {
      console.log("msg的值变化了", newValue, oldValue);
    })

    return {
      sum,
      msg
    };
  },
};
</script>

但是这么写很明显太麻烦了,我想监听多个,那我就需要写多个 watch 监听函数,还不如 Vue2的配置项直接定义一个对象来的方便,所以Vue3 也提供了简便写法,那就是通过数组形式一次性监听多个数据:

// 通过 [sum,msg] 一次性监听多个数据
watch([sum,msg], (newValue, oldValue) => {
  console.log("sum或msg的值变化了", newValue, oldValue);
})

同时,我们改变 sum和msg,发现返回的 newValue 和 oldValue 分别是两个数组

  • 第一步:改变 sum ,newValue 数组中 sum 值改变,msg值不变,oldValue 数组中的值就是 sum 和 msg 的初始值
  • 第二步:改变 msg,newValue 数组中 sum 值不变,msg值改变变,oldValue 数组中的值就是 sum 和 msg 的上一次的值

情况三:通过 watch 中的 immediate: true 初始化监听 ref 定义的基础类型响应式数据

watch(sum, (newValue, oldValue) => {
  console.log("sum的值变化了", newValue, oldValue);
},{immediate: true})

可以发现,初始化监听成功,在组件初始化, sum 未发生改变时 监听动作就已经执行了。

情况四:通过 watch 监听 ref 定义的对象类型的响应式数据 -- 存在bug( 无法正确获取 oldValue )

我们用 ref 定义一个响应式对象数据,但是这两有两个点需要注意:

  1. 通过 ref 定义的对象数据,其实底层还是通过 reactive 来实现响应式的
  2. 通过 ref 定义的数据是一个 RefImpl对象,在 js 代码中使用时,不会自动解包,需要 .value 
<template>
  <p>{{person.name}}</p>
  <p>{{person.age}}</p>
  <button @click="person.name += '~'">更改name</button>
  <button @click="person.age++">更改age</button>
</template>

export default {
  name: "TestComponent",

  setup() {
    
    let person = ref({
      name: "al",
      age: 28,
    });

    watch(person.value, (newValue, oldValue) => {
      console.log("person的值变化了", newValue, oldValue);
    });

    return {
      person,
    };
  },
};

然后我们就会发现下面这么问题:

  1. 当更改 name 属性时,newValue 中的 name 改变了,但是 oldValue 中的 name 也同步变了
  2. 当更改 age 属性时,newValue 中的 age改变了,但是 oldValue 中的 age也同步变了

这和我们想的也太不一样了啊,不是说好了 oldValue 是上一次的值么,怎么还同步更新了呢? 这其实就是 Vue3 的监听bug,暂时官方也没有给出具体的解决办法,但是其实我们在开发过程中也确实不太关注 oldValue 的值。

但是如果你一定想要监听的话,建议把对象拆成单个 ref ,或者把你需要监听的对象中的某个属性单独拆成一个 ref,例如:上面的例子中,我现摘指向单独监听 age,那我就不把age 塞到 person 对象里面了,单独拎出来使用 ref定义。

let person = ref({
  name: "al",
});

let age = ref(28)

watch(age.value, (newValue, oldValue) => {
  console.log("age的值变化了", newValue, oldValue);
});

如果你觉得 reactive 可以解决这个问题,你也不妨试一试通过 reactive 定义的响应式数据能否达到你想要的效果。

关于 reactive 和 ref 的原理及对比请参考之前的博文 -- Vue2与Vue3响应式原理对比 里面通过源码详细解释了 ref 和 reactive 定义对象数据的底层关系

情况五:深度监听 ref 定义的对象形式的响应式数据 -- 默认开启 deep:true。监听整个对象。可通过配置关闭( 但是在 Vue3.2之前的版本,好像是不能进行配置更改的 )

其实我们按照上面的例子,将数据嵌套多几层,然后改变数据

let person = ref({
  name: "al",
  age: 28,
  job:{
    j1: {
      work: '前端',
      salary:1
    }
  }
});

watch(person.value, (newValue, oldValue) => {
  console.log("person.salary的值变化了", newValue, oldValue);
});

然后改变数据

<template>
  <p>{{ person.job.j1.work }}</p>
  <p>{{ person.job.j1.salary }}</p>
  <button @click="person.job.j1.salary++">涨薪</button>
</template>

我们可以发现,此时 深度嵌套的值也能被监听到。但是 在 Vue2 中监听深度嵌套数据时,我们是需要配置 deep:true  才能实现的 ,但是在 Vue3 中不用开启 deep:true 就可以直接实现深度监听了。

所以我们可以大胆假设,在 Vue3 中,deep:true 是默认开启的,如果我们进行配置关闭,那会有什么结果呢?

watch(person.value, (newValue, oldValue) => {
  console.log("person.salary的值变化了", newValue, oldValue);
},{deep:false});    // 关闭深度监听

关闭之后我们可以发现,无法监听到深度嵌套数据的改变了。

但是,有一个问题需要说明,因为我的 Vue  版本是 "vue": "^3.2.13" 所以看起来配置 是生效的。但是如果好像是3.2以下的版本,这个配置是不生效的,只能默认开启 deep:true。

 情况六:监听 ref 定义的对象形式的响应式数据 -- 只监听嵌套对象中的某个基础属性

<template>
  <p>工作:{{ person.age }}</p>
  <button @click="person.age++">改变年龄</button>
</template>

let person = ref({
  name: "al",
  age: 28,
});

watch(person.value.age, (newValue, oldValue) => {
  console.log("person.age改变了", newValue, oldValue);
});

但是点击按钮之后,我们发现 控制台上并没有打印出数据,按理来说这是肯定不可能的,Vue肯定会放开这个口子供开发者使用,所以我们需要从使用语法中查找问题。

// 之前watch 的第一个参数,要么是 变量,要么是数组。
// 但是在这里,我们需要使用一个函数,通过函数的返回值来表明我们需要监听对象中的那个属性
watch(() => person.value.age, (newValue, oldValue) => {
  console.log("person.age改变了", newValue, oldValue);
});

然后我们就会发现,控制台上已经可以正常打印正确数据了。

情况七:监听 ref 定义的对象形式的响应式数据 -- 只监听嵌套对象中的某些属性

按照情况二和情况七的结合,我们可以大致上了解监听嵌套对象中的某些属性应该怎么做。

// 通过 数组模式和函数返回模式相结合,实现了监听对象中某些属性
watch([() => person.value.name,() => person.value.age], (newValue, oldValue) => {
  console.log("name或age改变了", newValue, oldValue);
});

情况八:监听 ref定义的对象形式的响应式数据 -- 深度监听嵌套对象中的值为复杂类型数据的属性

之前的几种监听情况大致上可以分为

  • 监听 ref 定义的基础类型数据
  • 监听 ref 定义的对象类型数据 --
    • 监听的是整个对象( 包括深度嵌套对象 )
    • 监听的是对象( 包括深度嵌套对象 )中的某个属性 -- 基础类型属性
    • 监听的是对象( 包括深度嵌套对象 )中的某几个属性 -- 都是基础类型属性

如果我们现在监听的是 深度嵌套对象中某个值为复杂类型数据的属性时,那需要怎么做呢?

我们还是用之前的数据来进行测试

let person = ref({
  name:'al',
  job:{
    j1: {
      work: '前端',
      salary:1
    }
  }
});

然后我们按照 情况六的模式来进行监听

<template>
  <p>工作:{{ person.job.j1.salary }}</p>
  <button @click="person.job.j1.salary++">涨薪</button>
</template>

// 监听属性通过函数返回
watch(() => person.value.job, (newValue, oldValue) => {
  console.log("job中的数据该变了", newValue, oldValue);
});

发现此时并没有监听成功。按理来说这也不应该啊,我是按照之前的情况实现的啊,只不过是把监听的数据类型该变了,怎么就不行了呢?

此时我们回忆一下,在我们监听完整的对象时,默认开启 deep:true 。但我们监听的是整个对象,而不是对象中的某个属性。

如果我们监听的是对象中的某个对象属性时,这一套就不适用了,所以我们需要手动配置 deep:true

watch(() => person.value.job, (newValue, oldValue) => {
  console.log("name或age改变了", newValue, oldValue);
},{deep: true});

此时改变  salary 后,控制台上能打印出数据,表atch功

watch 监听 ref 与 reactive 定义的数据使用方式区别

在之前的例子中我们定义数据都是使用的 ref, 虽然我有说 ref 定义的对象类型实际上底层还是通过 reactive 来实现响应式,但在 watch 监听时,使用方式还是存在区别的。

分别用 ref 和 reactive 定义深层嵌套的响应式数据

let person1 = ref({
  job: {
    j1: {
      work: "前端",
      salary: 1,
    },
  },
});

let person2 = reactive({
  job: {
    j2: {
      work: "后端",
      salary: 2,
    },
  },
});

 然后使用 watch 分别监听。这里我们就可以看出区别在哪了。

watch(person1.value,(newValue, oldValue) => {
    console.log("j1中salary改变了", newValue, oldValue);
  },
);

watch(person2,(newValue, oldValue) => {
    console.log("j2salary改变了", newValue, oldValue);
  },
);

ref 定义的数据需要解包,而 reactive 定义的数据则可以直接使用。这一点可以在 之前的博文 ref函数与reactive函数的对比 中可以详细了解一下。 

这是因为,通过 ref 转化的复杂数据,是一个 refImpl实例对象,其中 value属性的值才是真的响应式数据,是通过 reactive 方法中的 Proxy 代理对象实现响应式。这相比 reactive 直接转化就多了一层 refImpl 实例。所以在 js脚本中使用数据时,需要 .value 解包。

然后,我们参考一下情况八,其实对于 ref 定义的深层嵌套对象我们也可以通过手动配置 deep:true 的形式来实现深度监听

watch(person1,(newValue, oldValue) => {
  console.log("j1中salary改变了", newValue, oldValue);
  },
  { deep: true }
);

总结

  1. Vue3 中能够使用 Vue2 的模式来实现 watch监听 动作
  2. Vue3 中的 watch 因为是组合式api,所以也需要先引入再使用,和 computed 一致
  3. Vue3 中的 watch 是一个函数,接收三个参数:                                                                          参数一:需要监听的数据,参数类型可以是变量,数组或函数
      参数二:监听的回调函数,接收两个参数,分别代表新值和旧值
      参数三:一个对象,包含复杂监听的配置,例如深度监听 ( dep:true ),初始化监听( immediate: true )
  4. Vue3 中 setup 中的 watch 不需要使用变量接收,也不用返回,因为 watch 监听是动作,而 computed 计算属性最终返回的是值
  5. Vue3 的watch 存在几种情况需要注意:
    1. 通过 ref 定义的简单类型数据,可以直接实现监听,无bug -- 情况一 & 情况二 & 情况三
    2. 通过 ref 或 reactive 定义的复杂类型数据在监听时存在bug:
      1. 无法正确获得 oldValue -- 情况四
      2. 监听的是完整对象,则强制开启深度监听 -- 情况五
      3. 监听的是对象中的某个基础数据类型属性,则需要通过函数返回该属性 -- 情况六 & 情况七
      4. 监听的是对象中的某个值为复杂类型数据的属性,则需要通过函数返回,且需要手动配置 deep:true 实现深度监听 -- 情况八
  6. Vue3 中的watch 在监听 ref 和 reactiv定义的响应式数据时,使用方式是存在区别的,那就是 ref 定义的数据,在使用时是需要解包的,而 reactive 则是不需要。

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

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

相关文章

conda环境下在pycharm中调试scrapy项目

前提条件 已经创建好了conda环境已经安装好了scrapy框架项目初始化完成 编写一个爬虫脚本 import scrapyclass StackOverflowSpider(scrapy.Spider):name stackoverflowstart_urls [http://stackoverflow.com/questions?sortvotes]def parse(self, response):print("…

阿一网络安全实战演练之利用 REST URL 中的服务器端参数污染

所需知识 要解决这个实验室问题&#xff0c;您需要了解以下内容&#xff1a; 如何确定用户输入是否包含在服务器端的 URL 路径或查询字符串中。如何使用路径遍历序列尝试更改服务器端请求。如何查找 API 文档。 这些内容在我们的 API 测试学院主题中有涵盖。 进入实验室 研…

终极解决CondaValueError: Malformed version string ‘~’: invalid character(s)问题

conda 创建环境时出现&#xff1a; Solving environment: failed CondaValueError: Malformed version string ‘~’: invalid character(s)以下两种方法都不行时&#xff1a; 原因一&#xff1a; 添加的镜像源中&#xff0c;清华镜像源是https&#xff08;错误&#xff09;&a…

软件测试 - 测试分类(静态测试、动态测试、白盒测试、黑盒测试、灰盒测试、单元测试、集成测试、系统测试、验收测试等)

一、为什么要对软件测试进⾏分类&#xff1f; 软件测试是软件⽣命周期中的⼀个重要环节&#xff0c;具有较⾼的复杂性&#xff0c;对于软件测试&#xff0c;可以从不同的⻆度 加以分类&#xff0c;使开发者在软件开发过程中的不同层次、不同阶段对测试⼯作进⾏更好的执⾏和管理…

R语言管道操作详解-高效编程

引言 R语言是一种广泛应用于统计分析和图形表示的编程语言和软件环境。随着数据分析和数据科学的发展&#xff0c;R语言的管道操作符已经成为提高代码可读性和效率的重要工具。本文将详细介绍R语言中的管道操作符&#xff0c;包括它们的用途、语法和一些实用的示例。 目录 引…

手写签名怎么变成电子签名?

教大家一个快速生成有效电子签名的方法&#xff01;&#xff08;有效电子签名即通过正轨平台绑定了CA数字证书、防伪防盗的签名&#xff09; 1.登录【微签】&#xff0c;点击【自己签】。 2.点【添加文件】&#xff0c;上传需要签名的电子文件&#xff08;格式不限&#xff09;…

一起学习LeetCode热题100道(46/100)

46.二叉树展开为链表(学习) 给你二叉树的根结点 root &#xff0c;请你将它展开为一个单链表&#xff1a; 展开后的单链表应该同样使用 TreeNode &#xff0c;其中 right 子指针指向链表中下一个结点&#xff0c;而左子指针始终为 null 。 展开后的单链表应该与二叉树 先序遍历…

Affine Transformations仿射变换

什么是仿射变换 仿射变换&#xff08;Affine Transformation&#xff09;是数学和计算机图形学中的一种线性变换&#xff0c;它包括了平移、旋转、缩放、剪切等操作。仿射变换保留了几何图形的“仿射性质”&#xff0c;即平行线在变换后仍然平行&#xff0c;线性组合在变换后仍…

电机预测性维护模组

设备简介 本模组为了对电机进行预测性运维而开发&#xff0c;可以采集电机的 3 路加速度振动传感器、3 路电流&#xff08;电机供电互感器输出信号&#xff09;、1 路转速&#xff08;电机转速&#xff09;、8 路温度&#xff08;PT100 温度传感器&#xff09;。 模组计算振动…

一文带你看懂安全生产管理系统

通过集成多种先进技术和设备&#xff0c;实现对企业安全生产全过程的智能化、精细化管理。系统分为五个核心层面&#xff0c;各层面相互协作&#xff0c;共同确保企业的安全生产。 1. 数据采集层 设备终端&#xff1a;利用防爆终端、防爆平板、无线传感器、电子标签、定位设备、…

WinForm DataGridView整行选中并且checkbox勾选

WinForm DataGridView选中行设置 文章目录 WinForm DataGridView选中行设置添加checkbox列列和选中行效果选中行代码 添加checkbox列 列和选中行效果 选中行代码 public Basic_configuration(){InitializeComponent();...........//添加鼠标事件this.dataGridView_basic.CellMo…

node.js: mssql2019 sequelize6 es6+ ORM in vscode and WebStorm 2023.1

mssql: insert into [dbo].[tutorials]([title],[description],[published],[createdAt],[updatedAt]) values(N涂聚文,N涂聚文,0,2025-05-04,2025-05-04); go insert into [dbo].[tutorials]([title],[description],[published],[createdAt],[updatedAt]) values(Ngeovindu,N…

实战OpenCV之图像的属性

基础入门 图像的属性指的是描述图像基本信息的数据&#xff0c;包括但不限于&#xff1a;图像的尺寸、颜色通道数、像素数据类型等。这些属性对于图像处理非常重要&#xff0c;因为它们直接关系到如何正确地读取、处理和存储图像。常见的图像属性包括&#xff1a; 尺寸&#xf…

WandB 简明教程【Weights Bias】

在机器学习实验领域&#xff0c;调整超参数类似于微调复杂机器的旋钮和刻度盘。这些参数通常很微妙但至关重要&#xff0c;能够显著影响我们模型的性能和行为。WandB&#xff08;权重和偏差 ) 是一个强大的在线工具集&#xff0c;旨在简化模型训练、评估和分析的过程。 随着我…

TCP shutdown 之后~

目录 摘要 1 API 2 shutdown(sockfd, SHUT_WR) 3 shutdown(sockfd, SHUT_WR) 4 kernel 是怎么做的&#xff1f; 附 摘要 通过 shutdown() 关闭读写操作&#xff0c;会发生什么&#xff1f;具体点呢&#xff0c;考虑两个场景&#xff1a; 场景一&#xff1a;C 发送数据完毕…

VBA技术资料MF184:图片导入Word添加说明文字设置格式

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

C ++初阶:C++入门级知识点

&#x1f37a;0.前言 言C之言&#xff0c;聊C之识&#xff0c;以C会友&#xff0c;共向远方。各位博友的各位你们好啊&#xff0c;这里是持续分享C知识的小赵同学&#xff0c;今天要分享的C知识是C入门知识点&#xff0c;在这一章&#xff0c;小赵将会向大家展开聊聊C入门知识…

基于Mediapipe的手势识别系统 | OpenCV | Mediapipe | C++ | QT | Python | C# | Unity

基于Mediapipe的手势识别系统 OpenCV、Mediapipe C (QT)、Python (PyCharm)、C# (Visual Studio) Unity 3D 登录界面 图片手势识别 视频文件手势识别 摄像头实时手势识别 演示视频 基于Mediapipe的手势识别系统

UDP和TCP协议段格式分析

目录 UDP协议 特点 UDP协议的缓冲区 UDP协议段格式 TCP协议 特点 如何理解TCP是传输控制协议&#xff1f; TCP协议段格式 四位首部长度 16位窗口大小 32位序号 32位确认序号 TCP/IP四层模型&#xff1a; UDP协议 UDP&#xff08;User Datagram Protocol &#xff…

十大护眼落地灯品牌哪款好?十大护眼落地灯品牌

十大护眼落地灯品牌哪款好&#xff1f;根据国际市场的研究数据表明&#xff0c;我国在日常生活中对电子产品的依赖度极高&#xff0c;每天看电子产品的时间超过8小时&#xff0c;出现眼睛酸痛、干涩、视觉疲劳的人群也不再少数&#xff0c;而给眼睛带来伤害的除了电子产品中所含…