React原理

慕课 前端框架及项目面试 聚焦VueReactWebpack 第八章 React 原理

函数式编程(不可变值)

  • 函数式编程是一种编程范式(面向对象也是一种编程范式),概念比较多
  • 函数式编程 重点:
    • 纯函数给定固定的输入,就一定会有固定的输出,且不能对传入的参数进行修改。纯函数不能涉及 异步 或者是关于 时间 的操作,一旦函数中涉及 AJAX请求、new Date()、setTimeout() ,他就 不是纯函数。要求reducer是纯函数
    • 不可变值:不管是setState还是redux创建action时,都必须使用不可变值。
  • 好处:给前端自动化测试带来很大便捷性。只需要给函数一个输入值,看看函数输出的时候符合预期即可。
  • 整个Redux都是函数式编程的范式,要求reducer是纯函数也是自然而然的事情,使用纯函数才能保证相同的输入得到相同的输入,保证状态的可预测。

虚拟dom和diff算法

虚拟DOM

vue和react实现vdom细节不同,但核心概念和实现思路是一样的。

出现原因

  • DOM操作非常消耗性能,以前使用原生js可以自己控制DOM操作的时机来进行性能优化(缓存DOM操作,合并DOM操作),react是数据驱动视图,不会直接操作DOM,想有效控制DOM操作提升性能就依靠虚拟DOM
  • 虚拟DOM 就是一个JS对象,用它来描述 真实的DOM。把DOM计算更多的转为js(虚拟DOM)计算(js执行速度很快),计算出最小的变更后再操作DOM(计算过程就是diff算法)。React对性能的提升在于减少了 真实DOM对象 的生成与比较,取而代之的是使用 虚拟DOM(JS对象) 来完成数据改变后的生成与比较。而用JS形成一个 JS对象 性能损耗非常小,生成一个 DOM元素 性能损耗大能。(React中的虚拟DOM

js对象(vnode)模拟DOM结构

js对象模拟DOM结构

  • js对象的结构主要包含三部分:标签名tag、属性props、子元素children,具体命名可能有所不同,但结构都这样
    • class在ES6是关键词了,所以这里用的className
  • 试试看,要自己能从左图写出右图

diff算法

  • diff算法是虚拟DOM中最核心的部分,虚拟DOM的比对就是通过diff算法
  • diff算法能在日常使用react中体现出来(如key)
  • diff是一个广泛的概念,和组件化一样,她不是react独创的,如linux diff命令、git diff等
  • 两棵树做diff,两个js对象也可以做diff
  • diff算法 优化时间复杂度
    1. 只比较同一层级,不跨级比较diff图示
      逐层比对,如果第一层就不同,react就不会往下比对,而是直接使用 新的虚拟DOM 去生成真实的DOM。虽然可能会造成DOM节点渲染的浪费,但同层比对的算法简单,效率快,大大减少了两个虚拟DOM之间比对的性能消耗
    2. tag不相同,则直接删掉重建,不再深度比较diff图示2
    3. tag和key两者都相同,则认为是相同节点,不再深度比较key值在React中我们会根据key值给 虚拟DOM 命名,数据修改后 新的 虚拟DOM 就会根据key值和 原始的 虚拟DOM 进行快速比对,多出来的就是修改的DOM。这会大大提升性能。(所以key值需要是稳定的,可变的key值也就失去了它存在的意义

JSX的本质

  • JSX编译出来是什么本质就是什么
  • babel官网的“试一试”可以直接看到JSX编译的结果babel官网的“试一试”遇到组件会继续拆分组件的jsx结构遇到组件会继续拆分组件的jsx结构
  • 总结:JSX编译出来JSX编译出来
    • **JSX的本质就是React.creatElement()**:
      • 参数1:组件/html标签(大小写区分)
      • 参数2:属性
      • 参数3:子元素
    • React.creatElement()执行生成vnode(虚拟DOM)

React的合成事件机制

  • react中所有事件(SyntheticEvent)挂载在document上,而原生的事件(event)是绑定到DOM上面的
  • react的event不是原生的,是react封装的SyntheticEvent合成事件对象,模拟出了 DOM 事件对象 所有的能力。event.nativeEvent 才是原生事件对象
  • react事件 和 DOM 事件不一样,和 Vue 事件也不一样

合成事件机制
合成事件机制

  1. div上绑定的事件被触发后冒泡到顶层document上,react中所有事件都绑定在document上
  2. react会实例化成统一的react合成事件(SyntheticEvent合成事件)再进行事件派发(通过target知道谁触发了事件以后就派发给他进行事件处理)将event对象交给对应的处理器执行

为什么需要合成事件机制

  • 更好的兼容性和跨平台
  • 所有事件都挂载在document上,减少内存消耗避免频繁解绑
    • 单一标签移除时不需要解绑事件,因为事件都挂载在document上
    • 原生js中事件绑定,元素移除后需要解绑:
  • 方便事件的统一管理(如事务机制

React的batchUpdate机制(setState异/同步更新)

  • setState 可能是异步/同步更新,在合成事件和钩子函数中是“异步”的,在原生DOM事件和setTimeout 中都是同步的。
    • setState 的“异步”并不是说内部由异步代码实现,其实本身执行的过程和代码都是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”
    • 可以通过setState第二个参数 setState(partialState, callback) 中的callback拿到更新后的结果(同步)。
    • setState 的批量更新优化也是建立在“异步”(合成事件、钩子函数)之上的,在原生事件和setTimeout 中不会批量更新,在“异步”中如果对同一个值进行多次setState,setState的批量更新策略会对其进行覆盖,取最后一次的执行,如果是同时setState多个不同的值,在更新时会对其进行合并批量更新。
  • setState异步更新前合并/不合并
    • setState中传入对象,会被合并,多个setState只执行一次
    • setState中传入函数,不会被合并,多个setState执行多次

setState主流程

setState主流程

batchUpdate机制

setState异步或同步 主要看能否命中batchUpdate机制(通过isBatchingUpadates判断)
batchUpdate机制与setState异步或同步1
batchUpdate机制与setState同步
注意:实际上isBatchingUpadates是在入口定义的,不是在函数里,上图只是演示方便

能命中batchUpdate机制的入口(setState异步更新)

react可以“管理”的入口:
生命周期(和他调用的函数)
react中注册的事件(合成事件)(和他调用的函数)

不能命中batchUpdate机制的入口(setState同步更新)

react“管不到”的入口:
setTimeout setInterval等(和他调用的函数)
自定义的DOM事件(和他调用的函数)

transaction(事务)机制

transaction(事务)机制
transaction(事务)机制服务于batchUpdate机制

React组件渲染和更新的过程

组件渲染过程

  1. state数据+render()中解析JSX结构(creatElement函数)生成虚拟DOM
  2. 虚拟DOM 生成 真实的DOM ,显示在页面上

组件更新过程

  1. state 数据 发生改变
  2. 数据 + JSX模版 结合,生成 新的 虚拟的DOM
  3. 比较 原始虚拟DOM 和 新的虚拟DOM 的区别(diff算法,纯js计算),找到区别
  4. 直接操作有区别的DOM(将diff结果渲染DOM)

React-fiber如何优化性能

  • React-fiber是React内部运行机制(是一种基于浏览器的单线程调度算法),开发者体会不到
  • 了解背景和基本概念即可
  • 组件更新过程中,diff算法计算了虚拟DOM的区别以后才进行页面渲染,而JS是单线程,且和DOM渲染共用一个线程。当组件足够复杂,组件更新时计算和渲染都压力大,同时再有DOM操作需求(动画,鼠标拖拽等),将卡顿
    • React-fiber就是解决卡顿的方法。它将递归diff算法比对虚拟DOM这个阶段进行任务拆分(DOM渲染阶段无法拆分),DOM需要渲染时暂停,空闲时恢复(window.requestIdleCallback)

前端路由

React-router使用 路由模式(hash、H5 history)知乎

  • hash特点
    • hash变化会触发网页跳转,即浏览器的前进、后退、跳转
    • hash变化不会刷新页面,单页面应用必需的特点
    • hash永远不会提交到server端(前端自生自灭)
  • history特点
    • 页面和浏览器的history保持一致
    • 需要后端支持
  • react-router-dom是应用程序中路由的库。React库中没有路由功能,需要单独安装react-router-dom。它提供两个路由器 BrowserRouter和HashRouter。
    • BrowserRouter,这是对Router接口的实现。使得页面和浏览器的history保持一致。等于:window.location。http://127.0.0.1:3000/user/type
    • HashRouter,和上面的一样,只是使用的是url的hash部分,等于:window.location.hash。http://127.0.0.1:3000/#/user/type不一定是这样,但#是少不了的)
  • 如果你使用的是一个非静态的站点、要处理各种不同的url那么你就需要使用BrowserRouter。
  • 相反的如果你的server只处理静态的url,那么就使用HashRouter。
,