inject 响应式_基于访问路径的弱响应式状态管理 - Relinx

463a82cb0f9916e8f440085a042c7032.png

Introduction

近些年,从Redux开始,已经出现了大量关于状态管理实现方案;比如React-Redux, Mobx, React-easy-state, Rematch, react-tracked... 还有蚂蚁比较官方应用的framework dva ;从另一个方面也体现出来了随着前端逻辑复杂度的提高,对于如何进行数据层的高性能处理越发显得重要。全局的状态在一定程度上解决了prop drilling;同时合理的使用,在组件的re-render上也能够有很大的性能提升

通过对比会发现,状态管理在所需属性的收集上主要分为两种

  1. 声明型:以Redux为例,通过mapStateToProps 函数来显示的声明当前组件所具体需要的属性。
  2. 响应式:以Mobx为例,它通过inject的方式注入store,但是具体到组件需要哪些属性,Mobx通过Observable的机制,自动进行绑定。

通过上面的简单对比会发现,在使用上Mobx会更简单一些。这个也是目前业内比较一致的观点。同时这个也是reactive programming带来的利好,自动实现访问属性的绑定。所以,在中间的一段时间,也在团队进行过Mobx的推广,但是随着业务的迭代以及人员的迭代,你会发现响应式更新方式,虽然在写法上很便捷,但是会使组件层面的渲染变的不是那么可控。

Michel Weststrate将Mobx的运行机制比作spreadsheet:一个值的更改,自动触发关联对象以及关联的衍生对象的更改。一个美好的愿景,但是也正是它的灵活性,新人加入时,会有一定的适应曲线。同时还有一个问题是,因为是自动更新的,在一定程度上,希望延迟更新会存在问题(比如长列表共同引用的一个属性,它的变化,为了性能的考量并不一定需要马上所有的item进行更新)

Why Relinx

随着对Reactive Programming理解的加深,尝试完成了Relinx的开发实现。Relinx在写法上主要有下面几个特点

  1. 概念上遵照社区的习惯,同时在结构上很大的程度借鉴dva;目前Relinx包含action, reducers, effects, model, dispatch等概念。所以上手程度非常简单
  2. 在组件中通过Proxy object,自动将访问的属性路径进行收集
  3. 通过dispatch的方式对base data进行更改,application会根据通过Paths创建的PathNodeTree定向更新对应的组件

整个的设计理念可以通过下面的图片获得比较直观的理解

34dd73b8db0b0ba63b6d229e1e2ce7f5.png
  1. 一个observed组件会创建一个tracker和一个patcher,tracker是用来收集它所访问到的属性路径;patcher是用来在路径上的值发生变化时触发组件的更新
  2. Patcher会被添加到顶层的application,根据它的paths,application会创建一个PathNodeTree
  3. Dispatch触发state的更新,而更新的值会被传递给application
  4. 在application中,基于PathNodeTree进行diff比较;如果说一个pathNode上的值发生了变化,那么它对应的patchers就不被加入到pendingPatchers
  5. 当diff结束并且完成了patcher的clean up以后,pendingPatchers会被触发执行,那么patcher对应的组件就会重新渲染
  6. 组件重新渲染的时候,tracker会触发paths的更新,paths的更新会同步到patcher;最新的patcher又会被加入到application更新PathNodeTree,以等待接下来的数据更新监听

如何进行Path的收集

Tracker

Tracker是基于Proxy和defineProperty来实现的自动收集访问属性路径的模块。它会对base data进行getter trap的封装,

const base = {
  a: {
    b: 1,
  },
  c: {
    d: 2,
    e: [{ f: 1 }],
  },
  g: {
    h: { i: 4 },
    j: 5,
    k: 6,
  }
}

const baseNode = Tracker({ base: base.a })
const b = baseNode.proxy.b
baseNode.proxy.runFn('getRemarkableFullPaths') // [['b]]

