react 源码2

useCallback useMemo useContext createContext

新增了一些hook。

js 复制代码
// 创建虚拟dom的函数 
// 函数组件会被babel最初解析成一个特殊的对象,因为还没有运行函数
// function createElement(type, props, ...children) {
//   // console.log(type)
//   return {
//     type,
//     props: {
//       ...props,
//       children: children.map((child) =>
//         typeof child === "object" ? child : createTextElement(child)
//       ),
//     },
//   };
// }

function createElement(type, props, ...children) {
  // console.log("debug: ", children, children.flat()); 
  return {
    type, 
    props: {
      ...(props || {}), 
      children: children.flat().map(i => typeof i === 'object' && i !== null ? i : createTextElement(i))
    }
  }
}

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createDom(fiber) {
  const dom =
    fiber.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(fiber.type);

  updateDom(dom, {}, fiber.props || {});

  return dom;
}

const isEvent = (key) => key.startsWith("on");
const isProperty = (key) => key !== "children" && !isEvent(key);
const isNew = (prev, next) => (key) => prev[key] !== next[key];
const isGone = (prev, next) => (key) => !(key in next);
function updateDom(dom, prevProps, nextProps) {
  prevProps = prevProps || {}; 
  nextProps = nextProps || {}; 
  //Remove old or changed event listeners
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key))
    .forEach((name) => {
      const eventType = name.toLowerCase().substring(2);
      dom.removeEventListener(eventType, prevProps[name]);
    });

  // Remove old properties
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => {
      dom[name] = "";
    });

  // Set new or changed properties
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {
      dom[name] = nextProps[name];
    });

  // Add event listeners
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]);
    });
}

function commitEffects() {
  const _ = (fiber) => {
    if(!fiber || fiber === null) return;

    if(fiber && fiber.hooks && fiber.type instanceof Function) {
      fiber.hooks.filter(hk => hk.isEffect === true).forEach(hk => {
        let nx = null; 
        if(hk.cb) {
          nx = hk.cb();
          hk.cb = null;
        }
        if(nx) {
          if(hk.clean) hk.clean(); 
          hk.clean = nx; 
        }
      });
    }
    _(fiber.child);
    _(fiber.sibling);
  };
  _(wipRoot);
}

function commitRoot() {
  deletions.forEach(commitWork);
  commitWork(wipRoot.child);
  commitEffects();
    currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }

  let domParentFiber = fiber.parent;
  // console.log("si: ", fiber.parent)
  while (!domParentFiber.dom) {
    domParentFiber = domParentFiber.parent;
  }

  const domParent = domParentFiber.dom; // 找到第一个有真实dom的元素

  if (fiber.effectTag === "PLACEMENT" && fiber.dom != null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === "UPDATE" && fiber.dom != null) {
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  } else if (fiber.effectTag === "DELETION") {
    commitDeletion(fiber, domParent);
  }

  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom);
  } else {
    commitDeletion(fiber.child, domParent);
  }
}

// 将element部署到container上,只会在加载页面的时候进行一次。
function render(element, container) {
  // console.log("debug: ", element, container);
  // console.log("debug: ", currentRoot);
  // 初始化工作区的fiber-tree
  wipRoot = {
    dom: container, // root
    props: {
      children: [element],
    },
    alternate: currentRoot,
  };
  deletions = []; // 需要删除的元素
  nextUnitOfWork = wipRoot; // 下一个需要工作的fiber节点
}

let nextUnitOfWork = null; // 下一次工作的目标fiber,初始化为null
let currentRoot = null; // 当前渲染的fiber-dom树
let wipRoot = null; // 正在工作的fiber-dom树
let deletions = null; // 需要删除的元素的数组

// 工作片,最小工作单元
function workLoop(deadline) {
  // console.log(deadline);
  let shouldYield = false; // 初始化为false,当为ture的时候需要暂停

  // 当有下一个工作的单元并且不需要暂停时
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // 执行nextUnitOfWork
    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextUnitOfWork && wipRoot) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

// 浏览器函数,每次自动运行回调
requestIdleCallback(workLoop);

