前言

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

实现 createElement 方法

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

import React from 'react'import ReactDOM from 'react-dom'ReactDOM.render(<h1 className='title'>Hello Reacth1>, 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 类型,值就是一个二进制值。

为什么是 Symbol?主要防止 XSS 攻击伪造一个假的 React 组件。因为 JSON 中是不会存在 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)}

欢迎关注我掘金账号和Github技术博客:

  • 掘金:https://juejin.im/user/1257497033714477
  • Github:https://github.com/Jacky-Summer
  • 觉得对你有帮助或有启发的话欢迎 star,你的鼓励是我持续创作的动力~
  • 如需在微信公众号平台转载请联系作者授权同意,其它途径转载请在文章开头注明作者和文章出处。

react遍历对象的值_React 原理之实现 createElement 和 render 方法相关推荐

  1. 使用 React 遍历对象

    今天使用React完成一个小案例,使用react把数据渲染到页面,效果如下 首先,既然要是要使用react遍历对象吗,那我们就得引入react的相关插件引入,并且把我们要渲染到页面的data.js数据 ...

  2. 直接修改html文本页面没变化,VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析...

    本文实例讲述了VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法.分享给大家供大家参考,具体如下: 业务场景 我们在使用vue 编写 代码时,我们有一个 多行文本框控件,希望在页面 ...

  3. react 遍历对象_React 和 Vue 之间的相爱相杀

    React 和 Vue 应该是国内当下最火热的前端框架,当然 Angular 也是一个不错的框架,但是这个产品国内使用的人很少再加上我对 Angular 也不怎么熟悉,所以就不在这篇文章中做对比了. ...

  4. react 遍历对象_React 源码系列 | React Children 详解

    本文基于 React V16.8.6,本文代码地址 测试代码 源码讲解 React 中一个元素可能有 0 个.1 个或者多个直接子元素,React 导出的 Children 中包含 5 个处理子元素的 ...

  5. react 遍历对象_探索:跟随《Build your own React》实现一个简易React

    文章介绍 build-your-own-react是一篇操作说明书,指导用户一步步实现一个简易的React,从中了解到React的大体工作流程.这篇文章是我的观后整理和记录,或许对大家会有所帮助. 构 ...

  6. react如何获取input值_react中怎么获取input的值?

    react中怎么获取input输入框的值?下面本篇文章给大家介绍一下.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. react 获取input 输入框的值第一种方法:通过event ...

  7. js遍历对象key,value

    声明一个对象: let obj = {name: 'Kamen',age: '23',hobby: 'eat eat eat'} 方法一:转化为操作数组forEach遍历 遍历对象属性 //遍历对象属 ...

  8. 关于javascript遍历对象

    1:遍历对象属性var obj={a:'aa',b:'bb'} for(var i in obj) {alert(i); //输出 a b } var obj={'a':'aa','b':'bb'} ...

  9. react循环key值_React源码揭秘(三):Diff算法详解

    编者按:本文作者奇舞团前端开发工程师苏畅. 代码参照React 16.13.1 什么是Diff 在前两篇文章中我们分别介绍了 React 的首屏渲染流程1和组件更新流程2,其中 首屏渲染会渲染一整棵 ...

最新文章

  1. 2018-3-21李宏毅机器学习视频笔记(十三)--“Hello Wrold” of Deep learning
  2. OpenCV中cvADDS()为啥第二个参数要是CvScalar类型?掩码mask=NULL又是何意?
  3. spring mvc DispatcherServlet详解之interceptor和filter的区别
  4. 10-Platform Interrupt Controller API
  5. Java源码解析:hashCode与相同对象的关系
  6. MacOS/MacBook设置短语快捷键
  7. 张娟娟(为奥运冠军名字作诗)
  8. linux如何自动化部署脚本实现免密登录并访问资源
  9. 在Ubuntu 18.04 LTS 入门 ROS Melodic 机器人 操作系统
  10. 【论文】Awesome Relation Extraction Paper(关系抽取)(PART IV)
  11. 【赠书活动】赠送清华社的《好好学Java:从零基础到项目实战》
  12. 语义分割和实例分割_2019 语义分割指南
  13. 程序员新电脑装机软件
  14. 宏基 4560G笔记本 AMD APU A6-3400试用报告
  15. 关于阿里云服务器租用费用的介绍
  16. 面向对象核心(继承)
  17. 颈椎病的康复锻炼方法
  18. Lan9252-FPGA调试笔记
  19. 袋鼠过河问题(DP)
  20. 数据结构课程大纲和教学设计

热门文章

  1. java的自增自减_Java中自增和自减操作符(++/--)的那些事
  2. unity怎么设置游戏页面_杭州有没有正规的unity游戏开发培训机构?
  3. 源码安装NASM,无root权限
  4. faster rcnn的测试
  5. c语言中for语句的作用是,c语言中for语句是怎么用的
  6. JAVA格式化当前日期或者取年月日
  7. AMBA总线协议AHB、APB
  8. 设△ABC的内角A,B,C,所对的边分别为a,b,c,且acosB-bcosA=3/5c,则tan(A-B)的最大值为
  9. 130242014022 蓝宏铮 第2次实验
  10. 2017.4.11 AM