说说 React 中的 context 的理解?应用场景?
问题解析
面试官考察点:
- 是否理解 Context 的作用和使用场景
- 是否掌握 Context API 的使用
- 是否了解 Context 的性能优化
- 对 React 数据流设计的理解
核心概念
什么是 Context
Context 提供了一种在组件之间共享数据的方式,而不必显式地通过组件树的逐层传递 props。
Context 主要解决的是 "prop drilling"(属性钻取) 问题,即数据需要经过多层不相关的组件传递才能到达目标组件。
Context 的设计思想
App (提供数据)
├── Toolbar
│ ├── ThemedButton (消费数据)
│ └── UserAvatar (消费数据)
└── Sidebar
└── UserInfo (消费数据)
在没有 Context 的情况下,Toolbar 和 Sidebar 即使不需要数据,也需要接收并传递 props。
详细解答
1. 基本使用
创建 Context
// ThemeContext.js
import React from 'react';
// 创建 Context,传入默认值
const ThemeContext = React.createContext('light');
export default ThemeContext;
Provider 提供数据
import ThemeContext from './ThemeContext';
function App() {
const [theme, setTheme] = useState('light');
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<Toolbar />
<Sidebar />
</ThemeContext.Provider>
);
}
Consumer 消费数据
import ThemeContext from './ThemeContext';
// 方式一:使用 Consumer(类组件或函数组件)
function ThemedButton() {
return (
<ThemeContext.Consumer>
{({ theme, setTheme }) => (
<button
style={{
background: theme === 'dark' ? '#333' : '#fff',
color: theme === 'dark' ? '#fff' : '#333'
}}
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
// 方式二:使用 contextType(仅类组件)
class ThemedButtonClass extends React.Component {
static contextType = ThemeContext;
render() {
const { theme, setTheme } = this.context;
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle Theme
</button>
);
}
}
// 方式三:使用 useContext(函数组件推荐)
import React, { useContext } from 'react';
function ThemedButtonHook() {
const { theme, setTheme } = useContext(ThemeContext);
return (
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Current theme: {theme}
</button>
);
}
2. 多个 Context 的使用
// 创建多个 Context
const ThemeContext = React.createContext('light');
const UserContext = React.createContext({ name: 'Guest' });
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState({ name: 'John' });
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
<UserContext.Provider value={{ user, setUser }}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 使用多个 Context
function UserProfile() {
const { theme } = useContext(ThemeContext);
const { user } = useContext(UserContext);
return (
<div style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
<p>User: {user.name}</p>
</div>
);
}
// 类组件使用多个 Context
class UserProfileClass extends React.Component {
render() {
return (
<ThemeContext.Consumer>
{({ theme }) => (
<UserContext.Consumer>
{({ user }) => (
<div style={{ background: theme === 'dark' ? '#333' : '#fff' }}>
<p>User: {user.name}</p>
</div>
)}
</UserContext.Consumer>
)}
</ThemeContext.Consumer>
);
}
}
3. 动态 Context
// 创建一个完整的主题 Context
const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
function ThemeProvider({ children }) {
const [theme, setTheme] = useState(themes.light);
const toggleTheme = () => {
setTheme(prevTheme =>
prevTheme === themes.dark ? themes.light : themes.dark
);
};
const value = useMemo(() => ({
theme,
toggleTheme
}), [theme]);
return (
<ThemeContext.Provider value={value}>
{children}
</ThemeContext.Provider>
);
}
function App() {
return (
<ThemeProvider>
<Content />
</ThemeProvider>
);
}
4. Context 与 useReducer 结合
// 创建 Context
const StateContext = React.createContext();
const DispatchContext = React.createContext();
const initialState = { count: 0, user: null };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'setUser':
return { ...state, user: action.payload };
default:
return state;
}
}
function AppProvider({ children }) {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StateContext.Provider value={state}>
<DispatchContext.Provider value={dispatch}>
{children}
</DispatchContext.Provider>
</StateContext.Provider>
);
}
// 自定义 hooks
function useAppState() {
return useContext(StateContext);
}
function useAppDispatch() {
return useContext(DispatchContext);
}
// 使用
function Counter() {
const { count } = useAppState();
const dispatch = useAppDispatch();
return (
<div>
<p>{count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
</div>
);
}
深入理解
Context 的渲染机制
function App() {
const [value, setValue] = useState({ count: 0 });
return (
<MyContext.Provider value={value}>
<Child />
</MyContext.Provider>
);
}
// 问题:每次渲染都创建新对象,导致所有消费组件都重渲染
// 解决:使用 useMemo
function App() {
const [count, setCount] = useState(0);
const value = useMemo(() => ({ count }), [count]);
return (
<MyContext.Provider value={value}>
<Child />
</MyContext.Provider>
);
}
Context 的性能优化
// 问题:Context 变化导致所有消费组件重渲染
// 解决:将 Context 拆分
// 拆分为多个 Context
const ThemeContext = React.createContext();
const UserContext = React.createContext();
const LocaleContext = React.createContext();
function App() {
const [theme, setTheme] = useState('light');
const [user, setUser] = useState(null);
const [locale, setLocale] = useState('en');
// 每个 Context 独立更新
const themeValue = useMemo(() => ({ theme, setTheme }), [theme]);
const userValue = useMemo(() => ({ user, setUser }), [user]);
const localeValue = useMemo(() => ({ locale, setLocale }), [locale]);
return (
<ThemeContext.Provider value={themeValue}>
<UserContext.Provider value={userValue}>
<LocaleContext.Provider value={localeValue}>
<Layout />
</LocaleContext.Provider>
</UserContext.Provider>
</ThemeContext.Provider>
);
}
// 组件只订阅需要的 Context
function Header() {
const { theme } = useContext(ThemeContext);
const { locale } = useContext(LocaleContext);
// 不会受 UserContext 变化影响
return <header style={{ background: theme }}>{locale}</header>;
}
Context 的局限性
// Context 不适合高频更新的场景
function MouseTracker() {
const [position, setPosition] = useState({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
setPosition({ x: e.clientX, y: e.clientY });
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
// 高频更新会导致所有消费组件频繁重渲染
return (
<MouseContext.Provider value={position}>
<App />
</MouseContext.Provider>
);
}
// 解决方案:使用 ref 或状态管理库
function BetterMouseTracker() {
const positionRef = useRef({ x: 0, y: 0 });
useEffect(() => {
const handleMouseMove = (e) => {
positionRef.current = { x: e.clientX, y: e.clientY };
};
window.addEventListener('mousemove', handleMouseMove);
return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
return (
<MouseContext.Provider value={positionRef}>
<App />
</MouseContext.Provider>
);
}
应用场景
1. 主题切换
const ThemeContext = React.createContext('light');
function ThemedApp() {
return (
<ThemeContext.Provider value="dark">
<Toolbar />
<MainContent />
<Footer />
</ThemeContext.Provider>
);
}
2. 用户认证
const AuthContext = React.createContext({
user: null,
login: () => {},
logout: () => {}
});
function AuthProvider({ children }) {
const [user, setUser] = useState(null);
const login = async (credentials) => {
const user = await api.login(credentials);
setUser(user);
};
const logout = () => {
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout }}>
{children}
</AuthContext.Provider>
);
}
3. 多语言支持
const LocaleContext = React.createContext({
locale: 'en',
messages: {}
});
function LocaleProvider({ children, locale }) {
const messages = useMemo(() => loadMessages(locale), [locale]);
return (
<LocaleContext.Provider value={{ locale, messages }}>
{children}
</LocaleContext.Provider>
);
}
function useTranslation() {
const { messages } = useContext(LocaleContext);
return (key) => messages[key] || key;
}
4. 路由信息
const RouterContext = React.createContext({
pathname: '/',
navigate: () => {}
});
function Router({ children }) {
const [pathname, setPathname] = useState(window.location.pathname);
const navigate = (path) => {
window.history.pushState(null, '', path);
setPathname(path);
};
return (
<RouterContext.Provider value={{ pathname, navigate }}>
{children}
</RouterContext.Provider>
);
}
最佳实践
1. 合理拆分 Context
// 不推荐:一个巨大的 Context
const AppContext = React.createContext({
theme: {},
user: {},
locale: {},
settings: {}
});
// 推荐:按功能拆分
const ThemeContext = React.createContext();
const UserContext = React.createContext();
const LocaleContext = React.createContext();
2. 封装自定义 Hook
// 封装 useTheme
function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error('useTheme must be used within a ThemeProvider');
}
return context;
}
// 使用
function Button() {
const { theme, toggleTheme } = useTheme();
return <button style={{ background: theme }} onClick={toggleTheme}>Toggle</button>;
}
3. 默认值的使用
const ThemeContext = React.createContext('light');
// 没有 Provider 时,使用默认值
function Button() {
const theme = useContext(ThemeContext); // 'light'
return <button style={{ background: theme }}>Click</button>;
}
面试要点
- Context 的作用:解决 prop drilling,实现跨层级数据共享
- 使用方式:createContext、Provider、Consumer/useContext
- 性能注意:Provider value 变化会导致所有消费组件重渲染
- 适用场景:主题、用户认证、多语言等全局数据
- 不适用场景:高频更新的数据
常见追问:
- Context 和 Redux 有什么区别?
- 如何避免 Context 导致的过度渲染?
- Context 的默认值什么时候生效?
- 多个 Context 如何嵌套使用?