实现自己的mini-react

这篇具有很好参考价值的文章主要介绍了实现自己的mini-react。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

创建运行环境

pnpm create vite
  • 选择Vanilla创建项目 选择javascript就行
    实现自己的mini-react,react.js,前端,javascript
  • 删除多余文件 保留最简单目录
    实现自己的mini-react,react.js,前端,javascript

实现最简单mini-react

渲染dom

  • index.html
    <div id="root"></div>
    <script type="module" src="./main.js"></script>
  • main.js代码
	const dom = document.createElement("div");
	dom.id="app"
	document.querySelector("#root").appendChild(dom);
	 
	const text = document.createTextNode("");
	text.nodeValue = "hello mini react";
	dom.append(text)

这样就可以在浏览器上看到hello mini react了

封装创建虚拟dom节点

  • 首先抽离节点
const textNode = {
    type: "TEXT_ELEMENT",
    props: {
        nodeValue: "hello mini react",
        children: []
    }
}
const el = {
    type: "div",
    props: {
        id: "app",
        children: [textNode]
    }
}
  • 渲染dom
	const dom = document.createElement(el.type);
	dom.id=el.props.id
	document.querySelector("#root").appendChild(dom);
	 
	const text = document.createTextNode("");
	text.nodeValue = textNode.props.nodeValue;
	dom.append(text)

可以看到结果是一样的

封装函数

  • 把上面的el和textNode封装一下方便调用
function createTextNode(nodeValue) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue,
            children: [],
        },
    };
}
function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children,
        },
    };
}
  • 渲染dom
    const textNode=createTextNode("hello mini react");
    const el = createElement("div",{id:"app"},textNode)
	const dom = document.createElement(el.type);
	dom.id=el.props.id
	document.querySelector("#root").appendChild(dom);
	 
	const text = document.createTextNode("");
	text.nodeValue = textNode.props.nodeValue;
	dom.append(text)

可以看到结果是一样的

封装render函数

  1. 创建一个DOM节点,根据el的类型来决定是创建一个文本节点还是一个元素节点
  2. 遍历el的props属性,将除了children之外的属性都赋值给dom节点
  3. 获取el的children属性 遍历children,对每个子元素调用render函数进行递归渲染
  4. 把子节点添加到父节点中
function render(el, container) {
    // 创建一个DOM节点,根据el的类型来决定是创建一个文本节点还是一个元素节点
    const dom =
        el.type === "TEXT_ELEMENT"
            ? document.createTextNode("")
            : document.createElement(el.type);
    // 遍历el的props属性,将除了children之外的属性都赋值给dom节点
    Object.keys(el.props).forEach(key => {
        if (key !== "children") {
            dom[key] = el.props[key];
        }
    })
    // 获取el的children属性
    const children = el.props.children
    // 遍历children,对每个子元素调用render函数进行递归渲染
    children.forEach(child => {
        render(child, dom)
    })
    // 将dom添加到container中
    container.append(dom);
}
  • 重构createElement函数 之前我们传递节点是createTextNode(“hello mini react”) 现在想之间写"hello mini react" 需要修改函数
function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === "string" ? createTextNode(child) : child
            }),
        },
    };
}
  • 渲染dom
    const el = createElement("div",{id:"app"},"hello mini react")
	render(el,document.querySelector("#root"))

对齐react 调用方式

  1. 创建core 文件夹 里面包含React.js和ReactDOM.js两个文件
  2. 创建src文件夹 里面包含App.js文件
  • React.js
function createTextNode(nodeValue) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue,
            children: [],
        },
    };
}
function createElement(type, props, ...children) {
    return {
        type,
        props: {
            ...props,
            children: children.map(child => {
                return typeof child === "string" ? createTextNode(child) : child
            }),
        },
    };
}

function render(el, container) {
    const dom =
        el.type === "TEXT_ELEMENT"
            ? document.createTextNode("")
            : document.createElement(el.type);
    Object.keys(el.props).forEach(key => {
        if (key !== "children") {
            dom[key] = el.props[key];
        }
    })
    const children = el.props.children
    children.forEach(child => {
        render(child, dom)
    })
    container.append(dom);
}
const React={
    render,
    createElement
}
export default React
  • ReactDOM.js
import React from './React.js'
const ReactDOM = {
    createRoot(container) {
        return {
            render(App){
                React.render(App, container)
            }
        }
    }
}

export default ReactDOM
  • App.js
import React from '../core/React.js'
const App =<div>Hello mini react! <span>Hi React</span></div>

export default App
  • main.js
import App from './src/App.js'
import ReactDOM from './core/ReactDOM.js'
ReactDOM.createRoot(document.querySelector("#root")).render(App)

运行项目发现效果是一样的

使用 jsx

因为刚开始使用vite创建的项目 所以把App.js和main.js改成App.jsx和main.jsx 然后在index.hrml script引用 在运行项目即可

以上就是我们对mini-react的基本搭建

任务调度器&fiber架构

