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

React-TypeScriptへ移行しむずしかったこと

ReactをTypeScriptで記述した際に難しかったことについて

初めましてQiita初投稿です。

初心者がReactをTypeScriptで記述した際に難しかったことについてまとめてみました。

複雑なデータの型定義

外部のAPIからデータを取得する際に取得するAPIのデータの型の定義に苦戦しました。

{
  0: {
    id: 1,
    name: 'sample',
    text: 'Hello World'
  }
}

このようなデータの型の定義の仕方に苦戦しました。

私はTypeinterfaceを用いて型の定義をしてみようとしていましたが、上手くできませんでした。

type.ts
type ChildData = {
  id: number;
  name: string;
  text: string;
}

type ParentData = {
  [data:string] : ChildData
}

stack overflowで解決策をいただきました。

このように複雑なデータの型は分解して定義をするのが良いと分かりました。

Reactのイベントの型

Reactでイベントを扱う場合には型が必要だと知らずにはじめたので最初は戸惑いました。

sample.tsx
  const addState = (e: React.ChangeEvent<HTMLInputElement>) => {
    setState(e.targer.value)
  }

React.ChangeEvent<HTMLInputElement>について最初は分かりませんでした。

現在はイベントに対して型が必要だと理解しています。DOMのイベントの型を定義しているという認識です。

tsconfig.json

初めてtsconfig.jsonを見たときは意味が分かりませんでした。
TypeScriptの設定を記述するファイルだと認識しています。
まだ理解できていないところが多いです。現在はtsconfig.jsonを編集せずに開発をしていますが、今後はanyを使えないように設定して開発をしてみたいと考えています。

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

Reactアプリ - 文章中キーワード黒塗りはがし

はじめに

 Reactを使って文章中の特定のキーワードの黒塗り文字列をクリックして黒塗りをはがして読めるようにするアプリを作りましたので紹介いたします。

 このアプリは単純で、黒塗りをはがすためにはキーワードの黒塗り部分をクリックします。再びキーワードをクリックすると黒塗りに戻ります。

前提条件

 node.jsがインストールされている事が必要です。まだインストールしていない場合には、インストールしておいてください。

Reactプロジェクトを作成します

以下のようにcreate-react-appコマンドを実行します。少し、時間がかかります。

  • macOS
terminal
(base) macpro:dev teruroom$ cd /Users/teruroom/dev/react/
(base) macpro:react teruroom$ npx create-react-app blackout-peeling
  • Windows10
powershell
PS C:\> cd C:\dev\react\
PS C:\dev\react\> npx create-react-app blackout-peeling
macOS実行結果
Creating a new React app in /Users/teruroom/dev/react/blackout-peeling.
・・・・・・・・途中省略・・・・・・・・・・・
Success! Created blackout-peeling at /Users/teruroom/dev/react/blackout-peeling
Inside that directory, you can run several commands:
  npm start
    Starts the development server.
  npm run build
    Bundles the app into static files for production.
  npm test
    Starts the test runner.
  npm run eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!
We suggest that you begin by typing:
  cd react-boardgame
  npm start
Happy hacking!

デフォルトで生成されたファイルを全て削除します

  • macOS
terminal
(base) macpro:react teruroom$ cd blackout-peeling/src
(base) macpro:src teruroom$ rm -f *
  • Windows10
powershell
PS C:\dev\react> cd .\blackout-peeling\src
PS C:\dev\react\blackout-peeling\src> rm -Force *

必要なJavaScriptとCSSファイルを作成します

  • macOS
terminal
(base) macpro:src teruroom$ touch index.css index.js
  • Windows10
powershell
PS C:\dev\react\blackout-peeling\src> New-Item -type file index.css
PS C:\dev\react\blackout-peeling\src> New-Item -type file index.js

