惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Google DeepMind News
Google DeepMind News
Simon Willison's Weblog
Simon Willison's Weblog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Proofpoint News Feed
A
Arctic Wolf
T
Threat Research - Cisco Blogs
Apple Machine Learning Research
Apple Machine Learning Research
V
Visual Studio Blog
博客园 - Franky
Cyberwarzone
Cyberwarzone
宝玉的分享
宝玉的分享
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
T
Tailwind CSS Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Scott Helme
Scott Helme
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
C
Cisco Blogs
罗磊的独立博客
Stack Overflow Blog
Stack Overflow Blog
AWS News Blog
AWS News Blog
IT之家
IT之家
MongoDB | Blog
MongoDB | Blog
人人都是产品经理
人人都是产品经理
The Cloudflare Blog
Know Your Adversary
Know Your Adversary
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
博客园_首页
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
Vercel News
Vercel News
Recorded Future
Recorded Future
Engineering at Meta
Engineering at Meta
D
Darknet – Hacking Tools, Hacker News & Cyber Security
博客园 - 司徒正美
C
Check Point Blog
T
The Exploit Database - CXSecurity.com
I
Intezer
P
Palo Alto Networks Blog
爱范儿
爱范儿
The Hacker News
The Hacker News
Microsoft Azure Blog
Microsoft Azure Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
S
Securelist
Security Latest
Security Latest
The GitHub Blog
The GitHub Blog
H
Help Net Security
B
Blog RSS Feed
量子位
Martin Fowler
Martin Fowler
I
InfoQ

博客园 - 张润昊

Coding Agent 工程化实践《一》 : 前端页面自验实践 大模型为什么会产生幻觉?从原理到工程防御 Codex 的 Skill 到底是怎么被调用的? Superpowers:如何约束 Coding Agent 使用 Codex 的一次危险提醒 我的Nginx 的配置分析 NextJS 安全漏洞分析 AI工具学习02 - 使用 ChatGPT 进行 PRD 产品设计 NextJS 02 - 服务端渲染 NextJS01 - page router - 问题列表 Flutter02-布局 Flutter01-本地数据库 isar React 学习 03-两种 Api 的使用 React 学习 02-commit 渲染 React 学习 01-时间切片 安卓低机型卡顿分析以优化方案 命令行 查找某个结尾的文件. 总结下vim快捷键 React技术揭密学习(二) React技术揭密学习(一) 前端监控系统博客总结 记账项目 - webpack优化 React源码解携(二): 走一趟render流程 macos更新后, MySQL不能启动问题
Build your own React
张润昊 · 2022-02-01 · via 博客园 - 张润昊

构建 简化版 react

参考: Build your own React

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>

  <body>
    <button id="btn">Update</button>
    <div id="root"></div>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script type="text/babel">
      function createElement(type, props, ...children) {
        return {
          type,
          props: {
            ...props,
            children: children.map((child) => {
              return typeof child === "object"
                ? child
                : createTextElement(child);
            }),
          },
        };
      }

      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";
      const isNew = (prev, next) => (key) => prev[key] !== next[key];
      const isGone = (prev, next) => (key) => !(key in next);
      function updateDom(dom, prevProps, nextProps) {
        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]);
          });
        Object.keys(prevProps)
          .filter(isProperty)
          .filter(isGone(prevProps, nextProps))
          .forEach((name) => {
            dom[name] = "";
          });
        Object.keys(nextProps)
          .filter(isProperty)
          .filter(isNew(prevProps, nextProps))
          .forEach((name) => {
            dom[name] = nextProps[name];
          });
        Object.keys(nextProps)
          .filter(isEvent)
          .filter(isNew(prevProps, nextProps))
          .forEach((name) => {
            const eventType = name.toLocaleLowerCase().substring(2);
            dom.addEventListener(eventType, nextProps[name]);
          });
      }

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

      function commitWork(fiber) {
        if (!fiber) return;
        let domParentFiber = fiber.parent;
        while (!domParentFiber.dom) {
          domParentFiber = domParentFiber.parent;
        }
        const domParent = domParentFiber.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);
        }
      }

      function render(element, container) {
        wipRoot = {
          dom: container,
          props: {
            children: [element],
          },
          alternate: currentRoot,
        };
        deletions = [];
        nextUnitOfWork = wipRoot;
      }

      let nextUnitOfWork = null;
      let currentRoot = null;
      let wipRoot = null;
      let deletions = null;

      function workLoop(deadline) {
        let shouldYield = false;
        while (nextUnitOfWork && !shouldYield) {
          nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
          shouldYield = deadline.timeRemaining() < 1;
        }
        requestIdleCallback(workLoop);
        if (!nextUnitOfWork && wipRoot) {
          commitRoot();
        }
      }

      requestIdleCallback(workLoop);

      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;
      let hookIndex = null;
      function updateFunctionComponent(fiber) {
        wipFiber = fiber;
        hookIndex = 0;
        wipFiber.hooks = [];
        const children = [fiber.type(fiber.props)];
        reconcileChildren(fiber, children);
      }

      function useState(initial) {
        const oldHook =
          wipFiber.alternate &&
          wipFiber.alternate.hooks &&
          wipFiber.alternate.hooks[hookIndex];
        // 从alternate中取出上一次的hook.
        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];
      }

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

      function reconcileChildren(wipFiber, elements) {
        let index = 0;
        let oldFiber = wipFiber.alternate && wipFiber.alternate.child;
        let prevSibling;

        while (index < elements.length || oldFiber != null) {
          const element = elements[index];
          let newFiber = null;
          // compare oldFiber to element
          const sameType =
            oldFiber && element && element.type === oldFiber.type;
          if (sameType) {
            // Update the node
            newFiber = {
              type: oldFiber.type,
              props: element.props,
              dom: oldFiber.dom,
              parent: wipFiber,
              alternate: oldFiber,
              effectTag: "UPDATE",
            };
          }
          if (element && !sameType) {
            // Add this node
            newFiber = {
              type: element.type,
              props: element.props,
              dom: null,
              parent: wipFiber,
              alternate: null,
              effectTag: "PLACEMENT",
            };
          }
          if (oldFiber && !sameType) {
            // Delete the oldFiber's node
            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,
      };

      /** @jsx Didact.createElement */
      function Counter() {
        const [state, setState] = Didact.useState(1);
        const [num, setNum] = Didact.useState(1);
        return (
          <div>
            <h1 onClick={() => setState((c) => c + 1)}>Count: {state}</h1>
            <h1 onClick={() => setNum((c) => c + 1)}>Num: {num}</h1>
          </div>
        );
      }
      const container = document.getElementById("root");
      Didact.render(<Counter></Counter>, container);
    </script>
  </body>
</html>