使用了 requestIdleCallback
为什么使用requestIdleCallback
因为 render 函数中执行大量dom 渲染的时候 会导致卡顿,我们需要对任务进行拆分,拆分成一个个小任务,然后依次执行,从而避免卡顿

封装一个workLoop方法

  • React.js
// 工作循环函数
let nextWorkOfUnit = {};
function workLoop(deadline) {
  // 工作循环函数,用于不断执行任务直至满足条件
  let shouldDeadline = false; // 初始化一个变量用于判断是否需要满足截止时间
  while (!shouldDeadline && nextWorkOfUnit) {
    // 循环执行任务,直到满足截止时间条件或者没有任务可执行
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit); // 使用任务处理函数处理当前任务对象
    shouldDeadline = deadline.timeRemaining() < 1; // 判断当前任务的截止时间是否小于1
  }
  // 请求下一次执行该函数的时间间隔,并递归调用该函数
  requestIdleCallback(workLoop);
}
  requestIdleCallback(workLoop);
  • 实现 filber 架构(把树结构转变成链表结构)
  • 首现判断当前子节点(child)中有没有子节点(child)
  • 如果当前节点没有子节点,就找当前节点的兄弟节点(sibling)
  • 如果当前节点没有兄弟节点(sibling),就找当前节点的父节点(parent) 的兄弟节点(sibling)
  • 如果当前节点的父节点(parent) 没有兄弟节点(sibling),就在往上找
  • 如果当前节点没有 parent 那么就结束

实现自己的mini-react,react.js,前端,javascript

  • 实现performWorkOfUnit
function createDom(type) {
  return type === "TEXT_ELEMENT"
    ? document.createTextNode("")
    : document.createElement(type);
}

function updateProps(dom,props){
    Object.keys(props).forEach((key) => {
        if (key !== "children") {
          dom[key] = props[key];
        }
      })
}
function initChildren(fiber){
    const children = fiber.props.children;
    let prvChild = null;
    children.forEach((child, index) => {
      const newFiber = {
        type: child.type,
        props: child.props,
        parent: fiber,
        child: null,
        sibling: null,
        dom: null,
      };
      if (index === 0) {
        fiber.child = newFiber;
      } else {
        prvChild.sibling = newFiber;
      }
      prvChild = newFiber;
    });
}
function performWorkOfUnit(fiber) {
  if (!fiber.dom) {
    const dom = (fiber.dom =createDom(fiber.type));
    fiber.parent.dom.append(dom);
    updateProps(dom,fiber.props)
  }
  initChildren(fiber)
  if (fiber.child) {
    return fiber.child;
  }
  if (fiber.sibling) {
    return fiber.sibling;
  }
  return fiber.parent?.sibling;
}
  • 修改render
function render(el, container) {
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el],
    },
  };
}

统一提交&实现 function component

统一提交

我们使用 requestIdleCallback实现任务调度,但是它只有等待浏览器有空闲时间才会执行任务,如果任务很多,那页面渲染就只能看到一半渲染。

  • React.js
let nextWorkOfUnit = {}
let root = null
function render(el, container) {
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el],
    },
  };
  root=nextWorkOfUnit
}
function workLoop(deadline) {
  let shouldDeadline = false;
  while (!shouldDeadline && nextWorkOfUnit) {
    nextWorkOfUnit = sunWorkFun(nextWorkOfUnit);
    shouldDeadline = deadline.timeRemaining() < 1;
  }
  if(!nextWorkOfUnit&&root){
    commitRoot()
  }
  requestIdleCallback(workLoop);
}
requestIdleCallback(workLoop);

function commitRoot(){
  commitWork(root.child)
}

function commitWork(fiber){
  if(!fiber) return;
  if(fiber.dom){
    fiber.parent.dom.append(fiber.dom);
  }
  commitWork(fiber.child)
  commitWork(fiber.sibling)
}

实现支持 function component

现在我们渲染dom是这样

  • ReactDOM.createRoot(document.querySelector("#root")).render(App)
    我们想改成
  • ReactDOM.createRoot(document.querySelector("#root")).render(<App />)
  • React.js
// 创建元素节点
/**
 * 创建一个元素
 * @param {string} type - 元素的类型
 * @param {Object} props - 元素的属性
 * @param {...any} children - 元素的子元素
 * @returns {Object} - 创建的元素对象
 */
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        /**
         * 判断子元素是否为文本节点
         * @type {boolean}
         */
        const isTextNode =
          typeof child === "string" || typeof child === "number";
        return isTextNode ? createTextNode(child) : child;
      }),
    },
  };
}
// 提交节点
function commitWork(fiber) {
  // 检查fiber是否存在
  if (!fiber) return;

  // 初始化fiber的父级节点
  let fiberParent = fiber.parent;

  // 循环找到有dom节点的父级节点
  while (!fiberParent.dom) {
    fiberParent = fiberParent.parent;
  }
  fiberParent.dom.append(fiber.dom);

  // 递归调用commitWork函数处理fiber的子节点
  commitWork(fiber.child);

  // 递归调用commitWork函数处理fiber的兄弟节点
  commitWork(fiber.sibling);
}
/**
 * 更新函数组件
 * 
 * @param {Object} fiber - Fiber对象
 */
