乐趣区

关于react.js:react源码解析20总结第一章的面试题解答

react 源码解析 20. 总结 & 面试题解答

视频解说(高效学习):进入学习

往期文章:

1. 开篇介绍和面试题

2.react 的设计理念

3.react 源码架构

4. 源码目录构造和调试

5.jsx& 外围 api

6.legacy 和 concurrent 模式入口函数

7.Fiber 架构

8.render 阶段

9.diff 算法

10.commit 阶段

11. 生命周期

12. 状态更新流程

13.hooks 源码

14. 手写 hooks

15.scheduler&Lane

16.concurrent 模式

17.context

18 事件零碎

19. 手写迷你版 react

20. 总结 & 第一章的面试题解答

21.demo

总结

至此咱们介绍了 react 的理念,如果解决 cpu 和 io 的瓶颈,要害是实现异步可中断的更新

咱们介绍了 react 源码架构(ui=fn(state)),从 scheduler 开始调度(依据过期事件判断优先级),通过 render 阶段的深度优先遍历造成 effectList(两头会执行 reconcile|diff),交给 commit 解决实在节点(两头交叉生命周期和局部 hooks),而这些调度的过程都离不开 Fiber 的撑持,Fiber 是工作单元,也是节点优先级、更新 UpdateQueue、节点信息的载体,Fiber 双缓存则提供了比照前后节点更新的根底。咱们还介绍了 jsx 是 React.createElement 的语法糖。Lane 模型则提供了更细粒度的优先级比照和计算,这所有都为 concurrent mode 提供了根底,在这之上变能够实现 Suspense 和 batchedUpdate(16、17 版本实现的逻辑不一样),18 章 context 的 valueStack 和 valueCursor 在整个架构中运行机制,19 章介绍了新版事件零碎,包含事件生产、监听和触发

面试题简答(详见视频源码角度解说)

  1. jsx 和 Fiber 有什么关系

    答:mount 时通过 jsx 对象(调用 createElement 的后果)调用 createFiberFromElement 生成 Fiber
    update 时通过 reconcileChildFibers 或 reconcileChildrenArray 比照新 jsx 和老的 Fiber(current Fiber)生成新的 wip Fiber 树

  2. react17 之前 jsx 文件为什么要申明 import React from ‘react’,之后为什么不须要了

    答:jsx 通过编译之后编程 React.createElement,不引入 React 就会报错,react17 扭转了编译形式,变成了 jsx.createElement

    function App() {return <h1>Hello World</h1>;}
    // 转换后
    import {jsx as _jsx} from 'react/jsx-runtime';
    
    function App() {return _jsx('h1', { children: 'Hello world'});
    }
  1. Fiber 是什么,它为什么能进步性能

    答:Fiber 是一个 js 对象,能承载节点信息、优先级、updateQueue,同时它还是一个工作单元。

    1. Fiber 双缓存能够在构建好 wip Fiber 树之后切换成 current Fiber,内存中间接一次性切换,进步了性能
    2. Fiber 的存在使异步可中断的更新成为了可能,作为工作单元,能够在工夫片内执行工作,没工夫了交还执行权给浏览器,下次工夫片继续执行之前暂停之后返回的 Fiber
    3. Fiber 能够在 reconcile 的时候进行相应的 diff 更新,让最初的更新利用在实在节点上

hooks

  1. 为什么 hooks 不能写在条件判断中

    答:hook 会按顺序存储在链表中,如果写在条件判断中,就没法放弃链表的程序

状态 / 生命周期

  1. setState 是同步的还是异步的

    答:legacy 模式下:命中 batchedUpdates 时是异步 未命中 batchedUpdates 时是同步的

    concurrent 模式下:都是异步的

  2. componentWillMount、componentWillMount、componentWillUpdate 为什么标记 UNSAFE

    答:新的 Fiber 架构能在 scheduler 的调度下实现暂停持续,排列优先级,Lane 模型能使 Fiber 节点具备优先级,在高优先级的工作打断低优先级的工作时,低优先级的更新可能会被跳过,所有以上生命周期可能会被执行屡次,和之前版本的行为不统一。

组件

  1. react 元素 $$typeof 属性什么

    答:用来示意元素的类型,是一个 symbol 类型

  2. react 怎么辨别 Class 组件和 Function 组件

    答:Class 组件 prototype 上有 isReactComponent 属性

  3. 函数组件和类组件的相同点和不同点

    答:相同点:都能够接管 props 返回 react 元素

    不同点:

    编程思维:类组件须要创立实例,面向对象,函数组件不须要创立实例,接管输出,返回输入,函数式编程

    内存占用:类组建须要创立并保留实例,占用肯定的内存

    值捕捉个性:函数组件具备值捕捉的个性 上面的函数组件换成类组件打印的 num 一样吗

    export default function App() {const [num, setNum] = useState(0);
      const click = () => {setTimeout(() => {console.log(num);
        }, 3000);
        setNum(num + 1);
      };
    
      return <div onClick={click}>click {num}</div>;
    }
    
    
    export default class App extends React.Component {
      state = {num: 0};
    
      click = () => {setTimeout(() => {console.log(this.state.num);
        }, 3000);
        this.setState({num: this.state.num + 1});
      };
    
      render() {return <div onClick={this.click}>click {this.state.num}</div>;
      }
    }
    
    

可测试性:函数组件不便测试

状态:类组件有本人的状态,函数组件没有只能通过 useState

生命周期:类组件有残缺生命周期,函数组件没有能够应用 useEffect 实现相似的生命周期

