前言
我是从React 15开始学的,那时候全是Class组件。写一个简单的计数器要这样:
jsx
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ count: this.state.count + 1 });
}
componentDidMount() {
console.log('组件挂载了');
}
componentDidUpdate() {
console.log('组件更新了');
}
componentWillUnmount() {
console.log('组件要卸载了');
}
render() {
return (
<button onClick={this.handleClick}>
点击次数: {this.state.count}
</button>
);
}
}
第一次写的时候我都懵了——怎么这么多重复代码?this是什么东西?为什么点击事件要.bind(this)?生命周期方法怎么这么乱?
后来React 16.8出了Hooks,一切都变了。同样的计数器,用Hooks写:
jsx
function Counter() {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
点击次数: {count}
</button>
);
}
就这几行,代码少了三分之二,逻辑清晰多了。这就是Hooks的魅力——让React组件变得更简单、更容易理解和维护。
为什么需要Hooks?
Class组件的痛点
在说Hooks之前,先聊聊为什么Facebook要发明Hooks:
1. 逻辑复用困难
Class组件复用逻辑只有两种方式:HOC(高阶组件)和Render Props。这两种方式都会让代码嵌套很深,调试困难。
jsx
// HOC示例
const withUser = (Component) => {
return class WithUser extends React.Component {
constructor(props) {
super(props);
this.state = { user: null };
}
componentDidMount() {
fetchUser().then(user => this.setState({ user }));
}
render() {
return <Component {...this.props} user={this.state.user} />;
}
};
};
// 使用高阶组件
const UserPage = withUser(ProfilePage);
// 问题:层层嵌套
const EnhancedComponent = withLogging(withTheme(withUser(OriginalComponent)));
2. 生命周期逻辑混乱
相关逻辑被分散到不同生命周期里,比如数据获取,可能在componentDidMount里发请求,在componentDidUpdate里处理更新,在componentWillUnmount里清理。但相关的逻辑其实应该放在一起。
jsx
class UserProfile extends React.Component {
componentDidMount() {
this.fetchUser();
this.startPolling();
this.setupEventListeners();
this.updateDocumentTitle();
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
this.updateDocumentTitle();
}
}
componentWillUnmount() {
this.stopPolling();
this.removeEventListeners();
}
// 问题:相关的逻辑分散在不同地方,很难维护
}
3. Class的this问题
jsx
class MyComponent extends React.Component {
handleClick() {
// 这里的this是undefined!必须bind或者用箭头函数
this.setState({ clicked: true });
}
render() {
return (
<div>
{/* 方法1:bind */}
<button onClick={this.handleClick.bind(this)}>点击1</button>
{/* 方法2:构造函数里bind */}
<button onClick={this.handleClick}>点击2</button>
{/* 方法3:箭头函数 */}
<button onClick={() => this.handleClick()}>点击3</button>
</div>
);
}
}
Hooks带来的改变
Hooks是React 16.8引入的新特性,它让你在不写Class的情况下使用state和其他React特性:
- 逻辑复用更简单:自定义Hook让逻辑复用变得直观
- 代码更简洁:不用写Class,少打很多字
- 更容易理解:相关的逻辑放在一起
- 告别this:函数组件没有this问题
- 更容易测试:Hook是普通函数,可以单独测试
核心Hooks详解
useState:状态管理
useState是最基础的Hook,用来在函数组件中添加状态。
基础用法:
jsx
import { useState } from 'react';
function Counter() {
// count是状态值,setCount是用来更新状态的函数
const [count, setCount] = useState(0);
return (
<div>
<p>计数: {count}</p>
<button onClick={() => setCount(count + 1)}>增加</button>
<button onClick={() => setCount(count - 1)}>减少</button>
<button onClick={() => setCount(0)}>重置</button>
</div>
);
}
函数式更新
当新的状态依赖于旧状态时,用函数式更新更安全:
jsx
// 普通方式:快速点击时可能出问题
setCount(count + 1);
// 函数式更新(推荐,尤其在快速点击时)
setCount(prevCount => prevCount + 1);
// 更复杂的情况
setCount(prev => {
if (prev >= 10) return 0;
return prev + 1;
});
对象类型状态
jsx
// 对象状态需要整体更新
const [user, setUser] = useState({ name: '', age: 0, email: '' });
// 正确:用展开运算符保留其他属性
setUser(prev => ({ ...prev, name: '小明' }));
setUser(prev => ({ ...prev, age: prev.age + 1 }));
// 错误:会丢失其他属性
setUser({ name: '小明' }); // age和email都没了!
数组类型状态
jsx
const [items, setItems] = useState([]);
// 添加元素
setItems(prev => [...prev, newItem]);
// 删除元素
setItems(prev => prev.filter(item => item.id !== id));
// 更新元素
setItems(prev => prev.map(item =>
item.id === id
? { ...item, ...updates } // 合并更新
: item
));
// 清空数组
setItems([]);
多状态管理
jsx
function UserForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState({});
// ...
}
useEffect:副作用处理
useEffect用来处理副作用,比如数据获取、订阅、手动修改DOM、定时器等。
基础用法:
jsx
import { useState, useEffect } from 'react';
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 这个函数会在组件挂载后执行
console.log('副作用执行了');
// 可选:返回一个清理函数
return () => {
console.log('清理副作用');
};
}, []); // 空依赖数组表示只在挂载时执行
return <div>...</div>;
}
数据获取实战:
jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
// 创建AbortController用于取消请求
const controller = new AbortController();
async function fetchUser() {
setLoading(true);
setError(null);
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal
});
if (!response.ok) {
throw new Error('获取用户信息失败');
}
const data = await response.json();
setUser(data);
} catch (err) {
// 忽略取消请求的错误
if (err.name !== 'AbortError') {
setError(err.message);
}
} finally {
// 只有请求没被取消时才更新loading
if (!controller.signal.aborted) {
setLoading(false);
}
}
}
fetchUser();
// 清理:组件卸载或userId变化时取消请求
return () => controller.abort();
}, [userId]); // userId变化时重新获取
if (loading) return <div>加载中...</div>;
if (error) return <div>出错了: {error}</div>;
if (!user) return <div>用户不存在</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
依赖数组的三种用法:
jsx
useEffect(() => {
// 1. 不传依赖数组:每次渲染都执行(容易造成无限循环)
// 慎用!通常需要配合useRef
});
useEffect(() => {
// 2. 空数组:只在挂载时执行一次(常用于初始化)
// 类似componentDidMount
}, []);
useEffect(() => {
// 3. 指定依赖:依赖变化时执行(最常用)
// 类似componentDidUpdate
}, [dependency1, dependency2]);
清理副作用:
jsx
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
// 清理:组件卸载时清除定时器
return () => {
clearInterval(interval);
};
}, []);
return <div>已过去 {seconds} 秒</div>;
}
function WindowSize() {
const [width, setWidth] = useState(window.innerWidth);
useEffect(() => {
function handleResize() {
setWidth(window.innerWidth);
}
window.addEventListener('resize', handleResize);
// 清理:移除事件监听
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);
return <div>窗口宽度: {width}</div>;
}
useRef:操作DOM和保存可变值
useRef有两个主要用途:操作DOM和保存不会触发渲染的可变值。
操作DOM:
jsx
import { useRef, useEffect } from 'react';
function AutoFocusInput() {
const inputRef = useRef(null);
useEffect(() => {
// 自动聚焦到输入框
inputRef.current.focus();
}, []);
return (
<input
ref={inputRef}
type="text"
placeholder="自动聚焦在这里"
/>
);
}
function ScrollToTop() {
const topRef = useRef(null);
const scrollToTop = () => {
topRef.current?.scrollIntoView({ behavior: 'smooth' });
};
return (
<div>
<div ref={topRef}>页面顶部</div>
<button onClick={scrollToTop}>回到顶部</button>
</div>
);
}
保存上一次的props或state:
jsx
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
// 每次value变化时,更新ref
ref.current = value;
});
// 返回的是上一次的值
return ref.current;
}
function Counter() {
const [count, setCount] = useState(0);
const previousCount = usePrevious(count);
return (
<div>
<p>现在: {count}, 上次: {previousCount}</p>
<button onClick={() => setCount(c => c + 1)}>增加</button>
</div>
);
}
保存定时器ID和其他可变值:
jsx
function Timer() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
const startTimer = () => {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
};
const stopTimer = () => {
if (intervalRef.current) {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
};
return (
<div>
<p>已过去 {seconds} 秒</p>
<button onClick={startTimer}>开始</button>
<button onClick={stopTimer}>停止</button>
</div>
);
}
useMemo和useCallback:性能优化
这两个Hook用来避免不必要的计算和渲染。
useMemo:缓存计算结果:
jsx
import { useMemo } from 'react';
function ExpensiveList({ items, filter, sortBy }) {
// 只有items、filter或sortBy变化时才重新计算
const filteredAndSorted = useMemo(() => {
console.log('开始过滤和排序...');
return items
.filter(item => item.name.includes(filter))
.sort((a, b) => {
if (sortBy === 'name') {
return a.name.localeCompare(b.name);
}
return a[sortBy] - b[sortBy];
});
}, [items, filter, sortBy]);
return (
<ul>
{filteredAndSorted.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
);
}
// 另一个例子:缓存计算结果
function App() {
const [count, setCount] = useState(0);
const [multiplier, setMultiplier] = useState(1);
// expensiveCalculation是耗时计算
const result = useMemo(() => {
return expensiveCalculation(count);
}, [count]);
return (
<div>
<p>{count} * {multiplier} = {result}</p>
<button onClick={() => setCount(c => c + 1)}>增加计数</button>
<button onClick={() => setMultiplier(m => m + 1)}>增加倍数</button>
</div>
);
}
useCallback:缓存函数:
jsx
import { useCallback } from 'react';
function Parent() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 用useCallback缓存函数
const handleClick = useCallback((id) => {
console.log('点击了', id);
setCount(c => c + 1);
}, []); // 空依赖,函数永远不变
const handleSubmit = useCallback((data) => {
console.log('提交数据:', data);
setName(data.name);
}, []); // 空依赖
return (
<div>
<Child onClick={handleClick} />
<Form onSubmit={handleSubmit} />
<p>点击次数: {count}</p>
</div>
);
}
// 子组件使用React.memo避免不必要的重渲染
const Child = React.memo(function Child({ onClick }) {
console.log('Child渲染了');
return <button onClick={() => onClick(1)}>点击</button>;
});
注意:不要过度使用useMemo和useCallback,只有在确实有性能问题时才用。
自定义Hooks:逻辑复用
自定义Hook是React Hooks最强大的特性——它让你可以把组件逻辑提取成可复用的函数。
基础自定义Hook
jsx
// 自定义localStorage Hook
function useLocalStorage(key, initialValue) {
// 从localStorage读取初始值
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
console.error(error);
return initialValue;
}
});
// 更新localStorage
const setValue = (value) => {
try {
// 支持函数更新
const valueToStore = value instanceof Function
? value(storedValue)
: value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.error(error);
}
};
return [storedValue, setValue];
}
// 使用
function App() {
const [name, setName] = useLocalStorage('name', '');
const [theme, setTheme] = useLocalStorage('theme', 'light');
return (
<div>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<select value={theme} onChange={e => setTheme(e.target.value)}>
<option value="light">浅色</option>
<option value="dark">深色</option>
</select>
</div>
);
}
监听窗口大小
jsx
function useWindowSize() {
const [size, setSize] = useState({
width: window.innerWidth,
height: window.innerHeight
});
useEffect(() => {
function handleResize() {
setSize({
width: window.innerWidth,
height: window.innerHeight
});
}
window.addEventListener('resize', handleResize);
handleResize(); // 初始化
return () => window.removeEventListener('resize', handleResize);
}, []);
return size;
}
function ResponsiveComponent() {
const { width, height } = useWindowSize();
return (
<div>
<p>窗口大小: {width} x {height}</p>
{width < 768 && <MobileMenu />}
{width >= 768 && <DesktopMenu />}
</div>
);
}
异步数据请求
jsx
function useAsync(asyncFunction, immediate = true) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(immediate);
const [error, setError] = useState(null);
const execute = useCallback(async (...args) => {
setLoading(true);
setError(null);
try {
const response = await asyncFunction(...args);
setData(response);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
}, [asyncFunction]);
useEffect(() => {
if (immediate) {
execute();
}
}, [execute, immediate]);
return { data, loading, error, execute };
}
// 使用
function UserList() {
const { data: users, loading, error, execute: refetch } = useAsync(
async () => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('获取失败');
return response.json();
}
);
if (loading) return <div>加载中...</div>;
if (error) return <div>错误: {error.message}</div>;
return (
<div>
<button onClick={refetch}>刷新</button>
<ul>
{users?.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
防抖Hook
jsx
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => clearTimeout(handler);
}, [value, delay]);
return debouncedValue;
}
function SearchBox() {
const [searchTerm, setSearchTerm] = useState('');
const [results, setResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);
// 防抖500毫秒
const debouncedSearch = useDebounce(searchTerm, 500);
useEffect(() => {
if (!debouncedSearch) {
setResults([]);
return;
}
setIsSearching(true);
// 执行搜索
fetch(`/api/search?q=${debouncedSearch}`)
.then(res => res.json())
.then(data => setResults(data))
.finally(() => setIsSearching(false));
}, [debouncedSearch]);
return (
<div>
<input
value={searchTerm}
onChange={e => setSearchTerm(e.target.value)}
placeholder="输入搜索内容..."
/>
{isSearching && <p>搜索中...</p>}
{results.map(r => (
<div key={r.id}>{r.title}</div>
))}
</div>
);
}
表单处理Hook
jsx
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const [touched, setTouched] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleChange = (e) => {
const { name, value } = e.target;
setValues(prev => ({ ...prev, [name]: value }));
};
const handleBlur = (e) => {
const { name } = e.target;
setTouched(prev => ({ ...prev, [name]: true }));
};
const reset = () => {
setValues(initialValues);
setErrors({});
setTouched({});
setIsSubmitting(false);
};
const validate = (validator) => {
const newErrors = validator(values);
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
return {
values,
errors,
touched,
isSubmitting,
setIsSubmitting,
handleChange,
handleBlur,
reset,
validate
};
}
// 使用
function ContactForm({ onSubmit }) {
const {
values,
errors,
touched,
isSubmitting,
handleChange,
handleBlur,
reset,
validate
} = useForm({
name: '',
email: '',
message: ''
});
const handleSubmit = async (e) => {
e.preventDefault();
// 标记所有字段已被访问
setTouched({ name: true, email: true, message: true });
// 验证
if (!validate(v => {
const errors = {};
if (!v.name) errors.name = '姓名不能为空';
if (!v.email.includes('@')) errors.email = '邮箱格式不正确';
if (v.message.length < 10) errors.message = '留言至少10个字符';
return errors;
})) return;
setIsSubmitting(true);
await onSubmit(values);
setIsSubmitting(false);
reset();
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
name="name"
value={values.name}
onChange={handleChange}
onBlur={handleBlur}
placeholder="姓名"
/>
{touched.name && errors.name && <span>{errors.name}</span>}
</div>
<div>
<input
name="email"
value={values.email}
onChange={handleChange}
onBlur={handleBlur}
placeholder="邮箱"
/>
{touched.email && errors.email && <span>{errors.email}</span>}
</div>
<div>
<textarea
name="message"
value={values.message}
onChange={handleChange}
onBlur={handleBlur}
placeholder="留言"
/>
{touched.message && errors.message && <span>{errors.message}</span>}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '提交中...' : '提交'}
</button>
</form>
);
}
Hooks使用规则
React对Hook的调用有两条强制的规则:
规则1:只在顶层调用Hooks
不要在循环、条件语句或嵌套函数中调用Hook:
jsx
// 错误 ❌ - 在条件语句中调用Hook
function Component({ isLoggedIn }) {
if (isLoggedIn) {
const [name, setName] = useState(''); // 错误!
}
const [count, setCount] = useState(0);
}
// 正确 ✅ - 所有Hook在顶层调用
function Component({ isLoggedIn }) {
const [name, setName] = useState('');
const [count, setCount] = useState(0);
// 在条件块内部使用状态
if (isLoggedIn) {
console.log(name);
}
}
规则2:只在React函数中调用
只能在React函数组件或自定义Hook中调用:
jsx
// 错误 ❌
function ordinaryFunction() {
const [count, setCount] = useState(0); // 错误!
}
// 正确 ✅
function MyComponent() {
const [count, setCount] = useState(0);
}
// 正确 ✅ - 自定义Hook
function useCustomHook() {
const [data, setData] = useState(null);
// ...
}
从Class组件迁移到Hooks
很多老项目还在用Class组件,以下是常见迁移方案:
生命周期方法迁移
| Class组件 | Hooks版本 |
|---|---|
| constructor | useState初始化 |
| componentDidMount | useEffect(() => {}, []) |
| componentDidUpdate | useEffect(() => {}, [deps]) |
| componentWillUnmount | useEffect return cleanup |
| shouldComponentUpdate | React.memo 或 useMemo |
完整迁移示例
Class组件:
jsx
class UserProfile extends React.Component {
state = { user: null, loading: true, error: null };
componentDidMount() {
this.fetchUser();
document.title = '加载中...';
}
componentDidUpdate(prevProps) {
if (prevProps.userId !== this.props.userId) {
this.fetchUser();
}
document.title = this.state.user?.name || '加载中';
}
componentWillUnmount() {
this.cancelFetch();
}
async fetchUser() {
try {
this.setState({ loading: true, error: null });
const user = await api.getUser(this.props.userId);
this.setState({ user, loading: false });
} catch (error) {
this.setState({ error: error.message, loading: false });
}
}
render() {
const { user, loading, error } = this.state;
if (loading) return <div>加载中...</div>;
if (error) return <div>出错了: {error}</div>;
return <div>{user.name}</div>;
}
}
Hooks版本:
jsx
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let cancelled = false;
async function fetchUser() {
try {
setLoading(true);
setError(null);
const user = await api.getUser(userId);
if (!cancelled) {
setUser(user);
}
} catch (error) {
if (!cancelled) {
setError(error.message);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
fetchUser();
return () => {
cancelled = true;
};
}, [userId]);
useEffect(() => {
document.title = user?.name || '加载中';
}, [user]);
if (loading) return <div>加载中...</div>;
if (error) return <div>出错了: {error}</div>;
return <div>{user.name}</div>;
}
常见问题和解决方案
1. 闭包陷阱
jsx
// 问题:定时器会一直输出0
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远是0(闭包问题)
setCount(count + 1); // 永远执行 setCount(0 + 1)
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖,只执行一次
return <div>{count}</div>;
}
// 解决方案1:使用函数式更新
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 用函数获取最新值
}, 1000);
return () => clearInterval(timer);
}, []);
// 解决方案2:使用useRef保存最新值
function Counter() {
const [count, setCount] = useState(0);
const countRef = useRef(count);
useEffect(() => {
countRef.current = count;
}, [count]);
useEffect(() => {
const timer = setInterval(() => {
setCount(countRef.current + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}
2. 无限循环
jsx
// 问题:effect里更新状态,状态变化触发effect
useEffect(() => {
setData(newData); // 触发重新渲染
}, [data]); // data变化又触发effect → 无限循环!
// 解决方案1:检查值是否真的变化了
useEffect(() => {
if (data !== newData) {
setData(newData);
}
}, [newData]); // 只依赖newData
// 解决方案2:使用ref存储中间值
const dataRef = useRef(data);
useEffect(() => {
if (dataRef.current !== newData) {
dataRef.current = newData;
setData(newData);
}
}, [newData]);
3. 清理函数很重要
jsx
// 问题:订阅不清理会造成内存泄漏
useEffect(() => {
const subscription = api.subscribe(data => setData(data));
// 没写清理函数!组件卸载后订阅还在
}, []);
// 正确做法:返回清理函数
useEffect(() => {
const subscription = api.subscribe(data => setData(data));
return () => {
subscription.unsubscribe(); // 清理
};
}, []);
// WebSocket示例
useEffect(() => {
const ws = new WebSocket('wss://example.com');
ws.onmessage = (event) => {
setMessages(prev => [...prev, event.data]);
};
return () => {
ws.close(); // 关闭连接
};
}, []);
4. useEffect里的async函数
jsx
// 错误:useEffect不能直接接收async函数
useEffect(async () => {
const data = await fetchData();
setData(data);
}, []);
// 正确:在effect内部定义async函数
useEffect(() => {
async function fetchData() {
const data = await fetch('/api/data');
setData(data);
}
fetchData();
}, []);
// 或者用立即执行函数
useEffect(() => {
(async () => {
const data = await fetch('/api/data');
setData(data);
})();
}, []);
常用Hooks库推荐
react-use
最流行的React Hooks库,提供了大量实用的Hook:
bash
npm install react-use
jsx
import { useMouse, useDebounce, useLocalStorage } from 'react-use';
function Component() {
const [mouseX, mouseY] = useMouse();
const [value, setValue] = useLocalStorage('key', 'default');
const debouncedValue = useDebounce(value, 300);
return <div>鼠标位置: {mouseX}, {mouseY}</div>;
}
ahooks(阿里出品)
专为React开发的Hooks库,有中文文档:
bash
npm install ahooks
react-hook-form
高性能表单处理Hook:
bash
npm install react-hook-form
jsx
import { useForm } from 'react-hook-form';
function Form() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name", { required: true })} />
{errors.name && <span>必填</span>}
<input {...register("email", { pattern: /@/ })} />
{errors.email && <span>邮箱格式不对</span>}
<button type="submit">提交</button>
</form>
);
}
总结
React Hooks彻底改变了React开发的方式:
| 特性 | Class组件 | Hooks |
|---|---|---|
| 代码量 | 多 | 少 |
| 逻辑复用 | HOC/Render Props | 自定义Hook |
| this问题 | 需要处理 | 没有 |
| 状态逻辑 | 分散 | 集中 |
| 测试 | 需要渲染组件 | 可单独测试Hook |
| 学习曲线 | 陡(生命周期复杂) | 缓(概念简单) |
建议的学习路径:
- 熟练使用useState和useEffect
- 理解useRef的两种用法
- 学习useMemo和useCallback做性能优化
- 尝试编写自定义Hook
- 在项目中实践,逐渐迁移旧代码
Hooks不是要完全替代Class,而是给了我们更多选择。对于简单的组件用Hooks更方便,对于复杂的、有生命周期特殊需求的组件,Class仍然有用武之地。
相关推荐:
Hooks,让React开发更简单!

发表回复