function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)];
  initChildren(fiber, children);
}

function updateHostComponent(fiber) {
  // 如果fiber没有关联的dom节点
  if (!fiber.dom) {
    // 创建一个新的dom节点
    const dom = (fiber.dom = createDom(fiber.type));
    // 更新dom节点的属性
    updateProps(dom, fiber.props, {});
  }
  // 获取子元素
  const children = fiber.props.children;
  // 初始化子元素
  initChildren(fiber, children);
}
/**
 * 函数:performWorkOfUnit
 * 描述:用于渲染节点的函数
 * 参数:
 * - fiber:fiber对象,包含节点的信息
 */
function performWorkOfUnit(fiber) {
  /**
   * 变量:isFunctionComponent
   * 类型:boolean
   * 描述:判断fiber.type是否为函数节点
   */
  const isFunctionComponent = typeof fiber.type === "function";
  /**
   * 判断不是函数节点且fiber.dom不存在时,创建dom节点并更新属性
   */
  if (isFunctionComponent) {
    updateFunctionComponent(fiber);
  } else {
    updateHostComponent(fiber);
  }

  /**
   * 判断fiber是否有子节点,返回子节点
   */
  if (fiber.child) {
    return fiber.child;
  }
  /**
   * 变量:nextFiber
   * 类型:fiber对象
   * 描述:遍历fiber对象的父级节点
   */
  let nextFiber = fiber;
  while (nextFiber) {
    /**
     * 判断nextFiber是否有兄弟节点,返回兄弟节点
     */
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.parent;
  }
}

进军 vdom 的更新

实现绑定事件

  • App.jsx
function App() {
 
 function handleClick(){
    console.log("🚀 ~ App ~ App:")
  }
  
  return (
    <div>
      <button onClick={handleClick}>click</button> 
    </div>
  );
}
  • 修改updateProps函数
function updateProps(dom,props){
    Object.keys(props).forEach(key=>{
        if(key.startsWith('on')){
            // 事件名
            const eventType = key.slice(2).toLowerCase();// 或.substring(2);
            dom.addEventListener(eventType,props[key]);
        }else{
            dom[key] = props[key];
        }
    })
}

更新props

对比 new vdom tree VS old vdom tree,找出差异,更新dom

实现自己的mini-react,react.js,前端,javascript

  • 创建 update 函数 使用currentRoot变量来存放当前的根节点
let currentRoot=null;
/**
 * 递归地提交根节点的子节点工作
 */
function commitRoot() {
  commitWork(root.child);
  currentRoot = root
  root = null
}

function update() {
  // 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
  root = {
    dom: currentRoot.dom,
    props: currentRoot.props
  };
  root=nextWorkOfUnit
}
  • 找到老的节点
    实现自己的mini-react,react.js,前端,javascript
function update() {
  // 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
  root = {
    dom: currentRoot.dom,
    props: currentRoot.props,
    alternate: currentRoot //老的节点
  };
  root=nextWorkOfUnit
}
/**
 * 初始化子fiber对象
 * @param {Object} fiber - 当前fiber对象
 * @param {Array} children - 子子fiber对象数组
 */
function initChildren(fiber, children) {
  let prvChild = null; // 上一个子fiber对象
  let oldFiber = fiber.alternate?.child; // 备用fiber对象
  children.forEach((child, index) => { // 遍历子fiber对象数组
    const isSameType = oldFiber && oldFiber.type === child.type; // 判断是否为相同类型
    let newFiber = null; // 创建新的fiber对象
    if (isSameType) { // 如果是相同类型
      newFiber = {
        type: child.type, // 设置fiber类型为子fiber类型
        props: child.props, // 设置fiber属性为子fiber属性
        parent: fiber, // 设置fiber父fiber为当前fiber
        child: null, // 设置fiber子fiber为null
        sibling: null, // 设置fiber兄弟fiber为null
        dom: oldFiber.dom, // 设置fiber的dom为备选fiber的dom
        effectTag: "update", // 设置fiber效果标签为"update"
        alternate: oldFiber // 设置fiber备选fiber为备选fiber
      };
    } else { // 如果不是相同类型
      newFiber = {
        type: child.type, // 设置fiber类型为子fiber类型
        props: child.props, // 设置fiber属性为子fiber属性
        parent: fiber, // 设置fiber父fiber为当前fiber
        child: null, // 设置fiber子fiber为null
        sibling: null, // 设置fiber兄弟fiber为null
        dom: null, // 设置fiber的dom为null
        effectTag: "placement" // 设置fiber效果标签为"placement"
      };
    }

    if (oldFiber) { // 如果备选fiber存在
      oldFiber = oldFiber.sibling; // 将备选fiber指向下一个fiber
    }
    if (index === 0) { // 如果是第一个子fiber
      fiber.child = newFiber; // 设置当前fiber的子fiber为新fiber
    } else {
      prvChild.sibling = newFiber; // 设置前一个子fiber的兄弟fiber为新fiber
    }
    prvChild = newFiber; // 更新前一个子fiber为新fiber
  });
}
  • 比较diff props
  • old 有 new 没有 删除
  • new 有 old 没有 添加
  • old new 都有 更新 2和3 可以合并成一个处理
