20201014のReactに関する記事は7件です。

【React+TypeScript】GETエラーを感知して、404ページに遷移させる

めちゃめちゃ久しぶりの投稿です。
先日までコミットしていたプロジェクトの中で自分のリサーチとスキルではどうにもできなかった404ページへのリダイレクトの処理について。
先輩に教えていただいたので備忘録として書いておきます。

今回実現させたかったこと

今回実現させたかったのは、大きく分けて2つです。

  • /hogehoge/tokenのようなURLが叩かれたときにtokenが正規のものか判断する
  • 正規のものである場合、tokenが合致するデータをデータベースからGETする
  • 正規のものでない場合、404ページへリダイレクトさせる

これらをReact+TypeScriptのプロジェクト内で実現させるというタスクでした。

行き詰まった点

「tokenが正しいかどうかとか、どうやって判断したらいいん。。。」
大きくいってこの点でした。
今までHTMLとCSSでやってきたので、もう何を使えばいいんかもわからずどう調べればいいのかも分かりませんでした。

あとは今までリダイレクトさせる処理は.htaccessで記載してたので、それ以外でどうやった手法があるのかもわからず行き詰りました。

エラーを検知するtry...catch文

tokenの正誤判別にはtry...catch文を使いました。
MDNのドキュメントのリンクを貼っておきます。

try...catch文

で、サンプルコードです。

try {
  // テストする文
}
catch (e) {
  // エラー時に走る処理
}

このような感じにtry...catch文は使われます。
なので、今回私が実現したかったことを実現しようとすると

try {
  const { data: hogehogeList } = await request.get({
    uri: '/hoge',
    params: { token },
  });
}
catch (e) {
  // リダイレクト処理
}

みたいな書き方になります。
こうすることで、tokenが正規のものである場合はデータベースから該当するリストをGETしてきて、
もしtokenが正規のものでない場合は、errorとなりリダイレクトの処理が走ります。

ちなみにuriはAPIが置いてある場所で、paramsはデータをGETする際に必要なパラメータです。

リダイレクト処理をさせるモジュールhistory

リダイレクトさせるにあたって、今回はhistoryというモジュールを使用しました。

history

これを使用してリダイレクトの処理を実装しました。
参考にした記事は以下の記事になります。

history.push();でアクションを起こした時にページ遷移をする

実際に書いたコードは以下になります。

try {
  const { data: hogehogeList } = await request.get({
    uri: '/hoge',
    params: { token },
  });
}
catch (e) {
  history.push('/not-found');
  window.location.reload();
}

このようにすることで、先ほど書いたGETが失敗に終わった際には
404ページに遷移させることができました。

まとめ

今回の総括ですが、

  • try...catch文を使うことで、ある処理が失敗したときには別処理を走らせることができる
  • historyを使うと、遷移させたいページに遷移させる処理を行うことができる

なお、このプロジェクトではreact-router-domを使用してルーティングさせてましたので、
上記の処理を実装する流れとなりました。

先輩曰く、もっと良い処理があるらしいのですがそれはまた別の機会に。

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

react-autosuggest を React Hooks x TypeScript 上で導入したのでコード置いた

react-autosuggestのGitHubに書かれてるサンプルコードはクラスコンポーネントですが、Hooks勢であれば関数型で書き直したいと思うもの。

英語でも情報がなかったようなのでTypeScript込みで書き直してみた

GtiHubのサンプルコード

元ネタはこれ。一番基本的な使い方
GitHub: https://github.com/moroshko/react-autosuggest#installation

sample.jsx
import Autosuggest from 'react-autosuggest';

const languages = [
  {
    name: 'C',
    year: 1972
  },
  {
    name: 'Elm',
    year: 2012
  },
  ...
];

const getSuggestions = value => {
  const inputValue = value.trim().toLowerCase();
  const inputLength = inputValue.length;

  return inputLength === 0 ? [] : languages.filter(lang =>
    lang.name.toLowerCase().slice(0, inputLength) === inputValue
  );
};

const getSuggestionValue = suggestion => suggestion.name;

