前端面试通关宝典:解析44道React测试题
1. 你了解哪些 React hooks?
-
useState: 用于管理函数组件中的状态。
-
useEffect: 用于在函数组件中执行 side effects,例如获取数据或订阅事件。
-
useContext: 用于访问函数组件当中 React 上下文的值。
-
useRef: 用于为跨渲染持续存在的元素或值创建可变引用。
-
useCallback: 用于记忆函数,以防止不必要的重新渲染。
-
useMemo: 用于记忆值,即将成本高昂的计算结果缓存起来以提高性能。
-
useReducer: 负责使用 reducer 函数管理状态,原理类似于 Redux。
-
useLayoutEffect: 与 useEffect 类似,但效果会在所有 DOM 更改之后再同步运行。
这些 hooks 提供强大的工具,可用于管理状态、处理 side effects 和重用 React 函数组件当中的逻辑。
了解更多:https://react.dev/reference/react
2. 虚拟 DOM 是什么?
虚拟 DOM 是 React 中提出的概念,用于为实际 DOM(文档对象模型)创建一个轻量化虚拟表示,并将其存储在内存当中。这是一种用于优化 Web 应用程序性能的编程技术。
当 React 组件的数据或状态发生变更时,虚拟 DOM 也会随之更新,而非直接操作实际 DOM。此后,虚拟 DOM 计算组件先前状态与更新状态之间差异的过程,被称为“diffing”。
一旦发现存在差异,React 将仅更新实际 DOM 当中的必要部分,借此高效反映变更内容。这种方式最大限度减少了实际 DOM 上的操作数量,进而提高了应用程序的整体性能。
通过使用虚拟 DOM,React 在提供动态及交互式用户界面创建方法的同时,也保证应用程序始终拥有最佳效率和渲染速度。
3. 如何渲染一个元素数组?
要渲染一个元素数组,可以使用 map() 方法迭代该数组,并返回一个新的 React 元素数组。
const languages = [
"JavaScript",
"TypeScript",
"Python",
];
function App() {
return (
<div>
<ul>{languages.map((language) => <li>{language}</li>)}</ul>
</div>
);
}
了解更多:https://react.dev/learn/rendering-lists
4. 受控组件与非受控组件之间有何区别?
受控组件与非受控组件之间的最大区别,在于如何管理和更新自身状态。
受控组件的状态由 React 负责控制,该组件接受其当前值并通过 props 进行更新。当值发生改变时,它会触发回调函数,也就是说该组件不会存储自己的内部状态。相反,由父组件管理该值并将其传递给受控组件。
import { useState } from 'react';
function App() {
const [value, setValue] = useState('');
return (
<div>
<h3>Controlled Component</h3>
<input name="name" value={name} onChange={(e) => setValue(e.target.value)} />
<button onClick={() => console.log(value)}>Get Value</button>
</div>
);
}
另一方面,非受控组件则使用 refs 或其他方法在内部管理自身状态。这类组件独立存储并更新其状态,不依赖于 props 或回调。父组件对非受控组件的状态控制能力较弱。
import { useRef } from 'react';
function App() {
const inputRef = useRef(null);
return (
<div className="App">
<h3>Uncontrolled Component</h3>
<input type="text" name="name" ref={inputRef} />
<button onClick={() => console.log(inputRef.current.value)}>Get Value</button>
</div>
);
}
了解更多:https://react.dev/learn/sharing-state-between-components#controlled-and-uncontrolled-components
5. 基于类的 React 组件, 与函数式 React 组件之间有何区别?
基于类的组件和函数组件之间的主要区别,在于二者的定义方式和所用语法不同。
基于类的组件被定义为 ES6 类,属于 React.Component 类的扩展。它们使用 render 方法返回定义组件输出的 JSX(JavaScript XML)。类组件可以通过 this.state 和 this.setState() 访问其生命周期方法和状态管理。
class App extends React.Component {
state = {
value: 0,
};
handleAgeChange = () => {
this.setState({
value: this.state.value + 1
});
};
render() {
return (
<>
<p>Value is {this.state.value}</p>
<button onClick={this.handleAgeChange}>
Increment value
</button>
</>
);
}
}
另一方面,函数组件被定义为简单的 JavaScript 函数。它们接受 props 作为参数并直接返回 JSX。函数组件无权访问生命周期方法或者状态。但随着 React 16.8 中 React hooks 机制的出现,函数组件现在也可以管理状态并使用其他功能,例如上下文和效果。
import { useState } from 'react';
const App = () => {
const [value, setValue] = useState(0);
const handleAgeChange = () => {
setValue(value + 1);
};
return (
<>
<p>Value is {value}</p>
<button onClick={handleAgeChange}>
Increment value
</button>
</>
);
}
一般来讲,函数组件往往更简单、易于阅读和测试。所以除非确实需要类组件,否则建议大家尽量使用函数组件。
6. 组件的生命周期方法有哪些?
生命周期方法,属于一种钩入组件生命周期各个阶段的方法,允许开发者在特定时间执行特定的代码。
以下是几种主要生命周期方法:
-
constructor: 这也是创建组件时调用的第一个方法,用于初始化状态并绑定事件处理程序。在函数组件中,我们可以使用 useState hook 来实现类似的效果。
-
render: 此方法负责渲染 JSX 标记,并返回要在屏幕上显示的内容。
-
componentDidMount: 此方法将在组件于 DOM 中渲染后被立即调用,通常用于初始化任务,例如 API 调用或设置事件侦听器。
-
componentDidUpdate: 此方法会在组件的 props 或 state 发生变更时被调用,允许开发者执行 side effects、根据变更更新组件或者触发其他 API 调用。
-
componentWillUnmount: 此方法会在组件从 DOM 中删除之前被调用,用于清理conponentDidMount 中设置的一切资源,例如删除事件侦听器或取消计时器。
某些生命周期方法(例如 componentWillMount、componentWillReceiveProps 和 componentWillUpdate)现已被弃用,或者被其他方法或 hooks 所替代。
至于“this”方法,是指类组件的当前实例。我们可以用它访问组件内的属性和方法。在函数组件中不需要使用“this”,因为函数不会绑定至特定实例。
7. 使用 useState 时应该注意什么?
useState 会返回一个状态值和一条更新该值的函数。
const [value, setValue] = useState('Some state');
在初始渲染期间,返回的状态与传递来的首个参数值相匹配。setState 函数用于更新该状态,它采用新的状态值作为参数并对组件的重新渲染操作进行排队。setState 函数还可以接受回调函数作为参数,该函数会将之前的状态值作为参数。
了解更多:https://react.dev/reference/react/useState
8. 使用 useEffect 时应该注意什么?
useEffect hook 允许我们在函数组件中执行 side effects。
在 React 的渲染阶段,函数组件的主体之内不得出现突变、订阅、计时器、日志记录及其他 side effects,这些可能导致用户界面中出现难以理解的错误和一致性冲突。
相反,这里建议使用 useEffect。传递给 useEffect 的函数将在渲染被提交至屏幕后才开始执行;而如果您传递一组依赖项作为第二参数,则每当有依赖项发生变更时,都会调用该函数。
useEffect(() => {
console.log('Logging something');
}, [])
了解更多:https://react.dev/reference/react/useEffect
9. 如何跟踪函数组件是否被卸载?
一般来说,useEffect 所创建的资源需要在组件离开屏幕前进行清理或重置,例如订阅或计时器标记。
为此,传递给 useEffect 的函数可以返回一个清理函数。该清理函数将在组件被从用户界面中删除之前运行,防止发生内存泄漏。此外,如果组件经过多次渲染(属于常见情况),则在执行下一效果之前会先清除上一效果。
useEffect(() => {
function handleChange(value) {
setValue(value);
}
SomeAPI.doFunction(id, handleChange);
return function cleanup() {
SomeAPI.undoFunction(id, handleChange);
};
})
10. React 中的 props 是什么?
Props 是指从父组件传递给当前组件的数据。Props 有只读限制,无法更改。
// 父组件
const Parent = () => {
const data = "Hello, World!";
return (
<div>
<Child data={data} />
</div>
);
};
// 子组件
const Child = ({ data }) => {
return <div>{data}</div>;
};
了解更多:https://react.dev/learn/passing-props-to-a-component
11. 什么是状态管理器?你曾经使用过
哪些状态管理器,或者了解过哪些状态管理器?
状态管理器是帮助管理应用程序状态的工具或库,负责提供一个集中的存储或容器,用以容纳并管理可由应用程序中各个组件访问并更新的数据。
状态管理器可以解决以下几个问题。首先,最好将数据同与之相关的逻辑 / 组件彼此分离。第二,在使用本地状态并在组件之间进行传递时,由于组件中可能存在深层嵌套,因此代码往往会比较复杂。通过建立全局存储,我们可以访问并修改来自任意组件的数据。
除了 React Context 以外,常见的状态管理库还有 Redux 和 MobX。
了解更多:https://mobx.js.org/README.html
了解更多:https://redux-toolkit.js.org/
12. 在哪些情况下可以使用本地状态,
什么时候应该使用全局状态?
如果仅需要在单一组件内使用,而且无需传递给其他组件,则建议使用本地状态。本地状态还适用于组件只需在列表中表示单一项目的情况。但如果组件拆分涉及到嵌套组件,而且数据需要沿层次结构进行传递,则最好使用全局状态。
13. Redux 中的 reducer 是什么,
它会用到哪些参数?
Reducer 属于纯函数,并将状态和操作作为参数。在 reducer 内部,我们会跟踪接收到的 action 类型,再根据它修改状态并返回一个新的状态对象。
export default function appReducer(state = initialState, action) {
// Reducer通常会查看action类型字段来决定如何执行
switch (action.type) {
// 根据action的具体类型选择执行方式
default:
// 如果此reducer无法识别action类型
// 或者此action不重要,则直接返回现有状态
}
}
了解更多:https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers
14. Action 是什么,
我们如何改变 Redux 中的状态?
Action 属于简单的 JavaScript 对象,其组成为字段加类型。
{
type: "SOME_TYPE"
}
我们也可以为其添加数据作为负载payload。要改变状态,则须调用我们传递给action的dispatch调度函数。
{
type: "SOME_TYPE",
payload: "Any payload",
}
了解更多:https://redux.js.org/tutorials/fundamentals/part-3-state-actions-reducers
15. Redux 实现的是哪种模式?
Redux 实现的是 Flux 模式,即应用程序的可预测状态管理模式。它通过引入单向数据流与应用程序状态的集中存储机制,帮助管理应用程序状态。
16. Mobx 实现的是哪种模式?
Mobx 实现的是 Observer 模式,也被称为发布-订阅
模式。
了解更多:https://www.patterns.dev/posts/observer-pattern
17. 在使用 Mobx 时应该注意什么?
Mobx 提供 observable 和 computed 等修饰器来定义可观察状态与反应函数。用 action 修饰的操作用于修改状态,确保跟踪所有变更。Mobx 还提供自动依赖项跟踪、不同反应类型、对反应性的细粒度控制,以及通过 mobx-react 包与 React 的无缝集成。总的来说,Mobx 能够以可观察状态的变化为基础,自动执行更新过程以简化状态管理。
18. 如何访问 Mobx 状态下的变量?
我们可以使用 observalbe 装饰器将该变量定义为 observable,借此实现对状态下变量的访问。例如:
import { observable, computed } from 'mobx';
class MyStore {
@observable myVariable = 'Hello Mobx';
@computed get capitalizedVariable() {
return this.myVariable.toUpperCase();
}
}
const store = new MyStore();
console.log(store.capitalizedVariable); // Output: HELLO MOBX
store.myVariable = 'Hi Mobx';
console.log(store.capitalizedVariable); // Output: HI MOBX
在本示例中,使用 observable 装饰器将 myVariable 定义为 observable。之后,我们可以使用 store.myVariable 访问该变量。如此一来,对 myVariable 所做的任何更改都会自动触发相关组件或反应的更新。
了解更多:https://mobx.js.org/actions.html
19. Redux 与 Mobx 之间有什么区别?
Redux 是一种更简单也更严格的状态管理库,要求遵循单向数据流和不变性原则。它需要用到更多的样板代码和显式更新,但与 React 的集成效果非常出色。
另一方面,Mobx 则提供更加灵活、直观的 API,而且样板代码也更少。它允许开发者直接修改状态并自动跟踪变更,借此获得更好的应用程序性能。具体选择 Redux 还是 Mobx,要视实际需求和开发偏好而定。
20. 什么是 JSX?
默认情况下,可以使用以下语法在 React 中创建元素:
const someElement = React.createElement(
'h3',
{className: 'title__value'},
'Some Title Value'
);
但我们在实际开发中,往往更习惯用以下形式:
const someElement = (
<h3 className='title__value'>Some Title Value</h3>
);
这就是所谓 JSX,是一种用于简化代码理解和开发表达的语言扩展。
了解更多:https://react.dev/learn/writing-markup-with-jsx#jsx-putting-markup-into-javascript
21. React 中的 props 钻取是什么?
Props 钻取,是指在某些中间组件并不直接使用 props,仍通过多层嵌套组件传递 props 的过程。这可能导致代码结构繁琐且复杂。
// 父组件
const Parent = () => {
const data = "Hello, World!";
return (
<div>
<ChildA data={data} />
</div>
);
};
// 中间ChildA组件
const ChildA = ({ data }) => {
return (
<div>
<ChildB data={data} />
</div>
);
};
// 叶ChildB组件
const ChildB = ({ data }) => {
return <div>{data}</div>;
};
在此示例中,data prop 从父组件传递到了 ChildA,之后又从 ChildA 传递至 ChildB,其中 ChildA 并不直接使用该 prop。当存在多个层次的嵌套,或者当组件树中较靠下的组件需要访问数据时,可能会使用这种方式,但这会导致代码更难以维护和理解。
我们可以通过其他模式(例如 Redux 或 MobX 等上下文或状态管理库)来缓解 props 钻取。这些方法既允许组件访问数据,又不需要通过每个中间组件传递 props。
22. 如何根据条件渲染元素?
我们可以使用各种条件运算符,包括三元运算符。
return (
<div>
{isVisible && <span>I'm visible!</span>}
</div>
);
return (
<div>
{isOnline ? <span>I'm online!</span> : <span>I'm offline</span>}
</div>
);
if (isOnline) {
element = <span>I'm online!</span>;
} else {
element = <span>I'm offline</span>;
}
return (
<div>
{element}
</div>
);
23. userMemo 的用途是什么?它是如何起效的?
useMemo 用于缓存和记忆计算结果。
传递创建函数和依赖项数组。只有当任意依赖项的值发生变更时,useMemo 才会重新计算记忆值。这种优化方式能避免每次渲染都引发昂贵的计算过程。
使用首个参数,该函数将接受执行计算的回调;使用第二个依赖项数组,则仅当至少一个依赖项发生更改时,该函数才会重新执行计算。
const memoValue = useMemo(() => computeFunc(paramA, paramB), [paramA, paramB]);
了解更多:
https://react.dev/reference/react/useMemo
24. useCallback 的用途是什么,它是如何起效的?
useCallback hook 将返回回调的一个记忆版本,且仅当依赖项之一的值发生更改时该版本才会随之更改。其主要作用,就是在将回调传递给依赖于链接等效性的子组件时,避免触发不必要的渲染。
const callbackValue = useCallback(() => computeFunc(paramA, paramB), [paramA, paramB]);
了解更多:
https://react.dev/reference/react/useCallback
25. useMemo 和 useCallback 之间有什么区别?
-
useMemo 用于记忆计算结果,而 useCallback 用于记忆函数本体。
-
useMemo 缓存的是计算值,如果依赖项未更改,则在后续渲染时直接返回该值。
-
useCallback 缓存的是函数本体,只要依赖项未发生更改,则直接返回同一实例。
26. React Context 是什么?
React Context 是一项功能,提供一种通过组件树传递数据的方法,避免在每个层次上手动传递 props。它允许我们创建一个全局状态,树中的任何组件无论位置如何、均可以访问该状态。当我们需要在未通过 props 直接连接的多个组件之间共享数据时,就可以使用 Context。
React Context API 包含以下三个主要部分:
-
createContext: 此函数用于创建一个新的 context 上下文对象。
-
Context.Provider: 此组件用于向 context 提供值,其中打包有需要访问该值的组件。
-
Context.Consumer 或 useContext hook: 此组件或 hook 负责使用 context 中的值。它可以在上下文提供方内的任意组件中使用。
通过使用 React Context,我们可以避免 prop 钻取(即在多个层次的组件间传递项目)并轻松管理更高级别的状态,保证代码更具组织性的执行效率。
了解更多:
https://react.dev/learn/passing-data-deeply-with-context
27. useContext 的用途是什么,它是如何起效的?
在典型的 React 应用程序当中,数据使用 props 以自上而下(从父组件到子组件)的方式传递。但是,这样的方式对于某些特定类型的 props(例如选定的语言、UI 主题)来说可能过于繁琐,因为需要将其传递给应用程序中的多个组件。Context 上下文提供一种在组件之间共享此类数据的方法,而无需通过树结构中的各个层次显式传递 props。
当 context 的值改变时,调用 useContext 的组件也将随之进行重新渲染。如果重新渲染组件的成本很高,这里可以使用记忆机制进行优化。
const App = () => {
const theme = useContext(ThemeContext);
return (
<div style={{ color: theme.palette.primary.main }}>
Some div
</div>
);
}
了解更多:
https://react.dev/reference/react/useContext
28. useRef 的用途是什么,它是如何起效的?
useRef 返回一个可修改的 ref 对象,即一个属性。其中的当前值由传递的参数进行初始化。返回的对象将在组件的整个生命周期之内持续存在,且不会因渲染而发生改变。
其常见用法是以命令的形式访问后代,例如使用 ref,我们可以显式引用 DOM 元素。
const App = () => {
const inputRef = useRef(null);
const buttonClick = () => {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} type="text" />
<button onClick={buttonClick}>Focus on input tag</button>
</>
)
}
了解更多:
https://react.dev/reference/react/useRef
29. React.memo() 是什么?
React.memo() 是一种高阶组件。如果您的组件始终使用不变的 props 渲染相同的内容,则可以将其打包在 React.memo() 调用当中,通过记忆固定结果的方式提高性能。也就是说,React 将直接使用上次渲染的结果,避免重新执行渲染。React.memo() 只影响 props 的更改。如果一个函数组件被打包进 React.memo 并使用 useState、useReducer 或 useContext,那么当状态或上下文发生变化时,它将被重新渲染。
import { memo } from 'react';
const MemoComponent = memo(MemoComponent = (props) => {
});
了解更多:
https://react.dev/reference/react/memo
30. React Fragment 是什么?
在 React 当中,经常需要从组件返回多个元素。Fragments 允许我们建立子元素列表,而无需在 DOM 当中创建不必要的节点。
<>
<OneChild />
<AnotherChild />
</>
// or
<React.Fragment>
<OneChild />
<AnotherChild />
</React.Fragment>
了解更多:
https://react.dev/reference/react/Fragment
31. React Reconciliation 是什么?
Reconciliation 协调是一种 React 算法,用于区分一个元素树与另一个元素树,借此确定其中需要替换的部分。
Reconciliation 也就是我们之前提到的虚拟 DOM 背后的算法。其基本定义是:在渲染一个 React 应用程序时,描述该应用程序的元素树是在保留的内存当中生成。之后,该树将被包含在渲染环境当中——例如浏览器应用程序,并被翻译成一组 DOM 操作。当应用程序状态更新时,则会生成一个新树。将新树与之前的树进行比较,即可准确计算并得出对更新后的应用程序进行重绘所需要的操作。
了解更多:
https://react.dev/learn/preserving-and-resetting-state
32. 在使用 map() 时,为什么需要列表中的键?
这些键将帮助 React 确定哪些元素已经被更改、添加或者移除。只有指定了这些键,React 才能随时间推移匹配数组元素。在选择键时,最好使用那些能够明确区分列表项与其相邻的字符串。大多数情况下,我们可以使用数据中的 ID 作为键。
const languages = [
{
id: 1,
lang: "JavaScript",
},
{
id: 2,
lang: "TypeScript",
},
{
id: 3,
lang: "Python",
},
];
const App = () => {
return (
<div>
<ul>{languages.map((language) => (
<li key={`${language.id}_${language.lang}`}>{language.lang}</li>
))}
</ul>
</div>
);
}
了解更多:
https://react.dev/learn/rendering-lists#keeping-list-items-in-order-with-key
33. 如何在 Redux Thunk 中处理异步操作?
要使用 Redux Thunk,我们需要将其作为中间件导入。操作创建者不仅需要返回一个对象,还应返回一个以 dispatch 调度为参数的函数。
export const addUser = ({ firstName, lastName }) => {
return dispatch => {
dispatch(addUserStart());
}
axios.post('https://jsonplaceholder.typicode.com/users', {
firstName,
lastName,
completed: false
})
.then(res => {
dispatch(addUserSuccess(res.data));
})
.catch(error => {
dispatch(addUserError(error.message));
})
}
了解更多:
https://redux.js.org/usage/writing-logic-thunks
34. 如何跟踪函数组件中对象字段的变化?
要实现变化跟踪,我们需要使用 useEffect hook,并将对象的字段作为依赖项数组进行传递。
useEffect(() => {
console.log('Changed!')
}, [obj.someField])
35. 如何访问 DOM 元素?
这里可以使用由 React.createRef() 或 useRef() hook 创建的 refs,并将该 ref 属性附加至 React 元素。通过访问所创建的 ref 引用,即可通过 ref.current 来访问 DOM 元素。
const App = () => {
const myRef = useRef(null);
const handleClick = () => {
console.log(myRef.current);
};
return (
<div>
<input type="text" ref={myRef} />
<button onClick={handleClick}>Click Me</button>
</div>
);
}
export default App;
36. 什么是自定义 hook?
自定义 hook,是用于在不同组件之间重用逻辑的函数。这是一种打包可重用逻辑的方法,以便在多个组件之间轻松实现共享和重用。自定义 hook 通常是以单词“use”开头的函数,并可根据需求调用其他 hooks。
了解更多:
https://react.dev/learn/reusing-logic-with-custom-hooks
37. 什么是公共 API?
以索引文件为例,公共 API 通常是指向外部模块或组件公开、且可供访问的接口或函数。
以下是表示公共 API 的索引文件代码示例:
export function greet(name) {
return `Hello, ${name}!`;
}
export function calculateSum(a, b) {
return a + b;
}
在此示例中,index.js 文件充当公共 API,其中会导出函数 greet() 和 calculateSum()。通过导入,即可从其他模块处访问这些函数。其他模块可以导入,并使用这些函数作为自身实现的组成部分:
import { greet, calculateSum } from './index.js';
console.log(greet('John'));
console.log(calculateSum(5, 3));
通过从索引文件导出特定函数,我们即可定义模块的公共 API,并允许其他模块使用这些函数。
38. 创建自定义 hook 需要遵循哪些规则?
-
Hook 的名称以“use”开头。
-
如果必要,可使用现成的 hook。
-
不要为 hook 调用设置条件。
-
将可重用逻辑提取至自定义 hook 当中。
-
自定义 hook 必须为纯函数。
-
自定义 hook 可以返回值,也可以返回其他 hook。
-
尽可能在命名时清晰描述自定义 hook 的作用。
了解更多:
https://react.dev/learn/reusing-logic-with-custom-hooks
39. 什么是 SSR(服务器端渲染)?
服务器端渲染(SSR)是一种用于在服务器上渲染页面,再将完整渲染的页面发送至客户端进行呈现的技术。它允许服务器为网页生成完整的 HTML 标记(包括其中的动态内容),并将其作为请求的响应发送至客户端。
在传统的客户端渲染方法当中,客户端只接收最小的 HTML 页面,之后向服务器发出额外的数据和资源请求,再利用这些数据和资源在客户端渲染页面。这不仅会导致初始页面加载速度变慢,对于搜索引擎优化(SEO)也有负面影响,因为搜索引擎爬虫很难对 JavaScript 驱动的内容建立索引。
使用 SSR,服务器通过执行必要的 JavaScript 代码来生成用于最终页面呈现的 HTML。也就是说,客户端从服务器处接收到的完全渲染后的页面,从而减少了额外的资源需求。SSR 缩短了初始页面加载时间,并允许搜索引擎轻松索引内容,从而实现更好的 SEO 效果。
SSR 在各种框架和库中都很常见,例如用于 React 的 Next.js 和用于 Vue.js 的 Nuxt.js。这些框架能够帮助我们处理服务器端渲染逻辑,大大降低 SSR 的实现门槛。
40. 使用 SSR 有哪些好处?
- 缩短初始加载时间:SSR 允许服务器将完全渲染的 HTML 页面发送至客户端,从而减少客户端所需的处理量。这样可以缩短初始加载时间,让用户更快看到完整页面。
- SEO 友好:搜索引擎可以有效抓取并索引 SSR 页面的内容,因为完全渲染的 HTML 在初始响应中已经可用。这就提高了内容在搜索引擎中的可见性,有助于带来更好的搜索排名。
- 可及性:SSR 使得禁用 JavaScript 或者使用辅助选项的用户也能正常访问内容。通过在服务器上生成 HTML,SSR 能够为所有用户提供可靠且易于访问的浏览体验。
- 低带宽环境下性能更好:SSR 减少了客户端需要下载的数据量,有利于低带宽或高延迟环境下的用户体验。这一点对于使用移动网络、或者固网速度较慢的用户尤其重要。
尽管 SSR 具备诸多优势,但需要注意的是,与客户端渲染方法相比,这也会给服务器负载和维护带来更多复杂性。因此在实施之前,应认真考虑缓存、可扩展性和服务器端渲染性能优化等因素。
41. 你了解 Next.js 中的哪些主要函数?
1. getStaticProps: 此方法用于在构建过程中获取数据,并将页面预渲染为静态 HTML。它能确保构建时的数据可用性,且不会因后续请求而发生更改。
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: {
data
}
};
}
2. getServerSideProps: 此方法用于根据每个请求获取数据,并在服务器端预渲染页面。如果需要获取经常更改、或者仅供特定用户使用的数据,则可以使用此方法。
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: {
data
}
};
}
3. getStaticPaths: 此方法用于在动态路由当中,指定需要在构建时预渲染的路径列表,常用于获取带有参数的动态路由数据。
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map((post) => ({
params: { id: post.id }
}));
return {
paths,
fallback: false
};
}
了解更多:
42. 什么是 linters?
Linters 是用于检查源代码中是否存在潜在错误、bug 风格不一致及可维护性问题的工具,可帮助执行编码标准并保障整个代码库的代码质量与一致性。
Linters 的基本原理,是扫描源代码并将其与一组预先定义的规则或指南进行比较。具体规则可以包含语法与格式约定、最佳实践、潜在 bug 及代码异味。当 linter 发现违规情况时,将会生成警告或错误,并高亮显示需要注意的一行或多行代码。
使用 linter 有以下几大好处:
-
代码质量:Linters 有助于发现并防范潜在 bug、代码异味和反模式,从而提高代码质量。
-
一致性:Linters 会强制执行编码约定和风格指南,确保整个代码库的格式和代码结构保持一致,特别适合多位开发者处理同一项目的情况。
-
可维护性:通过尽早发现问题并推行良好的编码实践,linters 有助于提高代码的可维护性,让代码库更易于理解、修改和扩展。
-
效率:Linters 能够实现代码审查流程自动化,抢在常见错误引发开发 / 生产流程实际问题前将其发现,帮助开发人员节约时间。
常见的 linters 工具包括面向 JavaScript 的 ESLint,用于 CSS 的 Stylelint 以及 Sass。
了解更多:
https://eslint.org/docs/latest/use/getting-started
43. 你了解哪些 React 架构解决方案?
市面上有多种用于构建 React 项目的架构解决方案和模式,比较主流的包括:
-
MVC(模型 - 视图 - 控制器):MVC 是一种传统架构模式,主要将应用程序分为三大主要组成部分:模型、视图和控制器。React 可以在 View 视图层中渲染 UI,而其他库或框架则可用于 Model 模型层和 Controller 控制器层。
-
Flux:Flux 是 Facebook 专门针对 React 应用程序推出的应用架构。它遵循单向数据流,即数据仅沿单一方向流动,因此应用程序的状态更改更易于理解和调试。
-
原子设计:原子设计并非 React 所独有,而是一种将 UI 拆分成更小且可重用组件的设计方法。它鼓励构建小型、独立且能通过组合建立更复杂 UI 的组件。
-
容器与组件模式:此模式将表示(组件)与逻辑和状态管理(容器)区分开来。其中组件专门负责渲染 UI,而容器则处理业务逻辑和状态管理。
-
功能切片设计:这是一种用于组织和构建 React 应用程序的现代架构方法,强调根据功能或模块对应用程序代码库进行划分,从而解决可扩展性、可维护性和可重用性等难题。
44. 什么是功能切片设计?
这是一种用于组织和构建 React 应用程序的现代架构方法,强调根据功能或模块对应用程序代码库进行划分,从而解决可扩展性、可维护性和可重用性等难题。
在功能切片设计当中,应用程序的每个功能或模块都被组织为一个单独的目录,其中包含全部必要组件、actions、reducers 及其他相关文件。这有助于保持代码库的模块化和隔离性,降低开发、测试和维护的难度。
功能切片设计有助于对关注点做清晰划分,并为功能划定边界。这允许不同团队或开发人员独立处理不同功能,而不必分神于冲突或依赖项管理。