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

React Helmet のソースコードを読んでみたので、そのメモ。
間違い等がありましたら、ご指摘お願いします。
## React Helmet とは？
React で ヘッドタグを操作したい時に使う ライブラリ。
使い方は、特定のコンポーネントで、

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

を加えるだけ。これで、<Helmet> の中の <title> や <meta name="description"> が head タグ内に加わります。

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

## React Helmet のコード
[こちらのソースコード](https://github.com/nfl/react-helmet/tree/1b57ddb1524bab195df3eeabafef67768a6d2a15) を見ても分かるように、[src フォルダ には たったの３つしかファイルはありません](https://github.com/nfl/react-helmet/tree/1b57ddb1524bab195df3eeabafef67768a6d2a15/src)。

量だけなら簡単に読めそうですね。
まずは、[Helmet.js](https://github.com/nfl/react-helmet/tree/1b57ddb1524bab195df3eeabafef67768a6d2a15/src) から読んでみましょう。

```javascript: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](https://qiita.com/numanomanu/items/2b66d8b2887d44f857dc) というものになり、HelmetSideEffects の機能を使うための実装になります。

この HelmetSideEffects の中身の `withSideEffect` のソースコードは[こちら](https://github.com/gaearon/react-side-effect/blob/master/src/index.js)にありますが、Helmet のコードで 簡単に言えば、
１：`reducePropsToState` で 受け取った props を state に reduce し、
２：`reducePropsToState` で作った state (withSideEffect内部のみで分かる形) を受け取り、クライアントが動いているなら(Dom が使えるなら = canuseDom) `handleClientStateChange` を、サーバーで動いているなら (Dom が使えないなら) `mapStateOnServer` を実行し、state を view に反映させるなどしています。
ここで出てきた `reducePropsToState` と `handleClientStateChange` と `mapStateOnServer` が書いてあるファイルが、[HelmetUtils.js](https://github.com/nfl/react-helmet/blob/1b57ddb1524bab195df3eeabafef67768a6d2a15/src/HelmetUtils.js#L587) になります。

しかし `withSideEffect` で最初に実行される `reducePropsToState` を動かすには、`<Helmet></Helmet>` の中にある要素 ( [Children](https://qiita.com/jkr_2255/items/2e40f7c9ecf300e8d945) ) を Props にしてコンポーネントに渡す必要があります。

この Children を Props に渡す動作をしているのが、[Helmet.js](https://github.com/nfl/react-helmet/tree/1b57ddb1524bab195df3eeabafef67768a6d2a15/src) です。

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

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

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

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

```javascript: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` だけを見てみます。
ソースコードの該当箇所は、こちらです。

```javascript: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 を見てみましょう。

```javascript: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` を実行しています。
それぞれの実装を見たら、`setAttribute` や `document.title=` や `appendChild` があると思います。

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