React 16 源码瞎几把解读 【前戏】 为啥组件外面非得包个标签?

〇、看前准备

1.自行clone react最新代码

2.自行搭建一个能跑react的test项目

一、看表面:那些插件 如何解析JSX

有如下一段代码:

// ---- hearder.jsx 组件
import React,{Component} from 'react';

export default (props)=>(
    <h1 ref="h1">我是header.{props.kk}</h1>
);

// ---- Home.jsx 页面级组件
import React,{Component} from 'react';
import Header from '../components/header';
class Home extends Component {
    constructor(props){
        super(props);
    }
    componentWillMount(){
        console.log('willMount');
    }
    render(){
        let {name} = this.props;
        console.log(this);
        return (
            <div ref="home">
                <Header kk="jsx"/>
                <div>主页</div>
                <div>
                    <p>哈哈哈哈</p>
                </div>
            </div>
        )
    }
}

export default Home;


// React入口文件 app.js

import ReactDOM from 'react-dom';
import React from 'react';
import Home from './page/home';
let abc = ReactDOM.render(
    <div>
        <Home name="home"/>
    </div>
    ,
    document.getElementById('app')
);

发现每一个出现jsx语法的地方都要出现import React from 'react';

其实react 的引入就是为了将解析完的jsx 能有createElement方法被执行,在浏览器里打开控制台,我们发现代码被整成了这个样子:

// ----header.jsx

var _default = function _default(props) {
    return _react2.default.createElement(
        "h1",
        { ref: "h1" },
        "\u6211\u662Fheader.",
        props.kk
    );
};

exports.default = _default;


// home.jsx

var Home = function (_Component) {
    _inherits(Home, _Component);

    function Home(props) {
        _classCallCheck(this, Home);

        return _possibleConstructorReturn(this, (Home.__proto__ || Object.getPrototypeOf(Home)).call(this, props));
    }

    _createClass(Home, [{
        key: 'componentWillMount',
        value: function componentWillMount() {
            console.log('willMount');
        }
    }, {
        key: 'render',
        value: function render() {
            var name = this.props.name;

            console.log(this);
            return _react2.default.createElement(
                'div',
                { ref: 'home' },
                _react2.default.createElement(_header2.default, { kk: 'js' }),
                _react2.default.createElement(
                    'div',
                    null,
                    '\u4E3B\u9875'
                ),
                _react2.default.createElement(
                    'div',
                    null,
                    _react2.default.createElement(
                        'p',
                        null,
                        '\u54C8\u54C8\u54C8\u54C8'
                    )
                )
            );
        }
    }, {
        key: '__reactstandin__regenerateByEval',
        // @ts-ignore
        value: function __reactstandin__regenerateByEval(key, code) {
            // @ts-ignore
            this[key] = eval(code);
        }
    }]);

    return Home;
}(_react.Component);

var _default = Home;
exports.default = _default;


// app.js

_reactDom2.default.render(_react2.default.createElement(
    'div',
    null,
    _react2.default.createElement(_home2.default, { name: 'home' })
), document.getElementById('app'));

二、为啥外面非得包一个

我们知道我们在写react的时候,不管你用的是webpack也好还是fis3 、gulp 或者直接撸js+html,都需要引入jsx语法转义插件,这些插件把jsx语法的字符转变成了我们在浏览器看到的样子。

每个组件都有render方法,每个render方法都会return一个React.createElement 执行后的东西

我将header.jsx改为

export default (props)=>(
    <h1 ref="h1">我是header.{props.kk}</h1>
    <p>hahaha</p>
);

当我们在jsx中写若干个标签而外面不包东西的话,以babel-loader为例,丫会提示:

SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

我们通过追溯得到jsx语法的解析其实是在 baabel-core/node_modules/babylon/index.js 中解析的

我们注释掉出现的报错信息,因能力有限水平一般且对babylon有任何了解,所以只通过粗浅的改动以下代码让webpack能够编译通过(当然js语法肯定是不成功的)

Tokenizer.prototype.readRegexp = function readRegexp() {
......
    for (;;) {
      // if (this.state.pos >= this.input.length) this.raise(start, "Unterminated regular expression");
      var ch = this.input.charAt(this.state.pos);
      if (lineBreak.test(ch)) {
          // this.raise(start, "Unterminated regular expression");
          break;  // 增加一个break
      }
      ..............
    return this.finishToken(types.regexp, {
      pattern: content,
      flags: mods
    });
  };



pp$9.jsxParseElementAt = function (startPos, startLoc) {
  ............
  // if (this.match(types.relational) && this.state.value === "<") {
  //   this.raise(this.state.start, "Adjacent JSX elements must be wrapped in an enclosing tag");
  // }
  return this.finishNode(node, "JSXElement");
};

编译出来的代码结构如下:

var _default = function _default(props) {
    return _react2.default.createElement(
        "h1",
        { ref: "h1" },
        "\u6211\u662Fheader.",
        props.kk
    ) < p > hahaha < /p>/;
};

exports.default = _default;

可见babylon在解析jsx的时候会默认将第一个闭合标签 return 出去,就算第二个咱们改babylon改的再成功能正确解析p标签这个jsx,岂不是return 两个东西出去了,函数里怎么可能有两个return呢!

所以react的所有组件必须放在一个闭合标签里(当然了16 版本也可以是一个数组,不用放在闭合标签里,以后再说)。

看到

SyntaxError: Adjacent JSX elements must be wrapped in an enclosing tag

SyntaxError: Unterminated regular expression

的报错

一定要检查自己的jsx是否在一个闭合标签中,同时是否语法正确