const renderSuggestion = suggestion => (
  <div>
    {suggestion.name}
  </div>
);

class Example extends React.Component {
  constructor() {
    super();

    this.state = {
      value: '',
      suggestions: []
    };
  }

  onChange = (event, { newValue }) => {
    this.setState({
      value: newValue
    });
  };

  onSuggestionsFetchRequested = ({ value }) => {
    this.setState({
      suggestions: getSuggestions(value)
    });
  };

suggestions.
  onSuggestionsClearRequested = () => {
    this.setState({
      suggestions: []
    });
  };

  render() {
    const { value, suggestions } = this.state;

    const inputProps = {
      placeholder: 'Type a programming language',
      value,
      onChange: this.onChange
    };

    return (
      <Autosuggest
        suggestions={suggestions}
        onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
        onSuggestionsClearRequested={this.onSuggestionsClearRequested}
        getSuggestionValue={getSuggestionValue}
        renderSuggestion={renderSuggestion}
        inputProps={inputProps}
      />
    );
  }
}

関数がいろいろあってややこしいライブラリだが基本的bに1つの関数名(及びstate)と1つのpropsが対応していると見るとわかりやすい(getSuggests関数とonChange関数は別)

sample.jsx
      <Autosuggest
        suggestions={suggestions} // suggestionsはサジェスト候補のオブジェクトが格納されたstate
        onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} // suggestionsの状態を更新する関数 
        onSuggestionsClearRequested={this.onSuggestionsClearRequested} // suggestionsを初期状態にする関数
        getSuggestionValue={getSuggestionValue} // langagesのオブジェクトからnameを取り出す関数
        renderSuggestion={renderSuggestion} // サジェスト一覧表示に使うJSXを返す
        inputProps={inputProps} // placeholder, value, onChange の初期値を渡す
      />

Hooks & TypeScript で置き換えた

型ファイルも一緒に落としておく

yarn add react-autosuggest @types/react-autosuggest

使うデータへLangTypeの型を宣言したり、useStateを使ったりクラスを関数に書き変えたりして置き換えた。

codesandbox: https://codesandbox.io/s/react-autosuggest-typescript-ptn91?fontsize=14&hidenavigation=1&theme=dark

ReactSuggest.tsx
import React, { BaseSyntheticEvent, FC, useState } from "react";
import Autosuggest from "react-autosuggest";

type LangType = {
  name: string;
  year: number;
};

const languages: LangType[] = [
  {
    name: "CCC",
    year: 1972
  },
  {
    name: "CyberMan",
    year: 1972
  },
  {
    name: "EtherNet",
    year: 2012
  },
  {
    name: "Entertainer",
    year: 10000
  }
];

const getSuggestions = (value: string): LangType[] => {
  const inputValue = value.trim().toLowerCase();
  const inputLength = inputValue.length;

  return inputLength === 0
    ? []
    : languages.filter(
        (lang) => lang.name.toLowerCase().slice(0, inputLength) === inputValue
      );
};

const ReactSuggestion: FC = () => {
  const [value, setValue] = useState("");
  const [suggestions, setSuggestions] = useState<LangType[]>([]);

  const getSuggestionValue = (suggestion: LangType): string => {
    const { name } = suggestion;

    return name;
  };

  const renderSuggestion = (suggestion: LangType) => {
    return <div>{suggestion.name}</div>;
  };

  const onChange = (
    event: BaseSyntheticEvent,
    { newValue }: { newValue: string }
  ) => {
    if (event) setValue(newValue);
  };

  const onSuggestionsFetchRequested = ({ value }: { value: string }) => {
    const suggestions: LangType[] = getSuggestions(value);
    setSuggestions(suggestions);
  };

  // Autosuggest will call this function every time you need to clear suggestions.
  const onSuggestionsClearRequested = () => {
    setSuggestions([]);
  };

  const inputProps = {
    placeholder: "cかeを入力してみて",
    value,
    onChange
  };

  return (
    <Autosuggest
      suggestions={suggestions}
      onSuggestionsFetchRequested={onSuggestionsFetchRequested}
      onSuggestionsClearRequested={onSuggestionsClearRequested}
      getSuggestionValue={getSuggestionValue}
      renderSuggestion={renderSuggestion}
      inputProps={inputProps}
    />
  );
};

