发布于 2026-01-06 7 阅读
0

使用撤销和重置功能增强您的 React 应用 DEV 的全球展示挑战赛,由 Mux 呈现:展示您的项目!

使用撤销和重置功能增强您的 React 应用

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

在Medium上找到我

你是否曾经在开发过程中犯错,并希望有撤销功能?或者重置功能

幸运的是,我们使用的软件通常都具备撤销或重置功能。我指的是VS Code中的Ctrl + Z,或者 90 年代常见的表单中的重置按钮。

我们为什么需要撤销功能呢?因为人总是会犯错。无论是打字错误还是文章用词不当,我们都需要某种方法来撤销操作。但仔细想想,几乎在任何地方都有撤销的方法。铅笔有橡皮擦,手机可以拆开,用户可以重置密码,可擦笔可以擦掉墨迹——这样的例子不胜枚举。

但是,作为应用程序的开发者,如果要实现撤销或重置功能,该如何着手呢?应该从哪里开始?应该在哪里寻求建议?

别再犹豫了,我这就来教你如何为你的应用程序添加撤销重置功能!你会发现,这篇文章讲解起来并不难,你也可以做到

我们将构建一个用户界面,用户可以在其中通过姓名和性别添加好友。添加好友后,屏幕上会显示好友的注册信息卡片。此外,如果好友是女性,卡片上会显示粉色边框;如果是男性,则会显示青色边框。如果用户在注册好友时出错,可以选择撤销操作或将整个界面重置为初始状态。最后,用户还可以更改界面主题颜色,例如喜欢深色还是浅色。

效果大概是这样的:

最后的光芒

黑暗的

最终的黑暗

事不宜迟,我们开始吧!

在本教程中,我们将使用 create-react-app 快速生成一个 React 项目。

(如果您想从 GitHub 获取存储库的副本,请点击此处)。

请使用以下命令创建一个项目。在本教程中,我将把我们的项目命名为undo-reset

npx create-react-app undo-reset
Enter fullscreen mode Exit fullscreen mode

完成后,进入该目录:

cd undo-reset
Enter fullscreen mode Exit fullscreen mode

src/index.js我们要稍微清理一下主入口内部:

src/index.js

import React from 'react'
import ReactDOM from 'react-dom'
import App from './App'
import './styles.css'
import * as serviceWorker from './serviceWorker'

ReactDOM.render(<App />, document.getElementById('root'))
serviceWorker.unregister()
Enter fullscreen mode Exit fullscreen mode

以下是几种初始样式:

src/styles.css

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto',
    'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans',
    'Helvetica Neue', sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
Enter fullscreen mode Exit fullscreen mode

现在创建src/App.js。这将渲染我们在整个教程中将要构建的所有组件:

src/App.js

import React, { useState } from 'react'

const App = () => {
  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  return <div />
}

export default App
Enter fullscreen mode Exit fullscreen mode

由于我们将允许用户添加好友并指定姓名和性别,我们定义了一些 React Hook 来保存输入值,我们还将定义更新这些值的方法。

接下来,我们将实现钩子将要附加到的元素和输入字段:

src/App.js

