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

目录
  • 前言
  • 原始值的响应式系统的实现
  • 为什么ES6 解构,不能随意使用会破坏他的响应式特性
    • proxy背景
    • 实现原理
    • 解构 props 对象,因为它会失去响应式
    • 直接赋值reactive响应式对象
    • vuex中组合API赋值
  • 结语

前言

vue3发布以来经历两年风头正盛,现在大有和react 平分天下的势头,我们知道他是基于proxy 实现响应式的能力, 解决了vue2所遗留下来的一些问题,同时也正由于proxy的特性,也提高了运行时的性能

凡事有利有弊, proxy虽然无敌,但是他也有本身的局限,从而产生一些我认为的弊端(其实就是不符合js语言的自然书写方式,有的人觉得就是个特殊写法,他不属于弊端)

  • 1、 原始值的响应式系统的实现 导致必须将他包装为一个对象, 通过 .value 的方式访问
  • 2、 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 value 和set value 方法来进行原始值的访问,从而导致必须有.value 的操作 ,这其实也是个无奈的选择

相当于两瓶毒药,你得选一瓶 鱼与熊掌不可兼得

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

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

proxy背景

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

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

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

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

天底下的事情,哪有完美的呢?尤大的魄力就在于,舍弃一点现在,博一个未来!

实现原理

在理解了背景之后,我们再来假模假式的温习一下proxy 原理,虽然这个都被讲烂了。

但是,写水文,讲究什么:俩字-连贯

        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 的配合, 就能实现对于对象的拦截

如此依赖,就能实现响应式了,大家可以发现,这个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 对象,因为它会失去响应式
  • 2、 直接赋值reactive响应式对象
  • 3、 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)

上述代码中,我们发现, 解构赋值,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就相当于访问一个响应式对象,所以并不会失去响应式

以上就大致解释了为什么解构赋值,可能会失去响应式,我猜的文档中懒得解释其中缘由,索性就定了个规矩,您啊!

就别用了,省的以为是vue的bug,提前改变用户的使用习惯!不惯着

直接赋值reactive响应式对象

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

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

然后就发出疑问reactive不是响应式的吗? 为啥我赋值了以后,他的响应式就没了 ,接着破口大骂,垃圾vue

其实啊,这就是您对于js 原生的概念不清除,其实尤大 已经做了最大的努力,来防止你进行错误操作了

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

当你用解构赋值操作的时候,他直接禁用了那有人又问了, 为啥props 不给禁用了呢?因为你的props 的数据可能不是响应式的啊,不是响应式的,我得能啊,尤大他也不能干涉用户使用新语法啊

所以还是那句话:框架现在的呈现,其实充满了取舍,有时候真是两瓶毒药,挑一瓶!

回归正题,我们再来说说 原生js 语法,首先需要确认的是,原生js 的引用类型的赋值,其实是 按照引用地址赋值!

 // 当reactive 之后返回一个代理对象的地址被vue 存起来,
 // 用一个不恰当的比喻来说,就是这个地址具备响应式的能力
 const vue = reactive({ a: 1 })
 //  而当你对于vue重新赋值的时候不是将新的对象赋值给那个地址,而是将vue 换了个新地址
 // 而此时新地址不具备响应式,可不就失去响应式了吗
 vue = { b: 2 }

以上就是reactive失去响应式的解释,所以这个也是很多用户骂骂咧咧的原因。不符合他的使用习惯了,这都是被vue2 培养起来的一代

在这里我要替,尤大说句公道话,人家又没收你钱,还因为他,你有口饭吃,您自己不能与时俱进,拥抱新事物,那是您没能耐,这是典型的端起碗吃肉,放下筷子骂娘

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 包裹起来,其实道理是一样的,也是变量赋值的原因,在这里我们就不再赘述!

结语

