React待办事项开发记:Hook魔法与组件间的悄悄话

今天在React世界里捣鼓了一个有趣的待办事项应用,收获颇丰!整个过程就像在组装一台精密的通讯设备:组件之间要传递消息,数据需要持久保存,还要确保各个部件协调工作。下面让我用幽默的方式带你回顾这段奇妙旅程。

最终我们实现的效果:

那么我们是如何实现这个任务列表的功能的呢?


一、组件家族:各司其职的奇妙团队

想象一下我们的应用就像一个家庭聚会:

  1. App组件:佛系家长

    javascript 复制代码
    function App() {
      return (
        <>
          <Todos />
        </>
      )
    }

    这位家长深谙"无为而治"之道,直接把所有家务丢给了Todos组件,自己躺平喝咖啡(注释掉的那段CSS实验品就是它的休闲时光)。

  2. Todos组件:家庭总管

    ini 复制代码
    const Todos = () => {
      const { todos, addTodo, onToggle, onDelete } = useTodos()
      
      return (
        <div className='app'>
          <TodoForm onAddTodo={addTodo} />
          <TodoList Todos={todos} onToggle={onToggle} onDelete={onDelete}/>
        </div>
      )
    }

    总管大人手握核心机密(useTodos钩子),把任务分发给两个得力助手:表单管家和列表管家。

  3. TodoForm组件:家庭秘书

    javascript 复制代码
    const TodoForm = ({ onAddTodo }) => {
      const [text, setText] = useState('')
      
      const handleSubmit = (e) => {
        e.preventDefault()
        onAddTodo(text.trim())
        setText('')
      }
      
      return (
        <form onSubmit={handleSubmit}>
          <input value={text} onChange={e => setText(e.target.value)} />
          <button type='submit'>Add</button>
        </form>
      )
    }

    这位秘书有强迫症:输入必须.trim()去空格,提交后必须清空输入框。它的口头禅是:"数据状态和界面状态必须保持绝对一致!"

  4. TodoList组件:任务分发员

    ini 复制代码
    const TodoList = ({ Todos, onToggle, onDelete }) => (
      <ul className="todo-list">
        {Todos.length > 0 
          ? Todos.map(todo => (
              <TodoItem key={todo.id} todo={todo} 
                onToggle={() => onToggle(todo.id)}
                onDelete={() => onDelete(todo.id)}/>
            ))
          : <p>暂无代办事项</p>
        }
      </ul>
    )

    它像圣诞老人一样,遍历所有待办事项,给每个任务打包好专属ID礼物再分发给TodoItem。

  5. TodoItem组件:一线执行者

ini 复制代码
const TodoItem = ({ todo, onToggle, onDelete }) => (
  <div className="todo-item">
    <input type="checkbox" checked={todo.isComplete} onChange={onToggle}/>
    <span className={todo.isComplete ? 'completed' : ''}>{todo.text}</span>
    <button onClick={onDelete}>Delete</button>
  </div>
)

这位执行者最擅长变脸:任务完成时给文字加上.completed类名,就像给完成任务戴上胜利勋章。

在react中我们使用组件化的开发方式,就像拼积木一样,将开发的好的功能一个个拼接在一起,方便我们的复用

二、组件通讯:React世界的传纸条艺术

在React组件间传递数据就像学生时代传纸条:

  1. 父子通讯(向下传递) :爸爸给零花钱

    ini 复制代码
    // 父组件Todos给子组件TodoForm发"零花钱"
    <TodoForm onAddTodo={addTodo} />
    
    // 子组件TodoForm开心收下
    const TodoForm = ({ onAddTodo }) => { ... }
  2. 子父通讯(向上传递) :孩子要买玩具

    scss 复制代码
    // 子组件提交时"撒娇"
    const handleSubmit = (e) => {
      e.preventDefault()
      onAddTodo(text.trim()) // "爸,我想买这个!"
    }
    
    // 父组件慷慨回应
    addTodo = (text) => {
      setTodos([...todos, { id: Date.now(), text, isComplete: false }])
    }
  3. 兄弟通讯(间接传递) :通过父母中转

    ini 复制代码
    // 哥哥TodoList向弟弟TodoItem传话
    <TodoItem onToggle={() => onToggle(todo.id)}/>
    
    // 弟弟收到后执行操作
    <input type="checkbox" onChange={onToggle}/>

    这就像哥哥对弟弟说:"爸妈同意你吃糖了",弟弟才敢行动。

    这种通信方式非常繁琐,需要一层层汇报,之后会有useContext来更好的实现跨层级的组件通信,以及常用的Rudux状态集中管理工具,这里也是本项目未实现功能的遗憾

  4. ID护照制度:精准定位的秘诀

    scss 复制代码
    // 给每个任务发护照
    { id: Date.now(), text, isComplete: false }
    
    // 出入境检查
    onToggle={() => onToggle(todo.id)}
    onDelete={() => onDelete(todo.id)}

    有了ID这个专属护照,我们才能精准找到要操作的任务,避免"误伤无辜"。


