首页 > 其他分享 >vue3结构赋值失去响应式引发的问题思考

vue3结构赋值失去响应式引发的问题思考

时间:2023-11-29 10:55:36浏览次数:35  
标签:const reactive value 响应 proxy vue3 赋值

前言

vue3是基于 proxy 实现响应式的能力, 解决了vue2所遗留下来的一些问题,同时也正由于 proxy 的特性,也提高了运行时的性能

凡事有利有弊, proxy虽然无敌,但是他也有本身的局限,从而产生一些认为的弊端

    1. 原始值的响应式系统的实现 导致必须将他包装为一个对象, 通过 .value 的方式访问
    1. ES6 解构,不能随意使用。会破坏他的响应式特性

原始值的响应式系统的实现

在理解原始值的响应式系统的实现,先来温习一下 proxy 的能力!

const obj = {
  name: "win",
};

const handler = {
  get: function (target, key) {
    console.log("get--", key);
    return Reflect.get(...arguments);
  },
  set: function (target, key, value) {
    console.log("set--", key, "=", value);
    return Reflect.set(...arguments);
  },
};

const data = new Proxy(obj, handler);
data.name = "ten";
console.log(data.name, "data.name22");

上述代码中,proxy 的使用本身就是对于对象的拦截, 通过new Proxy的返回值,拦截了 obj 对象
如此一来,当访问对象中的值的时候,会触发 get 方法, 当修改对象中的值的时候会触发 set方法

但是到了原始值的时候,没有对象啊,咋办呢,new proxy 排不上用场了。
只能包装一下了,所以就有了使用.value访问了

来看看具体实现

import { reactive } from "./reactive";
import { trackEffects, triggerEffects } from './effect'

export const isObject = (value) => {
    return typeof value === 'object' && value !== null
}

// 将对象转化为响应式的
function toReactive(value) {
    return isObject(value) ? reactive(value) : value
}

class RefImpl {
    public _value;
    public dep = new Set; // 依赖收集
    public __v_isRef = true; // 是ref的标识
    // rawValue 传递进来的值
    constructor(public rawValue, public _shallow) {
        // 1、判断如果是对象 使用reactive将对象转为响应式的
        // 浅ref不需要再次代理
        this._value = _shallow ? rawValue : toReactive(rawValue);
    }
    get value() {
        // 取值的时候依赖收集
        trackEffects(this.dep)
        return this._value;
    }
    set value(newVal) {
        if (newVal !== this.rawValue) {
            // 2、set的值不等于初始值 判断新值是否是对象 进行赋值
            this._value = this._shallow ? newVal : toReactive(newVal);
            // 赋值完 将初始值变为本次的
            this.rawValue = newVal
            triggerEffects(this.dep)
        }
    }
}

上述代码,就是对于原始值的包装,他被包装为一个对象,通过get valueset value 方法来进行原始值的访问,从而导致必须有.value 的操作 ,这其实也是个无奈的选择

为什么 ES6 解构,不能随意使用会破坏他的响应式特性

第一个问题终于整明白了,那么来看看最重要的第二个问题,为什么结构赋值,会破坏响应式特性

proxy 背景

在开始之前,先来讨论一下为什么要更改响应式方案

vue2 基于Object.defineProperty  ,但是他有很多缺陷,比如  无法监听数组基于下标的修改,不支持 Map、Set、WeakMap 和 WeakSet 等缺陷 ,

其实这些也也不耽误开发,vue2 到现在还是主流,

理解就是与时俱进新一代的版本,一定要紧跟语言的特性,一定要符合新时代的书写风格,虽然proxy相对于 Object.defineProperty 有很多进步, 但是也不是一点缺点都没有,比如说 不兼容IE

实现原理

const obj = {
  count: 1,
};
const proxy = new Proxy(obj, {
  get(target, key, receiver) {
    console.log("这里是get");
    return Reflect.get(target, key, receiver);
  },
  set(target, key, value, receiver) {
    console.log("这里是set");
    return Reflect.set(target, key, value, receiver);
  },
});

console.log(proxy);
console.log(proxy.count);

以上代码就是 Proxy 的具体使用方式,通过和 Reflect 的配合, 就能实现对于对象的拦截