JavaScriptとCSSを実装します

  • VSCodeを起動し、blackout-peelingフォルダを開きます
  • srcフィルダ内のindex.jsindex.cssを以下のように実装します
    image.png

  • index.js

以下のようにクラスの階層構造を設計しました
Pageクラス:ページ
  |
  +--Sentenceクラス:ページ内の文章
       |
       +--Keywordクラス:文章内のキーワード

index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';

const white = '#fff';
const crimson = '#880E4F';
const marginwidth = '5px';
const fontweight = 'bold';

class Keyword extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      value: null,
      forecolor: blackOutColor,
      backcolor: blackOutColor,
      margin: marginwidth,
      fontweight: fontweight,
    };
    this.changeColor = this.changeColor.bind(this);
  }
  changeColor() {
    const newColor = this.state.backcolor === blackOutColor ? white : blackOutColor;
    this.setState({ forecolor: blackOutColor, backcolor: newColor })
  }
  render() {
    return (
      <span style={{
        color: this.state.forecolor
        , background: this.state.backcolor
        , marginLeft: this.state.margin
        , marginRight: this.state.margin
        , fontWeight: this.state.fontweight
      }}
        onClick={() => this.changeColor()} >
        {this.props.value}
      </span>
    );
  }
}

class Sentence extends React.Component {
  blackOut(keyString) {
    return <Keyword value={keyString} />;
  }

  render() {
    return (
      <div>
        <div className="maintitle">ホゲホゲについて</div>
        <ol>
          <li className="subtitle">FOO</li>
          <ul>
            <li>FOOは、{this.blackOut('BAA')}システムから利用申請し、「{this.blackOut('利用許可')}」を得てから使用する</li>
            <li>FOO{this.blackOut('ホゲ')}やハゲによる攻撃から守るため、BOO対策製品を導入する</li>
          </ul>
        一部省略
        </ol>
      </div>
    );
  }
}

class Page extends React.Component {
  render() {
    return (
      <div className="page">
        <div className="page-sentence">
          <Sentence />
        </div>
        <div className="page-info">
          <div>{/* status */}</div>
          <ol>{/* TODO */}</ol>
        </div>
      </div>
    );
  }
}

ReactDOM.render(
  <Page />,
  document.getElementById('root')
);
  • index.css
index.css
body {
  width: 1100px;
  font: 18px "Meiryo", "Century Gothic", sans-serif;
  margin: 0px;
  cursor: pointer;
}

ol, ul {
  padding-left: 30px;
}

td {
  width: 50%;
}

.maintitle {
  width: 100%;
  font-size: 30px;
  color: #fff;
  background: cornflowerblue;
}
.subtitle {
  width: 95%;
  font-size: 22px;
  font-weight: bold;
  color: rgb(26, 75, 182);
  background: rgb(210, 210, 211);
  margin: 4px;
  padding: 4px;
}

.page {
  display: flex;
  flex-direction: row;
}

.page-sentence {
  margin-left: 20px;
}

.page-info {
  margin-left: 20px;
}

React Webアプリを開発用に起動します

  • VSCodeの TerminalNew Terminalで新規のターミナルを起動します
  • Webアプリを起動コマンドを実行します
    image.png

  • macOS

terminal
(base) macpro:blackout-peeling teruroom$ npm start
  • 実行結果
    image.png

  • Windows10

powershell
PS C:\dev\react\blackout-peeling> npm start

Web ブラウザで http://localhost:3000 が自動的に立ち上がりページが表示されます。以降、VSCodeで実装コードを変更し、保存するたびごとにWeb ブラウザの http://localhost:3000 が自動的に再読み込み(Reload)されるようになります

最適化ビルドを実行します

React Webアプリが完成したら、最適化ビルドを実行してから公開します

  • macOS
terminal
(base) macpro:blackout-peeling teruroom$ npm run build
  • Windows10
powershell
PS C:\dev\react\blackout-peeling> npm run build
  • 実行結果

macOSの場合
省略