三、useTodos钩子:数据保险箱

这个自定义Hook堪称我们的应用大脑,实现了两大魔法:

魔法一:本地存储持久化

javascript 复制代码
export const useTodos = () => {
  // 从保险箱读取数据
  const [todos, setTodos] = useState(
    JSON.parse(localStorage.getItem('todos'))
  )

  // 数据变化时自动存回保险箱
  useEffect(() => {
    localStorage.setItem('todos', JSON.stringify(todos))
  }, [todos])
  
  // ...其他操作
}

这个设计太贴心了!就像有个小精灵在你修改待办事项时自动帮你保存:

  • 刷新页面?数据还在!
  • 关闭浏览器?数据还在!
  • 不小心踢掉电源?数据还在!

这里我简单的来演示一下使用本地存储和不使用本地存储的区别

const 复制代码
        const ul = document.getElementById('ul');
        function fn() {
            ul.innerHTML = list.map(item => `<li>${item}</li>`).join('')
        }
        fn();

        const input = document.getElementById('input');
        const button = document.getElementById('button');
        button.addEventListener('click', () => {
            if (input.value) {
                list.push(input.value);
            } else {
                alert('请输入内容')
            }
            fn();
        })

当我们不使用本地存储,我们的数据是死数据,我们添加的树会随着页面的刷新也丢失,就如同下面的效果

javascript 复制代码
// 获取 ul 元素
        const ul = document.getElementById('ul');
        // 获取 input 和 button 元素
        const input = document.getElementById('input');
        const button = document.getElementById('button');

        // 从 localStorage 获取列表数据,若没有则初始化为默认值
        function getList() {
            const storedList = localStorage.getItem('fruitList');
            return storedList ? JSON.parse(storedList) : ['苹果', '香蕉', '橘子', '西瓜'];
        }

        // 渲染列表到页面
        function renderList() {
            const list = getList();
            ul.innerHTML = list.map(item => `<li>${item}</li>`).join('');
        }

        // 初始渲染
        renderList();

        // 添加点击事件
        button.addEventListener('click', () => {
            const value = input.value.trim();
            if (value) {
                let list = getList();
                list.push(value);
                localStorage.setItem('fruitList', JSON.stringify(list)); // 保存回 localStorage
                renderList(); // 更新视图
                input.value = ''; // 清空输入框
            } else {
                alert('请输入内容');
            }
        });

当我们使用localStorage我们的数据会被保存到本地,刷新后也不会丢失

我们的localStorage存储的数据是存放在浏览器的,可以看到图中存放的数据是有地址区分的,不同的网页,有不同的本地存储的数据

之前我们写过一篇文章介绍了Cookie,http是无状态的,而我们的Cookie可以实现身份识别,这样我们所发送的Http请求就好像是带有了状态,其实这一切都是Cookie的功劳 大家感兴趣可以看看这篇文章当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊

这里我们简单的说一下两者的区别

特性 localStorage Cookie
存储容量 通常为5MB或更多,具体取决于浏览器。 通常每个域名4KB(包括所有元数据),实际可用空间更小。
有效期 数据没有过期时间,除非用户手动清除或者通过代码清除。 可以设置过期时间;如果不设置,默认是会话级别的,浏览器关闭后即失效。
传输 数据仅在客户端使用,不会自动发送到服务器。 每次HTTP请求都会将cookie发送给服务器(如果cookie的path和domain匹配)。
访问范围 同源策略下,所有同源窗口共享相同的数据。 同源策略下,所有同源窗口共享相同的数据。
安全性 支持通过设置StorageManager进行管理,但本身不提供加密等安全措施。 支持Secure标志,确保只通过HTTPS发送;支持HttpOnly标志,防止JavaScript访问。
操作便捷性 使用简单,主要通过setItem, getItem, removeItem等方法操作。 设置相对复杂一些,需要指定更多的参数如路径、过期时间等。
适用场景 适合存储大量不敏感的数据,例如用户偏好设置、应用状态等。 适合存储少量用于服务器验证的数据,例如会话ID、跟踪信息等。
API易用性 提供了简单直接的API,易于上手。 API相对较老,不如现代Web API直观,且有较多细节需要注意。

魔法二:任务管理三连击

