【前端面试】React相关

React有哪些hooks

  • useState
  • useEffect
  • useRef
  • useMemo
  • useCallback
  • useContext
  • useReducer
  • useLayoutEffect
  • useTransition
  • useId
  • useDebugValue
  • useDeferredValue

useLayoutEffect和useEffect的区别

useEffect:异步执行,在浏览器完成渲染后(页面已绘制)
useLayoutEffect:同步执行,在 DOM 更新后、浏览器绘制前(用户不可见),适用于需要读取 DOM 布局或强制同步更新的场景(如测量 DOM、修改样式)。

如何借助interSectionObserver实现一个dom可见性的自定义hook

有一个关键点是Ref的更新在useEffect执行前

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { useEffect, useState, useRef } from 'react';
const useInView = (
options = {
root: null,
rootMargin: '0px 0px',
threshold: 1,
},
triggerOnce = false, // 是否只触发一次
) => {
const [inView, setInView] = useState(false);
const targetRef = useRef(null);

useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setInView(true);
if (triggerOnce) {
// 触发一次后结束监听
observer.unobserve(entry.target);
}
} else {
setInView(false);
}
});
}, options);

if (targetRef.current) {
// 开始监听
observer.observe(targetRef.current);
}

return () => {
if (targetRef.current) {
// 组件卸载时结束监听
observer.unobserve(targetRef.current);
}
};
}, [options, triggerOnce]);

return [targetRef, inView];
};

export default useInView;

useEffect 中如何使用 async/await

  1. async函数抽离到外部
    1
    2
    3
    4
    5
    6
    7
    8
    9
    async function fetchMyAPI() {
    let response = await fetch("api/data");
    response = await res.json();
    ådataSet(response);
    }

    useEffect(() => {
    fetchMyAPI();
    }, []);
  1. async立即执行函数
    1
    2
    3
    4
    5
    useEffect(() => { 
    (async function anyNameFunction() {
    await loadContent();
    })();
    }, []);
  2. ahooks - useAsyncEffect
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    function useAsyncEffect(
    effect: () => AsyncGenerator<void, void, void> | Promise<void>,
    deps?: DependencyList,
    ) {
    useEffect(() => {
    const e = effect();
    let cancelled = false;
    async function execute() {
    if (isAsyncGenerator(e)) {
    while (true) {
    const result = await e.next();
    if (result.done || cancelled) {
    break;
    }
    }
    } else {
    await e;
    }
    }
    execute();
    return () => {
    cancelled = true;
    };
    }, deps);
    }

React hooks如何实现类似Redux的状态管理

useContext+useReducer

React key的作用

虚拟dom的标识,diff用

React useCallback使用场景

  1. 作为props传递的函数,集合memo一起使用;
  2. 作为更新触发的依赖项 主要目的是为了避免高昂的计算和不必要的重复渲染

React闭包陷阱

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import React, { useState, useEffect } from 'react';

function Counter() {
const [count, setCount] = useState(0);

useEffect(() => {
// 模拟一个异步操作,例如网络请求
const intervalId = setInterval(() => {
// 这里的 count 捕获的是 useEffect 第一次运行时 count 的值 (0)
// 因此,每次执行都会是 0 + 1 = 1,而不是递增
console.log('Stale count in setInterval:', count);
setCount(count + 1); // 这是一个闭包陷阱!
}, 1000);

return () => clearInterval(intervalId);
}, []); // 依赖项为空数组,表示只在组件挂载时执行一次
// 导致 setInterval 内部的 count 始终是初始值 0

return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}

export default Counter;

React reconcile

https://zh-hans.legacy.reactjs.org/docs/reconciliation.html#motivation

当多次重复点击按钮时,以下三个 Heading 是如何渲染的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import React, { memo, useMemo, useState } from "react";

const Heading = memo(({ style, title }) => {
console.log("Rendered:", title);
return <h1 style={style}>{title}</h1>;
});

export default function App() {
const [count, setCount] = useState(0);

const normalStyle = {
backgroundColor: "teal",
color: "white",
};

const memoizedStyle = useMemo(() => {
return {
backgroundColor: "red",
color: "white",
};
}, []);

return (
<>
<button
onClick={() => {
setCount(count + 1);
}}
>
Increment {count}
</button>
<Heading style={memoizedStyle} title="Memoized" />
<Heading style={normalStyle} title="Normal" />
<Heading title="React.memo Normal" />
</>
);
}

setState代码输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { useState } from "react";

export default function App() {
const [todo, setTodo] = useState({ id: 1, status: "TODO" });
return (
<div className="App">
<button
onClick={() => {
todo.status = !todo.status;
setTodo(todo);
}}
>
Toggle Status
</button>
<h1>{todo.status}</h1>
</div>
);
}

useCallback和useMemo性能优化

优化React项目性能

  1. 避免不必要的渲染,shouldComponentUpdate、React.memo、React.useMemo、React.useCallback。
  2. 代码分割,React.lazy 动态加载组件
  3. 使用 react-query,对请求响应进行缓存、重发等,避免多次请求,减少网络 IO 消耗及优化渲染次数
  4. 使用 useDebounce,对值及事件处理函数进行防抖,避免状态频繁变动,优化渲染次数
  5. 使用 useImmer

React的新特性