20190627のReactに関する記事は6件です。

Reactのchildren探訪

ReactのJSXでも子要素を定義できますが、これが結構面白いものでした。

自作エレメントにも子要素

ReactのJSXでは、(DOMを組み立てるものである以上当然ですが)<div>の中に<a>を書いて、そしてさらに文字列を書き込む、ということが可能です。

そして、これはHTML由来のエレメントだけでなく、自作のコンポーネントでも実現可能です。

function SomeWrapper({children}) {
  return(
    <div className="some-class">
      { children }
    </div>
  );
}

このように、子要素はchildrenというPropとして渡ってきます。

childrenの中身が知りたくて

では、このchildrenには何がどのような形式で来るのでしょうか。JSXの変換先であるReact.createElementソースコードに当たってみました。挙動はchildrenの数によって違います。

  • 0個…childrenにはpropとして渡したchildrenが(もしあれば)渡される
  • 1個…childrenには唯一のchildren引数がそのまま渡される1
  • 2個以上…childrenには引数で渡されたものが配列に詰め込まれる

公式な操作方法

現実問題として、今からchildrenの実装を変えてしまうということは互換性問題などを考えれば可能性は薄いのですが、公式にはchildrenのデータ形式は「非公開」ということになっています。そこで、childrenを操作するためのReact.Childrenというユーティリティ関数群があります(リファレンス)。

  • React.Children.mapchildrenの各要素に対して関数を実行して、結果を配列で得る。
  • React.Children.forEachchildrenの各要素に対して関数を実行する。
  • React.Children.countchildrenの個数を返す
  • React.Children.toArraychildrenを本物の配列に変換して返す
  • React.Children.onlychildrenが1つのJSXエレメントであることを保証する

なお、forEachmaptoArrayで処理する際に、キーは子要素間で一意となるようなものがReact側で振られます。また、子要素として<React.Fragment>を渡した場合、1つものとして扱われます。

childrenReact.memo

今のところ、React.createElementをキャッシュするような仕組みはないので、<br />のようなシンプルなJSX要素であっても、2回の実行でオブジェクトとして一致することはありません。さらに、上述のように複数の子要素をもたせた場合、childrenの配列を生成しますので、このインスタンスも一致しません。

そして、PureComponentReact.memochildrenを特別扱いしませんので、(文字列1つだけ指定するような例外的なケースは別として)childrenを指定するようなコンポーネントでは、メモ化は利かないということになってしまいます。

対策としては、

  • 子要素を取るコンポーネントを使う側の場合、「コンポーネント+子要素」全体を1つのコンポーネントとして、それをメモ化する
  • 子要素を取るコンポーネントを作る場合、メモ化を諦める

などが考えられます。そして、children以外でも、表示する文字列をpropで取る場合に、「HTMLタグで装飾したい」とその場で書いたJSXを渡せば同じ事態となります。こちらについては、React.useMemo、クラスのプロパティ、外側で変数に定義するなど、同じJSXインスタンスを使い回す形として対応が可能かと思います。


  1. Context.Consumerなどで子要素代わりにコールバックを書くことがありますが、1個なので関数がそのままchildrenに入る形となっています。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gatsbyのブログに目次を追加する

はじめに

ブログを見やすくして、構造化させるのに必須の項目「目次」を設定したのでその方法を記録に残したいと思います。

いちいち自分でマークダウンの目次を書くのが面倒くさいので自動で目次を生成してくれる良さそうなプラグインないかなと思いました。

最初はgatsby-transformer-remarkに標準で搭載されている機能を利用しようとしたのですが、リンクがうまく効かずに404エラーが出てしまっています。

そこで今回利用したのが「gatsby-remark-toc」です。

これを利用することで一瞬でマークダウンに目次を導入することができました。今回はその方法を紹介します。

gatsby-remark-tocの設定

まずはgatsby-remark-tocをインストールしましょう

yarn add gatsby-remark-toc 

