JavaScript
es6
reactjs
recompose

recomposeを使って電卓アプリを作る! ~0からreact習得記 day 10~

これまでのreact習得記は、こちらから閲覧できます。

はじめに

株式会社VRizeでインターンとして勤務しているryo_tです。
短期目標に「1ヶ月でReactのUIを開発できるように」と伝えられたので、Reactはほとんど未経験ですが一歩一歩頑張って行きます。

目的

前回、Reactのライブラリrecomposeを紹介したのですが、自分でも分かっていない部分があったので簡易電卓アプリを作成しながら値の受け渡しについて復習して行きたいと思います。recomposeって何?という方は前回の記事をご覧になっていただければなんとなく理解できると思います。

手順

開発環境のセットアップ

前回と同じように、今回もReact Boilerplateを用いて開発環境のセットアップを行います。更にrecomposestyled-componentsのインストールも行います。
https://github.com/nolotz/react-simple-boilerplate

#terminalで 
git clone https://github.com/nolotz/react-simple-boilerplate.git
#を入力してクローンします

# react-simple-boilerplate`内に移動する
cd react-simple-boilerplate

# パッケージをinstallする
npm install

# npmからrecomposeをインストールする。
npm install recompose --save

# npmからstyled-componentsをインストールする。
npm install --save styled-components

# webpackの開発サーバーを起動する
npm start

ブラウザのアドレスバーにlocalhost:3000と入力すると
Hello React :) と画面左端にこのように表示されるはずです。

Screen Shot 2017-09-25 at 10.33.04.png (13.1 kB)

recomposeを使ってみる前に、src/App.jsx内の<h1>Hello React :)</h1>を消します。同様に、styles/home.scss内の記述も全て消します。これらの記述はHello React :)と表示する為の記述で、今回は必要ありません。

カウンターアプリ

まずは+ボタンと-ボタンを押すと値が増減するお馴染みのカウンターアプリをrecomposeのwithStateHandlersを使って作成したいと思います。withStateHandlersの挙動がよく分からない方は、以前に書いたこちらを見てください。

  • 1) まず最初にstyled-componentsとrecomposeを読み込みます。今回はrecomposeの中のwithStateHandlersを使います。そして、Appコンポーネント内でCounterコンポーネントを呼びます。このCounterコンポーネント内に細かく記述していきます。
App.jsx
import React, {Component} from 'react';
import styled from 'styled-components';
import { withStateHandlers } from 'recompose';

const App = () => {
  return(
    <Counter />
  );
}

export default App;

  • 2) 次に、Counterコンポーネントを作ります。Cardというコンポーネントの中でButtonやNumコンポーネントを格納し、それぞれにスタイリングします。
App.jsx
const Card = styled.div`
  width: 200px;
  height: 200px;
  margin: 0 auto;
  margin-top: 300px;
  background: papayawhip;
`;

const Button = styled.button`
  width: 100px;
  height: 100px;
  background: skyblue;
  font-size: 40px;
  line-height: 60px;
  text-shadow: 2px 1px 2px rgba(0,0,0,.6);
`;

const Num = styled.h1`
  width: 200px;
  height: 100px;
  line-height: 100px;
  background: white;
  box-sizing: border-box;
  text-shadow: 2px 1px 2px rgba(0,0,0,.6);
  border: 1px solid grey;
  text-align: center;
  margin: 0;
`;

const Counter = () => {
  return(
    <Card>
      <Button ></Button>
      <Button ></Button>
      <Num ></Num>
    </Card>
  );
}

Screen Shot 2017-10-18 at 15.03.13.png (92.3 kB)

画像のように出力されていれば成功です。

  • 3) ここからwithStateHandlersを使用してステートレスに値を受け渡していきます。 initialCounterでwithStateHandlers内で初期値を決めます。counter: initialCounterでオブジェクト内のinitialCounterのキーをCounterに設定しています。キーplusとキーminus内での関数は引数として受け取ったvalueをcounterに足したり(引いたり)します。 exportの記述をexport default Enhance(App)に変更するのを忘れないでください。
