当前位置: 首页 > news >正文

React手撕组件和Hooks总结

计数器

const Counter=({initialState)=>{const [count,setCount]=useState(initialState);const handleIncrement=()=>{setCount(preCount=>preCount+1);}return (<div><div>当前计数:{count}</div><button onClick={handleIncrement}</button></div>)
}

setCount(count + 1)setCount(preCount => preCount + 1)

状态更新的直接与函数式差异

setCount(count + 1)直接基于当前count值进行更新。若在短时间内连续调用多次,可能因闭包问题导致更新未按预期累积。例如:

// 连续调用三次,可能因闭包中的count未及时更新而只生效一次
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);

函数式更新的可靠性

setCount(preCount => preCount + 1)通过函数参数接收最新的状态值,确保每次更新基于前一个状态。即使快速连续调用,也能正确累积:

// 连续调用三次,最终count会增加3
setCount(prev => prev + 1);
setCount(prev => prev + 1);
setCount(prev => prev + 1);

异步场景下的表现差异

在异步操作(如setTimeoutuseEffect)中,直接更新可能因闭包捕获旧值而出错。函数式更新始终能获取最新状态:

useEffect(() => {setTimeout(() => {setCount(count + 1); // 可能使用过时的countsetCount(prev => prev + 1); // 始终正确}, 1000);
}, []);

批量更新的处理

React的批量更新机制下,直接更新多次会被合并,函数式更新则按队列顺序执行:

// 直接更新:合并为一次+1
setCount(count + 1);
setCount(count + 1);// 函数式更新:按顺序执行两次+1
setCount(prev => prev + 1);
setCount(prev => prev + 1);

依赖项的注意事项

当状态更新依赖前一个状态时,函数式更新可避免手动添加依赖项到useEffectuseCallback的依赖数组,减少不必要的重渲染风险。

总结场景选择

  • 直接更新:适用于不依赖前状态的简单赋值或已知无并发风险的场景。
  • 函数式更新:必须用于依赖前状态的逻辑、异步环境或可能存在批量更新的场景

底层知识点useState

异步,React批量更新
在 setTimeout、原生事件 或 异步操作 中,setState 会立即触发更新:但在 React 18 中,所有更新(包括 Promise、setTimeout 等)默认启用自动批量更新,需使用 flushSync 强制同步:

在这里插入图片描述

useState简单手撕实现

React 内部通过 ​​链表结构​​ 管理 Hooks 状态,依赖调用顺序追踪状态。以下是简化版实现(闭包):

let stateQueue=[];
let setter=[];
let index=0;function createSetter(index){return (newValue)=>{stateQueue[index]=typeof newValue==="function"?newValue(stateQueue[index]):newValue;//重新渲染index=0;render();}
}function render(){ReactDom.render(<App/>,document.getElementById("root"))function useState(initialState){if(stateQueue[index]===undefined){stateQueue[index]=initialState;setter[index]=createSetter(index);}const currentState=stateQueue[index];const currentSetter=setter[index];index++;//下一个Hookreturn [currentState,currentSetter]
}

受控输入框组件

  • 如何添加输入验证功能?->验证函数,返回错误条件 err
  • 如果想在用户停止输入 500ms 后才更新内容,应该如何实现?->防抖 debounceTimer
const ControllerInput=()=>{const [inputValue,setInputValue]=useState("");const [debounceTimer,setDebounceTimer]=useState(null);const [err,setErr]=useState("");const validateInput=()=>{const regExp=/^[a-zA-Z0-9]*$/;if(!regExp.test(value))return "输入数据只能为数字和字母“return "";}const handleChange=(event)=>{const value=event.target.value;if(debounceTimer)clearTimeout(debounceTimer);//验证数据const validationErr=validateInput(value);setErr(validationErr);const timer=setTimeout(()=>{setInputValue();},500);setDebounceTimer(timer);}return (<div><input type="text"value={inputValue}onChange={handleChange}/></div>)
}
export default ControllerInput;

模态框组件

在这里插入图片描述

const Modal={visible,onClose,children}=>{useEffect(()=>{if(!visible)return;//Esc关闭&滚动锁定const handleEsc=(e)=>e.key==="Escape"&&onClose?.();document.body.style.overflow="hidden";window.addEventListener('keydown',handleEsc);return ()=>{window.removeEventListenr(ketdown',handleEsc);document.body.style.overflow="";}},[visible,onClose]);//Portal挂载到bodyreturn visible?ReactDOM.createPortal(<divstyle={{position:"fixed",top:0,left:0,right:0,bottom:0,display:"flex",justifyContent:"center",alignItems:"center"}}><div className="modal-content"style={{backgroundColor:"white">{children}	</div></div>,documnet.body):null;}//父组件
import React, { useState } from 'react';
import Modal from './Modal';function App() {const [isModalVisible, setIsModalVisible] = useState(false);// 控制模态框显示const showModal = () => {setIsModalVisible(true);};// 关闭模态框const hideModal = () => {setIsModalVisible(false);};return (<div><button onClick={showModal}>显示模态框</button>{/* 渲染模态框,传入 isVisible 和 onClose props */}<Modal isVisible={isModalVisible} onClose={hideModal}><h2>欢迎来到模态框</h2><p>这是模态框的内容!</p></Modal></div>);
}export default App;
  • 1.​​Portal挂载​​解决层级问题

  • 2.​​双动画CSS​​实现流畅体验

  • 3.​​滚动锁定​​需记录原始值

  • 4.​​事件隔离​​精准控制关闭条件
    在这里插入图片描述

  <Teleport to="body"><Transition name="modal"><!-- 遮罩层 --><div v-if="visible" class="modal-mask" @click.self="closeOnOverlayClick && $emit('close')"><!-- 模态框内容 --><div class="modal-container"><!-- 头部插槽 --><div class="modal-header"><slot name="header"><h2>默认标题</h2></slot></div><!-- 内容插槽 --><div class="modal-body"><slot>默认内容</slot></div><!-- 底部插槽(含关闭按钮) --><div class="modal-footer"><slot name="footer"><button @click="$emit('close')">关闭</button></slot></div></div></div></Transition></Teleport>
</template><script setup>
defineProps({visible: Boolean, // 控制显示closeOnOverlayClick: { type: Boolean, default: true } // 遮罩点击关闭开关
});
defineEmits(['close']); // 关闭事件// ESC 关闭监听
import { onMounted, onUnmounted } from 'vue';
const handleEsc = (e) => e.key === 'Escape' && props.visible && emit('close');
onMounted(() => window.addEventListener('keydown', handleEsc));
onUnmounted(() => window.removeEventListener('keydown', handleEsc));
</script><style scoped>
.modal-mask {position: fixed;top: 0; left: 0; right: 0; bottom: 0;background: rgba(0,0,0,0.5);display: flex;align-items: center;justify-content: center;
}
.modal-container {background: white;padding: 20px;border-radius: 8px;width: 80%;max-width: 500px;
}/* 动画效果 */
.modal-enter-from, .modal-leave-to { opacity: 0; }
.modal-enter-from .modal-container, .modal-leave-to .modal-container {transform: scale(0.9);
}
.modal-enter-active, .modal-leave-active {transition: opacity 0.3s, transform 0.3s;
}
</style>

在这里插入图片描述
在这里插入图片描述

手写一个Tab组件

const Tabs=({tabs})=>{const [activeTab,setActiveTab]=React.useState("tab1");const [tabs,setTabs]=useState([{label:"Tab1",content:"Tab1内容”,id:"tab1" }{label:"Tab2",content:"Tab2内容” ,id:"tab2" }]);const addTab=()=>{const newId=`tab${tabs.length+1}`;setTabs([...tabs,{label:`Tab${tabs.length+1}`,content:`Tab${tabs.length+1}内容`,id:`tab${tabs.length+1}`])}const removeTab=(index)=>{if(tabs.length<=1)return;const newTabs=tabs.filter((item)=>item.id!==index));const setTabs(newTabs);//更新activeTabif(activeTab>=index&&activeTab!==0){setActiveTab(activeTab-1)}}return (<div style={{display:"flex",borderBottom:"1px solod #ccc"}}>{tabs.map((tab)=>(<buttonkey={tab.id}style={{borderBottom:activeTab===index?"2px solid blue":"none"}}onClick={()=>setActiveTab(index)}{tab.label}</button>	//+<button onClick={addTab}>+</button><buttonstyle={{ position: "absolute", top: 0, right: 0, fontSize: 10 }}onClick={() => removeTab(tab.id)}>×</button>))}<button onClick={addTab}>+</button></div><div style={{padding:20px}}>{tabs[activeTab].content}</div>)
}<Tabs tabs={tabData}/>

在中间加元素

const addTab = (insertAfterIndex) => {const newId = `tab${tabs.length + 1}`;const newTabs = [...tabs];newTabs.splice(insertAfterIndex + 1, 0, {label: `Tab${tabs.length + 1}`,content: `Tab${tabs.length + 1}内容`,id: newId});setTabs(newTabs);setActiveTab(insertAfterIndex + 1);};

TODO List

在这里插入图片描述

const TodoList=()=>{const [todos,setTodos]=useState([{id:Date.now(),content:"to do list content1",complete:false},{id:Date.now(),content:"to do list content2",complete:false}]);const [inputVal,setInputVal]=useState("");//数据持久化useEffct(()=>{const saved=localstorage.getItem('todos');saved&&setTodos(JSON.parse(saved))},[])useEffect(()=>{localstorage.setItem('todos',JSON.stringify(todos))},[lists]);//增加const addTodos=()=>{if(!inputVal.trim())return;setTodos([...todos,{id:Date.now(),content:inputVal,completed:false}])setInputVal('');}//完成const toggleComplete=(id)=>{setTodos(todos.map(item=>item.id===id?{...todo,completed:false}:todo))}//删除任务const deleteTodo=(id)=>{seteTodos(todos.filter(todo=>todo.id!==id))}return (<div><ul>todos.map(todo=>(<li key={todo.id}><input type="checkbox" check={todo.completed}onChange={()=>toggleComplete(todo.id)}/><span style={{textDecoration:todo.complete?'line-through:"none">{todo.text}</span></li><button onClick={()=>deleteTodo(todo.id)}>删除</button>))</ul><input  value={inputVal} onChange={(event)=>setInputVal(event.target.value)}  /><button onClick={addTodos}>新增</button></div>)
}

倒计时

http://www.lryc.cn/news/622795.html

相关文章:

  • 自动化测试的下一站:AI缺陷检测工具如何实现“bug提前预警”?
  • illustrator插件大全 免费插件介绍 Ai设计插件集合 (3)
  • 知识点汇总linuxC高级 -2系统命令压缩与链接
  • 机器学习相关算法:回溯算法 贪心算法 回归算法(线性回归) 算法超参数 多项式时间 朴素贝叶斯分类算法
  • 022 基础 IO —— 文件
  • [系统架构设计师]系统质量属性与架构评估(八)
  • 【完整源码+数据集+部署教程】太阳能面板污垢检测系统源码和数据集:改进yolo11-RVB-EMA
  • Golang Seata 分布式事务方案详解
  • 正点原子【第四期】Linux之驱动开发篇学习笔记-1.1 Linux驱动开发与裸机开发的区别
  • MySQL 从入门到精通 9:视图
  • 【lucene】SegmentInfos
  • 并查集理论基础, 107. 寻找存在的路径
  • 零改造迁移实录:2000+存储过程从SQL Server滑入KingbaseES V9R4C12的72小时
  • 生产环境Redis缓存穿透与雪崩防护性能优化实战指南
  • CSV 生成 Gantt 甘特图
  • 解锁JavaScript性能优化:从理论到实战
  • 【数据分享】上市公司供应链成本分摊数据(2007-2024)
  • Cursor执行命令卡顿解决办法(Cursor卡住、Cursor命令卡住、Cursor执行慢、Cursor执行命令慢)改成以管理员身份运行就好!!!
  • redis存储原理与对象模型
  • 数据结构初阶(16)排序算法——归并排序
  • FFmpeg QoS 处理
  • 《WINDOWS 环境下32位汇编语言程序设计》第2章 准备编程环境
  • 汽车行业供应链EDI标准体系解析:构建高效协同的数字桥梁
  • Blackwell 和 Hopper 架构的 GPGPU 新功能全面综述
  • 要导入StandardScaler类进行数据标准化,请使用以下语句:
  • 【计算机视觉与深度学习实战】03基于Canny、Sobel和Laplacian算子的边缘检测系统设计与实现
  • 常见的交叉编译工具链
  • 第四章:大模型(LLM)】06.langchain原理-(5)LangChain Prompt 用法
  • 【Vibe Coding 工程之 StockAnalyzerPro 记录】- EP3.Phase 2股票列表管理功能
  • Camx-Tuning参数加载流程分析