// 提交节点
function commitWork(fiber) {
  // 检查fiber是否存在
  if (!fiber) return;

  // 初始化fiber的父级节点
  let fiberParent = fiber.parent;

  // 循环找到有dom节点的父级节点
  while (!fiberParent.dom) {
    fiberParent = fiberParent.parent;
  }

  // 如果fiber的effectTag为'update',则更新fiber的dom节点的属性
  if (fiber.effectTag === 'update') {
    updateProps(fiber.dom, fiber.props, fiber.alternate?.props)
  }
  // 如果fiber的effectTag为'placement',则将fiber的dom节点添加到fiber的父级节点中
  else if (fiber.effectTag === 'placement') {
    if (fiber.dom) {
      fiberParent.dom.append(fiber.dom);
    }
  }

  // 递归调用commitWork函数处理fiber的子节点
  commitWork(fiber.child);

  // 递归调用commitWork函数处理fiber的兄弟节点
  commitWork(fiber.sibling);
}
/**
 * 更新属性
 * @param {Object} dom - DOM元素
 * @param {Object} nextProps - 新的属性
 * @param {Object} prvProps - 旧的属性
 */
function updateProps(dom, nextProps, prvProps) {
  Object.keys(prvProps).forEach((key) => {
    if (key !== "children") { // 排除children属性
      if (!(key in nextProps)) { // 如果新属性中不存在该键
        dom.removeAttribute(key); // 移除该属性
      }
    }
  });
  Object.keys(nextProps).forEach((key) => {
    if (key !== "children") { // 排除children属性
      if (nextProps[key] !== prvProps[key]) { // 如果新旧属性值不相同
        if (key.startsWith("on")) { // 如果属性名以"on"开头
          const eventType = key.slice(2).toLowerCase() // 获取事件类型
          dom.removeEventListener(eventType, prvProps[key]) // 移除事件监听器
          dom.addEventListener(eventType, nextProps[key]) // 添加事件监听器
        } else {
          dom[key] = nextProps[key]; // 更新属性值
        }
      }
    }
  });
}
function updateHostComponent(fiber) {
  // 如果fiber没有关联的dom节点
  if (!fiber.dom) {
    // 创建一个新的dom节点
    const dom = (fiber.dom = createDom(fiber.type));
    // 更新dom节点的属性
    updateProps(dom, fiber.props, {});
  }
  // 获取子元素
  const children = fiber.props.children;
  // 初始化子元素
  initChildren(fiber, children);
}
  • 变量重命名 为了跟react保持一致性 root改为wipRoot initChildren改为reconcileChildren
  • 更新以下代码
function render(el, container) {
  // 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
  wipRoot = {
    dom: container,
    props: {
      children: [el],
    },
  };
  nextWorkOfUnit = wipRoot
}
function update() {
  // 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
  wipRoot = {
    dom: currentRoot.dom,
    props: currentRoot.props,
    alternate: currentRoot
  };
  nextWorkOfUnit = wipRoot
}
  • 导出update
  • 查看效果 App.jsx
	import React from "../core/React.js";
	let count = 10;
	let props = {id:'container'}
	function Counter({num}) {
	  function handleClick(){
	    count++;
	    props = {}
	    React.update()
	  }
	  return <div {...props}>Hi React <button onClick={handleClick}>count:{count}</button></div>;
	}
	function App(){
	    return <div>
	    Hello mini react!
	    <Counter num={10} />
	  </div>
	}
	export default App;

击杀 update children

  • 创建和删除(type不一致的时候 删除旧的 创建新的)
let deletions = [];
let wipFiber = null;
/**
 * 递归地提交根节点的子节点工作
 */
function commitRoot() {
  deletions.forEach(commitDeletions);
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
  deletions = [];
}

function commitDeletions(fiber) {
  if (fiber.dom) {
    // 初始化fiber的父级节点
    let fiberParent = fiber.parent;

    // 循环找到有dom节点的父级节点
    while (!fiberParent.dom) {
      fiberParent = fiberParent.parent;
    }
    fiberParent.dom.removeChild(fiber.dom);
  } else {
    commitDeletions(fiber.child);
  }
}
/**
 * 初始化子fiber对象
 * @param {Object} fiber - 当前fiber对象
 * @param {Array} children - 子子fiber对象数组
 */