App.jsx
const Enhance = withStateHandlers(
  ({initialCounter = 0}) => ({
    counter: initialCounter,
  }),
  {
    // { counter } はdestructuringを使っている
    plus: ({ counter }) => (value) => ({
    counter: counter + value,
  }),
    minus: ({ counter }) => (value) => ({
    counter: counter - value,
  }),
 }
)

export default Enhance(App);

4) 最後にpropsを各コンポーネントに渡す為の記述をします。withStateHandlersで定義したcounter,plus,minusを各コンポーネントで使用する為に各コンポーネントの引数にオブジェクトでキー名を入れる必要があります。最終的なコードは以下のようになります。(styled-componentsの記述は省略しています。)

App.jsx
import React from 'react';
import styled from 'styled-components';
import { withStateHandlers } from 'recompose';

//withStateHandlersで定義したcounter, plus, minusを読み込む。
//子コンポーネントではcounter = { counter }と定義する。

const Counter = ({counter, plus, minus}) => {
  return(
    <Card>
      <Button onClick = {() => plus(1) }></Button>
      <Button onClick = {() => minus(1) }></Button>
      <Num counter = { counter }>{ counter }</Num>
    </Card>
  );
}

const App = ({ counter, plus, minus}) => {
  return(
    <Counter counter={counter} plus={plus} minus={minus}/>
  );
}

const Enhance = withStateHandlers(
  ({initialCounter = 0}) => ({
    counter: initialCounter,
  }),
  {
    // { counter } はES6 の Destructuringを使っている
    plus: ({ counter }) => (value) => ({
    counter: counter + value,
  }),
    minus: ({ counter }) => (value) => ({
    counter: counter - value,
  }),
 }
)

export default Enhance(App);

m3uQB9Ptoc.gif (62.3 kB)

電卓アプリ

今回の作成する電卓アプリのソースコードはこちらで確認する事が出来ます。
続いては、カウンターアプリの応用で電卓アプリを作成したいと思います。
※開発環境のセットアップは省略します。

  • 1) まずは必要ライブラリをimportして、App内に外枠のCalculatorコンポーネントを作り、その中に子コンポーネントを記述していきます。そして適当にスタイリングします。
App.jsx
import React, {Component} from 'react';
import styled from 'styled-components';
import { withStateHandlers } from 'recompose';

const Calculator = styled.div`
  width: 600px;
  height: 300px;
  margin: 0 auto;
  margin-top: 200px;
  background: papayawhip;
`;

const App = () => {
  return(
    <Calculator>
      <Counter />
      <Counter />
    </Calculator>
  );
}

export default App;

  • 2) 次に、電卓の数字キーに必要な数字を配列に入れます。Javascriptのmapが配列内の数字の数だけボタンを作成していきます。mapは既存の配列の要素を順に処理して、新しい配列を作る関数です。
App.jsx
const Nums = [0,1,2,3,4,5,6,7,8,9];

const Counter = (props) => {
    console.log("Counter props is", props);
  const {setCounter, counter} = props;

  return (
    <section>
      <h1>current value is : {counter}</h1>
      {Nums.map(num => (
        <button onClick={() => setCounter(num)} >{num}</button>
      ))}
    </section>
  );
};

画像のように出力されれば成功です。
Screen Shot 2017-10-18 at 20.21.23.png (48.8 kB)

  • 3) 次にwithStateHandlersを使ってコンポーネント間でやり取りする関数を定義していきます。ここでは、withStateHandlers内で、key1列目が押された時の関数、key2列目が押された時の関数、+-×÷のどれかが押された時の関数が定義されています。withStatehandlers内でinitialCounter1, initialCounter2, initialOperatorを定義する事で、クリックされる前に表示する内容を決めることが出来ます。ここでは、initialCounter1=0, initialCounter2=0, initialOperator='+'と定義されているので、初期画面は画像のようになります。(Operatorはまだ定義していないので無視してください。)

image.png (51.0 kB)

