🧲

[React] 初心者講座05 <状態管理(Recoil)の準備> (ついでにjson-server, axios)

2022/02/07に公開

以下を順にinstallして準備する。

  • Recoil
    • +1, -1のcounterを表示する
  • json-server
    • jsファイルからdb.jsonを生成する
  • axios
    • json-serverからgetする

https://github.com/kosukekashiwa/cra-sample

Recoil

install

$ npm install recoil

準備

src直下にstateファイルを作成する。
(stateのサブディレクトリもあった方が管理しやすそうだけど、今回はお試しなので無し)

  • /src
    • /state
      • tmpCounterState.ts
    • /views
tmpCounterState.ts
import { useCallback } from 'react';
import { atom, useRecoilValue, useSetRecoilState } from 'recoil';

// atomを定義
// これ自体はexportしない
const tmpCounterState = atom<number>({
  // keyはファイル名と合わせる
  key: 'tmpCounterState',
  // 初期値
  default: 0,
});

// stateの値を取得する関数
export const useTmpCounterState = (): number => {
  return useRecoilValue(tmpCounterState);
};

// stateの値を'+1'する関数
export const useCountUpTmpCounterState = () => {
  // stateの値を変更するset関数を取得
  const setState = useSetRecoilState(tmpCounterState);

  // '+1'する処理
  const countUp = useCallback(() => {
    setState((currVal) => currVal + 1);
  }, [setState]);

  return countUp;
};

// stateの値を'-1'する関数(上の関数とほぼ同じ)
export const useCountDownTmpCounterState = () => {
  const setState = useSetRecoilState(tmpCounterState);

  const countDown = useCallback(() => {
    setState((currVal) => currVal - 1);
  }, [setState]);

  return countDown;
};

確認

DashboardView.tsx
const DashboardView: React.VFC = () => {
  // 現在のstateの値を取得
  const tempCount = useTmpCounterState();
  // stateを'+1'する関数
  const countUp = useCountUpTmpCounterState();
  // stateを'-1'する関数
  const countDown = useCountDownTmpCounterState();

  return (
    <Box>
      <ViewTitleLabel label="Dashboard" />
      {/* stateの値を表示 */}
      <Box>{tempCount}</Box>

      {/* stateの値を更新するButton */}
      <Button variant={'outlined'} onClick={countUp}>
        count+1
      </Button>
      <Button variant={'outlined'} onClick={countDown}>
        count-1
      </Button>
    </Box>
  );
};

ボタンをクリックすると数値が増減するようになった。

json-server

install

$ npm install json-server --save-dev

準備

package.json
  "scripts": {
    ...
    "server": "json-server --watch db.json --port 3001" // 追加
  },

実行

$ npm run server

db.jsonは作成していなくても勝手に作ってくれます。

  \{^_^}/ hi!

  Loading db.json
  Oops, db.json doesn't seem to exist
  Creating db.json with some default data

自動生成されるdb.jsonは以下。

db.json
{
  "posts": [
    {
      "id": 1,
      "title": "json-server",
      "author": "typicode"
    }
  ],
  "comments": [
    {
      "id": 1,
      "body": "some comment",
      "postId": 1
    }
  ],
  "profile": {
    "name": "typicode"
  }
}

確認

$ npm run serverが成功すると以下も表示されているはず。

 Resources
 http://localhost:3001/posts
 http://localhost:3001/comments
 http://localhost:3001/profile

 Home
 http://localhost:3001

http://localhost:3001/postsにアクセス。

jsファイルからdb.jsonを生成

ダミーデータとはいえ、jsonファイルを編集して大量データを作るのは大変なので、jsファイルからdb.jsonを生成できるようにする。
npm scriptを同時実行すると楽なので、以下をinstallしておく。

$ npm install npm-run-all --save-dev

genDb.jspackage.jsonと同じ階層に作成。
名前のランダム生成等はFaker.jsが便利でしたが、色々あったので数値つけるだけ。
for文の数値を増やせばdataを増やせるので一覧表示等が確認しやすくなる。

genDb.js
const db = {
  sample: [],
};

for (let i = 0; i < 5; i++) {
  db.sample.push({
    id: i,
    name: 'hoge' + i,
  });
}

console.log(JSON.stringify(db));

package.jsonを以下のように書き変えます。
run-sはnpm scriptを順番に実行してくれます。

package.json
  "scripts": {
    ...
    "mock:genDb": "node genDb.js > db.json",
    "mock:server": "json-server --watch db.json --port 3001",
    "mock": "run-s mock:genDb mock:server"
  },

npm run mockを実行して、
http://localhost:3001/sampleにアクセス。

axios

install

$ npm install axios

準備

axios共通処理を記述。

src/state/apiClient.ts
import axios from 'axios';

export const client = axios.create({
  baseURL: 'http://localhost:3001',
  headers: {
    'Content-Type': 'application/json',
  },
  timeout: 2000,
});

genDb.jsを修正してloginUserを追加。
これを取得、表示する。

genDb.js
const db = {
  // loginUserを追加
  loginUser: {
    id: 1,
    name: 'Login User',
  },
  sample: [],
};

for (let i = 0; i < 5; i++) {
  db.sample.push({
    id: i,
    name: 'hoge' + i,
  });
}

console.log(JSON.stringify(db));

atomを定義。

src/state/tmpLoginUserState.ts
import { atom, selector, useRecoilValue } from 'recoil';
import { client } from './apiClient';

type LoginUser = {
  id: number;
  name: string;
};

// memo: 試しにaxiosでget処理かく
const tmpLoginUserState = atom<LoginUser>({
  key: 'tmpLoginUserState',
  // default値をselectorを使って設定しているが、
  // defaultはundefined等にしておいてapi処理は別の箇所が良い気がする。
  default: selector({
    key: 'savedLoginuser',
    get: async () => {
      try {
        const respons = await client.get('/loginUser');
        return respons.data;
      } catch (e) {
        // memo: 仮error処理
        console.log(e);
      }
    },
  }),
});

export const useLoginUserState = (): LoginUser => {
  return useRecoilValue(tmpLoginUserState);
};

Recoilと非同期処理の組み合わせは、また今度やってみる。

https://recoiljs.org/docs/guides/asynchronous-data-queries

確認

axios呼び出しComponetをとりあえずSuspenseで囲む。
Suspenseの説明は省略。

Home.tsx
const Home: React.VFC = () => {
  ...

  return (
    <AppContainer>
      {/* memo: Suspense仮置き */}
      <Suspense fallback={<div>Loading...</div>}>
        <CRAHeader
          onApptitleClick={handleApptitleClick}
          onDashboardButtonClick={handleDashboardButtonClick}
          onDataListButtonClick={handleDataListButtonClick}
        />
      </Suspense>
CRAHeader.tsx
const CRAHeader: React.VFC<CRAHeaderProps> = (props) => {
  // axios使って値を設定しているstateの取得
  const loginUser = useLoginUserState();

  return (
    <AppHeader
      appTitle="CRA System"
      onApptitleClick={props.onApptitleClick}
      // 値を表示(AppHeaderに値を渡す)
      userName={loginUser.name}
      leftItems={[
        { id: 0, node: <DashboardButton onDashboardButtonClick={props.onDashboardButtonClick} /> },
        {
          id: 1,
          node: <DataListButton onDataListButtonClick={props.onDataListButtonClick} />,
        },
      ]}
    />
  );
};

genDb.jsに記述したnameが表示されている。

続き

...準備中

https://zenn.dev/kosukek/articles/627241ba357c67

Discussion