当前位置: 首页 > news >正文

react——jsx源码解析

背景

为了让大家深刻理解 JSX 的含义。有必要简单介绍了一下 JSX 稍微底层的运作原理,这样大家可以更加深刻理解 JSX 到底是什么东西,为什么要有这种语法,它是经过怎么样的转化变成页面的元素的。

思考一个问题:如何用 JavaScript 对象来表现一个 DOM 元素的结构,举个例子:

<div class='box' id='content'>
  <div class='title'>Hello</div>
  <button>Click</button>
</div>
复制代码

每个 DOM 元素的结构都可以用 JavaScript 的对象来表示。你会发现一个 DOM 元素包含的信息其实只有三个:标签名,属性,子元素。

所以其实上面这个 HTML 所有的信息我们都可以用合法的 JavaScript 对象来表示:

{
  tag: 'div',
  attrs: { className: 'box', id: 'content'},
  children: [
    {
      tag: 'div',
      arrts: { className: 'title' },
      children: ['Hello']
    },
    {
      tag: 'button',
      attrs: null,
      children: ['Click']
    }
  ]
}
复制代码

你会发现,HTML 的信息和 JavaScript 所包含的结构和信息其实是一样的,我们可以用 JavaScript 对象来描述所有能用 HTML 表示的 UI 信息。但是用 JavaScript 写起来太长了,结构看起来又不清晰,用 HTML 的方式写起来就方便很多了。

于是 React.js 就把 JavaScript 的语法扩展了一下,让 JavaScript 语言能够支持这种直接在 JavaScript 代码里面编写类似 HTML 标签结构的语法,这样写起来就方便很多了。编译的过程会把类似 HTML 的 JSX 结构转换成 JavaScript 的对象结构。

编译前代码

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Header extends Component {
  render () {
    return (
      <div>
        <h1 className='title'>React 小书</h1>
      </div>
    )
  }
}

ReactDOM.render(
  <Header />,
  document.getElementById('root')
)

复制代码

经过编译以后会变成:

import React, { Component } from 'react'
import ReactDOM from 'react-dom'
import './index.css'

class Header extends Component {
  render () {
    return (
     React.createElement(
        "div",
        null,
        React.createElement(
          "h1",
          { className: 'title' },
          "React 小书"
        )
      )
    )
  }
}

ReactDOM.render(
  React.createElement(Header, null), 
  document.getElementById('root')
);
复制代码

React.createElement 会构建一个 JavaScript 对象来描述你 HTML 结构的信息,包括标签名、属性、还有子元素等。这样的代码就是合法的 JavaScript 代码了。所以使用 React 和 JSX 的时候一定要经过编译的过程。

这里再重复一遍:所谓的 JSX 其实就是 JavaScript 对象。每当在 JavaScript 代码中看到这种 JSX 结构的时候,脑子里面就可以自动做转化,这样对你理解 React.js 的组件写法很有好处。

实现

注意到的是我们的JSX最终转化成为的是React.createElement这个方法:

第一个参数是字符串类型或者组件或者symbol,

代表的是标签元素, 如div, span
classComponent或者是functional Component,
原生提供的Fragment, AsyncMode等, 会被特殊处理
复制代码

第二个参数是一个对象类型, 代表标签的属性, id, class

其余的参数代表的是children,不涉及grand-children,当子节点中有孙节点的时候, 再递归使用React.createElement方法

const App = () => {
  return <div id="app" key="key">
    <section>
    	<img />
    </section>
    <span>span</span>
  </div>
}

"use strict";

var App = function App() {
  return React.createElement(
      "div", 
      {id: "app",key: "key"}, 
      React.createElement("section", null, 
              React.createElement("img", null)
          ), 
      React.createElement("span", null, "span"));
};

复制代码

当第一个参数是组件的时候,第一个参数是作为变量传入的, 可以想像的是, React.createElement内部有一个简单的判断, 如果传入的是组件的话, 内部还会调用React.createElement方法

const Child = () => {
	return <div>Child</div>
}
const App = () => {
  return <div id="app">
    <Child />
  </div>
}

"use strict";

var Child = function Child() {
  return React.createElement("div", null, "Child");
};

var App = function App() {
  return React.createElement(
      "div", 
      {id: "app"}, 
      React.createElement(Child, null)); //这里
}
}
复制代码

需要注意的是如果组件开头是一个小写的话, 会被解析成简单的字符串,在运行的时候就会报错

我们的createElement方法定义在packages/src/ReactElement.js