到此这篇关于vue3结构赋值失去响应式引发的问题思考的文章就介绍到这了,更多相关vue3结构赋值内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • vue3中reactive不能直接赋值的解决方案

    目录 vue3 reactive不能直接赋值 方法 vue3 reactive赋值不响应 (1)多嵌套一层 (2)使用ref (3)用obeject.assign vue3 reactive不能直接赋值 最近比较忙,都没什么时间学习了. 在vue3里,ref和reacitve都可以定义响应式数据,但是两者有所不同.在使用reactive定义复杂结构的响应式数据时,如果你要对其赋值,会丢失其响应性.然后赋值是我们经常进行的操作,那么该怎么解决呢? 方法 1. 改为ref定义 const arr=

  • Vue中input被赋值后,无法再修改编辑的问题及解决

    目录 input被赋值后,无法再修改编辑 我们直入主题 方法一 方法二 vue综合问题归纳input框赋值后不能进行编辑 项目场景 问题描述 原因分析 解决方案 input被赋值后,无法再修改编辑 我们直入主题 上面是我司的业务逻辑图,使用elementui框架,如图,请求数据赋值后,不仅有检测报错信息,而且还有无法修改编辑的问题. 起初以为是我的设置的对象问题,困扰我许久. 后来,翻阅了大佬的文章才了解. <el-form ref="formInline" :inline=&q

  • 关于vue3中的reactive赋值问题

    目录 vue3 reactive赋值问题 vue3 reactive的坑 清空数组 清空对象 vue3 reactive赋值问题 vue3中直接对reactive整个对象赋值检测不到 let obj = reactive({ name: 'zhangsan', age: '18' }) obj = { name: 'lisi' age: '' } // 上面这样赋值检测不到,因为响应式的是它的属性,而不是它自身 // 如需要对 reactive 赋值 // 方法1: 单个赋值 obj['name

  • vue如何动态给img赋值

    目录 vue动态给img赋值 1.如果直接给img的src绑定一个字符串 2.解决办法 vue动态赋值img的src,用require() vue动态给img赋值 1.如果直接给img的src绑定一个字符串 <img :src=nowIcon />  data () {     return {       nowIcon: ''     }   },   this.nowIcon = '../assets/64/' + 图片名 + '.png' vue会将这个路径当成字符串,不会给这个图片路

  • vue3中reactive数据被重新赋值后无法双向绑定的解决

    目录 reactive数据被重新赋值后无法双向绑定 推荐写法 vue3数据的双向绑定 一.script setup 二.ref() 函数 三.reactive()函数 reactive数据被重新赋值后无法双向绑定 这是因为reactive数据被重新赋值后,原来数据的代理函数和最新的代理函数不是同一个,无法被触发 推荐写法 import {reactive, toRefs} from 'vue' setup(props, context) { const state = reactive({ my

  • vue中provide inject的响应式监听解决方案

    目录 provide inject的响应式监听解决 vue监听赋值及provide与inject provide inject的响应式监听解决 提示:provide 和 inject 绑定并不是可响应的.这是刻意为之的.然而,如果你传入了一个可监听的对象,那么其对象的属性还是可响应的. 所以传值传对象即可 provide(){     return {       provObj: {         uuidList:{}       }     }   }, this._provided.p

  • Vue2 响应式系统之分支切换

    目录 场景 observer(data) new Watcher(updateComponent) data.ok = false data.text = "hello, liang" 问题 去重 重置 测试 总结 场景 我们考虑一下下边的代码会输出什么. import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { text: &quo

  • Vue2 响应式系统之深度响应

    目录 1.场景 2.方案 3.场景2 4.总结 1.场景 import { observe } from "./reactive"; import Watcher from "./watcher"; const data = { text: { innerText: { childText: "hello", }, }, }; observe(data); const updateComponent = () => { console.lo

  • Vue2响应式系统之嵌套

    目录 1.场景 2.执行过程 3.修复 4.测试 5.总结 1.场景 在 开发中肯定存在组件嵌套组件的情况,类似于下边的样子.Vue <!-- parent-component --> <div> <my-component :text="inner"></my-component> {{ text }} <div> <!-- my-component--> <div>{{ text }}</di

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

    目录 前言 原始值的响应式系统的实现 为什么ES6 解构,不能随意使用会破坏他的响应式特性 proxy背景 实现原理 解构 props 对象,因为它会失去响应式 直接赋值reactive响应式对象 vuex中组合API赋值 结语 前言 vue3发布以来经历两年风头正盛,现在大有和react 平分天下的势头,我们知道他是基于proxy 实现响应式的能力, 解决了vue2所遗留下来的一些问题,同时也正由于proxy的特性,也提高了运行时的性能 凡事有利有弊, proxy虽然无敌,但是他也有本身的局限

  • 关于vue3 vuex4 store的响应式取值问题解决

    场景: 在页面中点击按钮,数量增加,值是存在store中的,点击事件,值没变. <script setup lang="ts"> import { useStore } from '@/vuex'; const store = useStore() const onSubmit = () => { store.dispatch("incrementAction", 1); } let count = store.state.count </s

  • 茶余饭后聊聊Vue3.0响应式数据那些事儿

    "别再更新了,实在是学不动了"这句话道出了多少前端开发者的心声,"不幸"的是 Vue 的作者在国庆区间发布了 Vue3.0 的 pre-Aplha 版本,这意味着 Vue3.0 快要和我们见面了.既来之则安之,扶我起来我要开始讲了.Vue3.0 为了达到更快.更小.更易于维护.更贴近原生.对开发者更友好的目的,在很多方面进行了重构: 使用 Typescript 放弃 class 采用 function-based API 重构 complier 重构 virtual

  • Vue3中的ref和reactive响应式原理解析

    目录 1 ref 2 isref判断是不是一个ref对象 3 shallowref创建一个跟踪自身.value变化的 ref,但不会使其值也变成响应式的 4 triggerRef 5 customRef 6 reactive用来绑定复杂的数据类型 7 readonly 8 shallowReactive 9toRef 10toRefs 11toRaw Vue3系列4--ref和reactive响应式 本节主要介绍了响应式变量和对象,以及变量和对象在响应式和非响应式之间的转换. 1 ref 接受一

  • 关于Vue3中的响应式原理

    目录 一.简介 二.响应核心 1.核心源码 2.逐步分析上述示例代码 3.收集依赖和触发依赖更新 三.V3.2的响应式优化 四.后话 一.简介 本章内容主要通过具体的简单示例来分析Vue3是如何实现响应式的.理解本章需要了解Vue3的响应式对象.只注重原理设计层面,细节不做太多讲解. 二.响应核心 1.核心源码 export class ReactiveEffect<T = any> { //是否激活 active = true //依赖列表 deps: Dep[] = [] // can b

  • 稍微学一下Vue的数据响应式(Vue2及Vue3区别)

    什么是数据响应式 从一开始使用 Vue 时,对于之前的 jq 开发而言,一个很大的区别就是基本不用手动操作 dom,data 中声明的数据状态改变后会自动重新渲染相关的 dom. 换句话说就是 Vue 自己知道哪个数据状态发生了变化及哪里有用到这个数据需要随之修改. 因此实现数据响应式有两个重点问题: 如何知道数据发生了变化? 如何知道数据变化后哪里需要修改? 对于第一个问题,如何知道数据发生了变化,Vue3 之前使用了 ES5 的一个 API Object.defineProperty Vue

  • 关于vue2响应式缺陷的问题

    目录 vue2响应式缺陷 1.对象新增的属性没有响应式 2.数组的部分操作没有响应式 vue2与vue3的响应式原理 vue2响应式 vue3响应式雏形 vue3的响应式相较于vue2的优势 vue2响应式缺陷 响应式 : 数据改变 ==> 视图跟着改变 vue2响应式缺陷 1.对象新增的属性没有响应式 对象,新增属性b,修改b的值,值改变但视图并未更新 解决方案 : 使用vue提供的 api $set(对象,属性名,值) 效果如属性c 2.数组的部分操作没有响应式 数组中有7种操作有响应式 a

  • Vue3 reactive响应式赋值页面不渲染的解决

    目录 Vue3 reactive响应式赋值页面不渲染 问题描述 1.因数据结构而导致的未渲染解决方法 2.因页面节点未加载导致页面未渲染解决方法 3.因未指到具体点未渲染解决方法 Vue3 响应式原理-reactive Reactivity模块基本使用 编写reactive函数 Vue3 reactive响应式赋值页面不渲染 问题描述 //声明变量 let data = reactive([]) http().then(res=>{   data = res.data   console.log

  • Vue3.0数据响应式原理详解

    基于Vue3.0发布在GitHub上的第一版源码(2019.10.05)整理 预备知识 ES6 Proxy,整个响应式系统的基础. 新的composition-API的基本使用,目前还没有中文文档,可以先通过这个仓库(composition-api-rfc)了解,里面也有对应的在线文档. 先把Vue3.0跑起来 先把vue-next仓库的代码clone下来,安装依赖然后构建一下,vue的package下的dist目录下找到构建的脚本,引入脚本即可. 下面一个简单计数器的DEMO: <!DOCTY

  • Vue3 的响应式和以前有什么区别,Proxy 无敌?

    前言 大家都知道,Vue2 里的响应式其实有点像是一个半完全体,对于对象上新增的属性无能为力,对于数组则需要拦截它的原型方法来实现响应式. 举个例子: let vm = new Vue({ data() { return { a: 1 } } }) // ❌ oops,没反应! vm.b = 2 let vm = new Vue({ data() { return { a: 1 } }, watch: { b() { console.log('change !!') } } }) // ❌ oo

随机推荐