React 中不使用 JSX

1,629 阅读2分钟

我们知道在 React 实际是通过虚拟 DOM 来生成真实的 DOM, React 对 JavaScript 进行了扩展,使得 React 有能力在 JavaScript 中属性类 HTML 代码,让开发人员更加具有亲近感,但是这里,这里想要做的事看清楚这种亲近感的本质 --- JavaScript 的 DOM 描述。

创建一个 React 元素

  • API: React.createElement(component, props, ...children)
import React from 'react'
// 一个 React 组件
function App(props) {
  return (
    <div>{props.name}</div>
  )
}

// 渲染这个组件到指定的 DOM
ReactDOM.render(<App />, document.getElementById('app'))

经过 Babel 编译之后

import React from 'react'; // 一个 React 组件

function App(props) {
  return /*#__PURE__*/React.createElement("div", null, props.name);
} // 渲染这个组件到指定的 DOM


ReactDOM.render( /*#__PURE__*/React.createElement(App, null), document.getElementById('app'));

React.createElement 的实现

React.createElement / ReactEleme…, 它处理 props, 然后调用了 ReactElement 生成一个 React 元素

export function createElement(type, config, children) {
  let propName;
  const props = {};

  // other codes

  if (config != null) {
    // other codes

    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) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    // __DEV__ codes
    props.children = childArray;
  }

  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // __DEV__ codes
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

示例

  • 示例1: 嵌套传递 props
import React from 'react'
import ReactDOM from 'react-dom'

const h = React.createElement

const Comp2 = function Hello (props) {
  const { name } = props
  return h('div', null, `my name is ${name}`)
}

let Comp1 = h(Comp2, {name: 'tom'}, null)

ReactDOM.render(Comp1, document.getElementById('app'))
  • 示例2: 没声明函数的隐式函数组件, children 中含有变量
const name = 'Josh Perez';
const h = React.createElement

const element = /*#__PURE__*/h("h1", null, "Hello, ", name);
ReactDOM.render(element, document.getElementById('root'));
  • 示例3: children 中含有 函数
function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};
const h = React.createElement

const element = /*#__PURE__*/h("h1", null, "Hello, ", formatName(user), "!");
ReactDOM.render(element, document.getElementById('root'));
  • 示例4:函数组件中含有条件语句
const h = React.createElement

function getGreeting(user) {
  if (user) {
    return /*#__PURE__*/h("h1", null, "Hello, ", formatName(user), "!");
  }

  return /*#__PURE__*/h("h1", null, "Hello, Stranger.");
}
  • 示例5: 隐式函数组件中的属性
const h = React.createElement

const element1 = /*#__PURE__*/h("div", {
  tabIndex: "0"
});
const element2 = /*#__PURE__*/h("img", {
  src: user.avatarUrl
});
  • 示例6: 多个子元素
const h = React.createElement

const element = /*#__PURE__*/React.createElement("div", null, /*#__PURE__*/React.createElement("h1", null, "Hello!"), /*#__PURE__*/React.createElement("h2", null, "Good to see you here."));
  • 示例7: 一个类组件的编译
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

// 编译后
const h = React.createElement

class Welcome extends React.Component {
  render() {
    return /*#__PURE__*/h("h1", null, "Hello, ", this.props.name);
  }
}
  • 示例8: 一个相对复杂的函数组件
function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar"
          src={props.author.avatarUrl}
          alt={props.author.name}
        />
        <div className="UserInfo-name">
          {props.author.name}
        </div>
      </div>
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

// 编译后
const h = React.createElement

function Comment(props) {
  return /*#__PURE__*/h("div", {
    className: "Comment"
  }, /*#__PURE__*/h("div", {
    className: "UserInfo"
  }, /*#__PURE__*/h("img", {
    className: "Avatar",
    src: props.author.avatarUrl,
    alt: props.author.name
  }), /*#__PURE__*/h("div", {
    className: "UserInfo-name"
  }, props.author.name)), /*#__PURE__*/h("div", {
    className: "Comment-text"
  }, props.text), /*#__PURE__*/h("div", {
    className: "Comment-date"
  }, formatDate(props.date)));
}

这种复杂的组件,看起来嵌套就不容易看懂了,在写这种组件时,我们最好将子组件中的内容单独提取出来,使得逻辑简单。

这个时候模板的优势就显现出来了, 但是这个问题也是有解决办法,下面就是两个由社区提供的解决方案,然我们在 h 函数的时候,能够更加的舒畅...

React 社区的解决方案

对比 Vue 中的 $createElment

我们知道 Vue 中也可以使用 render 函数来编写 Vue 组件,render 函数的本质其实就是调用了 Vue 实例下的 $createElement 方法,与 React 思想上其实大同小异,都是使用了创建元素,然后使用虚拟 DOM 对比 DOM,到真实的 DOM 的过程。

Vue 中不使用模板 文章讲解了不使用 Vue 模板的使用方法

参考

本文使用 mdnice 排版