export default ReactSuggestion;

まとめ

こんな感じで動く

reactAutosuggest.gif

onChange関数の引数に使わないけれど event を入れておかないと怒られた。他にもオプションがあるのでGitHubリポジトリの方で確認してもらえればと思う。
GitHub: https://github.com/moroshko/react-autosuggest#installation

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

react-autosuggest を React Hooks x TypeScript 上に入れてサジェスト機能を実装した

react-autosuggestのGitHubに書かれてるサンプルコードはクラスコンポーネントですが、Hooks勢であれば関数型に書き直したいと思うもの。

英語でも情報がなかったようなのでTypeScript込みで書き直してみた

この記事の完成品

こんな感じで動くやつになる

reactAutosuggest.gif

【元ネタ】GtiHubのサンプルコード

元ネタはこれ。一番基本的な使い方
GitHub: https://github.com/moroshko/react-autosuggest#installation

sample.jsx
import Autosuggest from 'react-autosuggest';

const languages = [
  {
    name: 'C',
    year: 1972
  },
  {
    name: 'Elm',
    year: 2012
  },
  ...
];

const getSuggestions = value => {
  const inputValue = value.trim().toLowerCase();
  const inputLength = inputValue.length;

  return inputLength === 0 ? [] : languages.filter(lang =>
    lang.name.toLowerCase().slice(0, inputLength) === inputValue
  );
};

const getSuggestionValue = suggestion => suggestion.name;

const renderSuggestion = suggestion => (
  <div>
    {suggestion.name}
  </div>
);

class Example extends React.Component {
  constructor() {
    super();

    this.state = {
      value: '',
      suggestions: []
    };
  }

  onChange = (event, { newValue }) => {
    this.setState({
      value: newValue
    });
  };

  onSuggestionsFetchRequested = ({ value }) => {
    this.setState({
      suggestions: getSuggestions(value)
    });
  };

suggestions.
  onSuggestionsClearRequested = () => {
    this.setState({
      suggestions: []
    });
  };

  render() {
    const { value, suggestions } = this.state;

    const inputProps = {
      placeholder: 'Type a programming language',
      value,
      onChange: this.onChange
    };

    return (
      <Autosuggest
        suggestions={suggestions}
        onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
        onSuggestionsClearRequested={this.onSuggestionsClearRequested}
        getSuggestionValue={getSuggestionValue}
        renderSuggestion={renderSuggestion}
        inputProps={inputProps}
      />
    );
  }
}

関数がいろいろあってややこしいライブラリだが基本的に1つの関数名(及びstate)と1つのpropsが対応していると見るとわかりやすい(getSuggests関数とonChange関数は別)

sample.jsx
      <Autosuggest
        suggestions={suggestions} // suggestionsはサジェスト候補のオブジェクトが格納されたstate
        onSuggestionsFetchRequested={this.onSuggestionsFetchRequested} // suggestionsの状態を更新する関数 
        onSuggestionsClearRequested={this.onSuggestionsClearRequested} // suggestionsを初期状態にする関数
        getSuggestionValue={getSuggestionValue} // langagesのオブジェクトからnameを取り出す関数
        renderSuggestion={renderSuggestion} // サジェスト一覧表示に使うJSXを返す
        inputProps={inputProps} // placeholder, value, onChange の初期値を渡す
      />

Hooks & TypeScript で置き換えた

型ファイルも一緒に落としておく

yarn add react-autosuggest @types/react-autosuggest

使うデータへLangTypeの型を宣言したり、useStateを使ったりクラスを関数に書き変えたりして置き換えた。

codesandbox: https://codesandbox.io/s/react-autosuggest-typescript-ptn91?fontsize=14&hidenavigation=1&theme=dark

ReactSuggest.tsx
import React, { BaseSyntheticEvent, FC, useState } from "react";
import Autosuggest from "react-autosuggest";

type LangType = {
  name: string;
  year: number;
};