// 执行fiber任务的函数,返回下一个工作的单元
function performUnitOfWork(fiber) {
  const isFunctionComponent = fiber.type instanceof Function;
  if (isFunctionComponent) {
    // 函数组件:
    updateFunctionComponent(fiber);
  } else {
    // 原始标签
    updateHostComponent(fiber);
  }
  if (fiber.child) {
    return fiber.child;
  }
  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling;
    }
    nextFiber = nextFiber.parent;
  }
}

let wipFiber = null; // 当前工作的wib-fiber
let hookIndex = null;

function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  hookIndex = 0;
  wipFiber.hooks = [];
  const children = [fiber.type(fiber.props)]; // 函数组件返回的jsx元素
  reconcileChildren(fiber, children.flat()); 
}
/**
  useEffect(() => {
    console.log("执行");
  }, [])  
*/
function useEffect(f = () => {}, arr = []) {
  const oldHook = wipFiber.alternate 
  && wipFiber.alternate.hooks
  && wipFiber.alternate.hooks[hookIndex];  // 拿到旧的Effect
  let flag = false;
  if(!oldHook || 
    (oldHook.deps && (oldHook.deps.length !== arr.length
      || oldHook.deps.some((i, idx) => (i !== arr[idx]))
    ))
  ) flag = true; 
  console.log(flag)
  const hook = {
    deps: arr, 
    cb: flag ? f : null, 
    clean: oldHook ? oldHook.clean : null, 
    isEffect: true
  };

  if(!wipFiber.hooks) wipFiber.hooks = []; 
  wipFiber.hooks.push(hook); 
  hookIndex ++; 
}

function useMemo(calc, deps) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  
  let flag = false; 
  if(!oldHook || (
    Array.isArray(oldHook.deps) && (
      oldHook.deps.length !== deps.length || 
      oldHook.deps.some((i, idx) => i !== deps[idx])
    )
  ) ) flag = true;

  const hook = {
    deps, 
    result: flag ? calc() : oldHook.result
  }

  if(!Array.isArray(wipFiber.hooks)) wipFiber.hooks = [];
  wipFiber.hooks.push(hook);

  hookIndex ++; 
  return hook.result;
} 

function useCallback(fn, deps) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  
  let flag = false; 
  if(!oldHook || (
    Array.isArray(oldHook.deps) && (
      oldHook.deps.length !== deps.length || 
      oldHook.deps.some((i, idx) => i !== deps[idx])
    )
  ) ) flag = true;

  // console.log(oldHook?.deps, deps, oldHook?.deps?.length !== deps, 
  //    oldHook?.deps?.some((i, idx) => i !== deps[idx])
  // )

  const hook = {
    deps, 
    result: flag ? fn : oldHook.result
  };

  if(!Array.isArray(wipFiber.hooks)) wipFiber.hooks = [];
  wipFiber.hooks.push(hook);
  hookIndex ++; 
  return hook.result;
}

function useRef(initilal) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  
  const hook = {
    current: oldHook ? oldHook.current : initilal, 
  }; 

  if(!Array.isArray(wipFiber.hooks)) wipFiber.hooks = []; 
  wipFiber.hooks.push(hook);

  hookIndex ++;
  return hook;
} 

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];
}

// reducer
function useReducer(reducer, initialState, init) {
  const [state, setState] = useState(
    init ? init(initialState) : initialState
  );
  const dispatch = (action) => {
    setState(prev => reducer(prev, action));
  }; 
  return [state, dispatch];
}

// 创建一个上下文
function createContext(defaultValue) {
  function Provider(props) {
    return {
      type: Provider,
      props: props
    };
  }

  const context = {
    _defaultValue: defaultValue, 
    Provider: Provider,
    _id: Symbol("context")
  }; 

  return context;
}

function useContext(context) {
  let fiber = wipFiber;
  while (fiber && fiber.parent) {
    fiber = fiber.parent;
    if(fiber.type === context.Provider) {
      const hook = {
        context, 
        value: fiber.props.value 
      };
      wipFiber.hooks = wipFiber.hooks || []; 
      wipFiber.hooks.push(hook); 
      hookIndex ++; 
      return fiber.props.value;
    }
  }
  const hook = {
    context, 
    value: context._defaultValue
  }; 
  wipFiber.hooks = wipFiber.hooks || []; 
  wipFiber.hooks.push(hook); 
  hookIndex ++; 
  return hook.value;
}

function updateHostComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }
  reconcileChildren(fiber, fiber.props.children);
}

// 当前的fiber, 新节点的子元素 
function reconcileChildren(wipFiber, elements) {
  /**
   * diff 算法:
   * 给定两个列表,每个元素都有自己的key,要求尽可能复用原本的元素。
   * 对所有的key-type哈希成数值/带字母的,映射到它们对应的新的节点的索引上面,无key值的只能将其删除重新创建dom
   * map, 字典树
  */
  let index = 0;
  // 通过alternate链接旧的fiber
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
  let prevSibling = null;

  // console.log("reconcileChildren: ", oldFiber, elements);
  // 遍历新的elements,并且去寻找新的
  while (index < elements.length || oldFiber != null) {
    const element = elements[index]; // 拿到新的节点,注意此时还没有更新
    let newFiber = null; // 新fiber的初始化

    // 判断两者是否相等
    const sameType = oldFiber && element && element.type == oldFiber.type;

    // 两者相等的时候,可以复用真实dom,改下props就好。
    if (sameType) {
      newFiber = {
        type: oldFiber.type, // 复用,因为相等
        props: element.props,
        dom: oldFiber.dom,
        parent: wipFiber,
        alternate: oldFiber, 
        effectTag: "UPDATE", // 标记为UPDATE 
      };
    }
    if (element && !sameType) {
      // console.log("替换 ", );
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null,
        effectTag: "PLACEMENT",    // 替还原来的点
      };
    }
    if (oldFiber && !sameType) {
      oldFiber.effectTag = "DELETION"; 
      deletions.push(oldFiber);
    }

    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

    if (index === 0) {
      wipFiber.child = newFiber;
    } else if (element) {
      prevSibling.sibling = newFiber;
    }

    prevSibling = newFiber;
    index++;
  }
}

const Didact = {
  createElement,
  render,
  useState,
  useEffect, 
  useRef, 
  useMemo, 
  useCallback
};

/** @jsx Didact.createElement */
function Foo() {
  const [s1, setS1] = Didact.useState(2);
  return (
    <div>
      <div onClick={() => setS1((c) => (c += "1"))}>code__ccc</div>
      <h2> {s1} </h2>
    </div>
  );
}


let last = null; 
/** @jsx Didact.createElement */
function Counter() {
  const [state, setState] = Didact.useState(1);
  const [isShow, setShow] = Didact.useState(true);

  Didact.useEffect(() => {
    console.log("count发生了改变", state);
  }, [state]);

  const f1 = Didact.useCallback(() => {
  }, []); 
  console.log("debug: ", last === f1);
  last = f1; 

  const ref = Didact.useRef(2);

  return (
    <div>
      <h1 onClick={() => setState((c) => c + 1)} style="user-select: none">
        Count: {state}
        {/* <Foo /> */}
      </h1>
      {[1, 2, 3, 4].map((i, idx) => <li key={idx}>{i}</li>)} 
      {null}
      <div>
        {
          isShow ? <div>这个是一个div</div> : null
        }
      </div>

      <button onClick={() => {
        setShow((isShow) => {
          console.log("执行了");
          return !isShow;
        });
      }}>
        按钮
      </button>

      <h2>
        这是一个不会重新渲染的值: {ref.current}
      </h2>

      <button
        onClick={() => {
          ref.current = 3;
        }}
      >
        操控ref
      </button>
    </div>
  );
}

const element = <Counter x={1} />;
// const ele = <p>急啊纠结啊</p>; 
const container = document.getElementById("root");
Didact.render(element, container);
相关推荐
passerby606129 分钟前
完成前端时间处理的另一块版图
前端·github·web components
掘了36 分钟前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅39 分钟前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅1 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅1 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment1 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅2 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊2 小时前
jwt介绍
前端
爱敲代码的小鱼2 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax
Cobyte2 小时前
AI全栈实战:使用 Python+LangChain+Vue3 构建一个 LLM 聊天应用
前端·后端·aigc