function reconcileChildren(fiber, children) {
  let prvChild = null; // 上一个子fiber对象
  let oldFiber = fiber.alternate?.child; // 备用fiber对象

  children.forEach((child, index) => {
    // 遍历子fiber对象数组
    const isSameType = oldFiber && oldFiber.type === child.type; // 判断是否为相同类型
    let newFiber = null; // 创建新的fiber对象

    if (isSameType) {
      // 如果是相同类型
      newFiber = {
        type: child.type, // 设置fiber类型为子fiber类型
        props: child.props, // 设置fiber属性为子fiber属性
        parent: fiber, // 设置fiber父fiber为当前fiber
        child: null, // 设置fiber子fiber为null
        sibling: null, // 设置fiber兄弟fiber为null
        dom: oldFiber.dom, // 设置fiber的dom为备选fiber的dom
        effectTag: "update", // 设置fiber效果标签为"update"
        alternate: oldFiber, // 设置fiber备选fiber为备选fiber
      };
    } else {
      if (child) {
        // 如果不是相同类型
        newFiber = {
          type: child.type, // 设置fiber类型为子fiber类型
          props: child.props, // 设置fiber属性为子fiber属性
          parent: fiber, // 设置fiber父fiber为当前fiber
          child: null, // 设置fiber子fiber为null
          sibling: null, // 设置fiber兄弟fiber为null
          dom: null, // 设置fiber的dom为null
          effectTag: "placement", // 设置fiber效果标签为"placement"
        };
      }
      if (oldFiber) {
        deletions.push(oldFiber); // 将备选fiber添加到删除数组
      }
    }

    if (oldFiber) {
      // 如果备选fiber存在
      oldFiber = oldFiber.sibling; // 将备选fiber指向下一个fiber
    }
    if (index === 0) {
      // 如果是第一个子fiber
      fiber.child = newFiber; // 设置当前fiber的子fiber为新fiber
    } else {
      prvChild.sibling = newFiber; // 设置前一个子fiber的兄弟fiber为新fiber
    }
    if (newFiber) {
      prvChild = newFiber; // 更新前一个子fiber为新fiber
    }
  });

  while (oldFiber) {
    deletions.push(oldFiber); // 将备选fiber添加到删除数组
    oldFiber = oldFiber.sibling;
  }
}
// 工作循环函数
function workLoop(deadline) {
  // 工作循环函数,用于不断执行任务直至满足条件
  let shouldDeadline = false; // 初始化一个变量用于判断是否需要满足截止时间
  while (!shouldDeadline && nextWorkOfUnit) {
    // 循环执行任务,直到满足截止时间条件或者没有任务可执行
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit); // 使用任务处理函数处理当前任务对象
    if (wipRoot?.sibling?.type === nextWorkOfUnit?.type) {
      nextWorkOfUnit = undefined;
    }
    shouldDeadline = deadline.timeRemaining() < 1; // 判断当前任务的截止时间是否小于1
  }
  if (!nextWorkOfUnit && wipRoot) {
    // 如果没有任务可执行且存在根对象,则提交根对象
    commitRoot();
  }
  // 请求下一次执行该函数的时间间隔,并递归调用该函数
  requestIdleCallback(workLoop);
}
/**
 * 更新函数组件
 *
 * @param {Object} fiber - Fiber对象
 */
function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  const children = [fiber.type(fiber.props)];
  reconcileChildren(fiber, children);
}
function update() {
  const currentFiber = wipFiber;
  return () => {
    // 将 el 元素作为子元素,将 container 元素作为容器,创建 nextWorkOfUnit 对象
    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    };
    nextWorkOfUnit = wipRoot;
  };
}
  • App.jsx
function Bar() {
  const update =  React.update()
  function handleClick(){
    countBar++;
    update()
  }
  
  return <div>
    <h1>Foo</h1>
    {countBar}
    <button onClick={handleClick}>click</button>
  </div>;
}

搞定 useState

  • 首先经常见到 react更新状态的时候使用useState
  • App.jsx
function Foo() {
  const [count,setCount] = React.useState(1)
  function handleClick(){
    setCount((c)=>c+1)
  }
  
  return <div>
    <h1>Foo</h1>
    {count}
    <button onClick={handleClick}>click</button>
  </div>;
}
  • main.js
/**
 * useState(initialState)函数用于在当前fiber中创建一个新的stateHook。
 * @param {any} initialState - 初始状态值
 * @returns {Array} - 返回包含当前状态和setState方法的数组
 */
function useState(initialState) {
  const currentFiber = wipFiber;
  const oldHooks = currentFiber.alternate?.stateHook;
  const stateHook = {
    state: oldHooks ? oldHooks.state : initialState
  };
  currentFiber.stateHook = stateHook;
  
  /**
   * setState(actions)函数用于更新stateHook.state。
   * @param {Function|Object} actions - 更新状态的函数或要更新的状态对象
   */
  function setState(actions) {
    stateHook.state = actions(stateHook.state)
    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    };
    nextWorkOfUnit = wipRoot;
  }

  return [stateHook.state, setState];
}