const languages: LangType[] = [
  {
    name: "CCC",
    year: 1972
  },
  {
    name: "CyberMan",
    year: 1972
  },
  {
    name: "EtherNet",
    year: 2012
  },
  {
    name: "Entertainer",
    year: 10000
  }
];

const getSuggestions = (value: string): LangType[] => {
  const inputValue = value.trim().toLowerCase();
  const inputLength = inputValue.length;

  return inputLength === 0
    ? []
    : languages.filter(
        (lang) => lang.name.toLowerCase().slice(0, inputLength) === inputValue
      );
};

const ReactSuggestion: FC = () => {
  const [value, setValue] = useState("");
  const [suggestions, setSuggestions] = useState<LangType[]>([]);

  const getSuggestionValue = (suggestion: LangType): string => {
    const { name } = suggestion;

    return name;
  };

  const renderSuggestion = (suggestion: LangType) => {
    return <div>{suggestion.name}</div>;
  };

  const onChange = (
    event: BaseSyntheticEvent,
    { newValue }: { newValue: string }
  ) => {
    if (event) setValue(newValue);
  };

  const onSuggestionsFetchRequested = ({ value }: { value: string }) => {
    const suggestions: LangType[] = getSuggestions(value);
    setSuggestions(suggestions);
  };

  // Autosuggest will call this function every time you need to clear suggestions.
  const onSuggestionsClearRequested = () => {
    setSuggestions([]);
  };

  const inputProps = {
    placeholder: "cかeを入力してみて",
    value,
    onChange
  };

  return (
    <Autosuggest
      suggestions={suggestions}
      onSuggestionsFetchRequested={onSuggestionsFetchRequested}
      onSuggestionsClearRequested={onSuggestionsClearRequested}
      getSuggestionValue={getSuggestionValue}
      renderSuggestion={renderSuggestion}
      inputProps={inputProps}
    />
  );
};

export default ReactSuggestion;

まとめ

onChange関数の引数に使わないけれど event を入れておかないと怒られた。他にもオプションがあるのでGitHubリポジトリの方で確認してもらえればと思う。
GitHub: https://github.com/moroshko/react-autosuggest#installation

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

【小ネタ】その辺のReact(Vue)のGitHubリポジトリをcodesandboxで開く

超小ネタ注意報

「初めて出会ったあの時から知ってるわい」な人が多い気がするのだけど今まで全く気がつかなかったので同士へ向けて記事を書いてみた。

Reactでオートコンプリート(サジェスト機能)を実装したかったけどyarnで引っ張るの面倒だなあ「あれ、実はcodesandboxで普通にImportできるのでは?」と思ったら普通にできたので自分のケースでこの小ネタを紹介。

ソース

海外エンジニアYoutuberのBigless27さんの動画に貼ってあったポケモンの名前のオートコンプリートの実装サンプル動画が主の元ネタだ
Custom Autocomplete in React with Hooks! - https://www.youtube.com/watch?v=vXO5JMiKtM8&ab_channel=LessononCoding

今回引っ張ってくるGitHubリポジトリのリンクはこちら https://github.com/Bigless27/react-autocompelte

codesandboxの左側メニューの「Repositories」から「Import Repository」を選ぶ

git1.jpg

先ほどのGitHubリポジトリの動画を貼って「Import and Fork」する

git2.jpg

するとこんな風にimportできた

git3.jpg

感想

1千兆年より前から知っておきたかったしこういう小さなところで便利な世界はきっともっとあるんだろうな

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

Storybook v6.0新機能まとめ

はじめに

https://storybook.js.org/releases/6.0
2020年8月10日にStorybook6.0がリリースされました。
5.3からアップグレードを行う際に調べた新機能や変更点などを自分なりにおおまかにまとめてみました。
基本的にはドキュメントや公式の記事に書いてある内容になるので詳しく知りたい方はこちら

※間違いなどがありましたらご指摘お願いします。

Storybook 5.3からの主な変更点

ざっくり

  • セットアップが爆速でできるようになった
  • ストーリーの再利用性があがった
  • 複数のStorybookを一つにまとめられるようになった
  • ドキュメントが充実

Zero-config

