1. 怎么学习react?
答:死磕; 下面是我学习的思路!能力不够,朋友来凑。。。我身边有个喜欢研究这个代码的朋友,看这里,基于他代码理解自己的react。
2. 最常见的就是说调度reconcile,这个怎么理解?
怎么实现重虚拟数据变成真实dom,(这个不做虚拟dom怎么生成的,其实就是babel解析jsx)。
- 这需要先理解react 怎么调度,不是递归生成节点,它利用了浏览器给予的api requestIdleCallback,这个其实有浏览器兼容性问题,React 团队则是用 requestAnimationFrame 和 postMessage 模拟实现的,这里不做详细解释
- 下面是看代码
function workLoop(deadline) {
while (nextUnitOfWork && deadline.timeRemaining() > 1) {
nextUnitOfWork = performUnitOfWork( )
}
// nextUnitOfWork undefined 执行这个时候是performUnitOfWork里面没有数据运行了
if (!nextUnitOfWork && wipRoot) {
console.log(nextUnitOfWork)
console.log(wipRoot)
// debugger
commitRoot()
}
requestIdleCallback(workLoop) //解决递归一直循环完成才能出发问题
}
requestIdleCallback(workLoop) // 浏览器空闲的时候运行
- 上面代码就是空闲时间获取虚拟demo 生成真实dom入口 performUnitOfWork
function updateDom (fiber,elements){
let prevSibling = null
let oldFiber = fiber?.alternate?.child;
for(let i=0;i <elements.length;i++){
const element = elements[i]
const newFiber = {
type: element.type,
props: element.props,
parent: fiber,
alternate: oldFiber,
dom: null,
flag: FLAG_PLACEMENT,
}
const same = oldFiber && oldFiber.type === newFiber.type;
// same type: update
if (same) {
newFiber.flag = FLAG_UPDATE;
newFiber.dom = oldFiber.dom;
}
//这个地方的逻辑其实就是把自己的当前子元素赋值
if (i == 0) {
fiber.child = newFiber
} else {
prevSibling.sibling = newFiber
}
//这个地方利用了引用数据类型,其实给prevSibling赋值,newFiber下面也会出现sibling这个属性,这个地方赋值是为了performUnitOfWork 方法末端代码处理返回数据
// refers oldFiber to next
if (oldFiber) oldFiber = oldFiber.sibling;
prevSibling = newFiber
}
function updateHostComponent(fiber) {
f (!fiber.dom) {
if (fiber.type === "TEXT_ELEMENT") {
fiber.dom = document.createTextNode(fiber.props.nodeValue)
}else{
fiber.dom = document.createElement(fiber.type);
}
updateNodeProps(fiber.dom, {}, fiber.props);
}
updateDom(fiber, fiber.props.children);
}
function updateFunctionComponent(fiber){
currentHookFiber = fiber; //这个是让fiber拥有usestate 属性
hookIndex = 0; //重置state状态
fiber.hooks = [];
updateDom(fiber, [(fiber.type)(fiber.props)]);
}
function performUnitOfWork() {
let fiber = nextUnitOfWork
// 这个地方判断类型是为了区分节点,比如是方法还是真实阶段,还是string标签
if (typeof fiber.type === "string") {
updateHostComponent(fiber);
} else if (typeof fiber.type === "function") {
updateFunctionComponent(fiber);
} else {
updateDom(fiber, fiber.props.children);
}
if (fiber.child) {
return fiber.child
}
let nextFiber = fiber
while (nextFiber) {
if (nextFiber.sibling) {
return nextFiber.sibling
}
nextFiber = nextFiber.parent
}
}
如果上面的performUnitOfWork都处理好了,就会进入commitRoot这个方法
function getParentNode(fiber) {
//为了获取每一个真实父节点
let parentFiber = fiber.parent;
while (parentFiber) {
if (parentFiber.dom) {
return parentFiber.dom;
}
parentFiber = parentFiber.parent;
}
return null;
}
function commitRoot() {
commitWork(wipRoot.child) //开始生成当前自己的节点
currentRoot = wipRoot //这个地方赋值为了后面函数钩子使用
wipRoot = null //这个地方清空是为了不在进入commitRoot
}
function commitWork(fiber) {
if (!fiber) {
return
}
const { flag } = fiber;
flag & FLAG_PLACEMENT && fiber.dom && parentNode.appendChild(fiber.dom)
flag & FLAG_UPDATE && updateNodeProps(
fiber.dom,
fiber.alternate.props,
fiber.props
);
commitWork(fiber.child)//开始自己生成
commitWork(fiber.sibling)//开始兄弟生成
}
以上代码生成就会生成正式节点,没有属性的节点,下面就开始绑定熟悉,事件updateNodeProps这个方法。
function updateNodeProps(node, oldProps, newProps) {
if (!node?.setAttribute) return;
for(let key in newProps){
if (key === "children") {
if (newProps[key].length ) {
newProps[key].forEach((child,index) => {
if(child.type == 'TEXT_ELEMENT'){
if(node&&node.firstChild&&node.firstChild.nodeValue){
node.firstChild.nodeValue = child.props.nodeValue
//这个就是更新文本,比较简单的方法(个人理解)
}
}
})
}
continue;
}
if(key !== 'children'){
if(key.startsWith("on")){
//先删除事件在绑定事件
if (oldProps[key]) {
node.removeEventListener(
key.substring(2).toLowerCase(),
oldProps[key]
);
}
node.addEventListener(key.substring(2).toLowerCase(), newProps[key]);
}else{
//添加元素
node.setAttribute(key, newProps[key]);
}
}
}
for (let key in oldProps) {
if (key === "children") continue;
if (!(key in newProps)) {
node.removeAttribute(key);
}
}
}
以上代码就是我理解的react调度理解的代码