这样我们在使用上面dom的时候就可以正常使用了 但是还有问题 就是现在只有一个hook 假如有多个的话最后一个执行结果就会替换前面的 所有还需要把之前的存起来 并且批量执行函数

let stateHooks=[];
let stateHookIndex=0;

/**
 * 更新函数组件
 *
 * @param {Object} fiber - Fiber对象
 */
function updateFunctionComponent(fiber) {
  stateHooks = [];
  stateHookIndex = 0;
  wipFiber = fiber;
  const children = [fiber.type(fiber.props)];
  reconcileChildren(fiber, children);
}
/**
 * useState(initialState)函数用于在当前fiber中创建一个新的stateHook。
 * @param {any} initialState - 初始状态值
 * @returns {Array} - 返回包含当前状态和setState方法的数组
 */
function useState(initialState) {
  const currentFiber = wipFiber;
  const oldHooks = currentFiber.alternate?.stateHooks[stateHookIndex];
  const stateHook = {
    state: oldHooks ? oldHooks.state : initialState,
    queue: oldHooks ? oldHooks.queue : [],
  };
  // 执行stateHook.queue中的所有action,更新stateHook.state
  stateHook.queue.forEach((action) => {
    stateHook.state = action(stateHook.state);
  });
  stateHook.queue = [];
  stateHookIndex++;
  stateHooks.push(stateHook);
  currentFiber.stateHooks = stateHooks;
  
  /**
   * setState(actions)函数用于更新stateHook.state。
   * @param {Function|Object} actions - 更新状态的函数或要更新的状态对象
   */
  function setState(actions) {
    // 将action添加到stateHook.queue中
    stateHook.queue.push(actions);
    wipRoot = {
      ...currentFiber,
      alternate: currentFiber,
    };
    nextWorkOfUnit = wipRoot;
  }

  return [stateHook.state, setState];
}

这样的话就解决了上面的问题 但是假如说我们调用setCount(11)还是有问题 因为我们存入的不是function 所有还需要修改useState

function useState(initialState) {
   // 省略...
  function setState(actions) {
    const isFunction = typeof actions === "function";
    // 将action添加到stateHook.queue中
    stateHook.queue.push(isFunction ? actions : () => actions);
    // 省略...
  }

  return [stateHook.state, setState];
}
  • 提前检测 减少不必要的更新 假如说当前值是11,setCount的还是11 这样情况就没必要更新
function useState(initialState) {
  // 省略...
  function setState(actions) {
    const isFunction = typeof actions === "function";
    const eagerState = isFunction ? actions(stateHook.state) : actions;
    if (eagerState === stateHook.state) return;
    // 将action添加到stateHook.queue中
    stateHook.queue.push(isFunction ? actions : () => actions);
   // 省略...
  }
  return [stateHook.state, setState];
}

搞定 useEffect

  • useEffect 调用的时机是在 React 完成对 DOM 的渲染之后,并且浏览器完成绘制之前
  • useEffect 的第二个参数是依赖数组,不指定的时候副作用指挥在组件渲染后执行一次,如果指定了依赖数组,那么只有当依赖数组中的值发生变化时,副作用才会执行
  • App.jsx
function Foo() {
  console.log("🚀 ~ Foo ~ Foo:")
  const [count,setCount] = React.useState(1)
  function handleClick(){
    setCount((c)=>c+1)
  }

  React.useEffect(()=>{
    console.log('useEffect: ');
  },[])
  
  return <div>
    <h1>Foo</h1>
    {count}
    <button onClick={handleClick}>click</button>
  </div>;
}
  • React.js
/**
 * 递归地提交根节点的子节点工作
 */
function commitRoot() {
  deletions.forEach(commitDeletions);
  commitWork(wipRoot.child);
  commitEffectHooks();
  currentRoot = wipRoot;
  wipRoot = null;
  deletions = [];
}

function useEffect(callback, deps) {
  // 创建一个effectHook对象,包含callback、deps和cleanup属性
  const effectHook = {
    callback,
    deps,
    cleanup: undefined,
  };
  // 将effectHooks赋值给wipFiber的effectHooks属性
  wipFiber.effectHook = effectHook;
}
function commitEffectHooks() {
  // 开始执行effectHooks的回调函数
  function run(fiber) {
    if (!fiber) return;
     fiber.effectHook?.callback()
    // 递归执行run函数
    run(fiber.child);
    run(fiber.sibling);
  }
  // 开始执行effectHooks的回调函数
  run(wipRoot);
}
  • 上面函数初始化的时候就可以监听到了
  • 更新监听
function commitEffectHooks() {
  // 开始执行effectHooks的回调函数
  function run(fiber) {
    if (!fiber) return;

    if (!fiber.alternate) {
       fiber.effectHook?.callback()
    } else {
      const oldHooks = fiber.alternate?.effectHook;
          const needUpdate = oldHooks.deps.some((olDep, idx) => {
            return olDep !== fiber.effectHook.deps[idx];
          });
          needUpdate && fiber.effectHook.callback();
    }
    // 递归执行run函数
    run(fiber.child);
    run(fiber.sibling);
  }
  run(wipRoot);
}