公式がconfig書かなくていいよ!と言っている通り、セットアップが非常に簡単になりました。
具体的には以下になります。

  • Typescriptのサポート
  • Storybook Essentialsの登場

Typescriptのサポート

Typescriptサポートが組み込まれました。これにより、プロジェクト内のTS固有のwebpackやbabel構成を削除できるようになりました。指定がない限りは各フレームワークの基本構成を使用するそうです。わかりやすい。

Storybook Essentials

開発チームが厳選した、ほぼ必須アドオン達のよくばりパックです。もちろん面倒な設定を書く必要はありません。
Storybookには公式アドオンの他に多くのサードパーティアドオンがあり、カスタマイズ性がありましたが、ありすぎるが故にセットアップが困難になる可能性がありました。
このEssentialsはinitをしただけで勝手に追加され、よしなに動いてくれます。どれだけわかりやすいんだ。

Storybook Args

ArgsはStorybookの記法であるComponent StoryFormat(CSF)の新しい概念です。
コンポーネントのpropsの初期値の宣言や再利用ができるようになります。

こうかいていたのが

export const Default = () => (
  <Button label="hello" backgroundColor="#222222" />
);

export const Disabled = () => (
  <Button label="hello" backgroundColor="#222222" disabled={true} />
);

export const Large = () => (
  <Button label="hello" backgroundColor="#222222" size="large" />
)

Storybook Argsの場合こうやってかけます

const Template = (args) => <Button {...args} />;

export const Default = Template.bind({});
Default.args = { label: "hello", backgroundColor: "#222222" };

export const Disabled = Template.bind({});
Disabled.args = { ...Default.args, disabled:true };

export const Large = Template.bind({});
Loading.args = { ...Default.args, size: "large" };

propsの数が多くなればなるほど、恩恵を受けることができます。
また、この書き方はもはやStorybookに依存していないためJestやその他ツールなどでも利用できます。
便利です。

Controls

Storybook上からコンポーネントを動的に編集することができる新しいアドオンです。Essentialsに含まれており、特別なセットアップは必要ありません。
Storybook Argsで書いた場合コントロールが自動生成されます。べんり。
ezgif.com-video-to-gif.gif

Composition

複数のStorybookを1つにまとめる事ができます。
異なるフレームワークを使用したプロジェクト間でも、Storybookを切り替えることなく共通デザインシステムを参照したり、全体感を確認できるようになりました。
筆者はまだ試せていないので、詳細はこちら

公式ドキュメント

v6.0に合わせて一新されました。
チュートリアルやガイドが非常に充実していて、わかりやすかったです。

まとめ

v6.0からの新機能や変更点をおおまかにまとめてみました。
これ以外にも非推奨になったAPIやアドオンがあったり、変更された仕様などがあるので確認したい方は公式マイグレーションガイドを参照してください。

参考

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

作成済のRailsアプリケーションのView部分をReactに置き換えてみる

初めてのRailsアプリケーションを作成してから数か月、Reactの学習を兼ねて View を編集していこうと思います。

Railsアプリの確認

既に忘れている部分も多いのでまずはディレクトリ内の確認

直下に
package.json

他は rails new で作成されたものだと思う。

yarn が必要だという事なので確認

nvm -v
not found
npm -v
6.14.7
yarn -v
1.22.4

bin/ にインストールされているみたい。いつインストールしたのかは記憶にない。

次は gem webpacker が必要という事

Gemfile
gem 'webpacker'

追記したら 

$ bundle install

必要なファイルの作成&インストール

$ bin/rails webpacker:install

作成された app/javascript/ app/javascript/packs/ ディレクトリ内にファイルを作成していくみたいです。

React のインストール

$ bin/yarn add react react-dom

JSX を使用する

デフォルトでは JSX は使用できないようです。

$ bin/rails webpacker:install:react

Webpacker now supports react.js:tada: って出た。
あーさっきのコマンドで初めてwebpackerがreactを認識するんですね。
JSX は使えなくて当然でした・・・。

app/javascript/packs/ に hello_react.jsx が作成された。

app/views/layouts/application.html.slim 内に