export function createElement(type, config, children) {
  let propName;
  const props = {};
  
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
   
   // ref和key和其他props不同, 进行单独处理
  if (config != null) {
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    //将属性名挂载到props上
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  //第三个及以上的参数都被看作是子节点
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    //当数组长度确定时,这种方式比push要节省内存  
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    props.children = childArray; 
  // merge defaultProps
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}



复制代码

ReactElement定义如下



const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 每个react element的$$typeof属性都是REACT_ELEMENT_TYPE
    $$typeof: REACT_ELEMENT_TYPE, // react element的内置属性
    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner, //创建该元素的component
  };
  return element;
};
复制代码

总的来说就是返回一个react element, 该element带有props, refs, type

所以可以总结一下从 JSX 到页面到底经过了什么样的过程:

第一个原因是,当我们拿到一个表示 UI 的结构和信息的对象以后,不一定会把元素渲染到浏览器的普通页面上,我们有可能把这个结构渲染到 canvas 上,或者是手机 App 上。所以这也是为什么会要把 react-dom 单独抽离出来的原因,可以想象有一个叫 react-canvas 可以帮我们把 UI 渲染到 canvas 上,或者是有一个叫 react-app 可以帮我们把它转换成原生的 App(实际上这玩意叫 ReactNative)。

第二个原因是,有了这样一个对象。当数据变化,需要更新组件的时候,就可以用比较快的算法操作这个 JavaScript 对象,而不用直接操作页面上的 DOM,这样可以尽量少的减少浏览器重排,极大地优化性能。这个在以后的章节中我们会提到。

转载于:https://juejin.im/post/5cd9108c518825691e148dd5

相关文章:

  • 拿Proxy可以做哪些有意思的事儿
  • centos7 安装 rabbitmq
  • 如何快速学习Java?
  • SylixOS write 0字节问题
  • 理解Android 中的启动模式
  • 深入理解jvm jdk1,7(5)
  • 学会python可以上天!20行代码获取斗鱼平台房间数据,就是这么牛逼!
  • Golang 多goroutine异步通知error的一种方法
  • 前端base64加密
  • 关于kettle,表输入postgres中有jsonb字段的处理
  • Mybatis架构设计及源码分析-mapper.xml文件解析
  • AGC005 补题小结
  • Video.js的简单使用介绍
  • Bootstrap Table的 文本内容 垂直居中
  • 你的知识死角不能否定你的技术能力
  • $translatePartialLoader加载失败及解决方式
  • [译]如何构建服务器端web组件,为何要构建?
  • Android 初级面试者拾遗(前台界面篇)之 Activity 和 Fragment
  • Angular数据绑定机制
  • docker python 配置
  • Dubbo 整合 Pinpoint 做分布式服务请求跟踪
  • eclipse的离线汉化
  • JavaScript创建对象的四种方式
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • MySQL几个简单SQL的优化
  • oldjun 检测网站的经验
  • React 快速上手 - 07 前端路由 react-router
  • select2 取值 遍历 设置默认值
  • swift基础之_对象 实例方法 对象方法。
  • vue-cli在webpack的配置文件探究
  • 程序员最讨厌的9句话,你可有补充?
  • 看图轻松理解数据结构与算法系列(基于数组的栈)
  • 前端技术周刊 2019-02-11 Serverless
  • ​决定德拉瓦州地区版图的关键历史事件
  • #我与虚拟机的故事#连载20:周志明虚拟机第 3 版:到底值不值得买?
  • $.extend({},旧的,新的);合并对象,后面的覆盖前面的
  • (3)Dubbo启动时qos-server can not bind localhost22222错误解决
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (附源码)计算机毕业设计ssm本地美食推荐平台
  • (六)软件测试分工
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (算法)Game
  • (一)基于IDEA的JAVA基础1
  • (一)基于IDEA的JAVA基础10
  • (一)认识微服务
  • (转载)CentOS查看系统信息|CentOS查看命令
  • **Java有哪些悲观锁的实现_乐观锁、悲观锁、Redis分布式锁和Zookeeper分布式锁的实现以及流程原理...
  • .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
  • ??javascript里的变量问题
  • @cacheable 是否缓存成功_让我们来学习学习SpringCache分布式缓存,为什么用?
  • @zabbix数据库历史与趋势数据占用优化(mysql存储查询)
  • [ 隧道技术 ] 反弹shell的集中常见方式(四)python反弹shell
  • []我的函数库
  • [acm算法学习] 后缀数组SA