enpitsulin

enpitsulin

这个人很懒,没有留下什么🤡
twitter
github
bilibili
nintendo switch
mastodon

JSXとは何ですか

JSX の本質#

JSX とは一体何か、まずは React 公式サイトが示す定義を見てみましょう:

JSX は JavaScript の一種の構文拡張で、テンプレート言語に非常に近いですが、JavaScript の能力を十分に備えています。

JSX に関して、facebook は「構文拡張」と定義し、同時に十分に備えたJS の能力を持つとしています。しかし実際には、HTML に非常に似ており、馴染みのある JavaScript とは異なります。これはどう説明すれば良いのでしょうか?

JSX 構文と JavaScript の関係#

実際の答えは非常にシンプルです、見てくださいReact 公式サイト

JSX は React.createElement () にコンパイルされ、React.createElement () は「React Element」と呼ばれる JS オブジェクトを返します。

つまり、実際には JSX は一度コンパイルされ、React.createElement() の呼び出しによって React Element の JS オブジェクトに変わるのです。

では、コンパイルはどのように行われるのでしょうか?実際には、ECMAScript 2015+ バージョンのコードに対して、通常は Babel というツールを使用して古いバージョンのブラウザとの互換性を持たせる必要があります。

例えば、ES2015+ で非常に便利なテンプレート文字列の構文糖:

var text = 'World'
console.log(`Hello ${text}!`) //Hello World!

Babel はこのコードをほとんどの低バージョンのブラウザでも認識できる ES5 コードに変換することができます:

var text = 'World'
console.log('Hello'.concat(text, '!')) //Hello World!

同様に、Babel も JSX 構文を JavaScript コードに変換する能力を持っています。 では、Babel は具体的に JSX をどのように処理するのでしょうか?【例子】

Babel コンパイル | 1622x173

見ると、JSX のタグはすべて対応する React.createElement の呼び出しに変換されています。つまり、実際には JSX は React.createElement を書いているということです。だから、見た目は HTML のようですが、内部は JS なのです。

JSX の本質は React.createElement という JavaScript 呼び出しの構文糖であり、これにより「JSX は十分に JavaScript の能力を備えている」という React 公式の言葉に完璧に呼応しています。

上の図のように、JSX の利点は明らかです。JSX コードは階層が明確で、ネスト関係がはっきりしています。しかし、React.createElement を使用すると、見た目が JSX よりも混乱し、読みづらく、書くのも大変です。

JSX 構文糖は、フロントエンド開発者が最も馴染みのある HTML タグの構文を使用して仮想 DOM を作成できるようにし、学習コストを下げると同時に、開発効率と開発体験を向上させます。

JSX はどのように DOM にマッピングされるのか?#

まずは createElement のソースコードを見てみましょう。ここに注釈付きのソースコードがあります。

export function createElement(type, config, children) {
  var propName
  //保留名を抽出
  var props = {}

  var key = null
  var ref = null
  var self = null
  var source = null
  //タグの属性が空でない場合、タグに属性値があることを示し、特別処理:key と ref を個別の変数に割り当てる
  if (config != null) {
    //合理的な ref がある
    if (hasValidRef(config)) {
      ref = config.ref
    }
    //合理的な key がある
    if (hasValidKey(config)) {
      key = '' + config.key
    }

    self = config.__self === undefined ? null : config.__self
    source = config.__source === undefined ? null : config.__source

    //config の残りの属性で、かつ原生属性(RESERVED_PROPS オブジェクトの属性)でないものは、新しい props オブジェクトに追加
    for (propName in config) {
      if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
        props[propName] = config[propName] //config から key/ref を除去し、他の属性を props オブジェクトに放り込む
      }
    }
  }
  // 子要素の数(第3引数以降の引数はすべて子要素、兄弟ノード)
  var childrenLength = arguments.length - 2

  if (childrenLength === 1) {
    props.children = children
  } else if (childrenLength > 1) {
    var childArray = Array(childrenLength) //配列を宣言
    //順次 children を配列に push
    for (var i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2]
    }

    {
      //配列を凍結し、元の childArray を返し、変更できないようにする。ライブラリのコアオブジェクトが変更されるのを防ぎ、オブジェクトを凍結することでパフォーマンスが大幅に向上する。
      if (Object.freeze) {
        Object.freeze(childArray)
      }
    }
    props.children = childArray //親コンポーネントは this.props.children を通じて子コンポーネントの値を取得
  }

  //子コンポーネントにデフォルト値を設定。一般的にはコンポーネントに対して。
  //class com extends React.component であれば、com.defaultProps で現在のコンポーネントの静的メソッドを取得。
  if (type && type.defaultProps) {
    //現在のコンポーネントにデフォルトの defaultProps があれば、現在のコンポーネントのデフォルト内容を defaultProps に定義。
    var defaultProps = type.defaultProps

    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        //親コンポーネントの対応する値が undefined の場合、デフォルト値を props の属性として割り当てる。
        props[propName] = defaultProps[propName]
      }
    }
  }

  {
    //ref または key が存在する場合
    if (key || ref) {
      //type がコンポーネントの場合
      var displayName =
        typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type
      if (key) {
        defineKeyPropWarningGetter(props, displayName)
      }

      if (ref) {
        defineRefPropWarningGetter(props, displayName)
      }
    }
  }

  //props:1.config の属性値 2.children の属性(文字列/配列)3.default の属性値
  return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props)
}

