LoginSignup
1
1

More than 1 year has passed since last update.

React-Helmet のソースコードを読んでみる

Posted at

React Helmet のソースコードを読んでみたので、そのメモ。
間違い等がありましたら、ご指摘お願いします。

React Helmet とは?

React で ヘッドタグを操作したい時に使う ライブラリ。
使い方は、特定のコンポーネントで、

import {Helmet} from "react-helmet"
// 省略
const hoge = () => {
  return(
    <Helmet>
      <title>hoge</title>
      <meta name="description" content="hoge"/>
    </Helmet>
    // 省略
  )
}

を加えるだけ。これで、 の中の や が head タグ内に加わります。

では、読んでみましょう!

React Helmet のコード

こちらのソースコード を見ても分かるように、src フォルダ には たったの3つしかファイルはありません

量だけなら簡単に読めそうですね。
まずは、Helmet.js から読んでみましょう。

Helmet.js
import React from "react";
import PropTypes from "prop-types";
import withSideEffect from "react-side-effect";
// 省略
const Helmet = Component =>
    class HelmetWrapper extends React.Component {
      // 省略
      render() {
            const {children, ...props} = this.props;
            let newProps = {...props};

            if (children) {
                newProps = this.mapChildrenToProps(children, newProps);
            }

            return <Component {...newProps} />;
      }

const NullComponent = () => null;

const HelmetSideEffects = withSideEffect(
    reducePropsToState,
    handleClientStateChange,
    mapStateOnServer
)(NullComponent);

const HelmetExport = Helmet(HelmetSideEffects);

const Helmet = Component => class HelmetWrapper extends React.Component という変わった書き方になっていますが、これは Higher-order Components というものになり、HelmetSideEffects の機能を使うための実装になります。

この HelmetSideEffects の中身の withSideEffect のソースコードはこちらにありますが、Helmet のコードで 簡単に言えば、
1:reducePropsToState で 受け取った props を state に reduce し、
2:reducePropsToState で作った state (withSideEffect内部のみで分かる形) を受け取り、クライアントが動いているなら(Dom が使えるなら = canuseDom) handleClientStateChange を、サーバーで動いているなら (Dom が使えないなら) mapStateOnServer を実行し、state を view に反映させるなどしています。
ここで出てきた reducePropsToStatehandleClientStateChangemapStateOnServer が書いてあるファイルが、HelmetUtils.js になります。

しかし withSideEffect で最初に実行される reducePropsToState を動かすには、<Helmet></Helmet> の中にある要素 ( Children ) を Props にしてコンポーネントに渡す必要があります。

この Children を Props に渡す動作をしているのが、Helmet.js です。

内容は割愛しますが、上のコードの

Helmet.js
            if (children) {
                newProps = this.mapChildrenToProps(children, newProps);
            }

部分の this.mapChildrenToProps(children, newProps) が、children と props を結合させている実装です。

その後、children が Props と結合した後で 呼び出されるのが、reducePropsToStatehandleClientStateChange (とサーバーサイドなら mapStateOnServer) です。
まずは、受け取った Props を state に変換する reducePropsToState を見てみましょう。

HelmetUtils.js
const reducePropsToState = propsList => ({
    baseTag: getBaseTagFromPropsList(
        [TAG_PROPERTIES.HREF, TAG_PROPERTIES.TARGET],
        propsList
    ),
    bodyAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.BODY, propsList),
    defer: getInnermostProperty(propsList, HELMET_PROPS.DEFER),
    encode: getInnermostProperty(
        propsList,
        HELMET_PROPS.ENCODE_SPECIAL_CHARACTERS
    ),
    htmlAttributes: getAttributesFromPropsList(ATTRIBUTE_NAMES.HTML, propsList),
    linkTags: getTagsFromPropsList(
        TAG_NAMES.LINK,
        [TAG_PROPERTIES.REL, TAG_PROPERTIES.HREF],
        propsList
    ),
    metaTags: getTagsFromPropsList(
        TAG_NAMES.META,
        [
            TAG_PROPERTIES.NAME,
            TAG_PROPERTIES.CHARSET,
            TAG_PROPERTIES.HTTPEQUIV,
            TAG_PROPERTIES.PROPERTY,
            TAG_PROPERTIES.ITEM_PROP
        ],
        propsList
    ),
    // 省略
    scriptTags: getTagsFromPropsList(
        TAG_NAMES.SCRIPT,
        [TAG_PROPERTIES.SRC, TAG_PROPERTIES.INNER_HTML],
        propsList
    ),
    styleTags: getTagsFromPropsList(
        TAG_NAMES.STYLE,
        [TAG_PROPERTIES.CSS_TEXT],
        propsList
    ),
    title: getTitleFromPropsList(propsList),
    titleAttributes: getAttributesFromPropsList(
        ATTRIBUTE_NAMES.TITLE,
        propsList
    )
});

見れば分かるように、連想配列を返していますが、その key に linkTags・metaTags・scriptTags・styleTags・title があるのが読めますね。
ここでは、それぞれの連想配列の key で props から 該当するattribute (href や relなど) がある要素(titleやlinkやmetaなど) のみを取り出す関数を呼び出しています。

ここまでで、withSideEffect の最初に段階 reducePropsToState が読めました。
次は、handleClientStateChange (とサーバーサイドなら mapStateOnServer) です。ここでは、state を view に反映させる = head に title や meta や link を書き込むため、canUseDom でクライアントが動かせる場合に動く handleClientStateChange だけを見てみます。
ソースコードの該当箇所は、こちらです。

HelmetUtils.js
const handleClientStateChange = newState => {
    if (_helmetCallback) {
        cancelAnimationFrame(_helmetCallback);
    }

    if (newState.defer) {
        _helmetCallback = requestAnimationFrame(() => {
            commitTagChanges(newState, () => {
                _helmetCallback = null;
            });
        });
    } else {
        commitTagChanges(newState);
        _helmetCallback = null;
    }
};

newState という引数に、reducePropsToState で返している連想配列が入ります。
newState に defer があろうとなかそうと、commitTagChanges を実行していますね。では、この commitTagChanges を見てみましょう。

HelmetUtils.js
const commitTagChanges = (newState, cb) => {
    const {
        baseTag,
        bodyAttributes,
        htmlAttributes,
        linkTags,
        metaTags,
        noscriptTags,
        onChangeClientState,
        scriptTags,
        styleTags,
        title,
        titleAttributes
    } = newState;
    updateAttributes(TAG_NAMES.BODY, bodyAttributes);
    updateAttributes(TAG_NAMES.HTML, htmlAttributes);

    updateTitle(title, titleAttributes);

    const tagUpdates = {
        baseTag: updateTags(TAG_NAMES.BASE, baseTag),
        linkTags: updateTags(TAG_NAMES.LINK, linkTags),
        metaTags: updateTags(TAG_NAMES.META, metaTags),
        noscriptTags: updateTags(TAG_NAMES.NOSCRIPT, noscriptTags),
        scriptTags: updateTags(TAG_NAMES.SCRIPT, scriptTags),
        styleTags: updateTags(TAG_NAMES.STYLE, styleTags)
    };

    // 省略
    };

見れば分かるように、body・html タグでは updateAttributes を、titleタグでは updateTitle を、その他のタグでは updateTags を実行しています。
それぞれの実装を見たら、setAttributedocument.title=appendChild があると思います。

ここまでで、React-Helmet の簡単な流れを把握することができました。
withSideEffect など一見難しそうに見える実装もありましたが、中身を見ると意外と読めますね。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1