1

首先jsx绝不是一个新的语言,我觉得他仅仅是js的超集,或者只是一种语法糖。原本FB是有自己的jsx-transform来负责jsx的解析,不过现在已经停止更新,jsx语法完全依赖babel进行解析。

babel

babel在识别jsx的时候,会将标签直接作为AST的node,然后转化

class Square extends React.Component {
  render() {
    return (
      <button className="square">
        {this.props.value}
      </button>
    );
  }
}

将被babel转化为

class Square extends React.Component {
  render() {
    return React.createElement(
      "button",
      { className: "square" },
      this.props.value
    );
  }
}

这里为了方便和阅读对es6的转化,暂时忽略。

AST

还是针对上面的代码,来看一下babylon AST的解析结果,只包含return部分
整个标签部分是JSXElement
其中会有三个部分openingElement,closingElement和children,属性则会成为openingElement的JSXAttribute

jsx在babel的定义

defineType("JSXElement", {
  builder: ["openingElement", "closingElement", "children", "selfClosing"],
  visitor: ["openingElement", "children", "closingElement"],
  aliases: ["JSX", "Immutable", "Expression"],
  fields: {
    openingElement: {
      validate: assertNodeType("JSXOpeningElement"),
    },
    closingElement: {
      optional: true,
      validate: assertNodeType("JSXClosingElement"),
    },
    children: {
      validate: chain(
        assertValueType("array"),
        assertEach(
          assertNodeType(
            "JSXText",
            "JSXExpressionContainer",
            "JSXSpreadChild",
            "JSXElement",
            "JSXFragment",
          ),
        ),
      ),
    },
  },
});

AST的jsx节点最后会被替换为createElement的形式,也就是说不管怎么用jsx,其实也就只是一种语法糖,与createElement的写法无异

e.g

为了更好的理解jsx,那我解释一段最近看到的代码

methodsParamsList.map((ListSourceComponent, index) => {
       return <ListSourceComponent.component index={index + 1} />
})

核心也就是openingElement
<ListSourceComponent.component index={index + 1} />
在babel的眼里他做为Element有两个部分,名字和属性,属性不多说了。
ListSourceComponent.component会被认为是JSXMemberExpression,ListSourceComponent是JSXIdentifier的对象component则是属性
如果是
<ListSourceComponent index={index + 1} />
那么ListSourceComponent是JSXIdentifier的name

整个代码的结果也就是

 methodsParamsList.map((ListSourceComponent, index) => {
      return React.createElement(ListSourceComponent.component, { index: index + 1 });
    });

其实在转码的阶段是否将JSXMemberExpression的name或者对象的首字母大写是不重要的,真的导致报错的也不过是在createElement中

createElement

createElement()

React.createElement(type, [props],[...children])

Create and return a new React element of the given type. The type argument can be either a tag name string (such as 'div' or 'span'), or a React component type (a class or a function).
Code written with JSX will be converted to use React.createElement(). You will not typically invoke React.createElement() directly if you are using JSX. See React Without JSX to learn more.

reactComponent大写的原因

在刚学react的时候,老师就说过react component需要大写,react会把大写开口的作为react的组件,不然则会使普通的html dom,比如div,span
首先是不是在解析ast的时候就把大小写分开处理了,答案是否定的,在定义openingElement的type的时候完全没有对这个进行判断,不管是大写,小写均作为string,是element的type
但是在babel的结果里可以看到
React.createElement('div', ...)
React.createElement( Div, ...)
这两种结果带来的就是Div这个是一个字符串还是变量名,如果自己不慎写了个小写的component名,那么一个并不是html标签的字符串必然会造成错误
这里必须穿插一个JSX dot notation

JSX dot notation

这个和上面的例子相关就是dot notation在jsx中的解析

methodsParamsList.map((ListSourceComponent, index) => {
       return <ListSourceComponent.component index={index + 1}/>
    })
methodsParamsList.map((listSourceComponent, index) => {
       return <listSourceComponent.component index={index + 1}/>
    })

上面两段代码都不会导致错误,原因就是在解析JSXIdentifier的结果会是一组对象和属性,所以转码结果一定不是字符串而是obj.pro

jsx的变形

上面说到了在构建ast的过程中大小写并不进行区分,而是在@babel/plugin-transform-react-jsx中进行变化。
关于上面的几个转码的结果的解释都在下面的源码中

  const createIdentifierParser = (id: string) => () => {
    return id
      .split(".")
      .map(name => t.identifier(name))
      .reduce((object, property) => t.memberExpression(object, property));
  };

这里可以发现,react支持dot notation,但是比如
<A['x'] />
就一定会报错了,虽然看起来与
<A.x />
一样


求实亭下
142 声望13 粉丝

有着深度学习技能的前端开发工程师