阿里面试
阿里面试
提供了一个数组结构的 data,要求实现一个 query 方法,返回一个新的数组,query 方法内部有 过滤、排序、分组 等操作,并且支持链式调用,调用最终的 execute 方法返回结果:
class Query {
constructor(data) {
this.data = data;
this.filters = [];
this.sortByField = null;
this.groupByField = null;
}
where(filterFn) {
this.filters.push(filterFn);
return this;
}
sortBy(field) {
this.sortByField = field;
return this;
}
groupBy(field) {
this.groupByField = field;
return this;
}
execute() {
let result = [...this.data];
// Apply filters
for (const filterFn of this.filters) {
result = result.filter(filterFn);
}
// Sort by field
if (this.sortByField) {
result = result.sort((a, b) => a[this.sortByField] - b[this.sortByField]);
}
// Group by field
if (this.groupByField) {
const groups = {};
for (const item of result) {
const key = item[this.groupByField];
if (!groups[key]) {
groups[key] = [];
}
groups[key].push(item);
}
result = Object.values(groups);
}
return result;
}
}
// 使用示例
const list = [
{ id: 1, name: 'John', age: 20 },
{ id: 2, name: 'Jane', age: 25 },
{ id: 3, name: 'John', age: 30 },
{ id: 4, name: 'Bob', age: 22 },
{ id: 5, name: 'Jane', age: 28 }
];
const result = new Query(list)
.where(item => item.age > 18)
.sortBy('id')
.groupBy('name')
.execute();
console.log(result);
react 实现一个倒计时抢券组件,页面加载时从 10s 开始倒计时,倒计时结束之后点击按钮请求接口进行抢券,同时更新文案等等功能。
import React, { useState, useEffect } from 'react';
function CouponComponent() {
const [countdown, setCountdown] = useState(10);
const [canClaim, setCanClaim] = useState(false);
const [claimText, setClaimText] = useState('倒计时结束,点击抢券');
const [isClaiming, setIsClaiming] = useState(false);
useEffect(() => {
if (countdown > 0) {
const timer = setInterval(() => {
setCountdown(prevCountdown => prevCountdown - 1);
}, 1000);
return () => {
clearInterval(timer);
};
} else {
setCanClaim(true);
setClaimText('倒计时结束,点击抢券');
}
}, [countdown]);
const handleClaim = () => {
if (canClaim && !isClaiming) {
setIsClaiming(true);
setClaimText('正在抢券...');
// 发送抢券请求
// 假设请求成功,设置成功文案并执行其他逻辑
setTimeout(() => {
setClaimText('抢券成功');
// 执行其他逻辑...
}, 2000);
}
};
return (
<div>
<div>倒计时: {countdown}s</div>
<button onClick={handleClaim} disabled={!canClaim}>
{claimText}
</button>
</div>
);
}
export default CouponComponent;
React 代码层的优化
class 组件和 function 组件两种情况,核心是通过减少渲染次数达到优化目的,具体的优化手段有 PureComponent、shouldComponentUpdate、React.memo、React.useMemo、React.useCallback、React.useRef 等等。
useMemo 和 useCallback 有什么区别
useMemo
和useCallback
都是React中的Hook,用于优化函数组件的性能。它们的主要区别在于它们的用途和返回值类型。
-
useMemo
:useMemo
用于在依赖项发生变化时进行性能优化,以避免不必要的计算开销。useMemo
接受一个函数和依赖项数组作为参数,并返回计算结果的记忆化版本。useMemo
会在组件渲染过程中执行传递的函数,并将结果缓存起来。只有当依赖项发生变化时,才会重新执行函数并更新缓存的结果。- 当需要根据某些依赖项计算一个值,并且这个计算是昂贵的时候,可以使用
useMemo
来避免在每次渲染时重复计算。
-
useCallback
:useCallback
用于在依赖项发生变化时进行性能优化,以避免不必要的函数创建。useCallback
接受一个函数和依赖项数组作为参数,并返回一个记忆化版本的回调函数。useCallback
会在组件渲染过程中创建并返回一个回调函数。只有当依赖项发生变化时,才会重新创建回调函数。- 当需要将一个回调函数传递给子组件,但该回调函数依赖于某些值时,可以使用
useCallback
来确保子组件在依赖项不变的情况下不会重新渲染。
需要注意的是,尽管useMemo
和useCallback
可以用于性能优化,但在大多数情况下,它们并不是必需的。只有在遇到性能问题并确定存在性能瓶颈时,才应该考虑使用这些优化手段。在大多数情况下,React的自身优化机制已经足够处理性能问题。
useEffect 和 useLayoutEffect 有什么区别
useEffect
和useLayoutEffect
是React中的Hook,用于处理副作用操作。它们的主要区别在于它们执行的时机。
-
useEffect
:useEffect
在组件渲染之后异步执行副作用操作。useEffect
的副作用操作不会阻塞组件的渲染和布局过程。useEffect
中的副作用操作可能在浏览器绘制之后才会执行,因此对于布局和视觉上的变化,可能会有延迟。
-
useLayoutEffect
:useLayoutEffect
在组件渲染之后同步执行副作用操作。useLayoutEffect
的副作用操作会在组件渲染和布局之间同步执行。useLayoutEffect
中的副作用操作会在浏览器执行绘制之前完成,以确保在组件布局和视觉上的变化之前执行。
在大多数情况下,应优先考虑使用useEffect
,因为它不会阻塞渲染过程,并且在大多数情况下可以满足副作用操作的需求。只有在需要在布局和视觉上变化之前立即执行副作用操作时,才需要使用useLayoutEffect
。
需要注意的是,在使用useLayoutEffect
时,由于它在渲染和布局之间同步执行,可能会导致性能问题,因此需要谨慎使用,确保副作用操作的性能开销较小。在大多数情况下,使用useEffect
即可满足需求,并且能够更好地优化性能。
useEffect 对应在 class 中都生命周期怎么写
在类组件中,useEffect
的功能可以通过生命周期方法来实现。下面是useEffect
对应的一些常见生命周期方法以及它们的作用:
-
componentDidMount
:componentDidMount
在组件挂载后立即调用,可以用于执行副作用操作,例如订阅事件、请求数据等。- 在
componentDidMount
中执行的操作相当于在useEffect
的依赖项为空数组时执行。
-
componentDidUpdate
:componentDidUpdate
在组件更新后调用,在首次渲染后以及每次更新时都会调用。- 可以使用
componentDidUpdate
来执行副作用操作,但需要在操作之前进行条件检查,以避免无限循环。
-
componentWillUnmount
:componentWillUnmount
在组件卸载之前调用,可以用于清理副作用操作,例如取消订阅、清除定时器等。- 在
componentWillUnmount
中执行的操作相当于在useEffect
的清除函数中执行。
下面是一个示例,演示了如何使用类组件的生命周期方法来替代useEffect
的功能:
class MyComponent extends React.Component {
componentDidMount() {
// componentDidMount 对应 useEffect(() => { ... }, [])
console.log('Component did mount');
// 执行副作用操作,例如订阅事件、请求数据等
// ...
}
componentDidUpdate(prevProps, prevState) {
// componentDidUpdate 对应 useEffect(() => { ... }, [依赖项])
console.log('Component did update');
// 执行副作用操作,例如根据依赖项的变化请求数据等
// ...
}
componentWillUnmount() {
// componentWillUnmount 对应 useEffect 的清除函数
console.log('Component will unmount');
// 清理副作用操作,例如取消订阅、清除定时器等
// ...
}
render() {
return <div>My Component</div>;
}
}
在上面的示例中,componentDidMount
方法相当于在组件挂载后执行的副作用操作,componentDidUpdate
方法相当于在依赖项更新后执行的副作用操作,componentWillUnmount
方法相当于清理函数。
需要注意的是,在函数组件中使用useEffect
时,每次渲染都会创建新的副作用操作和清理函数,而在类组件中,生命周期方法只会在特定的时机调用一次。
如果在 if 里面写 useEffect 会有什么表现
在条件语句里面写 useEffect 控制台会出现报错,因为 hook 的规则就是不能在条件语句或者循环语句里面写,这点在 react 官方文档里面也有提到。
React 的 Fiber 架构是什么
作为架构来说,在旧的架构中,Reconciler(协调器)采用递归的方式执行,无法中断,节点数据保存在递归的调用栈中,被称为 Stack Reconciler,stack 就是调用栈;在新的架构中,Reconciler(协调器)是基于 fiber 实现的,节点数据保存在 fiber 中,所以被称为 fiber Reconciler。
作为静态数据结构来说,每个 fiber 对应一个组件,保存了这个组件的类型对应的 dom 节点信息,这个时候,fiber 节点就是我们所说的虚拟 DOM。
作为动态工作单元来说,fiber 节点保存了该节点需要更新的状态,以及需要执行的副作用。