likes
comments
collection
share

重识React — — 你好,JSX!

作者站长头像
站长
· 阅读数 23

前言

提到react,那就不得不提到随着react在2013年一起出现的JSX,回望自己走了2年的react之路,迈过类组件、拥抱函数组件、与性能优化作战,好像从来没有留意过一路走来,每一步结结实实踩着的,脚下的这条JSX铺成的路。

现在你要问我JSX是什么、有什么用?我好像只能回答它是一种方便、写起来爽的模板语法。像极了平日里受太多老婆的照顾,饭局上被朋友要求夸一夸老婆的优点,才意识到自己早已理所当然地把情分当作本分享受而局促地憋不出几句正经话,当众羞红了脸的中年男子😳。

于是我把一直以来望向远方的目光收回,蹲下身子,注视着脚下那一块块名为JSX的石砖。它们鳞次栉比地排列着,被一位又一位向前走去的开发者踏过。我用手掌慢慢拨开石砖上的细沙,才注意到它原本有着的令人惊叹的光泽,手指仔细地摩挲它的表面,轻轻叩击。而后我站起身,也许这一路上错过了太多风景,是时候重新认识一遍React了!

JSX的本质是什么,它和JS之间是什么关系?

JSX是JavaScript的一种语法扩展,它和模板语言很接近,但是它充分具备JavaScript的能力 from React官网

JSX语法是如何在JavaScript中生效的?

Babel 是一个工具链,主要用于在当前和旧的浏览器或环境中,将 ECMAScript 2015+ 代码转换为 JavaScript 向后兼容版本的代码。 from Babel官网

重识React — — 你好,JSX! 如上图所示,所有的JSX标签都被转换成了React.createElement()的调用。这也就表明,我们写的JSX,其实是在写React.createElement(),即

JSX本质就是React.createElement()这个JavaScript调用的语法糖。得益于此,我们前端开发者才能使用我们最习惯的类HTML标签语法来创建虚拟DOM,这不仅降低了JSX的学习成本,也提高了研发效率,提升了研发体验。

JSX
Babel
React.createElement
ReactElement

现在我们将镜头聚焦在React.createElement身上,它又是如何映射为Dom的呢?

啊?React.createElement,你是?

先来看看createElement源码👀

node_modules\react\umd\react.development.js

 
 function createElement(type, config, children) {
    // 用于储存后面需要用到的元素属性
    var propName; // Reserved names are extracted
    var props = {};// 用于储存元素属性的键值对集合
    var key = null;// 为React元素的属性
    var ref = null;// 为React元素的属性
    var self = null;// 为React元素的属性
    var source = null;// 为React元素的属性
    
    // config对象中存储的是元素的属性
    if (config != null) {
      if (hasValidRef(config)) {
        ref = config.ref;

        {
          warnIfStringRefCannotBeAutoConverted(config);
        }
      }

      if (hasValidKey(config)) {
        {
        // 将key值字符串化
          checkKeyStringCoercion(config.key);
        }

        key = '' + config.key;
      }
      // 到此为止,完成对ref、key、self、source的赋值
      self = config.__self === undefined ? null : config.__self;
      source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object
      //config 属性 => 之前声明的props中
      for (propName in config) {
        // 只有满足条件的才能被放入props
        if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
          props[propName] = config[propName];
        }
      }
    } // Children can be more than one argument, and those are transferred onto
    // the newly allocated props object.

    // 即当前元素子元素的个数,-2是因为createElement(type, config, children)还有两个参数type, config
    var childrenLength = arguments.length - 2;
    // 如果传入的children长度只有1,通常表示文本节点
    if (childrenLength === 1) {
      props.children = children;
    } 
    // 不唯一,则需要进行嵌套多个子元素的处理
    else if (childrenLength > 1) {
      // childArray = [ 空 *  childrenLength ]
      var childArray = Array(childrenLength);

      for (var i = 0; i < childrenLength; i++) {
        childArray[i] = arguments[i + 2];
      }

      {
        if (Object.freeze) {
          Object.freeze(childArray);
        }
      }

      props.children = childArray;
    } // Resolve default props


    if (type && type.defaultProps) {
      var defaultProps = type.defaultProps;

      for (propName in defaultProps) {
        if (props[propName] === undefined) {
          props[propName] = defaultProps[propName];
        }
      }
    }

    {
      if (key || ref) {
        var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type;

        if (key) {
          defineKeyPropWarningGetter(props, displayName);
        }

        if (ref) {
          defineRefPropWarningGetter(props, displayName);
        }
      }
    }
    // 最后返回一个调用ReactElement执行方法,并传入刚才处理过后的参数
    return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
  }