更新已经实现 但是还有问题 他现在只能监听一个 所有监听多个需要修改代码

/**
 * 更新函数组件
 *
 * @param {Object} fiber - Fiber对象
 */
function updateFunctionComponent(fiber) {
  stateHooks = [];
  stateHookIndex = 0;
  effectHooks = [];
  wipFiber = fiber;
  const children = [fiber.type(fiber.props)];
  reconcileChildren(fiber, children);
}
let effectHooks = [];
function useEffect(callback, deps) {
  // 创建一个effectHook对象,包含callback、deps和cleanup属性
  const effectHook = {
    callback,
    deps
  };
  // 将effectHook添加到effectHooks数组中
  effectHooks.push(effectHook);
  // 将effectHooks赋值给wipFiber的effectHooks属性
  wipFiber.effectHooks = effectHooks;
}

function commitEffectHooks() {
  // 开始执行effectHooks的回调函数
  function run(fiber) {
    if (!fiber) return;

    if (!fiber.alternate) {
      // 初始化effectHooks的cleanup方法
      fiber.effectHooks?.forEach((hook) => hook.callback());
    } else {
      // 更新effectHooks的cleanup方法
      fiber.effectHooks?.forEach((newHooks, index) => {
        if (newHooks.deps.length > 0) {
          const oldHooks = fiber.alternate?.effectHooks[index];
          const needUpdate = oldHooks.deps.some((olDep, idx) => {
            return olDep !== newHooks.deps[idx];
          });
          needUpdate && newHooks.callback();
        }
      });
    }
    // 递归执行run函数
    run(fiber.child);
    run(fiber.sibling);
  }
  // 开始执行effectHooks的回调函数
  run(wipRoot);
}
  • cleanup 实现
  • cleanup在调用useEffect之前调用 当deps为空的时候不会返回cleanup
  • 作用就是清空副作用
  • App.jsx 表现形式
function Foo() {
  console.log("🚀 ~ Foo ~ Foo:")
  const [count,setCount] = React.useState(1)
  function handleClick(){
    setCount((c)=>c+1)
    setBar('bar11')
  }

  React.useEffect(()=>{
    console.log('useEffect: ');
    return ()=>{
      console.log('return-cleanup: ');
    }
  },[])
   React.useEffect(()=>{
    console.log('count: ',count);
    return ()=>{
      console.log('return-cleanup: ');
    }
  },[count])
  
  return <div>
    <h1>Foo</h1>
    {count}
    <button onClick={handleClick}>click</button>
  </div>;
}
  • React.js
function useEffect(callback, deps) {
  // 创建一个effectHook对象,包含callback、deps和cleanup属性
  const effectHook = {
    callback,
    deps,
    cleanup: undefined,
  };
  // 将effectHook添加到effectHooks数组中
  effectHooks.push(effectHook);
  // 将effectHooks赋值给wipFiber的effectHooks属性
  wipFiber.effectHooks = effectHooks;
}
function commitEffectHooks() {
  // 开始执行effectHooks的回调函数
  function run(fiber) {
    if (!fiber) return;

    if (!fiber.alternate) {
      // 初始化effectHooks的cleanup方法
      fiber.effectHooks?.forEach((hook) => {
        hook.cleanup = hook.callback();
      });
    } else {
      // 更新effectHooks的cleanup方法
      fiber.effectHooks?.forEach((newHooks, index) => {
        if (newHooks.deps.length > 0) {
          const oldHooks = fiber.alternate?.effectHooks[index];
          const needUpdate = oldHooks.deps.some((olDep, idx) => {
            return olDep !== newHooks.deps[idx];
          });
          needUpdate && (newHooks.cleanup = newHooks.callback());
        }
      });
    }
    // 递归执行run函数
    run(fiber.child);
    run(fiber.sibling);
  }

  // 执行effectHooks的cleanup方法
  function runCleanup(fiber){
    // 如果fiber为空,直接返回
    if (!fiber) return;
    // 如果fiber的alternate属性存在,且effectHooks属性存在
    fiber.alternate?.effectHooks?.forEach(hook=>{
      // 如果hook的deps数组长度大于0
      if (hook.deps.length > 0) {
        // 如果hook有cleanup方法,则执行cleanup方法
        hook.cleanup && hook.cleanup();
      }
    })
    // 递归执行runCleanup方法,传入fiber的child属性作为参数
    runCleanup(fiber.child);
    // 递归执行runCleanup方法,传入fiber的sibling属性作为参数
    runCleanup(fiber.sibling);
  }

  // 开始执行effectHooks的回调函数
  runCleanup(wipRoot);
  run(wipRoot);
}

到此我们最简单的mini-react 就完成了
文中仅是自己在学习过程的总结,很多地方可能文笔有误,个人思考能力也不足。仅作为个人的学习结课文章,谢谢大家。

