🖌️
ngxs
  • 介绍
  • 入门
    • 为什么使用ngxs
    • 安装
  • 概念
    • 介绍
    • 存储(Store)
    • 动作(Actions)
    • 状态(State)
    • 选择(Select)
  • 高级
    • 动作处理程序
    • 动作的生命周期
    • 取消
    • 组合
    • 错误处理
    • Ivy迁移指南
    • 延迟加载
    • 生命周期
    • 映射子状态
    • 元归约器
    • 优化选择器
    • 选项
    • 共享状态
    • 状态令牌
    • 状态操作
    • 子状态
  • 菜单
    • 认证
    • 缓存
    • NGXS的组件事件
    • 防抖动动作
    • 动态插件
    • 不可变数据助手
    • 设计指南
    • 单元测试
  • 插件
    • 介绍
    • CLI
    • Logger
    • Devtools
    • Storage
    • Forms
    • Web Socket
    • Router
    • HMR
  • NGXS实验室
    • 介绍
  • 社区
    • FAQ
    • 资源
    • 贡献者
    • 贡献
    • 赞助商
  • 变更日志
Powered by GitBook
On this page
  • 问题
  • 解决方案
  • 状态操作符(State Operators)
  • immer
  • immutability-helper
  • object-path-immutable
  • immutable-assign
  • Ramda
  • icepick
  • 总结

Was this helpful?

  1. 菜单

不可变数据助手

Redux是一个微型的模式,将状态表示为不可变的对象。 Redux最初是为React设计的。 大多数Redux概念(例如纯函数)都围绕React生态系统。 如今,Redux与React没有直接关系。

Redux的基石是不变性。 不变性是一种惊人的模式,可以最大程度地减少代码中不可预测的行为。 本文将不讨论函数式编程。 但是,我们将研究非常有用的称为"不可变数据助手"(immutability helpers)的软件包。

问题

大多数开发人员必须处理所谓的“深层对象”,最重要的是要遵循不变性概念,以更改某些深层嵌套属性的值。 给出以下代码:

export interface Task {
  title: string;
  dates: {
    startDate: string;
    dueDate: string;
  };
}

export interface TrelloStateModel {
  tasks: {
    [taskId: string]: Task;
  };
}
@State<TrelloStateModel>({
  name: 'trello',
  defaults: {
    tasks: {}
  }
})
@Injectable()
export class TrelloState {}

让我们想象一下,我们有一个需要更改 dueDate 属性的需求:

export class UpdateDueDate {
  static readonly type = '[Trello] Update due date';
  constructor(public taskId: string, public dueDate: string) {}
}

让我们看看如何实现 updateDueDate 动作处理程序:

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    ctx.setState(state => ({
      tasks: {
        ...state.tasks,
        [action.taskId]: {
          ...state.tasks[action.taskId],
          dates: {
            ...state.tasks[action.taskId].dates,
            dueDate: action.dueDate
          }
        }
      }
    }));
  }
}

该代码可以工作,但不幸的是,维护和理解起来很复杂。 它不是自我描述的,对于新开发人员来说将是艰巨的。

解决方案

有多种方法可以改进此代码。 让我们看一些可以在这方面有所帮助的软件包。

状态操作符(State Operators)

import { patch } from '@ngxs/store/operators';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    ctx.setState(
      patch({
        tasks: patch({
          [action.taskId]: patch({
            dates: patch({
              dueDate: action.dueDate
            })
          })
        })
      })
    );
  }
}

immer

immer 是一个非常流行的库,它使您可以对不可变对象进行更改,就好像它们是可变的一样。 以下代码显示了如何在Immer的帮助下编写相同的代码:

import { produce } from 'immer';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const state = produce(ctx.getState(), draft => {
      draft.tasks[action.taskId].dates.dueDate = action.dueDate;
    });

    ctx.setState(state);
  }
}

Immer的 produce 函数也可以用作状态运算符:

import { produce } from 'immer';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    ctx.setState(
      produce(draft => {
        draft.tasks[action.taskId].dates.dueDate = action.dueDate;
      })
    );
  }
}

在immer库中,您可能关注这会减少多少代码,看起来有多好 :

使用Immer就像有个私人助理; 他拿了一封信(当前状态),并给了您一份副本(草稿) 来记录更改。 完成后,助手将使用你的草稿生成真正的不可变对像,即最后的封信 (下一个状态)。

immutability-helper

immutability-helper 是一个很小的包,可让您更改数据副本而无需更改原始源:

import update from 'immutability-helper';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const state = update(ctx.getState(), {
      tasks: {
        [action.taskId]: {
          dates: {
            dueDate: {
              $set: action.dueDate
            }
          }
        }
      }
    });

    ctx.setState(state);
  }
}

object-path-immutable

object-path-immutable 是一个小型库,可让您修改深层对象属性而不用修改原始对象。 让我们看看如何使用该库编写相同的代码:

import immutable from 'object-path-immutable';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const state = immutable.set(
      ctx.getState(),
      `tasks.${action.taskId}.dates.dueDate`,
      action.dueDate
    );

    ctx.setState(state);
  }
}

immutable-assign

immutable-assign 是追求相同目标的轻量级库。 它的语法类似于 immer:

import * as iassign from 'immutable-assign';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const state = iassign(ctx.getState(), state => {
      state.tasks[action.taskId].dates.dueDate = action.dueDate;
      return state;
    });

    ctx.setState(state);
  }
}

Ramda

Ramda 是用于函数式编程的出色库,并且已在许多项目中使用。 对于在项目中同时使用Ramda和NGXS的人,此示例可能很有用:

import * as R from 'ramda';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const property = R.lensPath(['tasks', action.taskId, 'dates', 'dueDate']);
    const state = R.set(property, action.dueDate, ctx.getState());
    ctx.setState(state);
  }
}

icepick

icepick 是用于不可变集合的零依赖库。 以下是重写后的代码:

import * as icepick from 'icepick';

export class TrelloState {
  @Action(UpdateDueDate)
  updateDueDate(ctx: StateContext<TrelloStateModel>, action: UpdateDueDate) {
    const state = icepick.setIn(
      ctx.getState(),
      ['tasks', action.taskId, 'dates', 'dueDate'],
      action.dueDate
    );

    ctx.setState(state);
  }
}

总结

我们研究了几种不同的库,它们可能有助于伴随不可变数据的概念。 你只需要选择一个适合您需求的。

Previous动态插件Next设计指南

Last updated 4 years ago

Was this helpful?

是NGXS提供的的一流的不可变数据助手,它开箱即用。如果选择状态运算符作为您的不可变数据助手,那么 patch 运算符将成为您最好的朋友。 让我们看看如何在patch 状态运算符的帮助下重新编写以上代码:

状态操作符
Immer 库
immutability-helper 库
object-path-immutable 库
immutable-assign 库
Ramda 库
icepick 库