Windows10の場合
> security-compliance@0.1.0 build C:\dev\react\blackout-peeling
> react-scripts build
Creating an optimized production build...
Compiled successfully.
File sizes after gzip:
  39.89 KB  build\static\js\2.73f5bc0c.chunk.js
  5.5 KB    build\static\js\main.11e48eb2.chunk.js
  782 B     build\static\js\runtime-main.623a1e28.js
  310 B     build\static\css\main.3c6f1d95.chunk.css
The project was built assuming it is hosted at /.
You can control this with the homepage field in your package.json.
The build folder is ready to be deployed.
You may serve it with a static server:
  npm install -g serve
  serve -s build
Find out more about deployment here:
  bit.ly/CRA-deploy

React Webアプリを本番用に起動します

オプション--host='0.0.0.0'をつけてコマンドを実行し、インバウンドとして全てのIPアドレスからのリクエストを受け付けるようにします

  • macOS ➡️ ubuntsサーバにデプロイ
bash
teruroom$ npm start --host='0.0.0.0'
  • Windows10 ➡️ Windows Server 2016にデプロイ
powershell
PS C:\dev\react\blackout-peeling> npm start --host='0.0.0.0'

React Webアプリを利用します

WebブラウザでURL http://192.168.0.16:3000 (192.168.0.16はWebアプリを動かしているPCのIPアドレス)にアクセスします

macOSの場合

デフォルトWebブラウザのSafariや後からインストールしたChromeで表示されます

image.png