const childNode = Tracker({ base: base.c })
const c = childNode.proxy.d
const f = childNode.proxy.e[0].f
const item = childNode.proxy.e[0]
childNode.proxy.runFn('getRemarkableFullPaths') // [['e', '0'], ['e', '0', 'f'], ['d']]

const grandChildNode = Tracker({ base: base.g })
const i = grandChildNode.proxy.h.i
const ff = item.f
grandChildNode.proxy.runFn('getRemarkableFullPaths') // [['h', 'i'], ['e', '0', 'f']]

概念上,Tracker和TrackerNode是一样的;一个TrackerNode可以包含多个TrackerNode;TrackerNode包含一个proxy属性,如果property value是一个数组或者对象的话,每一次的访问它都会判断是否需要创建一个新的child proxy然后将这个child proxy返回,从而实现对嵌套的对象中属性路径的记录。具体的数据结构如下

eb27018a5c12bdac16d12ab5040e3dd1.png

application如何运行

PathNode

首先Tracker会完成paths的收集,然后会将收集到的paths更新patcher;patcher会被添加到application。然后application根据patcher的paths创建PathNode。如下

e90f14a86690d32aa3349ffc40be6887.png

通过实际场景进行运行机制的描述

const store = {
  a: {
    b: 1
  },
  c: {
    d: 2,
    e: [{f: 1}]
  },
  g: {
    h: {i: 4},
    j: 5,
    k: 6,
  }
}

const A = observe(() => {
  const [state] = useRelinx('a')
  const b = state.b

  return (
    <div>
      <span>{b}</span>
      <C />
    </div>
  )
})

const C = observe(() => {
  const [state] = useRelinx('c')
  const { d, e } = state

  return (
    <div>
      <span>{d}</span>
      {e.map(item => <G item={item} />)}
    </div>
  )
})

const G = observe(props => {
  const [state] = useRelinx('c')
  const { h: { i }} = state
  const { item } = props

  return (
    <div>
      <span>{i}</span>
      <span>{item.f}</span>
    </div>
  )
})

比如上面是一个比较简单的例子,其中observe,useRelinx可以到Relinx进行查阅。

当dispatch一个action时,reducer会计算出changed value group;然后application会根据PathNodeTree进行diff比较

7f09fd392be4d1793f77f13b4aa1b645.png

1:当节点i的值放生变化时,它会将节点i对应的patchers [G]加入到待执行数组pendingPatchers中;同时会将[G] patchers从其他节点中删除以避免内存泄漏

2:当节点e被更新为空数组时,它会将length节点的patchers加入到待执行数组,同时会在比较结束以后,将整个Pathnode进行销毁。

总结

在进行Tracker的实现上,很大程度的借鉴了immer对trap的使用方式。在代码实现上借鉴了functional reactive programming library 比如meteor - tracker和xstream. 希望通过这个项目可以在状态管理以及trap的使用上对其它开发者有一个其它角度的理解

如果有兴趣可以去 A fast, intuitive, access path based react state management - Relinx 了解更多实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue中的provide/inject实现了跨组件的通信,但是默认情况下,提供的数据并不是响应式的。这是因为provide/inject的设计初衷并不是为了实现响应式数据的传递。引用 然而,如果你传入一个可监听的对象作为provide的值,那么该对象的属性仍然是可响应的。这意味着当对象的属性发生变化时,所有使用该属性的组件也会更新。引用 例如,在Vue组件A中使用provide提供一个响应式对象,然后在组件B中使用inject获取该对象。如果在组件A中修改了该对象的属性,组件B将会自动更新。引用 需要注意的是,只有provide提供的对象本身是响应式的,而不是该对象的嵌套属性。也就是说,如果在provide中传入的对象中的属性发生变化,那么只有直接使用这些属性的组件才会更新,而不是使用了整个对象的组件。引用<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [Vue中的Provide/Inject 实现响应式数据](https://blog.csdn.net/weixin_43459866/article/details/114691818)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [Vue Provide / Inject 详细介绍(跨组件通信、响应式变化、版本变化)](https://blog.csdn.net/qq_41809113/article/details/122071958)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值