image.png

如此依赖就能实现响应式了,大家可以发现,这个 obj 的整个对象就被拦截了,但是你发现对象在嵌套深一层

比如:

const obj = {
  count: 1,
  b: {
    c: 2,
  },
};

console.log(proxy.b);
console.log(proxy.b.c);

就无法拦截了,必须要来个包装

const obj = {
  a: {
    count: 1,
  },
};

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      console.log("这里是get");
      // 判断如果是个对象在包装一次,实现深层嵌套的响应式
      if (typeof target[key] === "object") {
        return reactive(target[key]);
      }
      return Reflect.get(target, key, receiver);
    },
    set(target, key, value, receiver) {
      console.log("这里是set");
      return Reflect.set(target, key, value, receiver);
    },
  });
}
const proxy = reactive(obj);

现在列举一下知道的响应式失去的几个情况:

    1. 解构  props  对象,因为它会失去响应式
    1. 直接赋值reactive响应式对象
    1. vuex中组合 API 赋值

解构  props  对象,因为它会失去响应式

const obj = {
  a: {
    count: 1,
  },
  b: 1,
};

//reactive 是上文中的reactive
const proxy = reactive(obj);
const { a, b } = proxy;
console.log(a);
console.log(b);
console.log(a.count);

image.png

上述代码中,发现解构赋值,b 不会触发响应式a如果你访问的时候,会触发响应式

这是为什么呢?

先来讨论为什么解构赋值,会丢失响应式呢?

解构赋值,区分原始类型的赋值,和引用类型的赋值,
原始类型的赋值相当于按值传递引用类型的值就相当于按引用传递

就相当于

// 假设a是个响应式对象
const a = { b: 1 };
// c 此时就是一个值跟当前的a 已经不沾边了
const c = a.b;

// 你直接访问c就相当于直接访问这个值 也就绕过了 a 对象的get ,也就像原文中说的失去响应式

那为啥a具备响应式呢?

因为a是引用类型,上述代码中的一个判断,如果他是个object 那么就重新包装为响应式

正式由于当前特性,导致,如果是引用类型, 你再去访问其中的内容的时候并不会失去响应式

// 假设a是个响应式对象
const a = { b: { c: 3 } };
// 当你访问a.b的时候就已经重新初始化响应式了,此时的c就已经是个代理的对象
const c = a.b;

// 你直接访问c就相当于访问一个响应式对象,所以并不会失去响应式

以上就大致解释了为什么解构赋值,可能会失去响应式

直接赋值reactive响应式对象

最初使用 vue3 的时候,指定会写出以下代码

const vue = reactive({ a: 1 });
vue = { b: 2 };

然后就发出疑问reactive不是响应式的吗? 为啥赋值了以后,他的响应式就没了,这就是对于 js 原生的概念不清除,其实尤大 已经做了最大的努力,来防止进行错误操作了

比如,由于解构赋值的问题, 他直接禁止了 reactive 的解构赋值

image.png

当你用解构赋值操作的时候,他直接禁用了

那有人又问了,为啥props 不给禁用了呢?

因为props 的数据可能不是响应式,得能是响应式,尤大他也不能干涉用户使用新语法啊

回归正题,再来说说 原生 js 语法

首先需要确认的是,原生 js 的引用类型的赋值,其实是 按照引用地址赋值!

// 当reactive 之后返回一个代理对象的地址被vue 存起来,
// 用一个不恰当的比喻来说,就是这个地址具备响应式的能力
const vue = reactive({ a: 1 });

//  而当你对于vue重新赋值的时候不是将新的对象赋值给那个地址,而是将vue 换了个新地址
// 而此时新地址不具备响应式,可不就失去响应式了吗
vue = { b: 2 };

以上就是reactive失去响应式的解释

vuex中组合 API 赋值

在 vuex 用赋值也可能会失去响应式

import { computed } from "vue";
import { useStore } from "vuex";

export default {
  setup() {
    const store = useStore();
    return {
      // 在 computed 函数中访问 state
      count: computed(() => store.state.count),

      // 在 computed 函数中访问 getter
      double: computed(() => store.getters.double),
    };
  },
};

