最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 女友 React 30 问 -- JSX 是什么?

    正文概述 掘金(厨猿加加)   2021-05-01   617

    引子

    今天有个同学问我,我们在写 React 项目的时候,文件后缀为什么是 .jsx? 我愣了一下,不是 .jsx,也可以是 .tsx 啊,你问这个是啥意思嘞。他接着说,我们写的这些都是 js 文件,那么我直接用 .js 后缀不行吗,为啥非要在文件名后缀上做功夫呢?

    JSX 本质是什么,它和 JS 有什么区别

    作为一名 React 的用户,日常的工作就是使用 JSX 去描述 React 组件内容。我们都知道 JSX 就是 React 提供的,对用户友好的一种编写组件的语法糖,但是它和 JS 有啥区别呢?

    JSX 的本质是 JS 的语法扩展

    JSX 既然作为 JS 的一种语法扩展,那肯定就是原始 JS 有些功能无法实现 React 这个工具定制化的功能,或者说为了用户体验,需要在原生 JS 上做一下扩展包装,让用户用起来更爽一点。而根据 React 官方介绍,我觉得为了用户体验更佳,写更少的代码,可能是使用 JSX 的更重要的原因吧。

    JSX 会被编译成一个叫 ReactElement 的对象,这个对象就是描述组件的真正主人。So,我们其实可以不用 JSX,而直接使用 React.createElement() 编写组件,这样还省了一层 Babel 转化。

    但是我们的组件太复杂,使用对象描述的时候就需要更多属性,层级越深,代码嵌套就越多,可读性就越差。这个时候面对屎山级代码,可能直接就劝退了。 React 为了留住用户,把困难留给自己,把顺畅留给用户,让大家尽可能使用属性的 类HTML 来编写组件,至于复杂的处理,就让 React 自己默默承受吧。

    这里也就解释了引子中的问题,为什么我们在写 React 组件的时候,习惯性会用 .jsx 作为后缀,因为我们需要给 babel 下个 flag,告诉它我们这边需要编译。当然你不用行不行,当然可以,babel 作为那么牛逼的工具,即便你不告诉它,它也能够根据你写的代码,通过根对象寻址的方式发现你就是一个 React 组件,你编写的是 JSX, 你需要编译成 ReactElement,然后对你进行服务。但是如果你不写,一来可读性差,无法区分普通 js 文件和 JSX 组件,二来确实会对编译效果产生部分影响。

    有些问题不是可不可以,是优不优雅。很多代码是可以用且没有太大的问题,但是就是恶心,难受不优雅罢了。同理,有些问题能不能问,回答准不准确,是需要上下文和实际情况的,比方说 React 是同步还是异步的,就是一个典型的为了问而问的问题,真想反问一句,你啥时候用它同步情况了,这样做真的不会被打吗?这个下次再聊。

    JSX 如何是如何映射为 DOM 的

    React.createElement() 源码

    export function createElement(type, config, children) {
      let propName;
    
      // 存储元素属性
      const props = {};
    
      // 几个比较特殊的属性,需要另外的变量来保存
      let key = null;
      let ref = null;
      let self = null;
      let source = null;
    
      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;
    
        for (propName in config) {
          if (
            // 筛选出可以放在 props 中的属性
            hasOwnProperty.call(config, propName) &&
            !RESERVED_PROPS.hasOwnProperty(propName)
          ) {
            props[propName] = config[propName];
          }
        }
      }
    
      // 前两个入参分别是 type 和 config,剩下的都是 children
      const childrenLength = arguments.length - 2;
      if (childrenLength === 1) {
        // 如果只有一个 child,直接赋值到 props.children
        props.children = children;
      } else if (childrenLength > 1) {
        // 如果是多个 child,则返回一个 child 数组
        const childArray = Array(childrenLength);
        for (let i = 0; i < childrenLength; i++) {
          childArray[i] = arguments[i + 2];
        }
        props.children = childArray;
      }
    
      if (type && type.defaultProps) {
        const defaultProps = type.defaultProps;
        for (let propName in defaultProps) {
          if (props[propName] === undefined) {
            props[propName] = defaultProps[propName];
          }
        }
      }
    
      return ReactElement(
        type,
        key,
        ref,
        self,
        source,
        ReactCurrentOwner.current,
        props
      );
    }
    

    分析入参

    Babel 将 JSX 转成 React.createElement(type,config,children),

    • type: 用于是不节点类型的参数;可以是 h1div 这些 hostCompoennt 的类型,也可以是 ReactComponent 这种类型
    • config: 组件中所有的属性都会以 config 对象的形式入参,包括 key, ref 这类特殊属性,也包括像 className, xxx 这类自定义属性
    • children: 子元素,子节点,以对象的的形式嵌套入参,里面也包含了 type, config 和 children 属性。

    例子:

    
    var element = React.createElement(
      "h1",
      {
        xxx: "111",
        ref: "myDiv",
      },
      React.createElement(
        "p",
        {
          yyy: "222",
        },
        "123"
      )
    );
    

    对应于 DOM

      <h1 xxx="111" ref="myDiv">
        <p yyy="222">123</p>
      </h1>
    

    拆解 createElement 的过程

    1. 入口 React.createElement -- 在 react/ReactElement 文件中
    2. 二次处理特殊的属性 ref, key, self, source
      • 这里 ref 可以注意一下,在我们使用 useRef 做穿透处理的时候,外层入参除了 props,还得单独传入 ref,就是因为 ref 不存储在 props 中
    3. 遍历 config, 筛选出可以提取进 props 中的属性
      • 除了 4个特殊属性和原型链遗传下来的属性,当前属性基本都存储在 props 中
    4. 提取子元素,加入到 props.children 中
      • 可以是单个 children 对象,也可以是对象数组
    5. 格式化 defaultProps
    6. 结合上述参数,通过 ReactElement 再次格式化,然后就成了一个 ReactElement 对象了。

    连接虚拟 DOM 和真实 DOM

    • createElement 最后返回的就是虚拟 DOM 树,它是真实 DOM 的一个映射
    • 在 Web 中,我们一般会使用 React.render(element,root) 来将虚拟 DOM 映射到真实 DOM 中去

    小结

    • JSX 创建的目的是为了简化创建 ReactElement 的语法糖,原生的 JSX 是不能被浏览器识别的,需要通过 babel 编译成 React.createElement(type,config,children) 的形式, 并最终表示为一个 ReactElement 对象
    • ReactElement 其实就是所谓的虚拟 DOM,根节点其实就是虚拟 DOM 树,因为它是一个树状结构,它是真实 DOM 的映射
    • 在 Web 中,我们一般用 React.render 将 ReactElement 映射到真实 DOM 中去,其中入参需要一个容器 DOM 节点
    • 引子中提到的使用 .jsx 后缀,一来是为了代码规范,方便阅读;二来是为了提示 babel 编译。
    • 在 React 17 之后,即便不引入 React,也可以直接编写 JSX,而不怕 babel 无法是不而报错了。

    起源地下载网 » 女友 React 30 问 -- JSX 是什么?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元