详解Jotai Immer如何实现undo redo功能示例详解

 更新时间:2023年04月27日 15:31:16   作者:wesin  
这篇文章主要为大家介绍了详解Jotai Immer如何实现undo redo功能示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

背景

之前项目中一直使用redux作为全局状态库使用,然后最近有个大功能改造,涉及undo、redo等功能影响,小伙伴推荐jotai来替代redux。于是稍事研究了下,对比redux,果然简单了很多,去除了redux里厚重的模板代码,使用也很方便。 所以为啥一开始不直接就jotai呢,还抱着redux啃了半天?反思了下,可能原因是redux名声更响,当年撸flutter、撸ios的时候就被这个名词轰炸了,所以一想到全局状态库,就直接选了它,忽略了前端发展的日新月异,早就有了更好的替代品,经验主义作祟,引以为戒。另一个原因估计是论坛里光顾着摸鱼沸点了,忽略了介绍jotai的文章。 小伙伴又推荐了结合immer的功能来实现undo、redo功能。好嘛,翻了翻文档,immer不就是个简化immutable对象修改的工具吗?咋就跟undo、redo搭上关系了。经解释,方知原来immer高级篇有produceWithPatches的功能支持对象的undo以及redo功能。 当然undo、redo功能我找了找其实是有现有库的,只是代码翻了翻多年没更新了,下载过来连示例都运行不起来,算了,配合以上的jotai、immer功能,貌似直接简单撸一个也不难的样子,那么就开干吧。

代码不多,直接上了

import { Patch, applyPatches, produceWithPatches } from "immer";
import { atomWithImmer } from "jotai-immer";
import { atom, createStore } from "jotai/vanilla";
import { JSONSchema7 } from "json-schema";
interface HistoryInfo {
  patch: Patch;
  inverse: Patch;
}
interface HistoryState {
  undo: HistoryInfo[];
  redo: HistoryInfo[];
}
// 业务代码涉及的undo、redo数据
export interface HistoryItem {
  businessA: { [key: string]: any };
  businessB: Record<string, JSONSchema7>;
  businessC: any;
}
// 构建一个undo、redo的数据起点
const historyItemAtom = atomWithImmer<HistoryItem>({
  businessA: {},
  businessB: {},
  businessC: {},
});
// 触发需要保存的undo的一个操作事件
export const fireHistoryAtom = atom(0.0);
export const businessAAtom = atomWithImmer<{ [key: string]: any }>({});
export const businessBAtom = atomWithImmer<Record<string, JSONSchema7>>({});
export const businessCAtom = atomWithImmer<any>();
export const historyAtom = atomWithImmer<HistoryState>({
  undo: [],
  redo: [],
});
export const store = createStore();
// 页面数据加载完毕写入初始化history
export const dataInit = () => {
  const newHis: HistoryItem = {
    businessA: store.get(businessAAtom),
    businessB: store.get(businessBAtom),
    businessC: store.get(businessCAtom),
  };
  store.set(historyItemAtom, newHis);
};
// ----------------------------------------------------------------
// atom subscriptions
store.sub(fireHistoryAtom, () => {
  const newHis: HistoryItem = {
    businessA: store.get(businessAAtom),
    businessB: store.get(businessBAtom),
    businessC: store.get(businessCAtom),
  };
  const oldHis = store.get(historyItemAtom);
  const [next, patch, inverse] = produceWithPatches(oldHis, (draft) => {
    draft = newHis;
    return draft;
  });
  store.set(historyItemAtom, next);
  store.set(historyAtom, (draft) => {
    draft.undo.push({
      patch: patch[0],
      inverse: inverse[0],
    });
    draft.redo = [];
  });
});
export const fireHistory = () => {
  setTimeout(() => {
    store.set(fireHistoryAtom, Math.random());
  }, 20);
};
// 执行业务代码
const doAction = (item: HistoryItem) => {
  store.set(businessAAtom, (draft) => {
    draft = item.businessA;
    return draft;
  });
  store.set(businessBAtom, (draft) => {
    draft = item.businessB;
    return draft;
  });
  store.set(businessCAtom, (draft) => {
    draft = item.businessC;
    return draft;
  });
  store.set(historyItemAtom, (draft) => {
    draft = item;
    return draft;
  });
};
export const undoAction = () => {
  const history = store.get(historyAtom);
  if (history.undo.length === 0) {
    return;
  }
  const old = history.undo[history.undo.length - 1];
  const currentItem = store.get(historyItemAtom);
  const item = applyPatches(currentItem, [old.inverse]);
  doAction(item);
  store.set(historyAtom, (draft) => {
    const current = draft.undo.pop();
    if (current) {
      draft.redo.push(current);
    }
  });
};
export const redoAction = () => {
  const history = store.get(historyAtom);
  if (history.redo.length === 0) {
    return;
  }
  const old = history.redo[history.redo.length - 1];
  const currentItem = store.get(historyItemAtom);
  const item = applyPatches(currentItem, [old.patch]);
  doAction(item);
  store.set(historyAtom, (draft) => {
    const current = draft.redo.pop();
    if (current) {
      draft.undo.push(current);
    }
  });
};