github文章来源地址https://www.toymoban.com/news/detail-814351.html

到了这里,关于实现自己的mini-react的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 从javascript到vue再到react:前端开发框架的演变

    目录 JavaScript: 动态语言的基础 JavaScript:Web开发的起点 Vue.js: 渐进式框架的兴起 Vue.js:简洁、高效的前端框架 React.js: 声明式UI的革新 React.js:强大、灵活的前端框架 演变之路与未来展望 演变过程 当提到前端开发中的框架时,JavaScript、Vue.js和React.js是三个最常见的名词。它

    2024年02月07日
    浏览(51)
  • JavaScript框架 Angular、React、Vue.js 的全栈解决方案比较

    在 Web 开发领域,JavaScript 提供大量技术栈可供选择。其中最典型的三套组合,分别是 MERN、MEAN 和 MEVN。前端框架(React、Angular 和 Vue)进行简化比较。 MERN 技术栈包含四大具体组件: MongoDB:一款强大的 NoSQL 数据库,以灵活的 JSON 格式存储数据。 Express.js:一套极简但强大的

    2024年02月03日
    浏览(54)
  • 2023年最佳JavaScript框架:React、Vue、Angular和Node.js的比较

    🎉欢迎来到Java学习路线专栏~探索2023年最佳JavaScript框架:React、Vue、Angular和Node.js的比较 ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:Java学习路线 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 🍹文章作者技术和水

    2024年02月11日
    浏览(47)
  • jQuery.js - 前端必备的Javascript库

    作者: WangMin 格言: 努力做好自己喜欢的每一件事 jQuery.js 是什么? jQuery是一个快速简洁、免费开源易用的JavaScript框架, 倡导写更少的代码,做更多的事情 。它封装JavaScript常用的功能代码,提供了一种简便的JavaScript设计模式,以及我们开发中常用到的操作DOM的API,优化HTML文

    2024年02月05日
    浏览(70)
  • Node.js npm V8 React Express的运行配合关系:构建JavaScript应用的基石

    目录 Node.js 和 V8 引擎 Node.js 和 npm LTS(Long Term Support) React Node.js的作用 Express Node.js 和 V8 引擎 Node.js 使用 Google 的 V8 JavaScript 引擎 来执行 JavaScript 代码。V8 是一个高性能的 JavaScript 和 WebAssembly 引擎,用于在 Google Chrome 浏览器和 Node.js 中运行 JavaScript。 V8 引擎的更新 通常包括

    2024年03月12日
    浏览(59)
  • 前端框架之争:Vue.js vs. React.js vs. Angular

    🎉欢迎来到Web前端专栏~前端框架之争:Vue.js vs. React.js vs. Angular ☆* o(≧▽≦)o *☆嗨~我是IT·陈寒🍹 ✨博客主页:IT·陈寒的博客 🎈该系列文章专栏:架构设计 📜其他专栏:Java学习路线 Java面试技巧 Java实战项目 AIGC人工智能 数据结构学习 🍹文章作者技术和水平有限,如果

    2024年02月07日
    浏览(91)
  • React.js前端 + Spring Boot后端员工管理

    该项目是一个员工管理系统,前端使用 React.js 构建,后端使用 Spring Boot 和 Data JPA 和 Lombok 构建。它提供了有效管理员工信息的全面解决方案。 特征 响应式设计:响应式 UI 设计,确保跨各种设备的可用性。 数据验证:验证用户输入以确保数据完整性。 使用的技术 前端:R

    2024年04月28日
    浏览(47)
  • web前端框架JS学习之JavaScript类型转换

    vascript有多种数据类型,如字符串、数字、布尔等,可以通过typeof语句来查看变量的数据类型。数据类型转换就是数据类型之间相互转换,比如把数字转成字符串、把布尔值转成字符串、把字符串转成数字等,这在工作也是经常碰到的。 本期我们就给大家说说web前端框架JS学

    2024年02月10日
    浏览(59)
  • 使用node搭建服务器,前端自己写接口,将vue或react打包后生成的dist目录在本地运行

    vue项目打包后生成的dist目录如果直接在本地打开index.html,在浏览器中会报错,无法运行起来。 通常我是放到后端搭建的服务上面去运行,当时前端自己也可以是node,nuxt搭建服务器,写接口等等 如果想在本地运行,我们可以借助node.js+express搭建一个服务器,将打包后的文件部

    2024年02月03日
    浏览(38)
  • 【前端灵魂脚本语言JavaScript⑤】——JS中数组的使用

    🐚 作者: 阿伟 💂 个人主页: Flyme awei 🐋 希望大家多多支持😘一起进步呀! 💬 文章对你有帮助👉关注✨点赞👍收藏📂 第一种: var 数组名 = new Array(); 创建一个空数组 第二种: var arr2 = new Array(10); 创建一个定长为10的数组 第三种 var arr3 = new Array(a,b,c); 创建时直接指定元素值

    2023年04月08日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包