Skip to main content

撤销重做功能

 撤销重做功能广泛用于浏览器的页面切换。

在我的项目中也有一个撤销操作,还有重做操作。

实现原理:

使用两个栈,一个为历史记录栈,一个为未来操作栈。


1)当用户输入的时候,历史记录栈插入当前的值,未来操作栈清空

2)点击撤销( < )的时候,历史记录栈出栈,未来操作栈入栈, past栈顶的元素为当前值

3)点击重做( > )的时候,未来操作栈出栈,历史记录栈入栈,past栈顶的元素为当前值


比如,当用户从""输入了“abc”的时候,栈里面的内容

present="abc"

past = ["a", "ab", "abc"]

future = []


当用户全部删除的时候:

present=""

past=[]

future=["abc", "ab", "a"]


第三方库: redux-undo

安装: npm install redux-undo --save

在原先的reducer的index.ts文件里面修改配置

import { configureStore } from "@reduxjs/toolkit";
import userReducer, { UserStateType } from "./userReducer";
import componentsReducer, { ComponentStateType } from "./componentsReducer";
import pageInfoReducer, { PageInfoType } from "./pageInfoReducer";
import undoable, { excludeAction, StateWithHistory } from "redux-undo";

export type StateType = {
user: UserStateType;
// component: ComponentStateType;
component: StateWithHistory<ComponentStateType>; // add undo
pageInfo: PageInfoType;
};

export default configureStore({
reducer: {
// store current login user information
user: userReducer,
// store component list info
// component: componentsReducer,
component: undoable(componentsReducer, {
limit: 20, // upper limit: 20
filter: excludeAction([
"component/resetComponents",
"component/changeSelectedId",
"component/selectPreviousComponent",
"component/selectNextComponent",
]),
}),
// store page info
pageInfo: pageInfoReducer,
},
});

可以看到: 修改了两处配置。第一处是StateType里面component改为了StateWithHistory<ComponentStateType>.

加了StateWithHistory之后,component就拥有了三个属性 .present, .future .past。和上面的原理一样。


第二处修改是在configureStore里面,

component套上了undoable()。然后下面的limit表示可以后退的次数,filter表示撤销不包含的action


然后在获取这些数据的地方 (在我的项目中是useGetComponentInfo.ts),相比于之前,要加上.present。因为是需要当前的最新的值。

const components = useSelector<StateType>(
(state) => state.component.present
) as ComponentStateType;


实现点击redo / undo的地方:

import { ActionCreators as UndoActionCreators } from "redux-undo";
// undo
const handleUndo = () => {
dispatch(UndoActionCreators.undo());
};
// redo
const handleRedo = () => {
dispatch(UndoActionCreators.redo());
};


然后,我打算实现一个功能:打算在项目中 根据是否可以redo/undo来 禁用对应的按钮

但是遇到了一个bug:

这个bug是: 明明我在filter里面禁用了把所有数据初始化这个dispatch的行为,意味着所有数据初始化之后才是第1步。但是刚加入页面的时候,加载所有服务端传过来的数据之后,还是可以点击撤销,然后回到全部清空的状态。

我debug了一阵子,有past,future的数组,我各自打印了他们的值。发现初始化数据发生在第一步,并没有被禁用掉。所以我怀疑可能是因为数据是异步加载的但是redux-undo不知道,所以加到了history里面。

所以我在初始化数据之后把历史都清空之后解决了这个问题。

dispatch(UndoActionCreators.clearHistory());




Comments

Popular posts from this blog

Templates

  Template - Polymorphism is the ability of the same code to operate on different types. This ability to operate on multiple types reduces code duplication by allowing the same piece of code to be reused across the different types it can operate on. - Polymorphism comes in a variety of forms. What we are interested in at the moment is parametric polymorphism, meaning that we can write our code so that it is parameterized over what type it operates on.  -That is, we want to declare a type parameter T and replace int with T in the above code. -Then, when we want to call the function, we can specify the type for T and get the function we desire. C++ provides parametric polymorphism through templates. Templated Functions - We can write a templated function by using the keyword template followed by the template parameters in angle brackets (<>). - Unlike function parameters, template parameters may be types, which are specified with typename where the type of the parameter wo...

useMemo的使用场景

 useMemo是用来缓存 计算属性 的。 计算属性是函数的返回值,或者说那些以返回一个值为目标的函数。 有些函数会需要我们手动去点击,有些函数是直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用。后者被称为计算属性。 e.g. const Component = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 = () => { console . log ( "我需要手动调用,你不点击我不执行" ); setParams1 (( val ) => val + 1 ); }; //这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。 const computedFun2 = () => { console . log ( "我又执行计算了" ); return params2 ; }; return ( < div onClick = { handleFun1 } > //每次重新渲染的时候我就会执行 computed: { computedFun2 () } </ div > ); }; 上面的代码中,在每次点击div的时候,因为setParams1的缘故,导致params1改变,整个组件都需要重新渲染,也导致comptedFunc2()也需要重新计算。如果computedFunc2的计算量很大,这时候重新计算会比较浪费。 可以使用useMemo: const Com = () => { const [ params1 , setParams1 ] = useState ( 0 ); const [ params2 , setParams2 ] = useState ( 0 ); //这种是需要我们手动去调用的函数 const handleFun1 ...

Valgrind

  Using Valgrind to check memory How to use Valgrind -To valgrind the program, run the valgrind command and give it our program name as an argument. -For example, if we want to run ./myProgram hello 42, we can simply run Valgrind ./myProgram hello 42.  Uninitialized Values -When we run the program, we may use uninitialized values. It needs to be fixed. Valgrind can tell us about the use of uninitialized values. But it only tell when the control flow of the program depends on the unitialized value. For example, uninitialized value appears in the conditional expression of an if, or a loop, or in the switch statement. -If we want to know where the uninitialized value is from, we can use Valgrind    --track-origins=yes ./myProgram -Using -fsanitize=address can find a lot of problems that Memcheck cannot.  -We can use Valgrind with GDB to debug. We can run: --vgdb=full --vgdb-error=0., then Valgrind will stop on the first error that it encounters and give control to ...