パラメータ分析#

function createElement(type, config, children) は三つのパラメータ type, config, children を持ち、それぞれの意味は次の通りです。

type:ノードのタイプを識別するために使用されます。これは「h1」や「div」のような標準 HTML タグの文字列であるか、React コンポーネントのタイプまたは React フラグメントのタイプである可能性があります。

config:オブジェクト形式で渡され、コンポーネントのすべての属性がキーと値のペアとして config オブジェクトに格納されます。

children:オブジェクト形式で渡され、コンポーネントタグ間にネストされた内容、すなわち「子ノード」「子要素」と呼ばれるものを記録します。

例えば、以下の呼び出し例があります。

React.createElement(
  'ul',
  {
    // 属性のキーと値のペアを渡す
    className: 'list',
    // 第三引数以降はすべて children
  },
  React.createElement(
    'li',
    {
      key: '1',
    },
    '1'
  ),
  React.createElement(
    'li',
    {
      key: '2',
    },
    '2'
  )
)

それに対応する DOM 構造は次の通りです。

<ul className="list">
  <li key="1">1</li>
  <li key="2">2</li>
</ul>

パラメータを理解した後、次に進みましょう。

config パラメータの分解#

if (config != null) {
  //合理的な ref がある
  if (hasValidRef(config)) {
    ref = config.ref
  }
  //合理的な key がある
  if (hasValidKey(config)) {
    key = '' + config.key
  }

  self = config.__self === undefined ? null : config.__self
  source = config.__source === undefined ? null : config.__source

  //config の残りの属性で、かつ原生属性(RESERVED_PROPS オブジェクトの属性)でないものは、新しい props オブジェクトに追加
  for (propName in config) {
    if (hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) {
      props[propName] = config[propName] //config から key/ref を除去し、他の属性を props オブジェクトに放り込む
    }
  }
}

ここでは、入参の config を ref, key, self, source, props のいくつかの属性に分解し、次に子要素の処理部分に進みます。

子要素の抽出#

上記の config を分解した後、次は子要素を処理するコードです。ここでは第二引数以降のすべての引数を props.children 配列に格納します。

// 子要素の数(第三引数以降の引数はすべて子要素、兄弟ノード)
var childrenLength = arguments.length - 2

if (childrenLength === 1) {
  props.children = children
} else if (childrenLength > 1) {
  var childArray = Array(childrenLength) //配列を宣言
  //順次 children を配列に push
  for (var i = 0; i < childrenLength; i++) {
    childArray[i] = arguments[i + 2]
  }
  {
    //配列を凍結し、元の childArray を返し、変更できないようにする。ライブラリのコアオブジェクトが変更されるのを防ぎ、オブジェクトを凍結することでパフォーマンスが大幅に向上する。
    if (Object.freeze) {
      Object.freeze(childArray)
    }
  }
  props.children = childArray //親コンポーネントは this.props.children を通じて子コンポーネントの値を取得
}

次に、親コンポーネントが children に props を渡す場合の処理です。子コンポーネントがデフォルト値を設定していて、親コンポーネントが props を渡さなかった場合(つまり値が undefined の場合)は、提供されたデフォルト値を使用します。

//子コンポーネントにデフォルト値を設定。一般的にはコンポーネントに対して。
//class com extends React.component であれば、com.defaultProps で現在のコンポーネントの静的メソッドを取得。
if (type && type.defaultProps) {
  //現在のコンポーネントにデフォルトの defaultProps があれば、現在のコンポーネントのデフォルト内容を defaultProps に定義。
  var defaultProps = type.defaultProps

  for (propName in defaultProps) {
    if (props[propName] === undefined) {
      //親コンポーネントの対応する値が undefined の場合、デフォルト値を props の属性として割り当てる。
      props[propName] = defaultProps[propName]
    }
  }
}

次に、keyref が割り当てられているかどうかを検査します。もしあれば、defineKeyPropWarningGetterdefineRefPropWarningGetter の二つの関数が実行され、その後、組み立てたデータを ReactElement に渡します。

ここで defineKeyPropWarningGetterdefineRefPropWarningGetter の二つの関数の役割は、ref と key が取得されるときにエラーを報告することです。

ReactElement#

createElement() が最終的に呼び出すメソッドで、実際には ReactElement() メソッドは渡された一連のデータに typesourceself などのマーク属性を追加し、直接 JS オブジェクトを返します。

JSX で使用される HTML に似たノードは、Babel の助けを借りて、ネストされた ReactElement オブジェクトに変換されます。これらの情報は、後でアプリケーションのツリー構造を構築する際に非常に重要であり、React はこれらのタイプのデータを提供することでプラットフォームの制限から脱却します。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。