Windows10の場合

 デフォルトWebブラウザのEdgeや後からインストールしたChromeで表示されます。IE11では残念ながらそのままでは表示や動作が上手くいきません。react-app-polyfillライブラリを別途インストールし、index.jsファイルの先頭でimport 'react-app-polyfill/ie11';` のようにインポートする必要があります。

 諸々の都合によりIE11を既定のブラウザにしている場合には、以下のようにWindowsショートカットなどにURLを仕込んでおいてください。ショートカットを開くとEdgeによりReact Webアプリが開かれるようになります

microsoft-edge:http://hoge-foo.ad.baa.co.jp:3000/

  • hoge-foo.ad.baa.co.jp:Active Directry内のReact Webアプリを起動しているPCの端末名

黒塗りをクリックすると剥がれてキーワード文字が読めるようになります

image.png

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

完成!!初めてReact.jsでWebサイト作った!!

とりあえず完成!

つまずいた点たくさんありました...
ハマったポイントを記事にしたのはまだ1つだけですが,今後作ってきます

一通り完成したので報告致します.<--急にかしこまる
https://shinnosuke7031.github.io/react-test/

意識したのは,ページ遷移しないで表示内容を変えること!

デザインはまだまだですが...

ぜひアドバイスとかいただけると幸いです

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

僕が考えた最強の React ファイル構成

技術スタック

僕が考えた最強の React 技術スタック

Grouping by file type

https://reactjs.org/docs/faq-structure.html

基本的にこの考え方でフォルダを分ける

ファイル構成

  • app.yaml
  • app.storybook.yaml
  • cloudbuild.yaml
  • firebase.json
  • api # 検討中
    • foo.ts
  • assets
  • components
    • Foo.ts
  • constants
  • containers
    • foo.ts
  • context
  • firebase
  • functions
  • layouts # 検討中
  • middleware
  • models # 検討中
  • pages
    • _app.tsx
    • _document.tsx
    • _error.tsx
    • index.tsx
  • public / static
    • favicon.ico
  • store # 検討中
    • store.ts
    • rootReducer.ts
    • actions
      • foo.ts
    • facades # 検討中
    • reducers
      • foo.ts
    • selectors
      • foo.ts
    • states
      • foo.ts
  • stories
    • foo.stories.ts
  • test
    • pages
      • __snapshots__
      • index.test.tsx / index.spec.tsx
  • types # 検討中

※ 自動生成されるファイルについてはいくつか省略してます

参考

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

関数合成とReactコンポーネントとReact hooks

はじめに

Reactが好まれる理由の一つとしてただの関数としてかけるからというのがあります。Reactの関数コンポーネントは関数のように合成できます。React hooksもただの関数ですので合成できます。簡単な例で説明できたら分かりやすいのではないかと思い、書いてみます。

普通のJavaScript

関数がない(一つだけ)の場合は、例えば次のようになります。

const main = (args) => {
  const a = args[0] * 2;
  const b = args[1] * 2;
  console.log([a, b]);
  return a + b;
};

関数は、再利用性を増すために使うことができます。

const double = (x) => x * 2;

const main = (args) => {
  const a = double(args[0]);
  const b = double(args[1]);
  console.log([a, b]);
  return a + b;
};

また、たった一度しか使わない関数であっても、名前をつけて読みやすくするために関数化する場合があります。

const double = (x) => x * 2;
const printForDebug = (x) => console.log(x);

const main = (args) => {
  const a = double(args[0]);
  const b = double(args[1]);
  printForDebug([a, b]);
  return a + b;
};

簡単な例ですが、これが普通のJavaScriptの場合です。

Reactコンポーネント

ReactコンポーネントはReactNodeを返す関数として書くことができます。JSXで書くことが多いので、それにならうと例えば次のようになります。

const App = () => {
  return (
    <div>
      <h1>Hello</h1>
      <h2>World</h2>
    </div>
  );
};

これを関数に分割して合成するには次のように書けます。

const Title = () => <h1>Hello</h1>;
const Subtitle = () => <h2>World</h2>;

const App = () => {
  return (
    <div>
      {Title()}
      {Subtitle()}
    </div>
  );
};

これは完全に同じ結果を生み出します。ただの関数ですから。

しかし、このパターンはあまり見たことがないでしょうし、実際非推奨です。推奨される書き方は、次のようになります。

const Title = () => <h1>Hello</h1>;
const Subtitle = () => <h2>World</h2>;

const App = () => {
  return (
    <div>
      <Title />
      <Subtitle />
    </div>
  );
};

見慣れた書き方かと思われます。こちらが推奨される理由は、TitleやSubtitleの関数を呼び出すタイミングをReactが制御できるからです。

<Title />createElement(Title, null) と同等です。(執筆時点) 参照

この書き方は、Reactコンポーネントを関数合成する際の制約とも言えます。関数として直接合成するのではなく、createElementを介して合成することで、Reactのスケジューリングの恩恵を受けることができます。

React hooks

では、React hooksの場合はどうなるでしょうか。同じように簡単な例を考えます。

const App = () => {
  const [a, setA] = useState(0);
  const incrementA = () => {
    setCountA((c) => c + 1);
  };
  const [b, setB] = useState(0);
  const incrementB = () => {
    setCountB((c) => c + 1);
  };
  useEffect(() => {
    console.log([a, b]);
  });
  return (
    <div>
      {a} <button onClick={incrementA}>+1</button>
      {b} <button onClick={incrementB}>+1</button>
    </div>
  );
};

共通している処理があるので関数に切り出してカスタムフックにしてみましょう。

const useCount = (initialCount) => {
  const [count, setCount] = useState(initialCount);
  const increment = () => {
    setCount((c) => c + 1);
  };
  return [count, increment];
};

const App = () => {
  const [a, incrementA] = useCount(0);
  const [b, incrementB] = useCount(0);
  useEffect(() => {
    console.log([a, b]);
  });
  return (
    <div>
      {a} <button onClick={incrementA}>+1</button>
      {b} <button onClick={incrementB}>+1</button>
    </div>
  );
};

場合によっては、共通処理でなくてもカスタムフックにすることで見通しが良くなるかもしれません。

const usePrintForDebug = (x) => {
  useEffect(() => {
    console.log(x);
  });
};

const App = () => {
  const [a, incrementA] = useCount(0);
  const [b, incrementB] = useCount(0);
  usePrintForDebug([a, b]);
  return (
    <div>
      {a} <button onClick={incrementA}>+1</button>
      {b} <button onClick={incrementB}>+1</button>
    </div>
  );
};

Reactコンポーネントの関数合成に制約があったように、React hooksにおける制約もあります。 Rules of Hooks に詳しく書かれていますが、この制約の範囲内でしかカスタムフックは作れないことになります。

React hooksの制約は直感的ではないと言えるかもしれませんが、Reactコンポーネントの(緩い)制約も決して直感的とは言えないでしょう。制約の数は少ない上、パターン化されているので迷いは少ないと思いますが。React hooksの制約は eslint plugin でほぼチェックできますので、これを使うことが「パターン」として必須と言えるでしょう。

おわりに

React hooksを使う際にどの程度で関数化すなわちカスタムフック化したらいいかは、一言で答えられない問題かと思います。一言で答えるなら、Reactコンポーネントを分割するように分割したらいいのではないかと言います。つまり答えになっていませんが。私自身は関数は小さくしたい派なので、コンポーネントもカスタムフックも小さく分割して合成することを目指します。この辺りはReactとしての制約はなく柔軟なので、プロジェクトでの方針や規約を決めるといいかもしれません。(コンポーネントが大きすぎるとパフォーマンスに影響は出そうです。カスタムフックが大きすぎてもパフォーマンスには関係なさそう)

コンポーネントの場合はAtomic Designなどの方法論があるのでカスタムフックにもそのような方法論があったり、アレンジして適用できるかもしれません。

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

Next.jsとGo言語(gqlgen)でGraphQLを使ったアプリケーションを構築する方法

この記事では、フロントエンドにNext.js、バックエンドにGo言語(gqlgen)を用いて、フロントエンドとバックエンド間のAPIにGraphQLを使ったアプリケーションを構築する方法をまとめます。

作成したのは以下のような簡易ブクマアプリです。
image.png
フロントエンドはほぼNext.jsのexamplesのままで、本記事ではバックエンドの実装をメインに解説します。

背景

個人的に、「フロントエンドフレームワークとGo言語の組み合わせで何か開発をしてみたい」と以前から思っていました。
そんな中、ReactのフレームワークであるNext.jsの使い勝手が良さそうという噂を耳にしました。

加えて、Next.jsのexamplesが充実していて、Apolloを使ったアプリケーションのひな形が簡単に作れることを知ったので、今回Go言語のバックエンドと組み合わせて動かしてみることにしました。

構成要素

図に書いてみると思っていた以上に構成要素が多かったので、それぞれの役割を大まかに説明します。
Untitled_LINE_Beacon_-_Cacoo.png

名前 種別 役割
JavaScript プログラミング言語 今回のフロントエンドの実装に用いるプログラミング言語
React ライブラリ コンポーネントベースでUIを構築できるJavaScriptライブラリ
Apollo Client ライブラリ GraphQLに対応した状態管理ライブラリ
Next.js フレームワーク Reactのサーバーサイドレンダリング(SSR)に対応するフレームワーク
GraphQL クエリ言語/ランタイム API向けに作られたクエリ言語およびランタイム
Go言語 プログラミング言語 今回のバックエンドの実装に用いるプログラミング言語, golangと表記されることもある
gqlgen ライブラリ SchemaベースでGraphQLサーバを構築するためのライブラリ

UIの構築

まずはNext.jsのexamples/with-apolloをベースにアプリケーションを作ります。

$ yarn create next-app
success Installed "create-next-app@9.4.4" with binaries:
      - create-next-app
✔ What is your project named? … with-apollo-ui
✔ Pick a template › Example from the Next.js repo
✔ Pick an example › with-apollo
Creating a new Next.js app in /Users/yokazaki/src/github.com/yuuu/with-apollo-ui.

# ログ省略

$ cd with-apollo-ui
$ yarn dev

出来上がったアプリケーションは、URLとタイトルをセットで登録・閲覧できる、いわゆる簡易ブクマアプリです。

image.png

この時点では、リクエストの送信先となっているバックエンドのGraphQLサーバはインターネット上に公開されているものを利用しています。
このため、列挙されているURLとタイトルは、世界中のユーザが登録したものがそのまま表示されています。

GraphQLサーバの構築

スキーマベースでGraphQLサーバを構築できることと、機能の拡張性を考慮して、以下記事を参考にEcho+gqlgenを使って構築しました。

gqlgen + EchoでgolangなGraphQLサーバを作るチュートリアル

ベース構築

$ mkdir with-apollo-api
$ cd with-apollo-api
$ go mod init github.com/yuuu/with-apollo-api
$ go get github.com/99designs/gqlgen
$ go get github.com/rs/cors

# gqlgenでgraph/schema.graphqlsを生成
$ gqlgen init

スキーマ定義

graph/schema.graphqls にGraphQLのスキーマを記述します。

graph/schema.graphqls
type Post {
  id: ID!
  title: String!
  votes: Int!
  url: String!
  createdAt: String!
}

type PostsMeta {
  count: Int!
}

type Query {
  allPosts(orderBy: OrderBy, first: Int!, skip: Int!): [Post!]!
  _allPostsMeta: PostsMeta!
}

enum OrderBy {
  createdAt_ASC,
  createdAt_DESC
}

type Mutation {
  createPost(title: String!, url: String!): Post!
  updatePost(id: ID!, votes: Int): Post!
}

スキーマを記述したらソースコードを生成します。

$ rm graph/schema.resolvers.go
$ gqlgen

QueryとMutationを実装

生成された graph/schema.resolvers.go を以下のように変更します。

graph/schema.resolvers.go
package graph

// This file will be automatically regenerated based on the schema, any resolver implementations
// will be copied through when generating and any unknown code will be moved to the end.

import (
    "context"
    "fmt"
    "sort"
    "strconv"
    "time"

    "github.com/yuuu/with-apollo-api/graph/generated"
    "github.com/yuuu/with-apollo-api/graph/model"
)

var posts []*model.Post = make([]*model.Post, 0)

func (r *mutationResolver) CreatePost(ctx context.Context, title string, url string) (*model.Post, error) {
    post := model.Post{
        ID:        fmt.Sprintf("%d", len(posts)+1),
        Title:     title,
        URL:       url,
        Votes:     0,
        CreatedAt: time.Now().Format("2006-01-02 15:04:05"),
    }
    posts = append(posts, &post)
    return &post, nil
}

func (r *mutationResolver) UpdatePost(ctx context.Context, id string, votes *int) (*model.Post, error) {
    if votes == nil {
        return nil, nil
    }
    i, _ := strconv.Atoi(id)
    posts[i-1].Votes = *votes
    return posts[i-1], nil
}

func (r *queryResolver) AllPosts(ctx context.Context, orderBy *model.OrderBy, first int, skip int) ([]*model.Post, error) {
    if skip > len(posts) {
        skip = len(posts)
    }
    if (skip + first) > len(posts) {
        first = len(posts) - skip
    }
    sortedPosts := make([]*model.Post, len(posts))
    copy(sortedPosts, posts)
    if orderBy != nil && *orderBy == "createdAt_DESC" {
        sort.SliceStable(sortedPosts, func(i, j int) bool {
            return sortedPosts[i].CreatedAt > sortedPosts[j].CreatedAt
        })
    }
    slicePosts := sortedPosts[skip : skip+first]
    return slicePosts, nil
}

func (r *queryResolver) AllPostsMeta(ctx context.Context) (*model.PostsMeta, error) {
    postsMeta := model.PostsMeta{Count: len(posts)}
    return &postsMeta, nil
}

// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }

type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

Playgroundで動作確認

以下コマンドでGraphQLサーバを起動します。

$ go run server.go

ブラウザで http://localhost:8080 にアクセスすると、PlayGroundが表示されます。

まずはmutationから動作確認してみましょう。
Yahoo! JAPANを登録してみます。
image.png
次にGoogleを登録してみます。
image.png
続いてQueryを試してみます。
image.png
このように、GraphQLの各リクエストが問題なく動作していることがわかります。

UIとGraphQLサーバを組み合わせる

CORS設定

現状、Next.jsが動いているオリジン(http://localhost:3000) とGraphQLサーバのオリジン(http://localhost:8080) が異なるため、このままではGraphQLサーバへのリクエストが失敗します。

server.goを以下のように変更することで、 http://localhost:3000 からの要求を受け付けられるようにします。

server.go
package main

import (
    "log"
    "net/http"
    "os"

    "github.com/99designs/gqlgen/graphql/handler"
    "github.com/99designs/gqlgen/graphql/playground"
    "github.com/rs/cors"
    "github.com/yuuu/with-apollo-api/graph"
    "github.com/yuuu/with-apollo-api/graph/generated"
)

const defaultPort = "8080"

func main() {
    port := os.Getenv("PORT")
    if port == "" {
        port = defaultPort
    }

    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))

    http.Handle("/", playground.Handler("GraphQL playground", "/query"))

    c := cors.New(cors.Options{
        AllowedOrigins:   []string{"http://localhost:3000", "http://localhost:8080"},
        AllowCredentials: true,
    })

    http.Handle("/query", c.Handler(srv))

    log.Printf("connect to http://localhost:%s/ for GraphQL playground", port)
    log.Fatal(http.ListenAndServe(":"+port, nil))
}

Next.jsのリクエスト先のURLを変更

lib/apolloClient.js のServer URLを http://localhost:8080/query に変更します。

lib/apolloClient.js
import { useMemo } from 'react'
import { ApolloClient } from 'apollo-client'
import { InMemoryCache } from 'apollo-cache-inmemory'
import { HttpLink } from 'apollo-link-http'

let apolloClient

function createApolloClient() {
  return new ApolloClient({
    ssrMode: typeof window === 'undefined',
    link: new HttpLink({
      uri: 'http://localhost:8080/query', // Server URL (must be absolute)
      credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
    }),
    cache: new InMemoryCache(),
  })
}

export function initializeApollo(initialState = null) {
  const _apolloClient = apolloClient ?? createApolloClient()

  // If your page has Next.js data fetching methods that use Apollo Client, the initial state
  // gets hydrated here
  if (initialState) {
    _apolloClient.cache.restore(initialState)
  }
  // For SSG and SSR always create a new Apollo Client
  if (typeof window === 'undefined') return _apolloClient
  // Create the Apollo Client once in the client
  if (!apolloClient) apolloClient = _apolloClient

  return _apolloClient
}

export function useApollo(initialState) {
  const store = useMemo(() => initializeApollo(initialState), [initialState])
  return store
}

動作確認

UIとGraphQLサーバをともに起動します。

$ cd with-apollo-ui # 移動先パスは適宜変更ください
$ yarn dev

# 以下は別のterminalで
$ cd with-apollo-api # 移動先パスは適宜変更ください
$ go run server.go

http://localhost:3000 へアクセスすると、URLの追加や投票が正常に動作することが確認できます。
image.png

まとめ

Next.jsのサンプルが充実しているおかげで、簡単にアプリケーションを構築できました。これに認証やバリデーションを追加して、UIを自分好みにカスタマイズすれば簡単にサービスをリリースできそうです。

Next.js・Go言語ともにもっと事例が増えると良いなと思っています。
みなさまも、ぜひお試しください。

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