app/views/layouts/application.html.slim
<head>
  = javascript_pack_tag 'hello_react'
</head>

で サンプルが表示される。

最後に MaterialUi をインストール

$ bin/yarn add @material-ui/core
$ bin/yarn add @material-ui/icons

次回から少しずつ変更していこうと思います。

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

Reactのディレクトリ構成どうしてる?

前置き

Reactのスターターキットを作る上で、ディレクトリ構成について迷いながらも考えてみたので、その構成について紹介したいと思います。

構成について言及している記事はまだ少なく、ベスト構成とというものは存在しないと思います。
今回紹介する構成もまだまだ改良していかなければいけないと思いますが、構成について迷っている方に少しでも役立てればと思います。

開発環境について

node v12.13.1
npm v6.13.4

使用している技術について

  • React (reactHooks)
  • Redux (Redux-toolkit)
  • TypeScript

今回作成したスターターは、redux-toolkitを使っています。
小さいプロジェクトにはredux不要では??という考えもあると思います。(自分もちょっとそう思います)
ですが、redux-toolkitの登場によってreduxを簡単に使えるようになったこと、
またreduxを追加実装するコストを考えると最初から入れてしまった方がいいのでは、という考えのもと、初期から含めることにしました!
↓この記事にものすごく感銘を受けました!
TypeScriptでReactをやるときは、小さいアプリでもReduxを最初から使ってもいいかもねというお話

公式での扱いは:eyes:??

Reactは推奨されるディレクトリ構成はありません。
公式でも考えすぎるべきではないと書かれています。

React はファイルをどのようにフォルダ分けするかについての意見を持っていません。

まだプロジェクトを始めたばかりなら、ファイル構成を決めるのに 5 分以上かけないようにしましょう。上述の方法の 1 つを選ぶか、自分自身の方法を考えて、コードを書き始めましょう! おそらく実際のコードをいくらか書けば、なんにせよ考え直したくなる可能性が高いでしょう。

規模によって適切なディレクトリ構成が変わり、一つの答えはありません。
また、React周りは新しいツールがどんどん出てきて、何を使うか、どのように使用するかは目まぐるしく変わっているように感じます。
それも相まってディレクトリ構成を決めることが難しくなってるのだと思います。

構成について

さて本題です!
早速ですが実際作成したコードはこちら↓
https://github.com/Sotq17/rtk_starter

簡単にどのディレクトリでどういったファイルを扱うかを説明したいと思います。
(実際のコードを見てもらえると早いかもしれません…)
構成は以下の通りになっています。

─── src
    ├── img (画像置き場)
    ├── pug (render先を指定)
    ├── ts (react外でjsを使うときに使用)
    ├── tsconfig.json
    └── tsx
        ├── index.tsx (エントリーポイント)
        ├── stores
        │   ├── index.ts (slicesディレクトリで作られたSliceを結合する)
        │   └── slices (このディレクトリ下で各sliceファイルを扱う) 
        ├── style(各page,componentのcssを切り分けたい時に使用)
        │   ├── GlobalStyle.tsx
        │   ├── components
        │   │   ├── atoms
        │   │   └── block
        │   ├── pages
        │   ├── resetStyle.tsx
        │   └── variables.tsx
        ├── utils(定数などを管理)
        └── views
            ├── components(使い回しのできる要素)
            │   ├── atoms(最小単位のcomponent)
            │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
            │   └── modules(機能を持ったcomponent)
            └── pages(各ページの呼び出し先)
                ├── login
                └── top

index.pugtsx/index.tsxをrenderする形なので、tsxディレクトリ以下を紹介していきます!

index.tsx

エントリーポイントです。
各ページを呼び出し、react-router-domでルーティングを行う役割です。

index.tsx
const app = document.getElementById('app')
// page
import Top from './views/pages/top/Top'
import Login from './views/pages/login/Login'

// react-router-domでページ遷移
ReactDOM.render(
  <Provider store={store}>
    <GlobalStyle />
    <Router>
      <Switch>
        <Route path="/top" component={Top} />
        <Route path="/" component={Login} />
      </Switch>
    </Router>
  </Provider>,
  app
)

