JSX 转换 虚拟 DOM
JSX(JavaScript XML) ,在 JS中写 HTML
- JSX中 HTML 本就不是真实的 DOM
- 通过 Babel 转换为 createElement
- createMelment 返回
树状对象数据
这段树状对象数据
就是虚拟 DOM
优势
- 跨平台
虚拟 DOM 是一个纯 JavaScript 对象
- 性能优化
频繁的 DOM 操作会导致性能瓶颈,而虚拟 DOM 可以将多次操作合并为一次,减少了重排和重绘的次数
递归渲染虚拟 DOM 树阻塞单线程
虚拟 DOM 中存在三部分
type(元素类型)
,Property(属性)
,Children(子元素)
缺点
长时间阻塞主线程,影响浏览器更优先的事件
利用空闲时间渲染
React16
将递归的无法中断
的更新重构为异步的可中断更新
,但用与递归的数据结构无法满足条件(记录当前节点与下一个渲染节点),于是,Fiber
结构应相应出来了
Fiber
通过
child
,parent
,sibling
记录当前节点与下一个渲染节点,更好的分块渲染
Fiber 结构
生成 Fiber
- 把元素添加到 dom 中
- 为元素的子元素都创建一个 fiber 结构
- 找到下一个工作单元
空闲时间渲染 Fiber
Render分割为 Render 和 Commit 阶段
上面的 createFiber 里 ,把元素直接添加到 dom 上,因为浏览器随时都有可能中断我们的操作,这样呈现给用户的就是一个不完整的 UI
需要等所有工作单元执行完后,我们再一并进行 所有 dom的添加
Diff 算法
Diff 算法特点
- 分层,同级比较:React 将整个 DOM 树分为多个层级,然后逐层比较,只比较同一层级的节点,从而减少比较的复杂度。同级比较时按照从左到右的顺序进行比较。
- 元素类型对比: 两个不同类型的元素会生成不同的树,如果元素类型发生了变化,React 会销毁旧树并创建新树。
- key 属性:React 使用 key 属性来标识节点的唯一性,从而在比较时能够快速定位到需要更新的节点。
更新 DOM
函数式组件
function App(props) {
return <h1>Hi {props.name}</h1>
}
const element = <App name="foo" />
const container = document.getElementById("root")
Didact.render(element, container)
函数式组件有两点不同,如下:
- 函数式组件没有 dom 节点 ?
- 他的 children 属性 不在 props 上,而是 他的返回值
Hooks
给函数式组件增加 状态(state)
// 保存当前的 fiber
let wipFiber = null
// 保存当前执行 hook 的索引,区分每次执行是哪个 hook
let hookIndex = null
function updateFunctionComponent(fiber) {
wipFiber = fiber
hookIndex = 0
wipFiber.hooks = []
const children = [fiber.type(fiber.props)]
reconcileChildren(fiber, children)
}
实现 useState 钩子
function useState(initial) {
const oldHook =
wipFiber.alternate &&
wipFiber.alternate.hooks &&
wipFiber.alternate.hooks[hookIndex];
const hook = {
// 存在旧值,则直接取,否则取传入的初始值
state: oldHook ? oldHook.state : initial,
// 存放 每次更新 状态的队列
queue: []
};
const actions = oldHook ? oldHook.queue : [];
actions.forEach(action => {
hook.state = action(hook.state);
});
const setState = action => {
hook.queue.push(action);
wipRoot = {
dom: currentRoot.dom,
props: currentRoot.props,
alternate: currentRoot
};
// 设置为下一个 工作单元,这样就可以开启一个新的 渲染
nextUnitOfWork = wipRoot;
deletions = [];
};
wipFiber.hooks.push(hook);
hookIndex++;
return [hook.state, setState];
}