React 原理

函数式编程

  • 纯函数
    reducer 必须是一个纯函数,即没有副作用的函数,不修改输入值,相同的输入一定会有相同的输出
  • 不可变值
    state 必须是不可变值,否则在 shouldComponentUpdate 中无法拿到更新前的值,无法做性能优化操作。

vdom 和 diff 算法

JSX 本质

  • React.createElement 函数
    • React.createElement(tag, props, child1, child2, child3)
    • React.createElement(tag, props, [child1, child2, child3])
  • 执行生成 vnode
javascript 复制代码
const elem = <div>
		<p>aaa</p>
		<p style={{ color: 'red' }}>bbb</p>
	</div>;
javascript 复制代码
const elem = React.createElement(
	"div", 
	null, 
	React.createElement("p", null, "aaa"), 
	React.createElement("p", { style: { color: "red" } }, "bbb")
);
javascript 复制代码
const lisElem = <div>
  {
    this.state.list.map((item, index) => {
	    return (<span key={item.id}>
	        {item.name}
	    </span>);
	 })
  }
</div>;
javascript 复制代码
const listElem = React.createElement(
	"div", 
	null, 
	(void 0).state.list.map((item, index) => {
		return React.createElement("span", { key: item.id }, item.name);
	})
);

合成事件

  • react 的事件不是原生事件 MouseEvent,而是合成事件 SyntheticEvent
  • react16 是挂载到 document 上的;react17 开始是挂载到 root 上的
  • 事件处理函数交给合成事件,事件冒泡到 document / root 上进行处理

出处:https://coding.imooc.com/lesson/419.html#mid=41943

合成事件的好处:

  • 更好的兼容性和跨平台:比如 react-native
  • 全部挂载到 document / root上,减少内存消耗,避免频繁解绑
  • 方便事件的统一管理(事务机制)

出处:https://coding.imooc.com/lesson/419.html#mid=41943

React17 开始挂载到 root 组件上:

  • document 只有一个,root 有多个,有利于多个 react 版本共存,例如:微前端

setState 和 batchUpdate

setState 主流程

出处:https://coding.imooc.com/lesson/419.html#mid=41943

  • 异步:左边分支
  • 非异步:右边分支

isBatchingUpdates

javascript 复制代码
class ListDemo extends React.Component
	constructor(props) {
		// ...
	}
	render() {
		// ...
	}
	
	increase = () => {
		// 开始: 处于 batchUpdate
		// isBatchingiUpdates = true 
		this.setState({
			count: this.state.count + 1
		});
		// 结束
		// isBatchingUpdates = false
	}
}
javascript 复制代码
class ListDemo extends React.Component
	constructor(props) {
		// ...
	}
	render() {
		// ...
	}
	
	increase = () => {
		// 开始: 处于 batchUpdate
		// isBatchingUpdates = true 
		setTimeout(() => {
			// 此时 isBatchingUpdates 是 false
			this.setState({
				count: this.state.count + 1
			});
		});
		// 结束
		// isBatchingUpdates = false
	}
}
javascript 复制代码
componentDidMount() {
	// 开始: 处于 batchUpdate
	// isBatchingUpdates = true 
	document.body.addEventListener('click', () => {
		// 此时 isBatchingUpdates 是 false
		this.setState({
			count: this.state.count + 1
		});
		console.log('count in body event', this.stae.count);
	});
	// 结束
	// isBatchingUpdates = false
}

哪些能命中 batchUpdate 机制:

  • 生命周期(和它调用的函数)
  • React 中注册的事件(和它调用的函数)
  • React 可以"管理"的入口

transaction 事务机制

javascript 复制代码
class ListDemo extends React.Component
	constructor(props) {
		// ...
	}
	render() {
		// ...
	}

	increase = () => {
		// 	开始:处于 batchUpdate
		// isBatchingUpdates = true

		// 其他任何操作

		// 结束
		// isBatchingUpdates = false
	}
}

出处:https://coding.imooc.com/lesson/419.html#mid=41943

javascript 复制代码
transaction.initialize = function() {
	console.log('initialize');
};

transaction.close = function() {
	console.log('close');
};

function method() {
	console.log('abc');
}

transaction.perform(method);

// 输出 'initialize'
// 输出 'abc'
// 输出 'close'

react 组件渲染过程

  1. JSX 如何渲染为页面:
    • 初始化时候继承 props 和 生成 state
    • 通过 render() 函数 生成 vnode
    • patch(elem, vnode):通过 patch 函数将 vonde 更新到 dom
  2. setState 之后如何更新页面:
    • setSate(newState) -> dirtyComponents(可能有子组件):通过 setState 产生新的 state,存到 dirtyComponent 进行异步更新
    • 通过 render() 函数生成新的 vnode
    • patch(elem, vnode):再通过 patch 函数用 newVnode 去更新旧的 vnode

react-fiber

react 的 patch 被拆分为两个阶段:

  • reconciliation阶段:执行 diff 算法,纯 js 计算
  • commit 阶段:将 diff 结果渲染成 dom

背景

  • js 是单线程的,且和 dom 渲染共用一个线程
  • 当组件足够复杂,组件更新时计算和渲染压力都很大
  • 同时再有 dom 操作需求,比如动画、鼠标拖拽等,那么将会卡顿

解决方案:fiber

fiber

  • react 内部的运行机制,开发者体会不到
  • reconciliation 阶段进行任务拆分(commit 无法拆分)
  • dom 需要渲染时暂停,空闲时恢复
  • 通过 window.requestidleCallback 进行控制(并非所有浏览器支持)

FQA

  1. JSX 的本质是什么?
    • jsx 的本质是 React.createElement 函数,执行生返回 vnode
  2. react 组件更新渲染的过程。
    • 初始化时候继承 props 和 生成 state
    • 通过 render() 函数生成 vnode
    • 再通过 patch 函数将 vonde 渲染成真实 dom
    • 通过 setState 修改产生新的 state
    • 触发 re-render 生成新的 vnode
    • 再通过 patch 函数用 newVnode 去更新旧的 vnode
  3. react 为什么要将 patch 过程拆分成 reconciliationcommit 两个阶段?
    • 因为js 是单线程的,且和 dom 渲染共用一个线程
    • 当组件很复杂的时候,组件更新时计算和渲染压力都很大
    • 同时再有 dom 操作需求,比如动画、鼠标拖拽等,那么将会卡顿
相关推荐
2401_860319525 小时前
在React Native鸿蒙跨平台开发中实现 二叉搜索树,如何实现一些基本的遍历方法,如中序遍历,中序遍历按顺序访问左子树、根节点、右子树
react native·react.js·harmonyos
大怪v6 小时前
【Virtual World 04】我们的目标,无限宇宙!!
前端·javascript·代码规范
狂炫冰美式6 小时前
不谈技术,搞点文化 🧀 —— 从复活一句明代残诗破局产品迭代
前端·人工智能·后端
xw57 小时前
npm几个实用命令
前端·npm
!win !7 小时前
npm几个实用命令
前端·npm
代码狂想家7 小时前
使用openEuler从零构建用户管理系统Web应用平台
前端
MobotStone7 小时前
为什么第一性原理思维可以改变你解决问题的方式
架构·前端框架
dorisrv8 小时前
优雅的React表单状态管理
前端
蓝瑟9 小时前
告别重复造轮子!业务组件多场景复用实战指南
前端·javascript·设计模式
dorisrv9 小时前
高性能的懒加载与无限滚动实现
前端