【react】撤销/重做功能的实现

基本原理

  • 模拟一个输入框的输入/撤销/重做的过程
    • 假设定义变量 present 存储当前输入框的值;定义栈结构数组 past 存储历史数据;定义栈结构数组 future 存储未来数据记录。
    // 1.输入框输入 a
    present = 'a'
    past = ['a']
    future = []
    // 2.输入框再输入 b
    present = 'ab'
    past = ['a', 'ab']
    future = []
    // 3.撤销: past 出栈, future 入栈, present 重新赋值
    present = 'a'
    past = ['a']
    future = ['ab']
    // 4.撤销: past 出栈, future 入栈, present 重新赋值
    present = ''
    past = []
    future = ['ab', 'a']
    // 5.重做: future 出栈, past 入栈, present 重新赋值
    present = 'a'
    past = ['a']
    future = ['ab']
    

使用 redux-undo 实现 简易todolist 撤销/重做 样例

// src/store/index.ts
import { configureStore } from '@reduxjs/toolkit'
import todoListReducer, { TodoItemType } from './todoList'

import undoable, { StateWithHistory, excludeAction } from 'redux-undo'

export type StateType = {
  todoList: StateWithHistory<TodoItemType[]>
}

export default configureStore({
  reducer: {
    // 没有撤销/重做
    // todoList: todoListReducer
    // 加入撤销/重做
    todoList: undoable(todoListReducer, {
      limit: 20, // 限制只能撤销 20 步
      filter: excludeAction(['todoList/toggleComplete']), // 屏蔽某些 action 不进行撤销/重做
    })
  }
})
// src/store/todoList.ts
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
import { nanoid } from "nanoid";

export type TodoItemType = {
  id: string,
  title: string,
  complete: boolean
}

const INIT_STATE: TodoItemType[] = [
  {
    id: nanoid(5),
    title: 'test',
    complete: false
  },
  {
    id: nanoid(5),
    title: 'test1',
    complete: true
  }
]

const todoListSlice = createSlice({
  name: 'todoList',
  initialState: INIT_STATE,
  reducers: {
    addTodo(state: TodoItemType[], action: PayloadAction<TodoItemType>){
      return [action.payload, ...state]
    },
    removeTodo(state: TodoItemType[], action: PayloadAction< { id: string } >){
      return state.filter(todo => todo.id !== action.payload.id)
    },
    toggleComplete(state: TodoItemType[], action: PayloadAction< { id: string } >){
      const {id: toggleId} = action.payload
      return state.map(todo => {
        const {id, complete} = todo
        if(id !== toggleId) return todo
        return {
          ...todo,
          complete: !complete
        }
      })
    },
  }
})

export const {addTodo, removeTodo, toggleComplete} = todoListSlice.actions

export default todoListSlice.reducer
// src/pages/TodoList.tsx
import React, {FC} from "react";
import { useSelector, useDispatch } from "react-redux";
import { StateType } from "../store/index";
import { TodoItemType, addTodo, removeTodo, toggleComplete } from "../store/todoList";
import { nanoid } from "nanoid";
import { ActionCreators } from "redux-undo";

const TodoList: FC = () => {
  const todoList = useSelector<StateType>(state => state.todoList.present) as TodoItemType[]
  const dispatch = useDispatch()

  function add(){
    const id = nanoid(5)
    const data = {
      id,
      title: `title-${id}`,
      complete: false
    }
    dispatch(addTodo(data))
  }

  function del(id: string) {
    dispatch(removeTodo({id}))
  }

  function toggle(id: string) {
    dispatch(toggleComplete({id}))
  }

  function undo() {
    dispatch(ActionCreators.undo())
  }

  function redo() {
    dispatch(ActionCreators.redo())
  }

  return (
    <>
      <button onClick={add}>添加</button>
      <ul>
        {todoList.map(todo => {
          const {id, title, complete} = todo
          return (
            <li key={id} style={{textDecoration: complete ? 'line-through' : ''}}>
              <span onClick={() => { toggle(id) }}>{title}</span>
              <button onClick={() => { del(id) }}>删除</button>
            </li>
          )
        })}
      </ul>
      <div>
        <button onClick={undo}>undo</button>
        <button onClick={redo}>redo</button>
      </div>
    </>
  )
}

export default TodoList
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值