React 面试题相关
常用的 hooks 及优化
什么是副作用:1. 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用 2. 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用
useState
,在类组件中,可以用 this.state 来定义类组件的状态useEffect
useEffect 又称副作用 hooks。作用:给没有生命周期的组件,添加结束渲染的信号。执行时机:在渲染结束之后执行。如果不接受第二个参数,那么在第一次渲染完成之后和每次更新渲染页面的时候,都会调用 useEffect 的回调函数。在这,我们可以对第二个参数传入一个数组,这个数组表示的是更新执行所依赖的列表,只有依赖列表改变时(当数组中的任意一项变化的时候,useEffect 会被重新执行 ),才会触发回调函数。传入的为空数组[],那么即告诉 useEffect 不依赖于 state、props 中的任意值,useEffect 就只会运行一次,常用场景为页面获取数据的方法可以写入此处进行调用,以获取页面初始数据。传入一个值构建的数组、或者多个值构建的数组,如[num]、[num,val],上述代码变更为如下。那么此时只有当数组中的值(任意一项即可)改变时,才会重新触发回调函数。return 一个函数清除副作用。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15useEffect(() => {
console.log('2222函数式组件结束渲染')
const updateMouse = (e) => {
console.log('打印当前位置')
setPositions({ x: e.clientX, y: e.clientY })
}
document.addEventListener('click', updateMouse) // 添加绑定方法事件(要修改依赖,绑定到依赖上)
return () => {
// 在每次执行useEffect之前都会执行上一次return中内容
document.removeEventListener('click', updateMouse)
// 移除绑定方法事件(要修改依赖,绑定到依赖上)
console.log('1111销毁')
}
})useLayoutEffect
。一般将 useLayoutEffect 称为有 DOM 操作的副作用 hooks。作用是在 DOM 更新完成之后执行某个操作。执行时机:在 DOM 更新之后执行。执行时机不同。useLayoutEffect 在 DOM 更新之后执行;useEffect 在 render 渲染结束后执行。执行示例代码会发现 useLayoutEffect 永远比 useEffect 先执行,这是因为 DOM 更新之后,渲染才结束或者渲染还会结束。useMemo
。使用 useMemo 可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)。1. 优化点击功能函数 2. 优化子组件重复渲染问题useCallback
。可以对父子组件传参渲染的问题进行优化。简单来说就是,父组件的传入函数不更新,就不会触发子组件的函数重新执行。useRef
。保存一个值,在整个生命周期中维持不变。重新赋值 ref.current 不会主动触发页面重新渲染。当我们将代码修改成下面这样,会在控制台打印发现 ref.current 的值打印为 111,但是页面视图上显示的还是空,这是因为 ref 保存的对象发生改变,不会主动通知,属性变更不会重新渲染useContext
。useContext 是让子组件之间共享父组件传入的状态的。作用通俗地说是带着子组件去流浪。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
31const Context = createContext(null)
function StateFunction() {
const [num, setNum] = useState(1)
return (
<div>
<button onClick={() => setNum((num) => num + 1)}>
增加num的值+1
</button>
<br></br>
这是一个函数式组件——num:{num}
<Context.Provider value={num}>
<Item3></Item3>
<Item4></Item4>
</Context.Provider>
</div>
)
}
function Item3() {
const num = useContext(Context)
return <div>子组件3: {num}</div>
}
function Item4() {
const num = useContext(Context)
return <div>子组件4: {num + 2}</div>
}useReducer
。以前是只能在类组件中使用 Redux,现在我们可以通过 useReducer 在函数式组件中使用 Redux。作用是可以从状态管理的工具中获取到想要的状态。如何使用 useReducer。Redux 必须要有的内容就是仓库 store 和管理者 reducer。而 useReducer 也是一样的,需要创建数据仓库 store 和管理者 reducer,即示例代码注释处。然后我们就可以通过 ① 处的定义一个数组获取状态和改变状态的动作,触发动作的时候需要传入 type 类型判断要触发 reducer 哪个动作,然后进行数据的修改。需要注意的地方是,在 reducer 中 return 的对象中,需要将 state 解构,否则状态就剩下一个 num 值了。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
40const store = {
age: 18,
num: 1,
} // 数据仓库
const reducer = (state, action) => {
switch (action.type) {
case 'add':
return {
...state,
num: action.num + 1,
}
default:
return {
...state,
}
}
} // 管理者
function StateFunction() {
const [state, dispacth] = useReducer(reducer, store) // ①
return (
<div>
<button
onClick={() => {
dispacth({
type: 'add',
num: state.num,
})
}}
>
增加num的值+1
</button>
<br></br>
这是一个函数式组件——num:{state.num}
</div>
)
}
【React Hooks】掌握及对比常用的 8 个 Hooks(优化及使用场景)
React Hooks 详解 【近 1W 字】+ 项目实战
Hooks 原理解析
为什么现在生命周期 getdervidStateFromProps 是个静态方法
- Fiber 架构下,reconciler 会进行多次,reconciler 过程又会调用多次之前的 willxxx,造成了语意不明确,因此干掉
- 多次都调用 Willxxx 会导致一些性能安全/数据错乱等问题,因此 Unsafe
- 静态函数 getDerivedStateFromProps,直接将其函数内的用户逻辑降低几个数量级,减少用户出错,提高性能,符合语意
- getSnapshotBeforeUpdate 替换之前 willxxxx,给想读取 dom 的用户一些空间,强逼用户到 mount 阶段才能操作 dom
- 提高性能,减少 try catch 的使用
Redux 和 Mobx 的对比
- redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
- redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
- redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
- mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
- mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易
react-redux 源码分析
react-redux 源码分析
redux 中间件的原理——从懵逼到恍然大悟
详解 redux 中间件
Redux 源码解析
问题清单:
- state 初始化如何让全局都能访问到?
- dispatch 之后,Redux 是如何去处理的?
- reducer 是如何处理的?
- state 中的数据被修改之后,订阅者们如何去收到更新后的数据?
- React 发送了 dispatch 之后,如何感知 state 的改变?
- applyMiddleware 中为什么一个临时变量 dispatch 被赋值了 2 次?
- applyMiddleware 中 middlewareAPI 的 dispatch 为什么要用匿名函数包裹?
React 16 的生命周期
React 16 的 Fiber 架构
React 实现可以粗划为两部分:reconciliation(diff 阶段)和 commit(操作 DOM 阶段)。在 v16 之前,reconciliation 简单说就是一个自顶向下递归算法,产出需要对当前 DOM 进行更新或替换的操作列表,一旦开始,会持续占用主线程,中断操作却不容易实现。当 JS 长时间执行(如大量计算等),会阻塞样式计算、绘制等工作,出现页面脱帧现象。所以,v16 进行了一次重写,迎来了代号为 Fiber 的异步渲染架构。
React Fiber 并不是所谓的纤程(微线程、协程),而是一种基于浏览器的单线程调度算法,背后的支持 API 是大名鼎鼎的:requestIdleCallback
,得到了这个 API 的支持,我们便可以将 React 中最耗时的部分放入其中。
Fiber
Fiber 核心是实现了一个基于优先级和 requestIdleCallback 的循环任务调度算法。它包含以下特性:(参考:fiber-reconciler)
- reconciliation 阶段可以把任务拆分成多个小任务
- reconciliation 阶段可随时中止或恢复任务
- 可以根据优先级不同来选择优先执行任务
参考资料
为 Luy 实现 React Fiber 架构
浅谈 React16 框架 - Fiber
React Fiber 是什么?
深入理解 React Fiber
★一文了解 React 重点:Fiber 架构、diff 算法等