面试题答案
一键面试在 React SSR 项目中结合 useEffect Hook
- 数据获取时机
- 问题:在 SSR 环境下,useEffect Hook 中的数据获取代码不会在服务器端运行,这可能导致首次渲染时页面缺少数据。例如,如果在 useEffect 中发起一个 API 调用获取用户信息,服务器端渲染时该 API 调用不会执行,渲染出的 HTML 中用户信息部分会是空的。
- 解决方案:可以在服务器端使用类似于
getServerSideProps
(在 Next.js 中)或getDataFromTree
(在 Gatsby 中)的方法来进行数据获取。这些方法允许在服务器端获取数据并将其传递给 React 组件。在客户端,仍然可以使用 useEffect 来处理数据更新,例如在用户状态发生变化时重新获取数据。 - 示例代码(Next.js):
import React, { useEffect, useState } from'react';
export async function getServerSideProps(context) {
const response = await fetch('https://example.com/api/user');
const data = await response.json();
return {
props: {
userData: data
}
};
}
const UserComponent = ({ userData }) => {
const [localUserData, setLocalUserData] = useState(userData);
useEffect(() => {
// 客户端数据更新逻辑,例如检测到用户登录状态变化重新获取数据
const fetchUserData = async () => {
const response = await fetch('https://example.com/api/user');
const data = await response.json();
setLocalUserData(data);
};
fetchUserData();
}, []);
return (
<div>
<p>{localUserData.name}</p>
</div>
);
};
export default UserComponent;
- 副作用执行的一致性
- 问题:由于 useEffect 在服务器端不执行,在客户端执行时可能会导致与服务器端渲染结果不一致。例如,在 useEffect 中操作 DOM(如添加事件监听器),服务器端渲染时没有这些操作,客户端渲染时添加了,可能会导致页面闪烁或行为异常。
- 解决方案:避免在 useEffect 中执行会改变页面初始渲染结构的副作用。对于 DOM 操作,可以使用
useLayoutEffect
替代useEffect
。useLayoutEffect
在浏览器布局和绘制之前执行,并且在服务器端渲染和客户端渲染时执行顺序更一致。另外,可以使用条件判断确保某些副作用仅在客户端执行,例如:
import React, { useEffect, useLayoutEffect } from'react';
const MyComponent = () => {
useLayoutEffect(() => {
// 在这里进行 DOM 操作,如添加事件监听器
const element = document.getElementById('my - element');
if (element) {
element.addEventListener('click', () => {
console.log('Clicked!');
});
}
return () => {
if (element) {
element.removeEventListener('click', () => {
console.log('Clicked!');
});
}
};
}, []);
useEffect(() => {
// 仅在客户端执行的逻辑,如设置第三方脚本
if (typeof window!== 'undefined') {
// 加载第三方脚本
const script = document.createElement('script');
script.src = 'https://example.com/third - party - script.js';
document.body.appendChild(script);
return () => {
document.body.removeChild(script);
};
}
}, []);
return <div id="my - element">Click me</div>;
};
export default MyComponent;
- 确保 SSR 与客户端渲染之间的平滑过渡
- 代码结构:将业务逻辑提取到独立的函数或模块中,以便在服务器端和客户端共享。例如,数据获取逻辑可以封装在一个函数中,在
getServerSideProps
和 useEffect 中都可以调用这个函数。 - 设计模式:采用单向数据流模式,例如 Redux 或 MobX。这样可以确保服务器端和客户端的数据状态管理方式一致。在服务器端,可以预先填充 Redux store 中的数据,然后在客户端 hydrate(注水)这个 store,使客户端渲染能够基于服务器端传递下来的初始数据继续运行,实现平滑过渡。
- 示例代码(Redux + Next.js):
- 代码结构:将业务逻辑提取到独立的函数或模块中,以便在服务器端和客户端共享。例如,数据获取逻辑可以封装在一个函数中,在
// store.js
import { createStore } from'redux';
const initialState = {
user: null
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'SET_USER':
return {
...state,
user: action.payload
};
default:
return state;
}
};
const store = createStore(reducer);
export default store;
// pages/_app.js
import React from'react';
import { Provider } from'react - redux';
import store from '../store';
function MyApp({ Component, pageProps }) {
return (
<Provider store={store}>
<Component {...pageProps} />
</Provider>
);
}
export default MyApp;
// pages/index.js
import React, { useEffect } from'react';
import { useDispatch } from'react - redux';
export async function getServerSideProps(context) {
const response = await fetch('https://example.com/api/user');
const data = await response.json();
return {
props: {
userData: data
}
};
}
const HomePage = ({ userData }) => {
const dispatch = useDispatch();
useEffect(() => {
if (userData) {
dispatch({ type: 'SET_USER', payload: userData });
}
}, [userData, dispatch]);
return (
<div>
{/* 页面内容 */}
</div>
);
};
export default HomePage;
通过以上方法,可以有效地在 React SSR 项目中结合 useEffect Hook,并确保 SSR 与客户端渲染之间的平滑过渡。