说说对 React Hooks 的理解?解决了什么问题?
问题解析
面试官考察点:
- 是否了解 Hooks 的设计动机和解决的问题
- 是否掌握常用 Hooks 的使用方法
- 是否理解 Hooks 的工作原理
- 对函数式编程思想的理解
核心概念
什么是 Hooks
Hooks 是 React 16.8 引入的新特性,它允许你在不编写 class 的情况下使用 state 以及其他的 React 特性。
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
Hooks 解决的问题
在 Hooks 出现之前,React 存在以下问题:
- 难以复用状态逻辑:高阶组件嵌套地狱,render props 代码冗余
- 复杂组件难以理解:生命周期函数中混杂不相关逻辑,相关逻辑分散在不同生命周期
- this 指向问题:需要绑定 this,增加学习成本
- 函数组件无状态:简单组件也需要写成 class
详细解答
1. useState - 状态管理
import React, { useState } from 'react';
function Counter() {
// 声明一个叫 "count" 的 state 变量
const [count, setCount] = useState(0);
// 使用对象作为 state
const [user, setUser] = useState({ name: '', age: 0 });
const updateName = (newName) => {
// 对象需要展开合并
setUser({ ...user, name: newName });
};
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
<p>User: {user.name}</p>
<input
value={user.name}
onChange={(e) => updateName(e.target.value)}
/>
</div>
);
}
2. useEffect - 副作用处理
import React, { useState, useEffect } from 'react';
function Example() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
// 相当于 componentDidMount + componentDidUpdate
useEffect(() => {
document.title = `Count: ${count}`;
});
// 只在 count 变化时执行
useEffect(() => {
console.log('Count changed:', count);
}, [count]);
// 只在挂载时执行(空依赖数组)
useEffect(() => {
console.log('Component mounted');
// 返回清理函数,相当于 componentWillUnmount
return () => {
console.log('Component will unmount');
};
}, []);
// 多个 effect,分离关注点
useEffect(() => {
// 订阅操作
const subscription = subscribeToFriendStatus(id, handleStatusChange);
return () => {
// 取消订阅
subscription.unsubscribe();
};
}, [id]);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Click</button>
</div>
);
}
3. useContext - 上下文消费
import React, { useContext } from 'react';
const ThemeContext = React.createContext('light');
function ThemedButton() {
// 使用 useContext 替代 Consumer
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
I am styled by theme context!
</button>
);
}
// 对比 class 组件的写法
class ThemedButtonClass extends React.Component {
static contextType = ThemeContext;
render() {
return (
<button style={{ background: this.context === 'dark' ? '#333' : '#fff' }}>
I am styled!
</button>
);
}
}
4. useRef - 引用
import React, { useRef, useEffect } from 'react';
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const timerRef = useRef(null);
const onButtonClick = () => {
// 访问 DOM 节点
inputEl.current.focus();
};
useEffect(() => {
// 保存任意可变值
timerRef.current = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(timerRef.current);
};
}, []);
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
5. useReducer - 复杂状态逻辑
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
Count: {state.count}
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
6. useMemo 和 useCallback - 性能优化
import React, { useMemo, useCallback } from 'react';
function ParentComponent({ data, onUpdate }) {
// 缓存计算结果
const processedData = useMemo(() => {
console.log('Computing...');
return data.map(item => item * 2);
}, [data]);
// 缓存回调函数
const handleClick = useCallback(() => {
onUpdate(processedData);
}, [onUpdate, processedData]);
return (
<div>
<ChildComponent data={processedData} onClick={handleClick} />
</div>
);
}
7. 自定义 Hooks
// useLocalStorage.js
import { useState, useEffect } from 'react';
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch (error) {
return initialValue;
}
});
const setValue = (value) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value;
setStoredValue(valueToStore);
window.localStorage.setItem(key, JSON.stringify(valueToStore));
} catch (error) {
console.log(error);
}
};
return [storedValue, setValue];
}
// 使用
function Example() {
const [name, setName] = useLocalStorage('name', 'Bob');
return (
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
);
}
深入理解
Hooks 的工作机制
// React 内部使用数组来存储 state
const React = (function() {
let hooks = [];
let idx = 0;
function useState(initialValue) {
const state = hooks[idx] !== undefined ? hooks[idx] : initialValue;
hooks[idx] = state;
const setState = (newValue) => {
hooks[idx] = newValue;
render();
};
idx++; // 下一个 hook 使用下一个索引
return [state, setState];
}
function render(Component) {
idx = 0; // 每次渲染重置索引
const comp = Component();
comp.render();
return comp;
}
return { useState, render };
})();
Hooks 规则
- 只在最顶层使用 Hooks:不要在循环、条件或嵌套函数中调用
- 只在 React 函数中调用 Hooks:函数组件或自定义 Hooks
// 错误示例
function BadExample() {
if (condition) {
// 在条件中调用 Hook 是错误的
const [state, setState] = useState(0);
}
for (let i = 0; i < 10; i++) {
// 在循环中调用 Hook 是错误的
const [state, setState] = useState(0);
}
}
// 正确示例
function GoodExample() {
const [state, setState] = useState(0);
useEffect(() => {
if (condition) {
setState(1);
}
}, [condition]);
}
最佳实践
- 使用 ESLint 插件:
eslint-plugin-react-hooks自动检查规则 - 合理拆分 Hooks:将相关逻辑封装到自定义 Hooks
- 注意依赖数组:确保 effect 的依赖项完整
- 避免过度优化:不是所有情况都需要 useMemo/useCallback
// 依赖项完整示例
useEffect(() => {
fetchData(id);
}, [id]); // 确保包含所有依赖
// 使用函数式更新避免依赖
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 不依赖外部 count
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖数组是安全的
面试要点
- Hooks 解决的问题:复用逻辑、分离关注点、消除 this
- 常用 Hooks:useState、useEffect、useContext、useRef、useReducer、useMemo、useCallback
- Hooks 规则:只在顶层调用、只在 React 函数中调用
- 自定义 Hooks:以 use 开头,封装可复用逻辑
- 与 Class 组件对比:更简洁、更符合函数式编程思想
常见追问:
- 为什么 Hooks 不能在循环或条件中使用?
- useEffect 的清理函数什么时候执行?
- useMemo 和 useCallback 的区别是什么?
- 如何模拟 componentDidMount?
- useState 和 useReducer 如何选择?