ini 复制代码
// 添加任务:生成专属ID
const addTodo = (text) => {
  setTodos([
    ...todos,
    { id: Date.now(), text, isComplete: false }
  ])
}

// 切换状态:精准定位
const onToggle = (id) => {
  setTodos(todos.map(todo => 
    todo.id === id 
      ? { ...todo, isComplete: !todo.isComplete } 
      : todo
  ))
}

// 删除任务:过滤大法
const onDelete = (id) => {
  setTodos(todos.filter(todo => todo.id !== id))
}

这三个操作展示了React不变性原则的精髓------永远不用直接修改状态,而是创建新副本。就像复印文件时修改复印件,原件永远安全!


四、关键技巧:React开发的武功秘籍

  1. 状态提升的艺术

    scss 复制代码
    // 在Todos组件统一管理状态
    const { todos, addTodo, onToggle, onDelete } = useTodos()

    把状态放在共同祖先组件,就像把家庭相册放在客厅而不是某个卧室,全家人都能访问。

  2. 闭包传参妙招

    ini 复制代码
    // 在TodoList中
    onToggle={() => onToggle(todo.id)}
    
    // 代替
    onToggle={onToggle} // 这样会丢失id信息!

    使用箭头函数包裹调用,就像给消息加上收件人地址,确保准确送达。

  3. 条件渲染的优雅表达

    javascript 复制代码
    {Todos.length > 0 
      ? Todos.map(...) 
      : <p>暂无代办事项</p>
    }

    比if/else更优雅,像诗人写代码:"若有任务,则列之;若无,则示空"。

  4. 样式切换的妙用

    css 复制代码
    <span className={isComplete ? 'completed' : ''}>
      {text}
    </span>

    完成任务时添加completed类名,就像给任务戴上小红花,视觉反馈让用户成就感满满。

五、踩坑警示:那些年我跳过的坑

  1. 初始状态陷阱

    scss 复制代码
    // 错误示范
    const [todos, setTodos] = useState([])
    
    // 正确姿势
    const [todos, setTodos] = useState(
      JSON.parse(localStorage.getItem('todos') || []
    )

    如果不考虑localStorage初始为空的情况,程序会崩溃,就像开保险箱忘记初始密码。

  2. ID冲突危机

    css 复制代码
    { id: Date.now(), ... }

    用时间戳做ID在99.99%情况下安全,但如果用户手速超光速(1毫秒内添加多个任务),可能产生冲突。生产环境建议使用uuid库。

  3. 直接修改状态的禁忌

    ini 复制代码
    // 大忌!
    const onToggle = (id) => {
      todos.find(todo => todo.id === id).isComplete = true
      setTodos(todos)
    }

    React状态必须视为不可变数据,直接修改就像在博物馆名画上涂改------后果很严重!相应的数据只能通过hook函数来修改


六、总结:React开发的智慧

  1. 组件设计哲学:单一职责原则,每个组件只做一件事
  2. 数据流动规范:单向数据流,避免数据混乱
  3. 状态管理智慧:合理提升状态,避免"道具钻探"
  4. 副作用处理:useEffect处理副作用,保持纯净渲染
  5. 持久化策略:善用localStorage,提升用户体验

这个待办事项应用虽小,却涵盖了React开发的精髓。就像乐高积木,简单组件组合出强大功能。最后分享我的编码心得:"好的React组件就像好笑话------不需要解释就能工作!"

现在每当我看到localStorage默默保存数据,就想起那句名言:"在数字世界里,唯有localStorage的爱是永恒的..."(刷新页面也不会消失!)

相关推荐
用户214118326360210 分钟前
首发!即梦 4.0 接口开发全攻略:AI 辅助零代码实现,开源 + Docker 部署,小白也能上手
前端
gnip2 小时前
链式调用和延迟执行
前端·javascript
SoaringHeart2 小时前
Flutter组件封装:页面点击事件拦截
前端·flutter
杨天天.2 小时前
小程序原生实现音频播放器,下一首上一首切换,拖动进度条等功能
前端·javascript·小程序·音视频
Dragon Wu2 小时前
React state在setInterval里未获取最新值的问题
前端·javascript·react.js·前端框架
Jinuss2 小时前
Vue3源码reactivity响应式篇之watch实现
前端·vue3
YU大宗师2 小时前
React面试题
前端·javascript·react.js
木兮xg2 小时前
react基础篇
前端·react.js·前端框架
ssshooter3 小时前
你知道怎么用 pnpm 临时给某个库打补丁吗?
前端·面试·npm
IT利刃出鞘3 小时前
HTML--最简的二级菜单页面
前端·html