首页 > 其他分享 >实现 React 简易版 createElement 和 render 方法

实现 React 简易版 createElement 和 render 方法

时间:2023-05-20 22:31:47浏览次数:59  
标签:render value React 简易版 key props newElement createElement

前言

在 React 中,我们都知道可以写 jsx 代码会被编译成真正的 DOM 插入到要显示的页面上。这具体是怎么实现的,今天我们就自己动手做一下。

实现 createElement 方法

这个方法平时开发我们并不会用到,因为它是经 babel 编译后的代码,我们新建一个 React 项目,index.js 最简单的代码结构如下:

import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render(<h1 className='title'>Hello React</h1>, document.getElementById('root'))

这里就 jsx 会变编译成真正的 DOM ,把 html 代码拿到 babel 官网编译

于是我们就看到了 React.createElement() 方法,但这只是调用这个方法,它具体做了什么返回什么我们还不知道,我们可以打印这个函数运行的结果:

console.log(
  React.createElement(
    'h1',
    {
      className: 'title',
    },
    'Hello React'
  )
)

返回的这个对象就是虚拟 DOM 了。

我们来分析它返回的对象参数,首先第一个是

  • $$typeof: REACT_ELEMENT_TYPE

这个是 React 元素对象的标识属性

REACT_ELEMENT_TYPE 的值是一个 Symbol 类型,代表了一个独一无二的值。如果浏览器不支持 Symbol 类型,值就是一个二进制值。

  • key:这个比如循环中会用到这个 key 值
  • props:传入的属性值,比如 id, className, style, children 等
  • ref: DOM 的引用
  • 剩下的是私有属性(本篇不展开讨论)

在本篇我们会用自己简单的方式实现这两个方法,而不是根据源码,所以实现上的方法只要能实现它的基本功能即可;有个基本概念在,以后再循序渐进学习源码。

而 createElement 中有三个参数,更确切说是 n 个参数:

  • type:表示要渲染的元素类型。这里可以传入一个元素 Tag 名称,也可以传入一个组件(如 div span 等,也可以是是函数组件和类组件)
  • props:创建 React 元素所需要的 props。
  • childrens(可选参数):要渲染元素的子元素,这里可以向后传入 n 个参数。可以为文本字符串,也可以为数组

初步 createElement 方法:

