Skip to content

再谈前端状态管理 #16

Description

@winixt

俗话说得好,程序 = 数据结构 + 算法。我们前端页面其实也可以简单抽象成:数据 + 逻辑。数据通常为服务端数据,逻辑可以分为两大类:无状态逻辑和有状态逻辑。

无状态逻辑,可以是公众函数、公共业务逻辑、service 处理器,因为是纯函数,可以抽象出来到处复用。

有状态逻辑,又可以分为两类:多实例有状态逻辑和单实例有状态。多实例有状态逻辑,本质上更偏向于无状态逻辑,具象一点可以理解为 vue3 composition API 和 react 的 hooks。前端应用大部分逻辑可以是无状态逻辑和多实例有状态逻辑,比较好处理。复杂的是单实例有状态逻辑。状态管理要解决的问题,也是单实例有状态逻辑的增删改查问题。下面来仔细分析一下。

单实例有状态逻辑,又可以分为,单实例非共享状态逻辑和单实例共享状态逻辑。

单实例非共享状态逻辑

单实例非共享状态逻辑,指的是状态在一个地方展示,可以背多个地方修改。为了方便说明,这里以A组件为中心:

  1. A 组件的子组件要修改 A 组件的状态

  2. A 组件的子组件的子组件(以及子子…组件)需要修改 A 组件的状态

  3. A 组件的父组件需要修改 A 组件的状态

  4. 其他情况

    1. A 组件的父组件的父组件(以及父父…组件)需要修改 A 组件的状态
    2. A 组件的兄弟组件需要修改 A 组件的状态
    3. A 组件的兄弟组件的子组件(或父组件)需要修改 A 组件的状态

场景一:A 组件的子组件要修改 A 组件的状态

代码内聚性通常意味着可维护性越强,这个时候子组件通过暴露事件出去给 A 组件使用会是一个比较佳的方式,或者传递方法给子组件。

场景二: A 组件的子组件的子组件(以及子子…组件)需要修改 A 组件的状态

层级嵌套过多,通过暴露事件的方式一层层往上浮动,写起来繁琐,事件名称有变动得到处改。不建议事件传递的方式。有两种方式可以考虑:

  1. 如果子组件的子组件相对独立,可以将子组件的子组件抽离出来,作为 A 组件的直接依赖组件,类似于常见的:Form 和 FormItem 的关系。
  2. 在 1 不满足的情况下,用 provide/inject,直接夸组件传递。

场景三:A 组件的父组件需要修改 A 组件的状态

这种情况下出现的情况相对还是比较多,通常我们会用 ref 解决。但也要尽量控制,用 vue3.2 的 script setup 会比较好,可以明确制定导出的方法。

场景四:其他场景

因为该数据只在一个地方展示,却在八杆子不着的地方修改了,这时更应该考虑交互的合理性。在交互合理的情况下,考虑组件划分非合理性。确认交互没问题的前提下,组件划分没问题的情况下,可用发布订阅模式,或者采用下文介绍的抽离共享状态的方式。个人偏向于后者。

单实例共享状态逻辑

单实例共享状态逻辑,指的是一个状态在多个地方展示。为了方便说明,这里以A组件为中心:

  1. A 组件的子组件要共享 A 组件的状态
  2. A 组件的子组件的子组件(以及子子…组件)需要共享 A 组件的状态
  3. A 组件的父组件(或父父…组件)需要共享 A 组件的状态
  4. A 组件的兄弟组件(或兄弟组件的子子…组件、父父…组件)需要共享 A 组件的状态

场景一:A 组件的子组件要共享 A 组件的状态

props,单项数据流,经典场景。

场景二: A 组件的子组件的子组件(以及子子…组件)需要共享 A 组件的状态

层级嵌套过多,通过 props 的方式一层层往下传,写起来繁琐,名称有变动得到处改。有两种方式可以考虑:

  1. 如果只是子组件的子组件,层级不深还是可以考虑 props。
  2. 如果子组件的子组件相对独立,可以将子组件的子组件抽离出来,作为 A 组件的直接依赖组件,类似于常见的:Form 和 FormItem 的关系。
  3. 在 1 和 2 不满足的情况下,用 provide/inject,直接夸组件传递。

场景三:A 组件的父组件(或父父…组件)需要共享 A 组件的状态

考虑两种场景:

  1. 如果状态相关逻辑相对 A 组件较为独立,可以考虑状态往上提。
  2. 如果状态相关逻辑和 A 强关联,或者不仅仅和A 强关联,在组件划分合理的前提下,如果可遇见的类似逻辑不多,可用 readonly 锁定状态,进行状态共享,否则直接用 vuex。无法判断的话直接用 vuex。

场景四:A 组件的兄弟组件(或兄弟组件的子子…组件、父父…组件)需要共享 A 组件的状态

处理方法和场景三一致。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions