~環境構築不要~ 絶対につまづかないReact入門

## 前提条件

### ワークショップにあたって必要な知識

- JavaScript(ES6)の基本的な構文
- HTML/CSSの基本的な構文

## はじめに

### 本ワークショップのゴール
#### 最終的な成果物
指定した色の補色と、その色の組み合わせを利用したUIのプレビューが見れるアプリケーション
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/de98c794-d0db-9025-376d-2b998e768f59.png)

#### 使用ツール / ライブラリ
| ツール/ライブラリ名　| 説明 |
| - | - |
| CodeSandbox | Web上で利用できるブラウザIDE（統合開発環境） |
| React | フロントエンドライブラリ |
| Chakra UI | Reactで利用できるUIコンポーネントライブラリ |


## 補色のカラーコードを教えてくれるアプリを作ろう！
### 補色とは
> 混合して[無彩色](https://ja.wikipedia.org/wiki/%E7%84%A1%E5%BD%A9%E8%89%B2)を作れる2色の有彩色の組み合わせを互いに**補色**(ほしょく、英: complementary color[[1]](https://ja.wikipedia.org/wiki/%E8%A3%9C%E8%89%B2#cite_note-1)）であるという
>
> 補色同士の色の組み合わせは、互いの色を引き立て合う[相乗効果](https://ja.wikipedia.org/wiki/%E7%9B%B8%E4%B9%97%E5%8A%B9%E6%9E%9C)があり、これは「補色調和」といわれる[[3]](https://ja.wikipedia.org/wiki/%E8%A3%9C%E8%89%B2#cite_note-3)。
> 
> 『フリー百科事典　ウィキペディア日本語版』

### 本セクションで実装するもの
- 任意の色を入力する機能
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/14084849-3477-94a6-77b1-6ed405db898e.png)

- 補色を表示する機能
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/458ca926-3020-d7a6-f5f4-3b01a31881e6.png)


### Reactプロジェクトの作成
1. CodeSandboxにアクセスする
1. 「Create Sandbox」を押下する
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/06e0e76a-6b70-9053-40b6-69a8982aa35a.png)
1. 「React」を選択する
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/26ad0954-7bf2-1cb3-647f-65f319cd6e79.png)
1. この画面が出たらOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/9ef33161-2270-9111-31f1-50ace957ba6d.png)

### テキストを書き換えてみる
1. *App.js*というファイルにある`<h1>`タグや`<h2>`タグで囲まれたテキストを好きな言葉に変更する

```jsx:App.js
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      {/* 「Hello CodeSandbox」を書き換える */}
      <h1>好きな言葉でいいよ！</h1>
      {/* 「Start editing to see some magic happen!」を書き換える */}
      <h2>これから補色調べっ子ちゃんを作るよ！</h2>
    </div>
  );
}
```

1. 画面右側のブラウザの文字が変わっていることを確認する
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/f93611cf-e220-47d8-fdfd-26aa81cacc28.png)

### 入力フォームを作る
色を入力してもらうための入力フォームをつくる

1. `<input>`タグを書く 

    ```jsx:App.js
    import "./styles.css";
    
    export default function App() {
      return (
        <div className="App">
          <h1>好きな言葉でいいよ！</h1>
          <h2>これから補色調べっ子ちゃんを作るよ！</h2>
          {/* inputを追加する type属性はcolorを選択する */}
          <input type="color" />　
        </div>
      );
    }
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/345014fb-16c8-e59c-53fe-d449de2baf99.png)


2. ユーザーが入力したデータを取得し、状態を保存する
    Reactで画面の状態を管理するための**useStateフック**を用いる
    `useState`は引数に任意の値を入れることで、
    **その値で初期化した変数**と、**その変数を更新するための関数**をペアで取得することができる
    
    今回は返却されたペアの名前を、それぞれ**color, setColor**とし、
    ユーザーが入力した色情報を保存するために、`setColor`をinput要素のonChange属性に渡す

    ```jsx:App.js
    import { useState } from "react";
    import "./styles.css";
    
    export default function App() {
      /* 状態を管理するための、値と更新用の関数をペアで取得する */
      const [color, setColor] = useState("");
    
      return (
        <div className="App">
          <h1>好きな言葉でいいよ！</h1>
          <h2>これから補色調べっ子ちゃんを作るよ！</h2>
          {/* 入力フォームの値が変わった時に、その値を保存する */}
          <input type="color" onChange={(event) => setColor(event.target.value)} />　
        </div>
      );
    }
    ```

### 保存した色を表示してみる

1. 変数colorを`<h2>`タグで表示してみる

    **JSX記法**(以下コードのHTMLを書いている部分)では、
    **ブラケット**({ })で囲むことにより、JavaScriptを書くことができるので、
    変数colorを{ }で囲み、`<h2>`タグ内に展開する

    ```jsx:App.js
    import { useState } from "react";
    import "./styles.css";
    
    export default function App() {
      const [color, setColor] = useState("");
    
      return (
        <div className="App">
          <h1>好きな言葉でいいよ！</h1>
          <h2>これから補色調べっ子ちゃんを作るよ！</h2>
          {/* 変数colorを展開し、保存した色情報を表示する */}
          <h2>選択中の色： {color}</h2>
          <input type="color" onChange={(event) => setColor(event.target.value)} />　
        </div>
      );
    }
    ```

    ブラウザの画面から色を変更すると、選んだ色が16進数(#b83d3dのような形式)で表示されていることがわかる
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/025b803b-84c7-e74c-0bd5-a032e92681b4.png)


### 保存した色情報から補色を計算し表示する

1. 新しく*utils.js*というファイルを作成し、以下の補色を計算する関数をコピペする
    (ネットから拾ってきたものなので他の箇所と書き方が異なりますがご容赦ください)

    ```js:utils.js
    export const hexToComplimentary = (hex) => {
      // Convert hex to rgb
      // Credit to Denis http://stackoverflow.com/a/36253499/4939630
      var rgb =
        "rgb(" +
        (hex = hex.replace("#", ""))
          .match(new RegExp("(.{" + hex.length / 3 + "})", "g"))
          .map(function (l) {
            return parseInt(hex.length % 2 ? l + l : l, 16);
          })
          .join(",") +
        ")";
    
      // Get array of RGB values
      rgb = rgb.replace(/[^\d,]/g, "").split(",");
    
      var r = rgb[0],
        g = rgb[1],
        b = rgb[2];
    
      // Convert RGB to HSL
      // Adapted from answer by 0x000f http://stackoverflow.com/a/34946092/4939630
      r /= 255.0;
      g /= 255.0;
      b /= 255.0;
      var max = Math.max(r, g, b);
      var min = Math.min(r, g, b);
      var h,
        s,
        l = (max + min) / 2.0;
    
      if (max == min) {
        h = s = 0; //achromatic
      } else {
        var d = max - min;
        s = l > 0.5 ? d / (2.0 - max - min) : d / (max + min);
    
        if (max == r && g >= b) {
          h = (1.0472 * (g - b)) / d;
        } else if (max == r && g < b) {
          h = (1.0472 * (g - b)) / d + 6.2832;
        } else if (max == g) {
          h = (1.0472 * (b - r)) / d + 2.0944;
        } else if (max == b) {
          h = (1.0472 * (r - g)) / d + 4.1888;
        }
      }
    
      h = (h / 6.2832) * 360.0 + 0;
    
      // Shift hue to opposite side of wheel and convert to [0-1] value
      h += 180;
      if (h > 360) {
        h -= 360;
      }
      h /= 360;
    
      // Convert h s and l values into r g and b values
      // Adapted from answer by Mohsen http://stackoverflow.com/a/9493060/4939630
      if (s === 0) {
        r = g = b = l; // achromatic
      } else {
        var hue2rgb = function hue2rgb(p, q, t) {
          if (t < 0) t += 1;
          if (t > 1) t -= 1;
          if (t < 1 / 6) return p + (q - p) * 6 * t;
          if (t < 1 / 2) return q;
          if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
          return p;
        };
    
        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
    
        r = hue2rgb(p, q, h + 1 / 3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1 / 3);
      }
    
      r = Math.round(r * 255);
      g = Math.round(g * 255);
      b = Math.round(b * 255);
    
      // Convert r b and g values to hex
      rgb = b | (g << 8) | (r << 16);
      return "#" + (0x1000000 | rgb).toString(16).substring(1);
    }
    ```

2. 1. で作成した関数をApp.jsで読み込み、先ほどの入力フォームから取得した値から補色を計算して表示する

    ```jsx:App.js
    import { useState } from "react";
    import "./styles.css";
    /* さっき作ったutils.jsの関数を読み込む */
    import { hexToComplimentary } from "./utils";

    export default function App() {
          const [color, setColor] = useState("");

      return (
        <div className="App">
          <h1>好きな言葉でいいよ！</h1>
          <h2>これから補色調べっ子ちゃんを作るよ！</h2>
          <h2>選択中の色： {color}</h2>
          {/* 補色情報を表示する */}
          <h2>補色: {hexToComplimentary(color)}</h2>
          <input type="color" onChange={(event) => setColor(event.target.value)} />　
        </div>
      );
    }
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/60150946-c091-60dc-beea-4f6cc759e769.png)


### コンポーネントを分ける

Reactを含む**コンポーネント指向**のフロントエンドライブラリでは、
作った要素をまとまった一つの単位(**コンポーネント**)として分割することができる

コンポーネントを分割することで、同じデザインや機能を持ったUIを使い回すことができるうえ、
元のファイルを変更すれば、使いまわした箇所全てに適用されるためメンテナンスも容易になる

今回は、「色の入力フォーム」と「結果の表示部分」を別のコンポーネントに切り出してみる

1. 新しく*ColorInput.js*というファイルを作成し、*App.js*の以下の行を切り取って移動する
    (この時点ではエラーになって大丈夫、画像左のような黄や赤の波線が出ている状態でOK)

    ```jsx:ColorInput.js
    <input type="color" onChange={(event) => setColor(event.target.value)} />　
    ```

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/7abe3bcd-fc18-cfc2-3c5b-71366e769db5.png)

2. Reactのコンポーネントを作成する
    (この時点ではエラーになって大丈夫)

    コンポーネントは**JSXを返却する関数**なので、*ColorInput.js*を以下のように変更する
    Reactのコンポーネントは**頭文字を大文字にする**というルールがあるので、関数名はColorInputとする
    ついでに、ラベルも表示しておく

    ```jsx:ColorInput.js
    export const ColorInput = () => {
      return (
        <div>
          <label>色を選択してください</label>
          <input type="color" onChange={(event) => setColor(event.target.value)} />　
        </div>
      )
    }
    ```

3. setColorを受け取れるようにする
    (この時点ではエラーになって大丈夫)

    Reactのコンポーネントでは、コンポーネント外からの値を
    **props**（propertiesの略）と呼ばれる変数を利用して受け取ることができる
    今回、setColorはこのファイルには存在せず、*App.js*に存在しているので、その値を受け取る必要がある
    外部から受け取った値は、propsの中に自動的に格納される

    ```jsx:ColorInput.js
    /* 外部からの値をpropsという変数で受け取れる */
    export const ColorInput = (props) => {
      /* propsからsetColorを取り出す */
      const setColor = props.setColor
      return (
        <div>
          <label>色を選択してください</label>
          <input type="color" onChange={(event) => setColor(event.target.value)} />　
        </div>
      )
    }
    ```

4. ColorInput.jsで作ったColorInputコンポーネントをApp.jsで使用する
    作成したコンポーネントは他のHTML要素のように使用することができる
    propsとして渡したい値は、他の属性と同じように渡すことができる

    ```jsx:App.js
    import { useState } from "react";
    /* さっき作ったColorInput.jsのColorInputコンポーネントを読み込む */
    import { ColorInput } from "./ColorInput";
    import "./styles.css";
    import { hexToComplimentary } from "./utils";
    
    export default function App() {
      const [color, setColor] = useState("");
    
      return (
        <div className="App">
          <h1>好きな言葉でいいよ！</h1>
          <h2>これから補色調べっ子ちゃんを作るよ！</h2>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {hexToComplimentary(color)}</h2>
          {/* 作ったコンポーネントはHTML要素のように使用することができる */}
          {/* setColorを渡す */}
          <ColorInput　setColor={setColor} />
        </div>
      );
    }
    ```

    この時点で、ブラウザで以下のように表示されていればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/7a402d48-b327-697f-0bac-50ee69f556e5.png)


5. 同様に、新しく*Result.js*ファイルを作り、*App.js*の14, 15行目をResultコンポーネントとして切り出す

    注意する点として、**コンポーネントでは返却するJSXは必ず1つの要素になっていなければいけない**
    そこで、14, 15行目をそのまま返却すると2つ要素になってしまうので、
    **React.Fragment**と呼ばれる空のタグ`<></>`で囲む
    divタグで囲むこともできるが、このFragmentであればHTMLタグとしては判定されないので、
    **無駄なタグを作らずに済み、パフォーマンスが良くなる**

    ```jsx:Result.js
    /* utils.jsの関数を読み込む */
    import { hexToComplimentary } from "./utils"
    
    export const Result = (props) => {
      /* 外から変数colorを受け取る */
      const color = props.color
      return (
        /* 一つの要素しか返却できないので<></>で囲み一つにする */
        <>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {hexToComplimentary(color)}</h2>
        </>
      )
    }
    ```
    
    
    ```jsx:App.js(ついでに不要な行を削除しています)
    import { useState } from "react";
    import { ColorInput } from "./ColorInput";
    /* さっき作ったResult.jsのResultコンポーネントを読み込む */
    import { Result } from "./Result";
    import "./styles.css";
    
    export default function App() {
      const [color, setColor] = useState("");
    
      return (
        <div className="App">
        　　　　{/* 結果の表示部分をResultに置き変える */}
          <Result color={color} />
          <ColorInput　setColor={setColor} />
        </div>
      );
    }
    ```

    以下のようにブラウザに表示されていればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/b689600d-7d35-71d5-1913-b922619a1099.png)


## UIコンポーネントライブラリを利用してオシャレにしよう！

### 本セクションで実装するもの
- 補色を利用したデザインのプレビューを表示する機能
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/2fce4d9f-5e60-e3bd-a43f-b4ca282128a7.png)


### Chakra UI（UIコンポーネントライブラリ）をインストールする
1. [Chakra UI の Getting Started](https://chakra-ui.com/docs/getting-started) にアクセスする

2. Getting Startedの「Installation」にある、以下のコマンドの`npm i`以降に書かれた各パッケージ名を、
    CodeSandboxの「Dependencies」というエリアの入力フォームに入力して検索し、
    表示されるパッケージ名を押下する
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/d658ddea-4544-161f-232d-f8aff6d04749.png)
    4つすべてインストールし、以下のように表示されればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/bbb507a0-86e9-cf4c-23a2-78d875c6290c.png)



3. Getting Startedの「Set up Provider」を参考に、以下のように*index.js*で**ChakraProvider**をimportし、`<App />`を`<ChakraProvider></ChakraProvider>`で囲む

    ```jsx:index.js
    import { StrictMode } from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    /* ChakraProviderを読み込む  */
    import { ChakraProvider } from "@chakra-ui/react";
    
    const rootElement = document.getElementById("root");
    ReactDOM.render(
      <StrictMode>
      　　　　{/* ChakraProviderでAppを囲む */}
        <ChakraProvider>
          <App />
        </ChakraProvider>
      </StrictMode>,
      rootElement
    );
    ```

### Chakra UIのコンポーネントを表示してみる

Chakra UIを含む**UIコンポーネントライブラリ**を利用すると、既に完成したコンポーネントを利用することができる
UIコンポーネントライブラリを使用することで、CSSなどのデザインや凝ったUIのJSの実装を省けるうえ、統一されたデザインのアプリケーションを簡単に作ることができる

[Chakra UIのドキュメント](https://chakra-ui.com/docs/form/button)を参考に、ボタンを表示してみる

1. Result.jsにChakra UIのボタンを追加する

    ```jsx:Result.js
    import { hexToComplimentary } from "./utils"
    /* ドキュメントを参考に、importする */
    import { Button } from "@chakra-ui/react"

    export const Result = (props) => {
      const color = props.color
      return (
        <>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {hexToComplimentary(color)}</h2>
          <Button colorScheme="blue">Button</Button>
        </>
      )
    }
    ```

    以下のようにブラウザに表示されればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/502c9292-0d46-c51c-49e6-c8b2d60acfab.png)


### Chakra UIのコンポーネントにpropsを渡してみる

UIコンポーネントライブラリのドキュメントには、そのコンポーネントで利用できるpropsの情報が記載されていることが多い

[Chakra UIのドキュメント](https://chakra-ui.com/docs/form/button#button-props)にもコンポーネントのpropsについて記載がある

1. **isLoading**というpropsを渡すとともに、colorSchemeというpropsの値を変更してみる

    ```jsx:Result.js
    import { hexToComplimentary } from "./utils";
    import { Button } from "@chakra-ui/react";
    
    export const Result = (props) => {
      const color = props.color;
      return (
        <>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {hexToComplimentary(color)}</h2>
          {/* isLoadingを追加 colorSchemeの値をblueからorangeに変更 */}
          <Button colorScheme="orange" isLoading={true} >Button</Button>
        </>
      );
    };
    ```

    以下のようにブラウザに表示されればOK
    isLoadingに`true`を入れたので、ローディングスピナー(ぐるぐる)が表示され、
    colorSchemeを`orange`に変更したので、ボタンの色がオレンジになったことがわかる
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/a2ad1271-86a1-9731-90d2-8ff92a2eae58.png)


### 補色を利用したデザインのプレビューを表示する

Chakra UIのコンポーネントには、[色を簡単に指定できるprops](https://chakra-ui.com/docs/features/style-props)が用意されているため、
それを利用して指定した補色を使った場合のUIのプレビューを表示してみる

1. Result.jsのButtonのpropsを変更し、ユーザーが指定した色とその補色を渡す  
    colorというpropsでメインの色、bgというpropsで背景色を指定することができる

    ```jsx:Result.js
    import { hexToComplimentary } from "./utils";
    import { Button } from "@chakra-ui/react";
    
    export const Result = (props) => {
      const color = props.color;
      return (
        <>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {hexToComplimentary(color)}</h2>
          {/* 文字色を指定するcolorにユーザーが選択したcolorを　背景色を指定するbgに補色を入れる */}
          <Button color={color} bg={hexToComplimentary(color)}>
            Button
          </Button>
        </>
      );
    };
    ```

    以下のようにブラウザに表示されればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/83bcf1d3-8ee5-3b22-13eb-31a53fadafbb.png)


2. Chakra UIのボタン以外のコンポーネントも表示してみる
    基本的にはドキュメントからコピペするだけで、コンポーネントを使うことができる
    また、**CSS in JS**という記法でCSSをJSの中に書くこともできる

    ```jsx:Result.js
    import { hexToComplimentary } from "./utils";
    /* 使いたいコンポーネントをimportする */
    import { Box, Button, Circle, Container, Radio, RadioGroup, Slider, SliderFilledTrack, SliderThumb, SliderTrack, Square, Stack, Text } from "@chakra-ui/react";
    
    export const Result = (props) => {
      const color = props.color;
      // 補色を変数に格納する
      const complimentaryColor = hexToComplimentary(color);
      return (
        <>
          <h2>選択中の色： {color}</h2>
          <h2>補色: {complimentaryColor}</h2>
    
          {/* 外枠のコンポーネント */}
          <Container borderColor={color} style={{
            borderWidth: 1,
            borderColor: color,
            borderRadius: 20,
            padding: 20
          }}>
    
            {/* ボタンのコンポーネント */}
            <Button color={color} bg={complimentaryColor}>
              Button
            </Button>
    
            {/* スライダーのコンポーネント */}
            <Slider aria-label="slider-ex-4" defaultValue={30}>
              <SliderTrack bg="gray.200">
                <SliderFilledTrack bg={color} />
              </SliderTrack>
              <SliderThumb boxSize={6}>
                <Box color={complimentaryColor} />
              </SliderThumb>
            </Slider>
    
            {/* ラジオボタンのコンポーネント */}
            <RadioGroup>
              <Stack direction="row">
                <Radio value="1" bg={complimentaryColor}><Text color={color}>First</Text></Radio>
                <Radio value="2" bg={complimentaryColor}><Text color={color}>Second</Text></Radio>
                <Radio value="3" bg={complimentaryColor}><Text color={color}>Third</Text></Radio>
              </Stack>
            </RadioGroup>
    
            {/* 図形のコンポーネント */}
            <Circle size="40px" bg={color}>
              <Square size="20px" bg={complimentaryColor} />
            </Circle>
    
            <Square size="40px" bg={complimentaryColor}>
              <Circle size="20px" bg={color} />
            </Square>
    
          </Container>
        </>
      );
    };
    ```

    以下のようにブラウザに表示されればOK
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1943565/6c562fb9-ca88-dbd3-83c9-6dc9d37ddb02.png)


## おわりに

Reactの機能は意外と多くありません。
今回紹介したpropsの受け渡しや、Hooksを利用した状態管理が主な機能です。
なので、実際にReactでアプリケーションを開発する場合には、
ページ遷移を行うためのライブラリや、
APIリクエストをするためのライブラリ、
プログラムのテストを行うライブラリなどを組み合わせることになります。
それぞれの役割を分けて考えると、Reactの開発は理解しやすくなると思います。

この記事が少しでも誰かのお役に立てると幸いです。
間違いを見つけた場合や、もっとこうするとわかりやすいなどのご意見があれば、
ぜひコメントしていただけると嬉しいです。