逻辑复用:类组件继承 Hoc(逻辑凌乱 嵌套),组合优于继承,函数组件 hook 逻辑复用

跳过更新:shouldComponentUpdate PureComponent,React.memo

倒退将来:函数组件将成为支流,屏蔽 this、标准、复用,适宜工夫分片和渲染

开放性问题

  1. 说说你对 react 的了解 / 请说一下 react 的渲染过程

    答:是什么:react 是构建用户界面的 js 库

    能干什么:能够用组件化的形式构建疾速响应的 web 应用程序

    如何干:申明式(jsx)组件化(不便拆分和复用 高内聚 低耦合)一次学习随处编写

    做的怎么样:优缺(社区凋敝 一次学习随处编写 api 简介)毛病(没有零碎解决方案 选型老本高 过于灵便)

    设计理念:跨平台(虚构 dom)疾速响应(异步可中断 增量更新)

    性能瓶颈:cpu io fiber 工夫片 concurrent mode

    渲染过程:scheduler render commit Fiber 架构

  2. 聊聊 react 生命周期
    详见第 11 章
  3. 简述 diff 算法
    详见第 9 章
  4. react 有哪些优化伎俩

    答:shouldComponentUpdate、不可变数据结构、列表 key、pureComponent、react.memo、useEffect 依赖项、useCallback、useMemo、bailoutOnAlreadyFinishedWork …

  5. react 为什么引入 jsx

    答:jsx 申明式 虚构 dom 跨平台

    解释概念:jsx 是 js 语法的扩大 能够很好的形容 ui jsx 是 React.createElement 的语法糖

    想实现什么目标:申明式 代码构造简洁 可读性强 构造款式和事件能够实现高内聚 低耦合、复用和组合 不须要引入新的概念和语法 只写 js,虚构 dom 跨平

    有哪些可选计划:模版语法 vue ag 引入了控制器 作用域 服务等概念

    jsx 原理:babel 形象语法树 classic 是老的转换 automatic 新的转换

  6. 说说 virtual Dom 的了解

    答:是什么:React.createElement 函数返回的就是虚构 dom,用 js 对象形容实在 dom 的 js 对象

    长处:解决了浏览器的兼容性 防备 xss 攻打 跨平台 差异化更新 缩小更新的 dom 操作

    毛病:额定的内存 首次渲染不肯定快

  7. 你对合成事件的了解

    类型 原生事件 合成事件
    命名形式 全小写 小驼峰
    事件处理函数 字符串 函数对象
    阻止默认行为 返回 false event.preventDefault()

    了解:

    • React 把事件委托到 document 上(v17 是 container 节点上)
    • 先解决原生事件 冒泡到 document 上在解决 react 事件
    • React 事件绑定产生在 reconcile 阶段 会在原生事件绑定前执行

    劣势:

    • 进行了浏览器兼容。顶层事件代理,能保障冒泡一致性(混合应用会呈现凌乱)
    • 默认批量更新
    • 防止事件对象频繁创立和回收,react 引入事件池,在事件池中获取和开释对象(react17 中废除)
      react17 事件绑定在容器上了
  1. 咱们写的事件是绑定在 dom 上么,如果不是绑定在哪里?
    答:v16 绑定在 document 上,v17 绑定在 container 上
  2. 为什么咱们的事件手动绑定 this(不是箭头函数的状况)
    答:合成事件监听函数在执行的时候会失落上下文
  3. 为什么不能用 return false 来阻止事件的默认行为?
    答:说到底还是合成事件和原生事件触发机会不一样
  4. react怎么通过 dom 元素,找到与之对应的 fiber对象的?
    答:通过 internalInstanceKey 对应

解释后果和景象

  1. 点击 Father 组件的 div,Child 会打印 Child 吗

    function Child() {console.log('Child');
      return <div>Child</div>;
    }
    
    
    function Father(props) {const [num, setNum] = React.useState(0);
      return (<div onClick={() => {setNum(num + 1)}}>
          {num}
          {props.children}
        </div>
      );
    }
    
    
    function App() {
      return (
        <Father>
          <Child/>
        </Father>
      );
    }
    
    const rootEl = document.querySelector("#root");
    ReactDOM.render(<App/>, rootEl);




答:不会,源码中是否命中 bailoutOnAlreadyFinishedWork
  1. 打印程序是什么

    function Child() {useEffect(() => {console.log('Child');
      }, [])
      return <h1>child</h1>;
    }
    
    function Father() {useEffect(() => {console.log('Father');
      }, [])
      
      return <Child/>;
    }
    
    function App() {useEffect(() => {console.log('App');
      }, [])
    
      return <Father/>;
    }


答:Child,Father,App,render 阶段 mount 时深度优先遍历,commit 阶段 useEffect 执行机会
  1. useLayout/componentDidMount 和 useEffect 的区别是什么

    class App extends React.Component {componentDidMount() {console.log('mount');
      }
    }
    
    useEffect(() => {console.log('useEffect');
    }, [])




答:他们在 commit 阶段不同机会执行,useEffect 在 commit 阶段结尾异步调用,useLayout/componentDidMount 同步调用

![react 源码 20.1](https://img-blog.csdnimg.cn/img_convert/1ff919a924bc0b632a70f999ea357042.png)
  1. 如何解释 demo_4、demo_8、demo_9 呈现的景象

    答:demo_4:useEffect 和 useLayoutEffect 的区别
    demo_8:工作的优先级无关,见源码剖析视频
    demo_9:批量更新无关,见源码剖析视频

退出移动版