インストールしたら次にgatsby-config.jsの設定をします。gatsby-remark-tocを追加してオプションを設定します。

headerには目次上部に出てくる見出しの名称を設定します。私は無難に「目次」と表示させます。

includeに対しては.mdファイルが存在して目次を表示させたいディレクトを指定します。これで指定したファイルのみに目次を表示させられるようになりました。

gatsby-config.js
{
      resolve: 'gatsby-transformer-remark',
      options: {
        plugins: [
          {
            resolve: 'gatsby-remark-toc',
            options: {
              header: '目次',
              include: ['src/pages/blog/*.md'],
           },

たったこれだけで目次の表示は完了です。お疲れ様でした。

これで問題なく目次の構造を作成してHTMLとして上部に出力することができます。ただし、デザインは入っていないので各自で自由にcssを作成して、反映するようにしましょう

まとめ

以上で目次の設定は完了です。非常に簡単でした。

皆さんもgatsby-remark-tocを利用して爆速で目次をマークダウンに作成してみましょう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

npm startしてもエラーが出る問題

今回のエラー

React Reduxの環境構築で躓きました
全てインストール出来たと思い、意気揚々とコマンドを打つ:sunglasses:

$ npm start

すると、以下のエラーコードが出てきて全く動きません:thinking:

There might be a problem with the project dependency tree....以下略
スクリーンショット 2019-06-26 11.38.40.png

反省点

どの言語も共通で、エラー文をよく読むこと
画像内にあるオレンジ色の番号通りに進めて行けば無事解決しました:ok_hand:
一応下に書き記しておきます

  1. Delete package-lock.json (not package.json!) and/or yarn.lock in your project folder.

  2. Delete node_modules in your project folder.

  3. Remove "webpack" from dependencies and/or devDependencies in the package.json file in your project folder.

  4. Run npm install or yarn, depending on the package manager you use.

In most cases, this should be enough to fix the problem.
If this has not helped, there are a few other things you can try:

  5. If you used npm, install yarn (http://yarnpkg.com/) and repeat the above steps with it instead.
     This may help because npm has known issues with package hoisting which may get resolved in future versions.

  6. Check if /Users/home/node_modules/webpack is outside your project directory.
     For example, you might have accidentally installed something in your home folder.

  7. Try running npm ls webpack in your project folder.
     This will tell you which other package (apart from the expected react-scripts) installed webpack.

参考元

npm startで怒られたときの話

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[メモ] ReactでGoogleAnalyticsを使う

下記ライブラリを入れます。

https://github.com/react-ga/react-ga

インストール方法

npm install react-ga --save

使用方法

import ReactGA from 'react-ga';
ReactGA.initialize('ここにID');
ReactGA.pageview(window.location.pathname + window.location.search);

これでトラッキングできてる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

redux-form の validation を関数合成使って見やすくしてみる

私は React + redux-form でフォーム画面を作ることが多いのですが、redux-form の validation がやや冗長になりやすい傾向にあります。

これを関数合成使って、見やすくメンテナンスしやすいコードに出来ないかなと思って試してみました。

※ 基本的な redux-form の知識が必要です。

どのあたりが冗長なの?

例えば、このようなフォームを実装するとします。

SnapCrab_NoName_2019-6-27_11-50-56_No-00.png

redux-form で validate 関数を定義していくと、こんな風に書くと思います。
(共通化などは一旦考えないで書いた場合)

const validate = (values, props) => {
  const errors = {};
  if (!values.option_id) {
    errors.option_id = '必須入力です';
  }
  if (!_.trim(values.name)) {
    errors.name = '必須入力です';
  }
  if (!_.trim(values.email)) {
    errors.email = '必須入力です';
  } else if (!EMAIL_VALIDATION.test(values.email)) {
    errors.email = '正しいメールアドレスを入力して下さい';
  }
  if (
    values.phone_number &&
    !_.inRange(
      values.phone_number.length,
      PHONE_NUMBER_MIN_LENGTH,
      PHONE_NUMBER_MAX_LENGTH
    )
  ) {
    errors.phone_number = '携帯電話は10桁以上20桁以下で入力できます。';
  }
  if (values.birth_day && !BIRTHDAY_PATTERN.test(values.birth_day)) {
    errors.birth_day = '不正な値です';
  }
  if (calcAge(values.birth_day) < ADULT_AGE && !values.aggreement) {
    errors.aggreement = '未成年者は保護者の同意が必要です';
  }
  return errors;
};

綺麗なコードには見えませんが、これぐらいなら許容範囲かもしれません。
しかし、例えば性別や生年月日といった項目が追加されるとどうでしょうか。かなり長くなってしまいます。

また、名前やメールアドレスといった必須入力かどうかを判定するロジックは同じ仕組みなので共通化したいですね。

どんな風に書けると嬉しい?

共通化するにあたって、どういう書き方ができると綺麗かなと考えてみました。
ここは個人的な思想なども入ると思うので一概に正しいとも言えませんが、今回は下記のように書けると良いかなと思います。

const validate = (values, props) => {
  return validates(
    validateRequired('option_id'),
    validateRequired('name'),
    validateRequired('email'),
    validateEmail('email'),
    validatePhoneNumber('phone_number'),
    validateBirthday('birth_day'),
    validateUnderCondition(
      calcAge(values.birth_day) < ADULT_AGE,
      validateRequired('aggreement')
    ),
  )(values, {});
};

どうでしょうか?
それなりに見やすくなったと思います。

validates 関数は高階関数で、values と errors の初期値(ここでは空の Hash) をとり、列挙された関数を順次実行してくれる関数です。

validateRequired など関数は、redux-form の values が hash であることから、指定された key に対して validate を行う関数です。
validateRequired であれば、その key が存在するかどうかといったチェックですね。

validateUnderCondition のみ特別で引数を複数取ります。第二引数に validateRequired といった validation 関数を指定するのですが、第一引数にそれを実行する条件を指定できるようにしてます。
今回では、未成年の場合のみ同意が必要といった validation を表現するために使います。

validates 関数

では、validates 関数を組んでいきます。

export const validates = (...fns) => (...args) => {
  if (fns.length > 1) {
    return fns.reduce((prevFn, nextFn, index) => {
      return nextFn.apply(
        null,
        typeof prevFn === 'function' ? prevFn.apply(null, args) : prevFn
      );
    })[1];
  } else if (fns.length === 1) {
    return fns[0].apply(null, args)[1];
  } else {
    return args[1];
  }
};

高階関数にしつつ、どちらの引数もリストで取るようにしました。
fns は関数のリストで、args は values や errors の初期値といった前提条件となる値のリストです。

reduce を使って関数合成しつつも、apply で動的引数である args を適用しています。
また、fns が一つの場合でも実行できるようにしています。
複数の関数を合成するだけであれば不要ですが、項目が 1 つだけの form もあり得ると思うので条件分岐しています。

残念なところは、6 行目あたりで prevFn が function かどうかの判定が必要になってしまっているところです。
後述しますが、apply を使っているためか、prevFn が関数ではなく prefFn の返り値そのものになってしまってうまく関数合成ができませんでした。

redux-form 以外でも利用できるようにするために apply 使っていたのですが、redux-form の validation に限れば、args を動的引数で取る必要性はないので、apply を辞めるという手もあります。
もし、もっと良い方法ご存知の方は教えて欲しいです。

各 validation 関数

各々の validation 関数は特別なことはしていません。
全て列挙すると長くなるので代表的なものを載せます。

export const validateRequired = key => (values, errors) => {
  if (!_.trim(values[key])) {
    errors[key] = '必須入力です';
  }
  return [values, errors];
};

export const validateUnderCondition = (condition, validateFunc) => (
  values,
  errors
) => {
  if (condition) {
    return validateFunc(values, errors);
  }
  return [values, errors];
};

特に難しいことはしていません。
redux-form の values が hash なので、高階関数の一つ目の引数に key を取って、それを使って値を割り出しています。
validation 関数に関しては、redux-form の構造にどっぷりっといった感じですが、values も errors もただの hash なので redux-form 以外でも使い回すことは出来そうです。

まとめ

この記事ですが、実はあまり redux-form には関係なかったりします。
元は関数合成使って、もう少しうまく書けないかなぁと思ったのが、たまたま redux-form の validate だっただけです。
記事にするにあたって、もっと汎用的に直してから書こうかと思いましたが、むしろイメージしづらいのではと思って、そのまま redux-form の validate を題材にしました。
もちろん、ここにあげているバリデーション以外にも redux-form の FieldArray を使った場合の validation 関数等々、色々と定義はしていますが、validates 関数のおかげで集約しつつ簡単にフォーム側の validate を書けるので重用してます。

また、redux-form 以外でも各 validation 関数はもちろん利用しています。
ただ共通化しただけの関数ですからね。

残念な部分もあるので、もっと改善していきたいと思います。

(書いた後に思いましたが、記事的に validates 関数と validation 関数があって、何が何やらって感じですね。。。表現力磨きたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React パフォーマンス改善 (Array系)

改善対象

配列を繰り返し処理によって表示しており、ソートや絞り込み等が行われる場合、レンダリングコストや繰り返し表示する数によっては、何も意識せずに実装した時に著しくパフォーマンスが悪くなることがあります。

そういった時に、パフォーマンス低下を防ぐ簡単な方法をひとつご紹介します。

Step1: Componentを改善する

React.PureComponentshouldComponentUpdate などを用いて再レンダリングを制御する。
shallowEqualの判定を考慮して使い分けが必要です。

https://github.com/facebook/fbjs/blob/master/packages/fbjs/src/core/shallowEqual.js

items = [{value: 1}, {value: 2}, {value: 3}]

{items.map(item => (
  <Item obj={item} />
))}

React.PureComponent

Class Item extends React.PureComponent {
  render() {
    return <div>{this.props.obj.value}</div>
  }
}

shouldComponentUpdate

Class Item extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.obj.value !== nextProps.obj.value
  }

  render() {
    return <div>{this.props.obj.value}</div>
  }
}

改善例

表示していたListが下記のように並び順が変わった場合

items = [
  {value: 1}, // key: 1 
  {value: 2}, // key: 2  
  {value: 3}  // key: 3 
]

items = [
  {value: 3}, // key: 1 
  {value: 2}, // key: 2  
  {value: 1}  // key: 3 
]

{value: 2} はkeyが変わらずかつ shouldComponentUpdate で比較されているobj.valueの値も同じため、
{value: 2} のComponent(<Item />)は再レンダリングされなくなります。

※ この例では、keyを指定していないためデファルトでindexが使われています。

Step2: ユニークかつ適切なkeyを指定する

shouldComponentUpdate と key を 利用

※ PureComponentの例は省略します。

Class Item extends React.Component {
  shouldComponentUpdate(nextProps) {
    return this.props.obj.value !== nextProps.obj.value
  }

  render() {
    return <div>{this.props.obj.value}</div>
  }
}

{items.map(item => (
  <Item key={item.obj.value} obj={item.obj} />
))}

改善例

表示していたListが下記のように並び順が変わった場合

items = [
  {value: 1}, // key: 1 
  {value: 2}, // key: 2  
  {value: 3}  // key: 3 
]

items = [
  {value: 3}, // key: 3 
  {value: 2}, // key: 2  
  {value: 1}  // key: 1 
]

この例では、keyが変わらずかつobj.valueの値も同じため、
すべてのComponent(<Item />)が再レンダリングされなくなります。

DEMOページ

https://codesandbox.io/s/objective-dijkstra-lutz9

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む