views

└── views
    ├── components(使い回しのできる要素)
    │   ├── atoms(最小単位のcomponent)
    │   │   └── Button.tsx
    │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
    │   │   └── Header.tsx
    │   └── modules(機能を持ったcomponent)
    │       └── ScrollTop.tsx
    └── pages(各ページの呼び出し先)
        └── 各ページのディレクトリ
            └── Login.tsx

pages

└── pages(各ページの呼び出し先)
       ├── Top
       │    ├── TopList.tsx
       │    └── Top.tsx
       └── Login
            └── Login.tsx

上記の tsx/index.tsxに呼び出されるコンポーネントです。
各ページに1つこの要素が存在し、そのページ内で使われるコンポーネントはさらにここから呼び出して使用します。
また、そのページにしかない固有の要素は同ディレクトリ内に配置します。
上記のtreeで言うと、TopList.tsが固有の要素にあたります。

例↓

top.tsx
const Top = () => {

  return (
    <div>
      <Header />
      <h1 css={TopTitle}>TOP</h1>
      <TopList />
      <Footer />
    </div>
  )
}

components

pageとは異なる、使い回すことのできる要素をこちらに置きます。
その中にも意味合いが異なる要素が多いため、さらに下記のディレクトリに分けていきます。
atomicデザインでできれば良かったのですが、知見がないためとりあえず下記のようにざっくり作ってます)

├── components(使い回しのできる要素)
       ├── atoms
       │  
       ├── block
       │   
       └── modules
  • atoms
  • block
  • module
atoms

ボタンなどの最小の要素です。変更出来るよう、styleや機能は流し込めるように作成します。

Button.tsx
import React from 'react'
import { css } from '@emotion/core'

export const Button = props => {
  const button = css({
    backgroundColor: `${props.bgColor}`,
    color: `${props.color}`,
    border: `1px solid ${props.bgColor}`,
    padding: '8px 16px',
    borderRadius: '4px',
    cursor: 'pointer'
  })

  return (
    <button css={button} onClick={props.onClick}>
      {props.name}
    </button>
  )
}

ちなみに呼び出すのはこんな感じです↓

Sample.tsx
const Sample = () => {
  const handleClick = () => {
   console.log("click!")
  }

  return (
    <div>
      <Button
        onClick={handleClick}
        name="Click!"
        color="#ffffff"
        bgColor="#33333"
      />
    </div>
  )
}
block

ページに1個しかないHeaderやFooter、また使い回すけどatomsを組み合わせて使う要素を置きます。

modules

atomsへの機能の流し込みが難しかったり、そのコンポーネント固有の機能があるものをここに置きます。
例えば、パンくずリストなどが該当します。

パンくずではないですが、サンプルです↓(この程度ならatomsでいいですけど…)

ScrollTopBtn.tsx
export const ScrollTop = () => {

  const scrollToTop = () => {
    scroll.scrollToTop()
  }

  return (
    <div css={ButtonContainer} onClick={scrollToTop}>
      <p css={ButtonContent}></p>
    </div>
  )
}

store

reduxで使用する要素は全てこちらに配置します。
redux-toolkitを使用しておりますが、使い方に関しては今回割愛します。

slicesディレクトリ以下で Reducer / Actionを定義し(createSlice)、
store/index.tsxでslicesディレクトリ以下で作られたファイルを結合するといった流れです。

├── stores
     ├── index.ts 
     └── slices
         └── userSlice.ts
index.ts
import { configureStore } from '@reduxjs/toolkit'
import loginReducer from './slices/userSlice'

// それぞれのSliceを呼び出して結合する
export default configureStore({
  reducer: {
    // 識別する名前: importしてきたReducer名
    login: loginReducer
  }
})
userSlice.ts
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import axios from 'axios'
import {apiURL} from "../../utils/constants"


// 非同期はSliceの外に出してcreateAsyncThunkを使用する
export const fetchAsyncLogin = createAsyncThunk('login/get', async () => {
  //  ログインAPIを叩く想定
  await axios.get(apiURL)
  //   console.log(res.data)
})