以上代码中发现store.getters.double 必须用computed 包裹起来,其实道理是一样的,也是变量赋值的原因,在这里就不再赘述!

标签:const,reactive,value,响应,proxy,vue3,赋值
From: https://www.cnblogs.com/wp-leonard/p/17864050.html

相关文章

  • Vue3 + Express 实现大文件分片上传、断点续传、秒传
    前言在日常开发中,文件上传是常见的操作之一。文件上传技术使得用户可以方便地将本地文件上传到Web服务器上,这在许多场景下都是必需的,比如网盘上传、头像上传等。但是当需要上传比较大的文件的时候,容易碰到以下问题:上传时间比较久;中间一旦出错就需要重新上传;一般服务端会对......
  • 【VUE基础】VUE3核心语法
    setupsetup是Vue3中一个新的配置项,值是一个函数,它是CompositionAPI“表演的舞台”,组件中所用到的:数据、方法、计算属性、监视......等等,均配置在setup中。特点如下:setup函数返回的对象中的内容,可直接在模板中使用。setup中访问this是undefined。setup函数会在beforeCreat......
  • springboot 自定义响应体大小测试接口
    @ResponseBody@RequestMapping("/def/response/body/service")publicStringBuilderdefResponseBodyService(@RequestParam(name="count")Integercount,HttpServletRequestHttpRequest)throwsInterruptedException{  StringbaseStr="0......
  • 注册Steam账号无响应的解决办法,教你怎么提供Steam的人机图片验证
    Steam注册问题解决办法分享注册Steam时进行人机身份验证,总是有一堆图片验证不完,点【继续】后上方弹出【您对CAPTCHA的响应似乎无效。请在下方重新验证您不是机器人。(引用ID:3811775249663914774)】的提示。】按照网上的方法,重复更换了浏览器和设备都无效,遇到这种提示应该怎么办......
  • Vite4+Typescript+Vue3+Pinia 从零搭建(4) - 代码规范
    项目代码同步至码云weiz-vue3-template要求代码规范,主要是为了提高多人协同和代码维护效率,结合到此项目,具体工作就是为项目配置eslint和prettier。editorconfig安装EditorConfigforVSCode插件,根目录下新建.editorconfig文件,增加以下配置[*.{js,jsx,ts,tsx,vue}]......
  • 记录后端不同请求方式的接口,使用vue3框架下的前端axio请求不同写法
    一.后端接口:@GetMapping("/index")publicResponseResultindex(){..} 前端接口:indexInfo().then(res=>{if(res.data.code==200){ElNotification({message:res.data.data.msg||"加载成功",ty......
  • 学习Vue3 第五章(Vue核心虚拟Dom和 diff 算法)
      介绍虚拟DOM虚拟DOM就是通过JS来生成一个AST节点树   为什么要有虚拟DOM?一个dom上面的属性是非常多的,所以直接操作DOM非常浪费性能介绍Diff算法diff算法的目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图,所以diff算法本质上就是......
  • 记一次请求接口出现400响应码的诡异错误实录
    前言最近业务碰到了一个诡异的400接口请求异常,部门用户通过浏览器访问会出现400响应码错误,部分用户又能正常访问。该接口用postman请求访问,都能正常返回数据。后端写客户端请求该接口,也都能返回正常的数据。本文就来记录一下这次问题整体简化版请求链路如图问题排查过程因为......
  • 学习Vue3 第四章 vue指令
    指令v-开头都是vue的指令v-text用来显示文本v-html用来展示富文本v-if用来控制元素的显示隐藏(切换真假DOM)v-else-if表示v-if的“elseif块”。可以链式调用v-elsev-if条件收尾语句v-show用来控制元......
  • vue3 项目中出现的空白页面的总结(巨坑)
    一、背景开局先说一句!!!!好坑!!!!!,我遇到的空白页面的问题,不是路由懒加载的原因,是在点击路由完整的状态下,点击菜单跳转页面,出现的空白页面,不能触发页面中任何钩子函数,但是路由是跳了的,重新刷新页面,页面内容即可出现,而且空白出现率相当高。打开浏览器控制台和项目控制台都不报......