1. 手写一个简单的计数器组件
要求:实现一个按钮,每次点击时数字加 1。
import React, { useState } from 'react';
function Counter({ initialCount }) {
const [count, setCount] = useState(initialCount);
const handleIncrement = () => {
// 使用 prevCount 确保获取到最新的 count 值
setCount(prevCount => prevCount + 1);
};
return (
<div>
<p>当前计数: {count}</p>
<button onClick={handleIncrement}>增加</button>
</div>
);
}
export default Counter;
2. 实现一个受控输入框组件
要求:用户输入内容后显示到页面上。
import React, { useState } from 'react';
function ControlledInput() {
// 初始化 state,用于保存输入框的值
const [inputValue, setInputValue] = useState('');
// 处理输入框变化的函数
const handleChange = (event) => {
// 更新 state 的值
setInputValue(event.target.value);
};
return (
<div>
<input
type="text"
value={inputValue} // 绑定 state 到输入框的 value 属性
onChange={handleChange} // 输入框内容变化时触发 handleChange
/>
<p>你输入的内容是: {inputValue}</p> {/* 显示输入框内容 */}
</div>
);
}
export default ControlledInput;
延伸问题:
- 如何添加输入验证功能?
- 如果想在用户停止输入 500ms 后才更新内容,应该如何实现?
import React, { useState, useEffect } from 'react';
function ControlledInput() {
// State for input value
const [inputValue, setInputValue] = useState('');
// State for error message
const [error, setError] = useState('');
// Timer for debouncing
const [debounceTimer, setDebounceTimer] = useState(null);
// Input validation function
const validateInput = (value) => {
// For demonstration: Only allow alphanumeric characters
const regex = /^[a-zA-Z0-9]*$/;
if (!regex.test(value)) {
return '输入只能包含字母和数字';
}
return '';
};
// Handle input change with debounce logic
const handleChange = (event) => {
const value = event.target.value;
// Clear the previous timer if any
if (debounceTimer) {
clearTimeout(debounceTimer);
}
// Set the error message based on validation
const validationError = validateInput(value);
setError(validationError);
// Set a new timer to update the state after 500ms
const timer = setTimeout(() => {
setInputValue(value);
}, 500);
setDebounceTimer(timer);
};
return (
<div>
<input
type="text"
value={inputValue}
onChange={handleChange}
placeholder="请输入字母和数字"
/>
{error && <p style={{ color: 'red' }}>{error}</p>}
<p>你输入的内容是: {inputValue}</p>
</div>
);
}
export default ControlledInput;
3. 手写一个模态框组件
要求:实现一个模态框,可以通过父组件控制其显示与隐藏。
- 模态框组件 (
Modal)
import React from 'react';
function Modal({ isVisible, onClose, children }) {
if (!isVisible) return null; // 如果不可见,不渲染
return (
<div style={overlayStyle}>
<div style={modalStyle}>
<button onClick={onClose} style={closeButtonStyle}>关闭</button>
{children} {/* 模态框内容可以通过 `children` 插槽传递 */}
</div>
</div>
);
}
// 样式:简单的模态框和遮罩层
const overlayStyle = {
position: 'fixed',
top: 0,
left: 0,
right: 0,
bottom: 0,
backgroundColor: 'rgba(0, 0, 0, 0.5)',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
};
const modalStyle = {
backgroundColor: 'white',
padding: '20px',
borderRadius: '5px',
maxWidth: '400px',
minWidth: '300px',
boxShadow: '0 5px 15px rgba(0, 0, 0, 0.2)',
};
const closeButtonStyle = {
position: 'absolute',
top: '10px',
right: '10px',
background: 'none',
border: 'none',
fontSize: '16px',
cursor: 'pointer',
};
export default Modal;
- 父组件 (
App)
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;
延伸问题:
- 如何优化性能,避免频繁渲染?
- 如何让模态框支持动画效果?
4. 手写一个 Tab 组件
要求:实现一个简单的标签页切换功能。
function Tabs({ tabs }) {
const [activeTab, setActiveTab] = React.useState(0);
return (
<div>
<div style={{ display: "flex", borderBottom: "1px solid #ccc" }}>
{tabs.map((tab, index) => (
<button
key={index}
style={{ marginRight: "10px", padding: "10px", borderBottom: activeTab === index ? "2px solid blue" : "none" }}
onClick={() => setActiveTab(index)}
>
{tab.label}
</button>
))}
</div>
<div style={{ padding: "20px" }}>{tabs[activeTab].content}</div>
</div>
);
}
使用示例:
const tabData = [
{ label: "Tab 1", content: "这是 Tab 1 的内容" },
{ label: "Tab 2", content: "这是 Tab 2 的内容" },
{ label: "Tab 3", content: "这是 Tab 3 的内容" },
];
<Tabs tabs={tabData} />;
延伸问题:
- 如何支持动态添加和删除标签页?
- 如何在切换标签页时添加动画?
5. 实现一个自定义 Hook
要求:实现一个自定义 Hook,用于倒计时功能。
import { useState, useEffect, useRef } from 'react';
function useCountdown(initialTime = 60) {
// 状态管理剩余时间
const [timeLeft, setTimeLeft] = useState(initialTime);
const [isRunning, setIsRunning] = useState(false); // 是否在倒计时中
const timerRef = useRef(null); // 用于存储定时器引用
// 启动倒计时
const start = () => {
if (timeLeft > 0 && !isRunning) {
setIsRunning(true);
}
};
// 暂停倒计时
const pause = () => {
setIsRunning(false);
};
// 重置倒计时
const reset = (newTime) => {
pause();
setTimeLeft(newTime !== undefined ? newTime : initialTime);
};
// 倒计时逻辑
useEffect(() => {
if (isRunning) {
timerRef.current = setInterval(() => {
setTimeLeft((prevTime) => {
if (prevTime <= 1) {
clearInterval(timerRef.current);
setIsRunning(false); // 停止倒计时
return 0;
}
return prevTime - 1;
});
}, 1000);
}
// 清理定时器
return () => clearInterval(timerRef.current);
}, [isRunning]);
return {
timeLeft,
isRunning,
start,
pause,
reset,
};
}
export default useCountdown;
延伸问题:
- 如何让倒计时支持暂停和恢复?
- 如何让倒计时结束后触发回调?
6. 实现一个高阶组件 (HOC)
要求:实现一个高阶组件,给组件添加计时功能。
function withTimer(WrappedComponent) {
return function TimerComponent(props) {
const [time, setTime] = React.useState(0);
React.useEffect(() => {
const interval = setInterval(() => setTime((prev) => prev + 1), 1000);
return () => clearInterval(interval);
}, []);
return <WrappedComponent {...props} time={time} />;
};
}
function DisplayTimer({ time }) {
return <p>已计时: {time} 秒</p>;
}
const EnhancedTimer = withTimer(DisplayTimer);
延伸问题:
- 如何将计时器状态提升到父组件?
- 与自定义 Hook 的区别?
7. 实现一个异步数据加载组件
要求:加载远程数据并显示,同时显示加载状态。
function DataLoader({ url }) {
const [data, setData] = React.useState(null);
const [loading, setLoading] = React.useState(true);
React.useEffect(() => {
setLoading(true);
fetch(url)
.then((res) => res.json())
.then((data) => setData(data))
.catch(console.error)
.finally(() => setLoading(false));
}, [url]);
if (loading) return <p>加载中...</p>;
if (!data) return <p>未加载到数据</p>;
return <pre>{JSON.stringify(data, null, 2)}</pre>;
}
延伸问题:
- 如何处理请求失败?
- 如何添加刷新功能?