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 呼び出しの構文糖であり、これが React 公式が述べた「JSX は JavaScript の能力を十分に備えている」という言葉に完璧に呼応しています。

上の図のように、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オブジェクトに追加
      }
    }
  }
  //子要素は1つ以上の引数になり得る、それらは新しく割り当てられた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]
    }

    {
      //arrayを凍結し、元の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)は 3 つのパラメータtype, config, childrenを持ち、それぞれの意味は次の通りです。

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

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

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

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

React.createElement(
  'ul',
  {
    // 属性のキーと値のペアを渡す
    className: 'list',
    // 第3引数以降に渡されるパラメータはすべて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のいくつかの属性に分解し、その後は children の処理部分に進みます。

子要素の抽出#

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

// 子要素は1つ以上の引数になり得る、それらは新しく割り当てられた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]
  }
  {
    //arrayを凍結し、元の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の 2 つの関数を実行します。その後、一連の組み立てられたデータをReactElementに渡します。

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

ReactElement#

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

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

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