简单实现React.createElement跟render函数

jsx 复制代码
import React from 'react'

console.log(<div>hello <span className='className' style={{
  color: 'red'
}}>baby</span></div>)

运行以上代码段,可以观察一下console打印出来的结果

这里其实是babel将jsx转换成了React.createElement。

jsx 复制代码
React.createElement('div', {}, 'hello', React.createElement('span', {
    className: 'className',
    style: {
        color: 'red'
    }
}, 'baby'))

最后这段代码返回的则是以上图打印的一个vnode对象

现在开始实现一个createElement函数

js 复制代码
const REACT_ELEMENT = Symbol('react.element')

function createElement(type, properties, ...children) {
  let ref = properties.ref || null;
  let key = properties.key || null;
   // 这里则是将一些特殊的属性去除掉。
  ["key", "ref"].forEach((key) => {
    delete properties[key];
  });
  let props = { ...properties };
  // 这里由于vnode的children有两种状态,一种是一个子vnode的时候则直接是对象
  // 如果是多个子vnode时候则是一个数组
  // 其实我觉得可以都统一为对象会更好统一。
  if (children.length <= 1) {
    // 对象或者undefined
    props.children = children[0];
  } else {
   // 数组
    props.children = Array.prototype.slice.call(arguments, 2);
  }

  return {
    type,
    $$typeof: REACT_ELEMENT,
    ref,
    key,
    props,
  };
}

接下来开始实现render函数

js 复制代码
// VNode则是createElement生成的虚拟dom对象
// containerDOM则是需要挂载的dom元素对象
function render(VNode, containerDOM) {
  // 将虚拟dom转化成真实Dom
  // 将得到的真实dom挂载到containerDOM中
  mount(VNode, containerDOM);
}

function mount(VNode, containerDOM) {
  let newDOM = createDOM(VNode);
  newDOM && containerDOM.appendChild(newDOM);
}

function createDOM(VNode) {
  // 1. 创建元素 2. 处理子元素 3. 处理属性值
  const { type, props } = VNode;
  let dom;
  if (type && VNode.$$typeof === REACT_ELEMENT) {
    dom = document.createElement(type);
  }
  if (props) {
    if (typeof props.children === "object" && props.children.type) {
      mount(props.children, dom);
    } else if (Array.isArray(props.children)) {
      mounArray(props.children, dom);
    } else if (typeof props.children === "string") {
      dom.appendChild(document.createTextNode(props.children));
    }
  }
  console.log(VNode)
  // 处理属性
  setPropsForDOM(dom, props);

  return dom;
}

function setPropsForDOM(dom, VNodeProps = {}) {
  if (!dom) return;
  for (let key in VNodeProps) {
    if (key === "children") {
      continue;
    }
    // onClick
    if (/^on[A-Z].*/.test(key)) {
        // TODO 暂不处理
    } else if (key === 'style') {
        Object.keys(VNodeProps[key]).forEach(styleName => {
            dom.style[styleName] = VNodeProps[key][styleName]
        })
    } else {
        // dom[key] = VNodeProps[key]
        dom.setAttribute(key, VNodeProps[key])
    }
  }
}

function mounArray(children, parent) {
  if (!Array.isArray(children)) return;
  for (let i = 0; i < children.length; i++) {
    if (typeof children[i] === "string") {
      parent.appendChild(document.createTextNode(children[i]));
    } else {
      mount(children[i], parent);
    }
  }
}

我们可以把render函数分为两个步骤

  1. 将虚拟dom转化成真实Dom
  2. 将得到的真实dom挂载到containerDOM中

createDOM函数中主要逻辑则是通过type对应元素具体类型,通过document.createElement方法创建对应的dom兑现 接下来则是需要考虑有子元素存在的情况

  1. 单个子元素
  2. 多个子元素
  3. 元素是否是字符串类型 如果是字符类型则直接映射为文本对象,通过document.createTextNode创建

最后则是处理props属性部分 首先需要去除一些特殊的key关键字的属性比如children onXXX style 之类 其实应该还有className需要转化成class之类,这里不具体深入,逻辑大致细节处理 现阶段主要考虑两个特殊问题

  1. style因为react要求的style传入的是只能是对象类型,则需要根据对象属性进行dom.style[key] = 'xxx'设置
  2. 另外一些属性则通过dom.setAttribute(key, value)设置
相关推荐
想学后端的前端工程师20 分钟前
【React Hooks深度实战指南:从原理到最佳实践】
前端·react.js·前端框架
San30.2 小时前
从面向对象 CSS 到原子化架构:Tailwind CSS 与 React 性能优化实践
css·react.js·架构
IT古董2 小时前
企业级官网全栈(React·Next.js·Tailwind·Axios·Headless UI·RHF·i18n)实战教程-第五篇:登录态与权限控制
javascript·react.js·ui
未知原色16 小时前
web worker使用总结(包含多个worker)
前端·javascript·react.js·架构·node.js
开发者小天17 小时前
React中useMemo的使用
前端·javascript·react.js
im_AMBER17 小时前
weather-app开发手记 04 AntDesign组件库使用解析 | 项目设计困惑
开发语言·前端·javascript·笔记·学习·react.js
Bigger17 小时前
在 React 里优雅地 “隐藏 iframe 滚动条”
前端·css·react.js
Bigger18 小时前
shadcn-ui 的 Radix Dialog 这两个警告到底在说什么?为什么会报?怎么修?
前端·react.js·weui
T___T19 小时前
React Props:从基础使用到高级组件封装
前端·react.js
学习非暴力沟通的程序员19 小时前
Immer 实战案例解析:让不可变数据操作更简单
react.js