App.jsx
const enhance = withStateHandlers(
    ({ initialCounter1 = 0, initialCounter2 = 0, initialOperator = '+' }) => ({
      counter1: initialCounter1,
      counter2: initialCounter2,
      operator: initialOperator
    }),
    {
      setCounter1: () => (value) => ({
        counter1: value,
      }),
      setCounter2: () => (value) => ({
        counter2: value,
      }),
      setOperator: () => (value) => ({
        operator: value
      }),
    }
  )

export default enhance(App);

  • 4) 「 + - × ÷ 」を表示する為にオペレーターを作ります。Operatorコンポーネントの中では各ボタンに、クリックされた記号を表示させる為の関数が入っています。
App.jsx
const Operator = (props) => {
  console.log("operator props is", props);
  const {setOperator, operator} = props;

  return (<div>
    <h1>Operator is { operator }</h1>
    <button onClick={() => setOperator("-")}>-</button>
    <button onClick={() => setOperator("+")}>+</button>
    <button onClick={() => setOperator("×")}>×</button>
    <button onClick={() => setOperator("÷")}>÷</button>
  </div>)  
}

ここまでではまた実際に表示をしていないので、Calculatorコンポーネントの中にOperatorタグを記述し表示します。

App.jsx
const App = (props) => {

  const {setCounter1, setCounter2, counter1, counter2, setOperator, operator} = props;

  return(
    <Calculator>
      <Counter setCounter={setCounter1} counter={counter1}/>
      <Counter setCounter={setCounter2} counter={counter2}/>
      <Operator operator={operator} setOperator={setOperator} />
    </Calculator>
  );
}

  • 5) 最後にResultコンポーネントを作り、受け取った値を計算して表示させます。Resultコンポーネント内では、引数で受け取ったoperatorが「+」ならcounter1とcounter2を足して返す、operatorが「-」なら引く、「×」ならかける、「÷」なら割ると、条件分岐させています。
App.jsx
const Result = ({ counter1, counter2, operator }) => {

  let value = '+';

  if(operator == '+')
    value = counter1 + counter2
  if(operator == '-')
    value = counter1 - counter2
  if(operator == '÷')
    value = counter1 / counter2
  if(operator == '×')
    value = counter1 * counter2
  console.log('value is' + value);

  return <h1> {counter1} {operator} {counter2} = { value } </h1>;
}

まだResultを表示させてはいないので、Operatorの時と同様、Calculatorコンポーネント内にResultタグを記述します。

App.jsx
<Calculator>
  <Counter setCounter={setCounter1} counter={counter1}/>
  <Counter setCounter={setCounter2} counter={counter2}/>
  <Operator operator={operator} setOperator={setOperator} />
  <Result counter1={counter1} counter2={counter2} operator={operator}/>
</Calculator>

こんな感じになっていれば成功です。

SERHQRgWt1.gif (66.7 kB)

ハマったところ

  • コンポーネントの引数にwithStateHandlers内で定義したオブジェクトのキーを記述する箇所

例)

App.jsx
const Result = ({ counter1, counter2, operator }) => {
  let value = '+ 
...
  • Appの子コンポーネントにpropsを渡す為にタグの属性にオブジェクトのキーを定義する箇所

例)

App.jsx
   <Calculator>
      <Counter setCounter={setCounter1} counter={counter1}/>
      <Counter setCounter={setCounter2} counter={counter2}/>
      <Operator operator={operator} setOperator={setOperator} />
      <Result counter1={counter1} counter2={counter2} operator={operator}/>
    </Calculator>

おわりに

recomposeを使用してステートレスなアプリをいくつか作ってみて、だいぶ理解が深まりました。recomposeでハマったところは大抵、ES2015で理解できてない部分(Destructuring)だったので積極的に復習します。

補足、間違っている箇所がございましたらご指摘よろしくお願いします。

株式会社VRizeは、VR広告ネットワークやVR動画アプリの制作等、VRに関わる様々なサービスを製作しています。VRとReactに興味があるフロントエンジニアさん、絶賛募集中です!
https://vrize.io/recruits/
https://www.wantedly.com/projects/79383