const App = () => {
  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  return (
    <div>
      <form className="form">
        <div>
          <input
            onChange={onNameChange}
            value={name}
            type="text"
            name="name"
            placeholder="Friend's Name"
          />
        </div>
        <div>
          <select onChange={onGenderChange} name="gender" value={gender}>
            <option value="Male">Male</option>
            <option value="Female">Female</option>
            <option value="Other">Other</option>
          </select>
        </div>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

src/styles.css

form {
  display: flex;
  align-items: center;
}

form > div {
  margin: auto 3px;
}

input,
select {
  transition: all 0.15s ease-out;
  border: 1px solid #ddd;
  padding: 10px 14px;
  outline: none;
  font-size: 14px;
  color: #666;
}

input:hover,
select:hover {
  border: 1px solid #c6279f;
}

select {
  cursor: pointer;
  padding-top: 9px;
  padding-bottom: 9px;
}

button {
  transition: all 0.15s ease-out;
  background: #145269;
  border: 1px solid #ddd;
  padding: 10px 35px;
  outline: none;
  cursor: pointer;
  color: #fff;
}

button:hover {
  color: #145269;
  background: #fff;
  border: 1px solid #145269;
}

button:active {
  background: rgb(27, 71, 110);
  border: 1px solid #a1a1a1;
  color: #fff;
}
Enter fullscreen mode Exit fullscreen mode

我并不喜欢教程界面过于简洁——毕竟,我珍惜您阅读我文章的时间,所以我花了一些心思在样式上,希望能让您不会感到无聊 :)

接下来,我们需要一个可靠的地方来放置撤销和重置逻辑,因此我们将创建一个自定义钩子来处理状态更新:

src/useApp.js

const useApp = () => {
  const onSubmit = (e) => {
    e.preventDefault()
    console.log('Submitted')
  }

  return {
    onSubmit,
  }
}

export default useApp
Enter fullscreen mode Exit fullscreen mode

上面的onSubmit事件将被传递到我们之前定义的表单中,以便在用户提交好友信息时将好友添加到好友列表中:

src/App.js

import React, { useState } from 'react'
import useApp from './useApp'

const App = () => {
  const { onSubmit } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  return (
    <div>
      <form className="form" onSubmit={onSubmit({ name, gender })}>
        <div>
          <input
            onChange={onNameChange}
            value={name}
            type="text"
            name="name"
            placeholder="Friend's Name"
          />
        </div>
        <div>
          <select onChange={onGenderChange} name="gender" value={gender}>
            <option value="Male">Male</option>
            <option value="Female">Female</option>
            <option value="Other">Other</option>
          </select>
        </div>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

需要注意的是,onSubmit 函数会接收字段参数作为参数。回顾一下我们的onSubmit处理程序,它并不是一个高阶函数。这意味着它会在组件挂载时立即被调用,因此我们需要将 onSubmit 处理程序转换为高阶函数,以避免这种情况,并使其能够接收字段值:

src/useApp.js

const useApp = () => {
  const onSubmit = (friend) => (e) => {
    e.preventDefault()
    console.log(friend)
  }

  return {
    onSubmit,
  }
}

export default useApp
Enter fullscreen mode Exit fullscreen mode

目前,我们有以下信息:

接下来我们将开始实现逻辑。但首先,我们需要定义状态结构

src/useApp.js

const initialState = {
  friends: [],
  history: [],
}
Enter fullscreen mode Exit fullscreen mode

本教程最重要的部分是历史记录。当用户提交操作时,我们会捕获应用程序的状态,并将其安全地存储在一个位置,以便稍后可以撤销用户操作。这个“存储”就是`state.history`,只有我们的自定义钩子需要知道它。不过,它也可以用于用户界面,实现一些有趣的功能——例如,允许用户通过网格查看之前的操作,并选择要返回到哪个操作。这是一个非常实用的小功能,可以给用户留下深刻印象!

接下来,我们将在 reducer 中添加 switch case,以便我们的状态能够真正更新:

src/useApp.js

const reducer = (state, action) => {
  switch (action.type) {
    case 'add-friend':
      return {
        ...state,
        friends: [...state.friends, action.friend],
        history: [...state.history, state],
      }
    case 'undo': {
      const isEmpty = !state.history.length
      if (isEmpty) return state
      return { ...state.history[state.history.length - 1] }
    }
    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode

当我们发送类型为“add-friend”的action 时,我们会将新好友添加到列表中。但用户不知道的是,我们其实也在默默地保存他们之前的编辑记录。我们会捕获应用的最新状态并将其保存到history数组中。这样,如果用户想要恢复到之前的状态,我们就可以帮他们实现 :)

由于我们使用了 React Hook API,因此切记要从React中导入它。此外,我们还需要在自定义 Hook 中定义useReducer 的实现,以便获取该 API 来发送信号,从而更新本地状态:

src/useApp.js

import { useReducer } from 'react'

// ... further down inside the custom hook:
const [state, dispatch] = useReducer(reducer, initialState)
Enter fullscreen mode Exit fullscreen mode

既然我们已经获得了这些 API,那就让我们把它们应用到需要的地方吧:

src/useApp.js

const onSubmit = (friend) => (e) => {
  e.preventDefault()
  if (!friend.name) return
  dispatch({ type: 'add-friend', friend })
}

const undo = () => {
  dispatch({ type: 'undo' })
}
Enter fullscreen mode Exit fullscreen mode

这是我们目前为止设计的定制钩子的样子:

src/useApp.js

import { useReducer } from 'react'

const initialState = {
  friends: [],
  history: [],
}

const reducer = (state, action) => {
  switch (action.type) {
    case 'add-friend':
      return {
        ...state,
        friends: [...state.friends, action.friend],
        history: [...state.history, state],
      }
    case 'undo': {
      const isEmpty = !state.history.length
      if (isEmpty) return state
      return { ...state.history[state.history.length - 1] }
    }
    default:
      return state
  }
}

const useApp = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const onSubmit = (friend) => (e) => {
    e.preventDefault()
    if (!friend.name) return
    dispatch({ type: 'add-friend', friend })
  }

  const undo = () => {
    dispatch({ type: 'undo' })
  }

  return {
    ...state,
    onSubmit,
    undo,
  }
}

export default useApp
Enter fullscreen mode Exit fullscreen mode

接下来,我们需要渲染插入到state.friends中的好友列表,以便用户可以在界面中看到他们:

src/App.js

const App = () => {
  const { onSubmit, friends } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  return (
    <div>
      <form className="form" onSubmit={onSubmit({ name, gender })}>
        <div>
          <input
            onChange={onNameChange}
            value={name}
            type="text"
            name="name"
            placeholder="Friend's Name"
          />
        </div>
        <div>
          <select onChange={onGenderChange} name="gender" value={gender}>
            <option value="Male">Male</option>
            <option value="Female">Female</option>
            <option value="Other">Other</option>
          </select>
        </div>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <div className="boxes">
        {friends.map(({ name, gender }, index) => (
          <FriendBox key={`friend_${index}`} gender={gender}>
            <div className="box-name">Name: {name}</div>
            <div className="gender-container">
              <img src={gender === 'Female' ? female : male} alt="" />
            </div>
          </FriendBox>
        ))}
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

如果你想知道这条奇怪的线是做什么用的:

<img src={gender === 'Female' ? female : male} alt="" />
Enter fullscreen mode Exit fullscreen mode

实际上,我只是为了演示目的,在界面中轻松区分男性女性,才提供了我自己的图片来渲染到img元素中。如果您需要这些图片,克隆此仓库的用户可以在src/images目录中找到它们 :)

我们在App.js的顶部导入女性/男性图像,然后在App组件的正上方定义一个FriendBox组件,该组件负责在用户向列表中添加好友时渲染好友框:

src/App.js

// At the top
import female from './images/female.jpg'
import male from './images/male.jpg'

// Somewhere above the App component
const FriendBox = ({ gender, ...props }) => (
  <div
    className={cx('box', {
      'teal-border': gender === 'Male',
      'hotpink-border': gender === 'Female',
    })}
    {...props}
  />
)
Enter fullscreen mode Exit fullscreen mode

为了从视觉角度进一步区分女性男性,我还添加了代表各自的基本风格:

src/styles.css

.teal-border {
  border: 1px solid #467b8f;
}

.hotpink-border {
  border: 1px solid #c1247d;
}
Enter fullscreen mode Exit fullscreen mode

以下是目前App.js文件的内容:

src/App.js

import React, { useState } from 'react'
import cx from 'classnames'
import female from './images/female.jpg'
import male from './images/male.jpg'
import useApp from './useApp'

const FriendBox = ({ gender, ...props }) => (
  <div
    className={cx('box', {
      'teal-border': gender === 'Male',
      'hotpink-border': gender === 'Female',
    })}
    {...props}
  />
)

const App = () => {
  const { onSubmit, friends } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  return (
    <div>
      <form className="form" onSubmit={onSubmit({ name, gender })}>
        <div>
          <input
            onChange={onNameChange}
            value={name}
            type="text"
            name="name"
            placeholder="Friend's Name"
          />
        </div>
        <div>
          <select onChange={onGenderChange} name="gender" value={gender}>
            <option value="Male">Male</option>
            <option value="Female">Female</option>
            <option value="Other">Other</option>
          </select>
        </div>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <div className="boxes">
        {friends.map(({ name, gender }, index) => (
          <FriendBox key={`friend_${index}`} gender={gender}>
            <div className="box-name">Name: {name}</div>
            <div className="gender-container">
              <img src={gender === 'Female' ? female : male} alt="" />
            </div>
          </FriendBox>
        ))}
      </div>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

这里使用的方框样式如下:

src/styles.css

.boxes {
  margin: 10px 0;
  padding: 3px;
  display: grid;
  grid-gap: 10px;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: 1fr;
}

.box {
  font-size: 18px;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.box-name {
  display: flex;
  align-items: center;
  height: 50px;
}

.box.gender-container {
  position: relative;
}

.box img {
  object-fit: cover;
  width: 100%;
  height: 100%;
}
Enter fullscreen mode Exit fullscreen mode

哎呀,糟糕!我们忘了添加撤销方法,这样我们就可以在界面中使用它了!现在把它从useApp中解构出来,放到撤销按钮上:

src/App.js

const App = () => {
  const { onSubmit, friends, undo } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  const resetValues = () => {
    setName('')
    setGender('Male')
  }

  return (
    <div>
      <form className="form" onSubmit={onSubmit({ name, gender }, resetValues)}>
        <div>
          <input
            onChange={onNameChange}
            value={name}
            type="text"
            name="name"
            placeholder="Friend's Name"
          />
        </div>
        <div>
          <select onChange={onGenderChange} name="gender" value={gender}>
            <option value="Male">Male</option>
            <option value="Female">Female</option>
            <option value="Other">Other</option>
          </select>
        </div>
        <div>
          <button type="submit">Add</button>
        </div>
      </form>
      <div className="undo-actions">
        <div>
          <button type="button" onClick={undo}>
            Undo
          </button>
        </div>
      </div>
      <div className="boxes">
        {friends.map(({ name, gender }, index) => (
          <FriendBox key={`friend_${index}`} gender={gender}>
            <div className="box-name">Name: {name}</div>
            <div className="gender-container">
              <img src={gender === 'Female' ? female : male} alt="" />
            </div>
          </FriendBox>
        ))}
      </div>
    </div>
  )
}
Enter fullscreen mode Exit fullscreen mode

现在,当用户点击“撤销”按钮时,应该恢复他们最后的操作!

2

一切都按计划进行。用户可以将好友添加到列表中,轻松地在界面上区分好友的性别,并撤销之前的提交操作。

……您是否也注意到App组件中现在新增了一个resetValues方法,它会作为第二个参数传递给onSubmit方法?用户可能会觉得有点奇怪,提交好友信息后,输入的内容不会被清除。他们还需要保留相同的姓名吗?除非他们有两三个同名好友,否则他们肯定会按下退格键手动清除。但作为开发者,我们有能力让用户的操作更便捷,所以我们实现了resetValues方法。

也就是说,由于我们已将其作为第二个参数传递给 UI 组件,因此应该将其声明onSubmit 的第二个参数:

src/useApp.js

const useApp = () => {
  const [state, dispatch] = useReducer(reducer, initialState)

  const onSubmit = (friend, resetValues) => (e) => {
    e.preventDefault()
    if (!friend.name) return
    dispatch({ type: 'add-friend', friend })
    resetValues()
  }

  const undo = () => {
    dispatch({ type: 'undo' })
  }

  return {
    ...state,
    onSubmit,
    undo,
  }
}
Enter fullscreen mode Exit fullscreen mode

我们的撤销功能现在应该已经 100% 正常工作了,但我还要再进一步,让它变得更复杂一些,因为撤销功能可以与几乎任何东西兼容。

因此,我们将允许用户为界面指定主题颜色,这样他们就不会对白色感到厌倦:

src/useApp.js

const initialState = {
  friends: [],
  history: [],
  theme: 'light',
}
Enter fullscreen mode Exit fullscreen mode

src/useApp.js

const reducer = (state, action) => {
  switch (action.type) {
    case 'set-theme':
      return { ...state, theme: action.theme, history: insertToHistory(state) }
    case 'add-friend':
      return {
        ...state,
        friends: [...state.friends, action.friend],
        history: insertToHistory(state),
      }
    case 'undo': {
      const isEmpty = !state.history.length
      if (isEmpty) return state
      return { ...state.history[state.history.length - 1] }
    }
    case 'reset':
      return { ...initialState, history: insertToHistory(state) }
    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode

此外,我还声明了一个insertToHistory工具,以便在将来我们为 state 参数传入奇怪的值时(如您在上面可能已经注意到的那样),能够带来额外的好处:

const insertToHistory = (state) => {
  if (state && Array.isArray(state.history)) {
    // Do not mutate
    const newHistory = [...state.history]
    newHistory.push(state)
    return newHistory
  }
  console.warn(
    'WARNING! The state was attempting capture but something went wrong. Please check if the state is controlled correctly.',
  )
  return state.history || []
}
Enter fullscreen mode Exit fullscreen mode

我想补充一点,随着应用程序规模越来越大、功能越来越复杂,养成提前思考的习惯非常重要。

接下来继续主题实现,我们将定义一个UI组件可以利用的自定义方法:

src/useApp.js

const onThemeChange = (e) => {
  dispatch({ type: 'set-theme', theme: e.target.value })
}

return {
  ...state,
  onSubmit,
  undo,
  onThemeChange,
}
Enter fullscreen mode Exit fullscreen mode

将主题组件和方法应用到界面:

src/App.js

import React, { useState } from 'react'
import cx from 'classnames'
import female from './images/female.jpg'
import male from './images/male.jpg'
import useApp from './useApp'

const FriendBox = ({ gender, ...props }) => (
  <div
    className={cx('box', {
      'teal-border': gender === 'Male',
      'hotpink-border': gender === 'Female',
    })}
    {...props}
  />
)

const App = () => {
  const { onSubmit, friends, undo, theme, onThemeChange } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  const resetValues = () => {
    setName('')
    setGender('Male')
  }

  return (
    <div>
      <div>
        <h3>What theme would you like to display?</h3>
        <div>
          <select onChange={onThemeChange} name="theme" value={theme}>
            <option value="light">Light</option>
            <option value="dark">Dark</option>
          </select>
        </div>
      </div>
      <div>
        <h3>Add a friend</h3>
        <form
          className="form"
          onSubmit={onSubmit({ name, gender }, resetValues)}
        >
          <div>
            <input
              onChange={onNameChange}
              value={name}
              type="text"
              name="name"
              placeholder="Friend's Name"
            />
          </div>
          <div>
            <select onChange={onGenderChange} name="gender" value={gender}>
              <option value="Male">Male</option>
              <option value="Female">Female</option>
              <option value="Other">Other</option>
            </select>
          </div>
          <div>
            <button type="submit">Add</button>
          </div>
        </form>
      </div>
      <div>
        <h3>Made a mistake?</h3>
        <div className="undo-actions">
          <div>
            <button type="button" onClick={undo}>
              Undo
            </button>
          </div>
        </div>
      </div>
      <div className="boxes">
        {friends.map(({ name, gender }, index) => (
          <FriendBox key={`friend_${index}`} gender={gender}>
            <div className="box-name">Name: {name}</div>
            <div className="gender-container">
              <img src={gender === 'Female' ? female : male} alt="" />
            </div>
          </FriendBox>
        ))}
      </div>
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

既然我们添加了主题更改功能,那么添加一些条件样式来适应这些更改可能也是个好主意,对吧?

 <div className={cx({
    'theme-light': theme === 'light',
    'theme-dark': theme === 'dark',
  })}
  // ...rest of the component
Enter fullscreen mode Exit fullscreen mode

以下是几种样式:

src/styles.css

.theme-light,
.theme-dark {
  box-sizing: border-box;
  transition: all 0.15s ease-out;
  padding: 12px;
  min-height: 100vh;
}

.theme-light {
  color: #145269;
  background: #fff;
}

.theme-dark {
  color: #fff;
  background: #0b2935;
}
Enter fullscreen mode Exit fullscreen mode

太棒了!这就是我们界面现在能做到的!

3

为自己走到这一步而鼓掌吧!

不过我们还不要高兴得太早,因为本文标题还提到了界面重置功能。

现在我们就通过在现有的 reducer 中直接定义 switch case 来实现这一点:

src/useApp.js

const reducer = (state, action) => {
  switch (action.type) {
    case 'set-theme':
      return { ...state, theme: action.theme, history: insertToHistory(state) }
    case 'add-friend':
      return {
        ...state,
        friends: [...state.friends, action.friend],
        history: insertToHistory(state),
      }
    case 'undo': {
      const isEmpty = !state.history.length
      if (isEmpty) return state
      return { ...state.history[state.history.length - 1] }
    }
    case 'reset':
      return { ...initialState, history: insertToHistory(state) }
    default:
      return state
  }
}
Enter fullscreen mode Exit fullscreen mode

当然,接下来就需要定义一个方法来通知 reducer 状态发生了变化。别忘了在 hook 的最后返回它!

src/useApp.js

const reset = () => {
  dispatch({ type: 'reset' })
}

const onThemeChange = (e) => {
  dispatch({ type: 'set-theme', theme: e.target.value })
}

return {
  ...state,
  onSubmit,
  onThemeChange,
  undo,
  reset,
}
Enter fullscreen mode Exit fullscreen mode

从 UI 组件的钩子中解构它:

src/App.js

const { onSubmit, friends, undo, theme, onThemeChange, reset } = useApp()
Enter fullscreen mode Exit fullscreen mode

src/App.js

<div>
  <h3>Made a mistake?</h3>
  <div className="undo-actions">
    <div>
      <button type="button" onClick={undo}>
        Undo
      </button>
    </div>
    <div>
      <button type="button" onClick={reset}>
        Reset
      </button>
    </div>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

最后,也是非常重要的一点,是用于使这些操作水平对齐的样式:

src/styles.css

.undo-actions {
  display: flex;
  align-items: center;
}

.undo-actions > div {
  margin: auto 3px;
}
Enter fullscreen mode Exit fullscreen mode

结果:

4

你不觉得撤销功能也能捕捉到重置界面的操作这一点很棒

如果您选择下载并克隆该存储库,您将看到如下所示的细微修改:

src/App.js

import React, { useState } from 'react'
import cx from 'classnames'
import useApp from './useApp'
import ThemeControl from './ThemeControl'
import AddFriend from './AddFriend'
import UndoResetControl from './UndoResetControl'
import Friends from './Friends'
import './styles.css'

const App = () => {
  const { friends, theme, onSubmit, onThemeChange, undo, reset } = useApp()

  const [name, setName] = useState('')
  const [gender, setGender] = useState('Male')
  const onNameChange = (e) => setName(e.target.value)
  const onGenderChange = (e) => setGender(e.target.value)

  const resetValues = () => {
    setName('')
    setGender('Male')
  }

  return (
    <div
      className={cx({
        'theme-light': theme === 'light',
        'theme-dark': theme === 'dark',
      })}
    >
      <ThemeControl theme={theme} onChange={onThemeChange} />
      <AddFriend
        onSubmit={onSubmit({ name, gender }, resetValues)}
        onNameChange={onNameChange}
        onGenderChange={onGenderChange}
        currentValues={{ name, gender }}
      />
      <UndoResetControl undo={undo} reset={reset} />
      <Friends friends={friends} />
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

代码本身没有变化,只是我把各个组件拆分到各自的文件中,使代码更易读、更易维护。

奖金

在本教程开头,我提到过一个可以向用户显示的界面——它允许用户选择如果需要可以恢复到应用程序的哪个先前状态。以下是一个使用示例:

奖金

结论

撤销操作对我们来说非常有用,因为我们人类总是会犯错……这是事实。希望这对你有所帮助 :)

下次再见!如果想继续阅读我的文章,可以关注我哦!

欢迎在Medium上关注我!

文章来源:https://dev.to/jsmanifest/enhance-your-react-app-with-undo-and-reset-powered-2l6h