const loginSlice = createSlice({
  //   slice名
  name: 'login',
  //   初期値
  initialState: {
    auth: {
      username: '',
      password: ''
    },
    isLogin: false
  },
  //各reducer 第一引数でstate情報を受け取り、第二引数でユーザーが操作した情報を受け取る
  reducers: {
    editUsername: (state, action) => {
      state.auth.username = action.payload

    },
    editPassword: (state, action) => {
      state.auth.password = action.payload
    },
    logout: (state, action) => {
      state.isLogin = false
    }
  },
  //   非同期の結果を受け取る
  extraReducers: builder => {
    builder.addCase(fetchAsyncLogin.fulfilled, (state, action) => {
      state.isLogin = true
    })
  }
})

// actionをexport
export const { editUsername, editPassword, logout } = loginSlice.actions
// state情報をexport
export const selectUser = (state: any) => state.login
// reducerをexport → storeへ
export default loginSlice.reducer

style

resetCSSや、CSS変数などを定義するために使います。

また、emotionを使っているのでtsxファイル内に書くことができますが、あえて分けたいと言う場合に使用します。
例えば、tsxファイル内が肥大化してしまう場合や、同じcssを使い回す場合などです(コンポーネント切り分けるのが難しいこともあると思うので…)。

ディレクトリの分け方はとりあえずviewsディレクトリと同じ構成にしております。

├── style(各page,componentのcssを切り分けたい時に使用)
       ├── GlobalStyle.tsx
       ├── components
       │   ├── atoms
       │   │   └── Button.tsx
       │   └── block
       │       ├── Footer.tsx
       │       └── Header.tsx
       ├── pages
       │   ├── Login.tsx
       │   └── Top.tsx
       ├── resetStyle.tsx
       └── variables.tsx

utils

tsx内で使用する変数、定数など、全体で使用する要素を置きます。

├── utils(定数などを管理)
     └── constants.tsx
constants.tsx
export const apiURL = ''
// など ...

全体

src下の全体は以下のような形です!

─── src
    ├── img (画像置き場)
    │   └── common
    │       └── favicon.png
    ├── pug (render先を指定)
    │   └── index.pug
    ├── ts (react外でjsを使うときに使用)
    │   └── app.ts
    ├── tsconfig.json
    └── tsx
        ├── index.tsx (エントリーポイント)
        ├── stores
        │   ├── index.ts (slicesディレクトリで作られたSliceを結合する)
        │   └── slices (このディレクトリ下で各sliceファイルを扱う)
        │       └── userSlice.ts
        ├── style(各page,componentのcssを切り分けたい時に使用)
        │   ├── GlobalStyle.tsx
        │   ├── components
        │   │   ├── atoms
        │   │   │   └── Button.tsx
        │   │   └── block
        │   │       ├── Footer.tsx
        │   │       └── Header.tsx
        │   ├── pages
        │   │   ├── Login.tsx
        │   │   └── Top.tsx
        │   ├── resetStyle.tsx
        │   └── variables.tsx
        ├── utils(定数などを管理)
        │   └── constants.tsx
        └── views
            ├── components(使い回しのできる要素)
            │   ├── atoms(最小単位のcomponent)
            │   │   └── Button.tsx
            │   ├── block(atomsを組み合わせたり、atomsでは管理しきれないcomponent)
            │   │   ├── Footer.tsx
            │   │   └── Header.tsx
            │   └── modules(機能を持ったcomponent)
            │       └── ScrollTop.tsx
            └── pages(各ページの呼び出し先)
                ├── login
                │   └── Login.tsx
                └── top
                    ├── Top.tsx
                    └── TopList.tsx(そのページでしか使われないreact-componentは同階層に設置)

作ってみた感想

これを作る上でご意見、ご協力いただいた方々には最高に感謝です:raised_hands:

まだまだ雑なところが多く、使ってみて思うことはたくさんあるのですが、viewsstoreで分ける構成は悪くないんじゃないかな…と思ってます。
うちではこうしてる、ここはこうした方が良いなど、ご意見いただけますと幸いです:hugging:

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