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的爱是永恒的..."(刷新页面也不会消失!)

相关推荐
用户9525115140155几秒前
最常用的JS加解密场景MD5
前端
Hilaku1 分钟前
“虚拟DOM”到底是什么?我们用300行代码来实现一个
前端·javascript·vue.js
打好高远球7 分钟前
mo契官网建设与SEO实践
前端
神仙别闹13 分钟前
基于Java+MySQL实现(Web)可扩展的程序在线评测系统
java·前端·mysql
心.c27 分钟前
react当中的this指向
前端·javascript·react.js
Java水解34 分钟前
Web API基础
前端
闲鱼不闲35 分钟前
实现iframe重定向通知父级页面跳转
前端
咸鱼青菜好好味36 分钟前
node的项目实战相关-2-前台接口
前端
春秋半夏37 分钟前
音乐播放、歌词滚动
前端·css
Sioncovy41 分钟前
Zustand 源码阅读计划(3)- JS 篇 - Middlewares 中间件逻辑
前端·javascript