React自定义 Hooks 不用死磕,轻松拿捏也能站上技术金字塔尖!

引言

最近小编忙于学习React 项目实战,其中React 智能学单词板块的文章可能来不及写,智能实战我将会在抽空在周末休息的时候,花费一天的时间将这两篇文章写出来。好了最近也是沉迷于学习React 学习历程当中,现在小编最近学到了React 自定义Hooks ,这块,今天就让我们基于TodoList项目的实际案例深入探讨一下自定义Hooks

项目结构和技术栈

项目结构分析

TodoList 作为React 生态中经久不衰的入门标杆,其经典组件化架构堪称理解React设计哲学的活教材。从语义层面拆解,整个应用如同一个精密协作的 "任务管理系统",每个组件都承担着边界清晰的职责:

js 复制代码
src/
├── App.jsx          # 根组件
├── main.jsx         # 应用入口
└── components/
    └── Todos/
        ├── index.jsx    # 主要状态管理
        ├── TodoForm.jsx # 表单组件
        ├── TodoList.jsx # 列表组件
        └── TodoItem.jsx # 单项组件

技术栈

主要在css这边使用新的技术栈: Stylus 是一款富有表现力的 CSS 预处理器,它旨在简化样式编写流程,同时赋予开发者更灵活的样式组织能力。那么如何在React当中引入Stylus,在main.js当中直接引入。

js 复制代码
import './global.styl'

二、任务单元就是组件

2.1组件Todos

在App组件当中引入Todos,所要开发的任务组件之后就在Todos文件下开发,创建componments组件文件夹,将以后所需要写的组件均放入该文件夹当中。根据项目功能划分为TodoForm.jsx表单组件,TodoList.jsx列表组件,再添加一个TodoItem.jsx单项组件。

Todos是整体的功能组件,需要将按照其功能细化,form表单组件,list列表组件,一般会在list列表组件下划分一个Item组件,使其更具有维护和性能。

在创建好各个具体组件页面之后将创建好的组件引入到父组件当中

js 复制代码
import TodoForm from "./TodoForm"
import TodoList from "./TodoList"

const Todos = () =>{
  return(
    <>
    <TodoForm />
    <TodoList />
    </>
  )
}

export default Todos

到这里界面的初步构建和功能组件的划分就基本完成了,现在需要引入Hooks函数useState来解决函数的响应式状态。其中父组件持有管理数据,再通过props传递数据,子组件通过props函数来通知父组件(报告老板)。之所以子组件要调用父组件的函数来通知状态发生改变,就是要维护子组件是不能修改状态的,只能通过父组件来修改。

js 复制代码
import {
  useState,
} from 'react'

import TodoForm from "./TodoForm"
import TodoList from "./TodoList"

const Todos = () =>{
    // 数据流管理
    // 父组件持有管理数据 props 传递数据 子组件通过props 自定义函数
    // 通知父组件
    const [todos,setTodos] = useState([
        {
          id: 1,
          text: 'AI自习室',
          isCompleted: false,
        },
        {
          id: 2,
          text: 'AI雀魂',
          isCompleted: false
        }
    ])
    return(
      <div>
          Todos
          <TodoForm />
          <TodoList />
      </div>
    )
}

export default Todos

isCompleted 是TodoItem的布尔状态属性,记录任务完成状态,驱动视图中复选框状态和文本样式变化,是连接数据与 UI 的关键标识。

现在需要在表单当中传递函数onAddTodo,porps当中包含自定义事件和数据传递。

js 复制代码
import {
  useState,
} from 'react'

import TodoForm from "./TodoForm"
import TodoList from "./TodoList"

const Todos = () =>{
    // 数据流管理
    // 父组件持有管理数据 props 传递数据 子组件通过props 自定义函数
    // 通知父组件
    const [todos,setTodos] = useState([
        {
          id: 1,
          text: 'AI自习室',
          isCompleted: false,
        },
        {
          id: 2,
          text: 'AI雀魂',
          isCompleted: false
        }
    ])
    // 新增todo
    const addTodo = () =>{
        // setTodo
    }

    return(
      <div>
          Todos
          {/* {自定义事件} */}
          <TodoForm onAddTodo={addTodo} />
          <TodoList todos={todos} />
      </div>
    )
}

export default Todos

让我们看到当前React开发组件的效果图:

当我们写完TodoForm当中的表单提交函数书写之后,更新 todos 状态。它创建了一个新数组,先将当前 todos 数组里的所有元素展开到新数组中,后续代码通常会添加新的待办事项。这种做法遵循了 React 中状态不可变的原则,即不直接修改原状态,而是创建新状态来更新,这样 React 才能检测到状态变化并重新渲染组件。

js 复制代码
        setTodos([
          ...todos,
          {
            id: Date.now(),
            text,
            isCompleted:false
          }

- 状态管理 :todos状态一般是在 Todos 组件中通过useState Hook创建和管理的。setTodos是更新该状 态的函数,因此操作todos状态的onToggleonDelete函数也定义在同一组件中,方便访问和修改状态。

- 组件职责:Todos 组件作为管理待办事项列表的父组件,负责处理待办事项的添加、切换状态和删除等核心逻辑。将这些操作函数定义在该组件中,符合单一职责原则,让组件的功能更加内聚。

- 属性传递:Todos组件可以将onToggle和onDelete函数通过属性传递给子组件(如TodoItem),子组件在触发相应事件(如点击完成按钮、删除按钮)时调用这些函数,实现与父组件的状态同步。

来看看index.jsx最终的代码吧❗❗❗

js 复制代码
import {
  useState,
} from 'react'

import TodoForm from "./TodoForm"
import TodoList from "./TodoList"

const Todos = () =>{
    // 数据流管理
    // 父组件持有管理数据 props 传递数据 子组件通过props 自定义函数
    // 通知父组件
    const [todos,setTodos] = useState([
        {
          id: 1,
          text: 'AI自习室',
          isCompleted: false,
        },
        {
          id: 2,
          text: 'AI雀魂',
          isCompleted: false
        }
    ])
    // 新增todo
    const addTodo = (text) =>{
        // setTodo
        // 数据状态是对象的时候
        setTodos([
          ...todos,
          {
            id: Date.now(),
            text,
            isCompleted:false
          }
        ])
    }

    const onToggle = (id) =>{
        // todos 数组找到id 为id, isComplete !isComplete
        // 响应式? 返回一个全新的todos
        setTodos(todos.map(
          todo => todo.id === id ? {...todo, 
            isCompleted: !todo.isCompleted} : todo
        ))
    }

    const onDelete =(id)=>{
      setTodos(todos.filter(todo => todo.id !== id))
    }

    return(
      <div className='app'>
          {/* {自定义事件} */}
          <TodoForm onAddTodo={addTodo} />
          <TodoList  
              todos={todos} 
              onToggle={onToggle}
              onDelete={onDelete}
          />
      </div>
    )
}

export default Todos

2.2组件TodoForm

在表单当中设计样式,其中在JSX当中一定得有唯一的最外层元素数来解析JSX

js 复制代码
import{
  useState  // 私有状态
} from 'react'
const TodoForm = () =>{
  // JSX 一定得有唯一的最外层元素 树来编译解析JSX,
  return(
    <>
      <h1 className='header'>TodoList</h1>
      <form>

      </form>
    </>
  )
}

export default TodoForm

在TodoForm当中如何优化解构从父组件当中拿过来的数据,让我们来看看下面的代码:

js 复制代码
const TodoForm = (props) =>{
  const {onAddTodo} = props
// 由于传递就只有一个函数可以简约书写
const TodoForm = ({ onAddTodo }) =>{
  const {text,setText} = useState('')

在下面代码当中为何text当中要有私有状态数据呢,首先在子组件当中私有状态不多。但在表单中,表单数据绝对是自己拥有的,不需要受到外部控制。在书写代码当中不要重复书写重复的代码,其中trim()是一个常用的字符串处理方法,其核心作用是去除字符串首尾的空白字符。来看看其最终代码所示:

js 复制代码
import{
  useState  // 私有状态
} from 'react'
const TodoForm = ({onAddTodo}) =>{
  // 数据
  // props 参数数据
  // state 私有的数据
  // 单向数据流
  const [text, setText] = useState('')
  const handleSubmit = (e) =>{
    e.preventDefault()
    const result = text.trim()
    if(!result) return
    onAddTodo(result)
    setText('')  // 数据状态和界面状态一致要敏感
  }
  // JSX 一定得有唯一的最外层元素 树来编译解析JSX,



  return(
    <>
      <h1 className='header'>TodoList</h1>
      <form className='todo-input' onSubmit={handleSubmit}>
          <input
               type="text"
               value={text}
               onChange={e=> setText(e.target.value)} // 数据绑定
               placeholder='请输入'
               required
          />
      </form>
    </>
  )
}

export default TodoForm

2.3组件TodoList

同理可得在TodoList当中同样引入TodoItem使其子组件引入到其父组件当中。

js 复制代码
import TodoItem from "./TodoItem"
const TodoList = ()=>{
  return(
    <>
    TodoList
    <TodoItem />
    </>
  )
}

export default TodoList

在TodoList中通过todos.length > 0判断待办事项数组是否为空:

若不为空,使用map方法遍历todos数组,为每个待办事项创建一个TodoItem组件实例。key={todo.id}为每个组件提供唯一标识,帮助React 高效更新列表。todo={todo}将当前待办事项对象传递给TodoItem组件。

js 复制代码
import TodoItem from "./TodoItem"
const TodoList = (props)=>{
  const{
      todos
  } = props
  return(
    <>
        {/* TodoList */}
        {
          todos.length > 0 ? (
              todos.map((todo)=>
                <TodoItem key={todo.id} todo={todo} />
              )
          ):(
            <p>暂无待办事项</p>
          )
        }
    </>
  )
}

export default TodoList

使用箭头函数() => onToggle(todo.id)()=>onDelete(todo.id)是为了延迟函数的执行。若直接写成 onToggle(todo.id)onDelete(todo.id),函数会在组件渲染时立即执行,而不是在用户触发相应事件(如点击按钮)时执行。通过箭头函数包装,只有当用户触发事件时,才会调用对应的函数并传入正确的todo.id

js 复制代码
import TodoItem from "./TodoItem"
const TodoList = (props)=>{
  const{
      todos,
      onToggle,
      onDelete
  } = props 
  return(
    <div className="todo-list">
        {/* TodoList */}
        {
          todos.length > 0 ? (
              todos.map((todo)=>
                <TodoItem 
                  key={todo.id} 
                  todo={todo} 
                  onToggle={()=>onToggle(todo.id)}  
                  onDelete={()=>onDelete(todo.id)}
                />
              )
          ):(
            <p>暂无待办事项</p>
          )
        }
        
    </div>
  )
}

export default TodoList

2.3.1 组件TodoItem

TodoItem 组件的主要作用是接收单个待办事项的数据(id 、text 、isComplete),并将待办事项的内容(text )渲染出来。此组件通常会在 TodoList 组件里被循环调用,用于渲染待办事项列表中的每一项。

js 复制代码
const TodoItem =(props)=>{
  const{
    id,
    text,
    isComplete
  } = props.todo
  return(
    <>
    {text}
    </>
  )
}

export default TodoItem

在TodoList上拆分一个TodoItem在性能上可以避免全列表重渲染:当单个任务状态(如isCompleted)变化时,仅对应 TodoItem 会重新渲染,而非整个 TodoList。配合React.memo可进一步拦截无意义的重渲染,尤其在任务数量较多时效果显著。

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

export default TodoItem

效果展示

可以看到实现的效果还是非常的Beautiful的,视觉效果也是非常OK的!!! 在下一篇文章当中将会继续续写React Hooks 的相关的localStorage

相关推荐
弗锐土豆3 分钟前
一个基于若依(ruoyi-vue3)的小项目部署记录
前端·vue.js·部署·springcloud·ruoyi·若依
Hilaku6 分钟前
我为什么放弃了“大厂梦”,去了一家“小公司”?
前端·javascript·面试
浅墨momo11 分钟前
搭建第一个Shopify App
前端·程序员
Codebee16 分钟前
OneCode 组件服务通用协议栈:构建企业级低代码平台的技术基石
前端·前端框架·开源
Running_C16 分钟前
常见web攻击类型
前端·http
jackyChan16 分钟前
ES6 Proxy 性能问题,你真知道吗?🚨
前端·javascript
lichenyang45316 分钟前
快速搭建服务器,fetch请求从服务器获取数据
前端
豆苗学前端21 分钟前
从零开始教你如何使用 Vue 3 + TypeScript 实现一个现代化的液态玻璃效果(Glass Morphism)登录卡片
前端·vue.js·面试
光影少年22 分钟前
react16-react19都更新哪些内容?
前端·react.js