参数
  • typetype 参数必须是一个有效的 React 组件类型,例如一个字符串标签名(如 'div''span'),或一个 React 组件(一个函数式组件、一个类式组件,或者是一个特殊的组件如 Fragment)。
  • configsconfigs 参数必须是一个对象或 null。如果你传入 null,它会被当作一个空对象。创建的 React 元素的 props 与这个参数相同。注意,configs 对象中的 refkey 比较特殊,它们 不会 作为 element.props.refelement.props.key 出现在创建的元素 element 上,而是作为 element.refelement.key 出现。
  • 可选 children:零个或多个子节点。它们可以是任何 React 节点,包括 React 元素、字符串、数字、portal、空节点(nullundefinedtruefalse),以及 React 节点数组。
例子
function Greeting({ name }) {
 return React.createElement(
    'h1',
    { className: 'greeting' },
    '你好',
    React.createElement('i', null, name),
    ',我是createElement!'
  );
  }

与之对应的JSX写法

function Greeting({ name }) {  
return (  
<h1 className="greeting">  
你好<i>{name}</i>,我是createElement!  
</h1>  
);  
}
小结

通过刚刚的源码分析和代码举例,我们算和React.creatElement好好地见了一面。 React.creatElement告诉我们,她将用户传入的3个参数进行了二次处理,然后再将处理后的参数传给了ReactElement这一执行方法,于是,在她的介绍下,我们又踏上了寻访ReactElement之路。

ReactElement

再来瞧瞧ReactElement源码👀

node_modules\react\umd\react.development.js

  var ReactElement = function (type, key, ref, self, source, owner, props) {
    var element = {
      // This tag allows us to uniquely identify this as a React Element
      $$typeof: REACT_ELEMENT_TYPE,// 常量,说明该对象是个ReactElement
      // Built-in properties that belong on the element
      // 内置属性赋值
      type: type,
      key: key,
      ref: ref,
      props: props,
      // 记录创造该元素的组件
      _owner: owner
    };

    {
      // The validation flag is currently mutative. We put it on
      // an external backing store so that we can freeze the whole object.
      // This can be replaced with a WeakMap once they are implemented in
      // commonly used development environments.
      element._store = {}; // To make comparing ReactElements easier for testing purposes, we make
      // the validation flag non-enumerable (where possible, which should
      // include every environment we run tests in), so the test framework
      // ignores it.

      Object.defineProperty(element._store, 'validated', {
        configurable: false,
        enumerable: false,
        writable: true,
        value: false
      }); // self and source are DEV only properties.

      Object.defineProperty(element, '_self', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: self
      }); // Two elements created in two different places should be considered
      // equal for testing purposes and therefore we hide it from enumeration.

      Object.defineProperty(element, '_source', {
        configurable: false,
        enumerable: false,
        writable: false,
        value: source
      });

      if (Object.freeze) {
        Object.freeze(element.props);
        Object.freeze(element);
      }
    }

    return element;
  };

总结下她做的事情就是,组装

开发者
createElement
ReactElement

如果我们直接打印刚刚的demo的话,会在控制台得到这样的结果

{
  "type": {
    "@t": "Function",
    "data": {
      "name": "Greeting",
      "body": "",
      "proto": "Function"
    }
  },
  "key": null,
  "ref": null,
  "props": {},
  "_owner": null,
  "_store": {}
}

这确实是一个ReactElement对象实例,本质上是一个以JavaScript形式存在的对Dom的描述即虚拟Dom

如何从虚拟Dom 变为 Dom?最后的最后,让我们再拜访一下ReactDOM.render

ReactDOM.render(在React 18中被废弃了), 我的老朋友😭!

小结
开发者
JSX代码
Babel
createElement
ReactElement
虚拟DOM
ReactDOM.render
真实DOM

为什么要用JSX,不用会有什么后果?

  1. 使用JSX会提高我们的开发效率,提升我们的开发体验
  2. 不用的话,手写React.creatElement(),当遇到复杂的组件开发时,会导致多层嵌套的React.creatElement(),既不利于开发,也不利于后续的代码维护,而且大大提高了出错风险。

JSX背后的功能模块是什么,这个功能模块做了哪些事情?

背后的功能模块是React.creatElement(),它是通过Babel编译后的JSX代码,将开发者输入的参数进行了二次处理,并返回ReactElement()的执行方法,得到一个ReactElement对象,ReactElement再将二次处理后的参数进行组装,得到“虚拟DOM”,最后,再通过ReactDOM.render()方法,将虚拟DOM变成真实DOM,最终呈现在网页上。

后言

写完这近5000字正文的文章,电脑右下角的时间已是晚上9点,早就送到的外卖还放在出租屋的门口,楼下蟋蟀的声音吵吵闹闹,听说名称来自于一个古希腊女神。今天是8月20日,杭州是不冷不热的一天,我决定从头再学一遍React。

转载自:https://juejin.cn/post/7269061890273181711
评论
请登录