大致讲下思路

定义 HistoryItem 作为undo、redo所需要恢复的业务数据,可随意扩展。undo、redo本质上就是你点了undo按钮后你的数据需要恢复到上一个状态。当业务复杂了之后,你一次操作可能包含了多个数据的变化,而你undo的操作应该是把这些数据一起还原。所以把涉及到变化的数据都包装在一起,形成一个historyitem。通过 immer提供的produceWithPatches生成撤销和恢复数据, 存在 HistoryState 的undo 里,然后你点击undo按钮,把数据从undo中取出,放入redo中。然后把数据状态通过jotai全局修改,差不多就完成了。

简单的jotai使用是不需要store参与的,这里为了取数据、改数据方便,所以使用了store的监听和set功能。 使用store的代码也很简单,直接在最外层包个provider即可

<Provider store={mainStore}>
	<MainPage />
</Provider>

使用方式应该挺简单的吧。 当你做完需要undo的操作后,调用fireHistory()函数即可。页面的undo、redo按钮可以直接把事件映射到undoAction、redoAction。至于undo、redo按钮是否能点的功能可以直接读取 historyAtom 里面undo、redo的数组长度。

以上就是详解Jotai Immer如何实现undo redo功能示例详解的详细内容,更多关于Jotai Immer实现undo redo的资料请关注脚本之家其它相关文章!

相关文章

  • 解决React报错useNavigate() may be used only in context of Router

    解决React报错useNavigate() may be used only in context of

    这篇文章主要为大家介绍了解决React报错useNavigate() may be used only in context of Router,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-12-12
  • React UI组件库ant-design的介绍与使用

    React UI组件库ant-design的介绍与使用

    Ant Design是阿里蚂蚁金服团队基于React开发的ui组件,主要用于中后台系统的使用,这篇文章主要介绍了React UI组件库ant-design的介绍与使用,需要的朋友可以参考下
    2023-12-12
  • react使用useImperativeHandle示例详解

    react使用useImperativeHandle示例详解

    这篇文章主要为大家介绍了react使用useImperativeHandle示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • React中useEffect与生命周期钩子函数的对应关系说明

    React中useEffect与生命周期钩子函数的对应关系说明

    这篇文章主要介绍了React中useEffect与生命周期钩子函数的对应关系说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-09-09
  • React服务端渲染原理解析与实践

    React服务端渲染原理解析与实践

    这篇文章主要介绍了React服务端渲染原理解析与实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-03-03
  • React Refs转发实现流程详解

    React Refs转发实现流程详解

    Refs是一个 获取 DOM节点或React元素实例的工具,在React中Refs 提供了一种方式,允许用户访问DOM 节点或者在render方法中创建的React元素,这篇文章主要给大家介绍了关于React中refs的一些常见用法,需要的朋友可以参考下
    2022-12-12
  • 通过React-Native实现自定义横向滑动进度条的 ScrollView组件

    通过React-Native实现自定义横向滑动进度条的 ScrollView组件

    开发一个首页摆放菜单入口的ScrollView可滑动组件,允许自定义横向滑动进度条,且内部渲染的菜单内容支持自定义展示的行数和列数,在内容超出屏幕后,渲染顺序为纵向由上至下依次排列,对React Native横向滑动进度条相关知识感兴趣的朋友一起看看吧
    2024-02-02
  • React替换传统拷贝方法的Immutable使用

    React替换传统拷贝方法的Immutable使用

    Immutable.js出自Facebook,是最流行的不可变数据结构的实现之一。它实现了完全的持久化数据结构,使用结构共享。所有的更新操作都会返回新的值,但是在内部结构是共享的,来减少内存占用
    2023-02-02
  • 记一次react前端项目打包优化的方法

    记一次react前端项目打包优化的方法

    这篇文章主要介绍了记一次react前端项目打包优化的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • React组件实例三大属性state props refs使用详解

    React组件实例三大属性state props refs使用详解

    这篇文章主要为大家介绍了React组件实例三大属性state props refs使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09

最新评论