React面试题相关

React 面试题相关

常用的 hooks 及优化

什么是副作用:1. 副作用 ( side effect ): 数据获取,数据订阅,以及手动更改 React 组件中的 DOM 都属于副作用 2. 因为我们渲染出的页面都是静态的,任何在其之后的操作都会对他产生影响,所以称之为副作用

  1. useState,在类组件中,可以用 this.state 来定义类组件的状态

  2. 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
    15
    useEffect(() => {
    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销毁')
    }
    })
  3. useLayoutEffect。一般将 useLayoutEffect 称为有 DOM 操作的副作用 hooks。作用是在 DOM 更新完成之后执行某个操作。执行时机:在 DOM 更新之后执行。执行时机不同。useLayoutEffect 在 DOM 更新之后执行;useEffect 在 render 渲染结束后执行。执行示例代码会发现 useLayoutEffect 永远比 useEffect 先执行,这是因为 DOM 更新之后,渲染才结束或者渲染还会结束。

  4. useMemo。使用 useMemo 可以传递一个创建函数和依赖项,创建函数会需要返回一个值,只有在依赖项发生改变的时候,才会重新调用此函数,返回一个新的值。简单来说,作用是让组件中的函数跟随状态更新(即优化函数组件中的功能函数)。1. 优化点击功能函数 2. 优化子组件重复渲染问题

  5. useCallback。可以对父子组件传参渲染的问题进行优化。简单来说就是,父组件的传入函数不更新,就不会触发子组件的函数重新执行。

  6. useRef。保存一个值,在整个生命周期中维持不变。重新赋值 ref.current 不会主动触发页面重新渲染。当我们将代码修改成下面这样,会在控制台打印发现 ref.current 的值打印为 111,但是页面视图上显示的还是空,这是因为 ref 保存的对象发生改变,不会主动通知,属性变更不会重新渲染

  7. 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
    31
    const 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>
    }
  8. 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
    40
    const 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 原理解析

探究 React Hooks 的背后原理

为什么现在生命周期 getdervidStateFromProps 是个静态方法

  • Fiber 架构下,reconciler 会进行多次,reconciler 过程又会调用多次之前的 willxxx,造成了语意不明确,因此干掉
  • 多次都调用 Willxxx 会导致一些性能安全/数据错乱等问题,因此 Unsafe
  • 静态函数 getDerivedStateFromProps,直接将其函数内的用户逻辑降低几个数量级,减少用户出错,提高性能,符合语意
  • getSnapshotBeforeUpdate 替换之前 willxxxx,给想读取 dom 的用户一些空间,强逼用户到 mount 阶段才能操作 dom
  • 提高性能,减少 try catch 的使用

Redux 和 Mobx 的对比

  1. redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
  2. redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
  3. redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
  4. mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
  5. mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

react-redux 源码分析

react-redux 源码分析
redux 中间件的原理——从懵逼到恍然大悟
详解 redux 中间件

Redux 源码解析

问题清单:

  • state 初始化如何让全局都能访问到?
  • dispatch 之后,Redux 是如何去处理的?
  • reducer 是如何处理的?
  • state 中的数据被修改之后,订阅者们如何去收到更新后的数据?
  • React 发送了 dispatch 之后,如何感知 state 的改变?
  • applyMiddleware 中为什么一个临时变量 dispatch 被赋值了 2 次?
  • applyMiddleware 中 middlewareAPI 的 dispatch 为什么要用匿名函数包裹?

Redux 源码解析

React 16 的生命周期

React16 生命周期函数深入浅出

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 算法等

面试题参考资料

「2021」高频前端面试题汇总之 React 篇(上)

Author: XavierShi
Link: https://blog.xaviershi.com/2021/11/01/React面试题相关/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.