// 创建 JSX 对象
function createElement(type, props, ...childrens) {
    return {
        type,
        props: {
          ...props,
          children: childrens.length <= 1 ? childrens[0] || '' : childrens,
        },
}

参数中 props 和 childrens 是并列关系,然后返回的 props 对象,里面包含了 children,所以我们需要再 props 里面添加 children 参数,然后根据 children 参数为一个或多个的可能在进行取值处理。

调用该方法:

console.log(
  createElement(
    'h1',
    {
      className: 'title',
    },
    'Hello React'
  )
)

除去其它本篇我们不讨论的属性,目前算是实现了一半;我们观察原来 React 自身方法输出的结果有 key, ref, 同输出的 props 也是并列关系,于是我们进一步作出处理

function createElement(type, props, ...childrens) {
  let ref, key
  if ('ref' in props) {
    ref = props['ref']
    props['ref'] = undefined
  }
  if ('key' in props) {
    key = props['key']
    props['key'] = undefined
  }
  return {
    type,
    props: {
      ...props,
      children: childrens.length <= 1 ? childrens[0] || '' : childrens,
    },
    ref,
    key,
  }
}

同样的方式调用结果如下:

如果添加多一些属性,我们来看看结果

console.log(
  createElement(
    'div',
    { id: 'box', className: 'box', style: { color: 'red' }, key: '20' },
    'this is text',
    createElement('h2', { className: 'title' }, 'hello'),
    createElement('div', { className: 'content' }, 'Hi')
  )
)

用了这种比较粗鲁的方式添加,设置为 undefined 在实现 render 方法的时候我们会根据这个忽略 props 内部的 key 和 props 属性,这里就实现了最基本的 createElement 方法了。

实现 render 方法

render 方法的第一个参数接收的是 createElement 返回的对象,也就是虚拟 DOM;第二个参数则是挂载的目标 DOM。同样的做法,我们用 babel 编译来看:

执行后,就被挂在到页面了

实现代码如下:

/*
 * 功能:把创建的对象生成对应的DOM元素,最后插入到页面中
 * objJSX: createElement 返回的 JSX 对象
 * container:挂载的容器,如 document.getElementById('root')
 */
function render(objJSX, container) {
  let { type, props } = objJSX
  let newElement = document.createElement(type)
  for (let attr in props) {
    // 遍历传入的 props 属性
    if (!props.hasOwnProperty(attr)) break // 不是私有的直接结束遍历
    let value = props[attr] // >如果当前属性没有值,直接不处理即可
    if (value == undefined) continue // NULL OR UNDEFINED

    // 对几个特殊属性单独设置
    switch (attr.toUpperCase()) {
      case 'ID':
        newElement.setAttribute('id', value)
        break
      case 'CLASSNAME':
        newElement.setAttribute('class', value)
        break
      case 'STYLE': // 传入的行内样式 style 是个对象,故需遍历赋值
        for (let styleAttr in value) {
          if (value.hasOwnProperty(styleAttr)) {
            newElement['style'][styleAttr] = value[styleAttr]
          }
        }
        break
      case 'CHILDREN':
        /*
         * 可能是一个值:可能是字符串也可能是一个JSX对象
         * 可能是一个数组:数组中的每一项可能是字符串也可能是JSX对象
         */
        // 首先把一个值也变为数组,这样后期统一操作数组即可
        !(value instanceof Array) ? (value = [value]) : null
        value.forEach((item, index) => {
          // 验证ITEM是什么类型的:如果是字符串就是创建文本节点,如果是对象,我们需要再次执行RENDER方法,把创建的元素放到最开始创建的大盒子中
          if (typeof item === 'string') {
            let text = document.createTextNode(item)
            newElement.appendChild(text)
          } else {
            render(item, newElement)
          }
        })
        break
      default:
        newElement.setAttribute(attr, value)
    }
  }
  container.appendChild(newElement)
}

标签:render,value,React,简易版,key,props,newElement,createElement
From: https://blog.51cto.com/u_16056437/6318146

相关文章

  • 1. react项目【前端】+C#【后端】从0到1
    1、创建前端基础框架 1.1前端创建软件: 1.1.1npxcreate-react-apppcps:pc是文件名; 1.1.2npmstart启动项目 2、创建后端基础框架软件: 2.1创建webapi项目  Program.cs是启动文件;  ......
  • 社招前端二面必会react面试题及答案
    高阶组件的应用场景权限控制利用高阶组件的条件渲染特性可以对页面进行权限控制,权限控制一般分为两个维度:页面级别和页面元素级别//HOC.jsfunctionwithAdminAuth(WrappedComponent){returnclassextendsReact.Component{state={......
  • 面试官:React怎么做性能优化
    前言最近一直在学习关于React方面的知识,并有幸正好得到一个机会将其用在了实际的项目中。所以我打算以博客的形式,将我在学习和开发(React)过程中遇到的问题记录下来。这两天遇到了关于组件不必要的重复渲染问题,看了很多遍官方文档以及网上各位大大们的介绍,下面我会通过一些demo结......
  • 面试官让你说说react状态管理?
    开发者普遍认为状态是组件的一部分,但是同时却又在剥离状态上不停的造轮子,这不是很矛盾么?对于一个最简单的文本组件而言functionText(){const[text,setText]=useState('载入')return(){<p>{text}</p>}}你觉得应该把text从Text组件中剥离么?如果......
  • 高级前端常考react面试题指南
    pureComponent和FunctionComponent区别PureComponent和Component完全相同,但是在shouldComponentUpdate实现中,PureComponent使用了props和state的浅比较。主要作用是用来提高某些特定场景的性能为什么虚拟DOM会提高性能虚拟DOM相当于在js和真实DOM中间加了一个缓存,利用DOM......
  • react-query-builder查询构建器中文文档
    简介react-query-builder是一个React组件库,用于构建可配置的查询构建器。使用react-query-builder,您可以轻松地构建复杂的查询表单,使用户能够以直观的方式构建和执行查询。以下是一些常见的用例:构建高级搜索表单,允许用户根据不同的条件搜索数据。在数据可视化应用程序中使用......
  • APP中RN页面热更新流程-ReactNative源码分析
    平时使用WebStorm或VSCode对RN工程中的文件修改后,在键盘上按一下快捷cmd+s进行文件保存,此时当前调试的RN页面就会自动进行刷新,这是RN开发相比于原生开发一个很大的优点:热更新。那么,从按一下快捷cmd+s到RN页面展示出最新的JS页面,这个过程是怎样发生的呢?下面根据时间顺序来梳理一下......
  • quasar [email protected] ssr Meta插件 使用 title无法reactive的bug
    为了使自己的网站SEO更友好,在项目里启用了quasar的Meta插件。但实际使用下来发现文档里的描述不正确。为了动态的更新title和meta信息,文档https://quasar.dev/quasar-plugins/meta里介绍ReactiveInthesectionabove,younoticedallofthemetapropsare“static”.But......
  • React.FC中父组件调用子组件方法
    https://blog.csdn.net/qq_36990322/article/details/1098588901.函数式和hooks写法其实下面的缺点基本不算缺点了,因为函数式写法,下面算是简单的了。使用forwardRef只会让你的组件定义的更复杂优点:1、写法简单易懂2、假如子组件嵌套了HOC,也可以指向真实子组件缺点:1、需要自定......
  • 部署带路由的React SPA 项目
    使用Nginx将/phone/*请求反向代理为/*启动项目serve-s./build-l3000项目内路由仍然是/phone/xxx不用改动,但需要在package.json中,指定静态资源根目录{..."homepage":"/phone/",...}......