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

React (JavaScript) で湯婆婆を実装してみる

はじめに

原作者様: Javaで湯婆婆を実装してみる - Qiita

この壮大なビッグウェーブならぬビッグ湯婆婆に乗るっきゃない!

コード

index.html
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="utf-8">
  <title>React 湯婆婆</title>
  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone/babel.min.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="text/babel">
    (() => {
      'use strict';

      const {useState} = React;

      function Yubaba() {
        const [name, setName] = useState('');
        const newName = name.substr(Math.floor(Math.random() * name.length), 1);

        return (
          <div>
            <p>契約書だよそこに名前を書きな</p>
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>
            <p>フン{name}というのかい贅沢な名だねぇ</p>
            <p>今からお前の名前は{newName}いいかい{newName}だよ分かったら返事をするんだ{newName}!!</p>
          </div>
        );
      }

      ReactDOM.render(
        <Yubaba/>,
        document.getElementById('root')
      );

    })();
  </script>
</body>
</html>

こちらがコードになります。これを textfile とかにコピペしてブラウザ起動すれば動きます。

コードの解説

湯婆婆の部屋に入る準備

React と babel を CDN で読み込む。

  <script src="https://unpkg.com/react/umd/react.development.js"></script>
  <script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
  <script src="https://unpkg.com/babel-standalone/babel.min.js"></script>

レンダー用の div 要素 (root) を記述して、あとは JS のお作法を記述する。

  <div id="root"></div>
  <script type="text/babel">
    (() => {
      'use strict';

      ...

    })();
  </script>

契約書と名前を奪う Yubaba コンポーネントを作成

Yubaba コンポーネントをつくる!
フック (useState) も使う!

      const {useState} = React;

      function Yubaba() {
        const [name, setName] = useState('');
        const newName = name.substr(Math.floor(Math.random() * name.length), 1);

        return (
          <div>
            <p>契約書だよそこに名前を書きな</p>
            <input type='text' value={name} onChange={e => setName(e.target.value)}/>
            <p>フン{name}というのかい贅沢な名だねぇ</p>
            <p>今からお前の名前は{newName}いいかい{newName}だよ分かったら返事をするんだ{newName}!!</p>
          </div>
        );
      }

      ReactDOM.render(
        <Yubaba/>,
        document.getElementById('root')
      );


実行結果

react-yubaba.png

おまけ

名前の入力が空白だった場合のクラッシュ湯婆婆は入室しませんでした!
SPA なので名前を言い終える前に湯婆婆は名前を奪い始めるという。早とちり湯婆婆降臨。

最後に

ここまで読んでいただきありがとうございました!

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

Reactでログイン機能を実装する

はじめに

react-router-domでPriateRouteを実現します。今回は以下の機能を実装していきたいと思います。

  1. ログイン済みユーザーしかアクセスできないページを作る
  2. ↑のページに、ログインせずにアクセスしようとするとログインページにリダイレクトされる。
  3. 2でログインページにリダイレクトされた後、ログインを行えば元のページ(アクセスしたかったページ)に遷移する。

画面構成は以下のようにします。

  • /login: ログイン画面。未ログインユーザーしかアクセスできない。
  • /: ホーム画面。ログイン済みユーザーしかアクセスできない。
  • /profile/:userId: ユーザーのプロフィール画面。ログイン済みユーザーしかアクセスできない。

プロジェクトはGithubのレポジトリにあげているのでよければご覧ください。

プロジェクトを作る

今回はcreate-react-appを用いてセットアップします。次にreact-router-domをインストールします

npx create-react-app private-router-sample --template typescript
npm install react-router-dom @types/react-router-dom

PrivateRouteを実装する

今回の肝となるPrivateRouteの実装です。(useAuthUser, AuthUserProviderについては別ファイルで定義しています。詳しい実装はGithubをご覧ください)

App.tsx
import React from 'react';
import { BrowserRouter as Router, Redirect, Route, RouteProps, Switch } from 'react-router-dom';

// PrivateRouteの実装
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
  const authUser = useAuthUser()
  const isAuthenticated = authUser != null //認証されているかの判定
  if (isAuthenticated) {
    return <Route {...props}/>
  }else{
    console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
    return <Redirect to="/login"/>
  }
}

const App: React.FC = () => {
  return (
    <AuthUserProvider>
      <Router>
        <Switch>
          <Route exact path="/login" component={LoginPage}/>

         {/* Routeと同じ形で使用 */}
          <PrivateRoute exact path="/" component={HomePage} /> 
          <PrivateRoute exact path="/profile/:userId" component={ProfilePage}/>
        </Switch>
      </Router>
    </AuthUserProvider>
  );
}

ポイントは、PrivateRoute内の以下の実装です。

if (isAuthenticated) {
  return <Route {...props}/>
} else {
  console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
  return <Redirect to="/login"/>
}

認証済身ならRouteを、認証されていなければRedirectを返すことでログインページへのリダイレクトを行っています。

UnAuthRoute

上記の実装では、「未ログインのユーザーが/にアクセス」することは防げましたが、「ログイン済みのユーザーが/loginにアクセス」することは防げていません。そこで、ログイン済みユーザーが/loginへアクセスした場合、/へリダイレクトする処理を実装します。

今回はPrivateRouteと逆の処理を行うUnAuthRouteを実装したいと思います。

App.tsx
import React from 'react';
import { BrowserRouter as Router, Redirect, Route, RouteProps, Switch } from 'react-router-dom';

// PrivateRouteの実装
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
  const authUser = useAuthUser()
  const isAuthenticated = authUser != null //認証されているかの判定
  if (isAuthenticated) {
    return <Route {...props}/>
  }else{
    console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
    return <Redirect to="/login"/>
  }
}

// ===追加部分 ==========
// UnAuthRouteの実装
const UnAuthRoute: React.FC<RouteProps> = ({ ...props }) => {
  const authUser = useAuthUser()
  const isAuthenticated = authUser != null
  if (isAuthenticated) {
    console.log(`ログイン済みのユーザーは${props.path}へはアクセスできません`)
    return <Redirect to="/" />
  } else {
    return <Route {...props} />
  }
}
// ===追加部分ここまで ======

const App: React.FC = () => {
  return (
    <AuthUserProvider>
      <Router>
        <Switch>
         {/* 未ログインユーザーのみアクセス可能 */}
          <UnAuthRoute exact path="/login" component={LoginPage}/>
         {/* ログイン済みユーザーのみアクセス可能 */}
          <PrivateRoute exact path="/" component={HomePage} /> 
          <PrivateRoute exact path="/profile/:userId" component={ProfilePage}/>
        </Switch>
      </Router>
    </AuthUserProvider>
  );
}

元のページへリダイレクト

ここまでの実装では、3の要件を満たしていません。

例えば、未ログインのユーザーが/profile/1にアクセスしたときの処理の流れは以下のようになります

1. /profile/1にアクセス
2. PrivateRouteによって/loginにリダイレクトされる
3. ログインが完了すればUnAuthRouteによって/にリダイレクトされる ← 要件を満たすならば`/profile/1`へリダイレクトされるべきです。

そこで、PrivateRouteUnAuthRouteを以下のように修正します。

App.tsx
const PrivateRoute: React.FC<RouteProps> = ({...props}) => {
  const authUser = useAuthUser()
  const isAuthenticated = authUser != null
  if (isAuthenticated) {
    return <Route {...props}/>
  }else{
    console.log(`ログインしていないユーザーは${props.path}へはアクセスできません`)
    return <Redirect to={{pathname: "/login", state: { from: props.location?.pathname }}}/> // fromに本来アクセス
  }
}

const UnAuthRoute: React.FC<RouteProps> = ({ ...props }) => {
  const authUser = useAuthUser()
  const isAuthenticated = authUser != null
  const { from } = useLocation<{from: string | undefined}>().state

  if (isAuthenticated) {
    console.log(`ログイン済みのユーザーは${props.path}へはアクセスできません`)
    return <Redirect to={from ?? "/"} />
  } else {
    return <Route {...props} />
  }
}

実装のポイントはPrivateRouteの <Redirect to={{pathname: "/login", state: { from: props.location?.pathname }}}/>です。PrivateRouteは未ログインユーザーをログインページにリダイレクトしますが、その際にユーザーがアクセスしようとしたURLをstateとして設定しています。

ここで設定したstateはUnAuthRouteで const { from } = useLocation<{from: string | undefined}>().stateという形で取り出されます。取り出されたURLは、return <Redirect to={from ?? "/"} />でリダイレクト先として指定されます。(?? "/"が指定されているのは、ユーザーが直接ログインページにアクセスした場合はfrom == nullとなるからです)

最後に

今回はログイン/未ログインの2状態を扱いましたが、サービスによってはユーザー登録が絡む場合もあるでしょう。その場合には、未ログイン/未登録/登録済みの3状態を扱うことになりますが、UnRegisteredRouteを追加するなどで応用できます。

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

SharePoint Framework で TODO管理アプリを作る (3/4) 一覧画面の作成

続きです。
今回は SharePoint Online のデータへのアクセスが含まれるので、SPFxっぽいかもです。
毎度ながら内容に関して React も TypeScript もあまり上手に使えるわけではないですので、そのあたりご容赦ください。。。

完成イメージ(4/4)

Teams のタブにもいい感じにおさまります。
GIF 2020-11-06 17-03-53.gif

下は画像が荒いですね。

もちろん SharePoint Online のページとしても使えます。
image.png

関連記事

Webパーツ初期設定編 1/4
https://qiita.com/nanoka/items/834ee5216a5437dc04bc
画面レイアウトと画面遷移編 2/4
https://qiita.com/nanoka/items/35dea4791ad767497ed6
一覧画面編 3/4
https://qiita.com/nanoka/items/fbda2f09796a445dd9e2
詳細画面編 4/4
https://qiita.com/nanoka/items/92093ca05096728e2deb

手順

 1. SharePoint Online でデータソースの作成
 2. データ取得用関数の作成
 3. 一覧画面用コンポーネントからの関数の呼び出し
 4. 一覧画面用コンポーネントの見た目の作成

手順の説明

1. SharePoint Online でデータソースの作成

今回は一覧画面を作成するのですが、そこに表示するデータを用意します。
以下のようなカスタムリストを作成し、適当なデータを準備します。

image.png

  • Title : デフォルトのタイトル列。文字通りタイトルを入力。 型は1行テキスト。
  • LimitDate : 期限を入力。 型は日付。
  • Note : メモを入力。 型は複数行テキスト。オプションでリッチテキストじゃないやつにする。
  • Status: 状態を入力。 型は選択肢。Run Done の2種類を用意。

2. データ取得用関数の作成

データソースの作成が終わったらそれにアクセスする関数を作成していきます。
それらの関数はまとめて api フォルダに配置します。

思い出のフォルダ構成
...src/
 └ webparts/
  └ spfxTodo/
     ├ api/ (データ操作用関数を管理)
     ├ assets/
     │ └ stylesheets/
     ├ components/
     │ ├ atoms/
     │ ├ molecules/ (中くらいの部品を管理)
     │ └ organisms/
     │ └ pages/ (画面を管理)
     │ └ templates/
     └ loc/

やっと関数を作成していきます。
import の部分では以下を読み込みます。
- SharePoint へのアクセスを簡単にしてくれるものたち
- 画面でもデータへのアクセスでも使う処理をまとめた util

量が多くなる場合はファイルを分ければよいのですが、今回はざっくりということで同じファイルにしています。
上から以下のように分けています。
- 画面から呼び出し用関数
- リストアイテムの操作用共通関数
- Spへのアクセス用共通関数

画面の呼び出し用関数は、その名の通り画面用コンポーネントから利用されます。
個別のロジック等はできる限りここで吸収します。
共通の仕様として意識したことは、setState を受け取り、処理を終えて取得した結果を setState して終了する感じです。

リストアイテムの操作用共通関数は、画面の呼び出し用関数から呼ばれます。
リストアイテムの操作ごとに汎用的に作ります。

Spへのアクセス用共通関数は、リストアイテムの操作用関数から呼ばれます。
SharePoint Online へ直接アクセスする場所で、Get Post その他ちょっと特殊なもののようなレベルで汎用的に作ります。
SharePoint Online とのやり取りで発生するエラーチェックなどもここで一元的に行います。
引用ばかりで恐縮ですが、以下を参考にされるといいと思います。

https://sharepoint.orivers.jp/article/10393

api/index.ts
import { SPHttpClient, SPHttpClientResponse, ISPHttpClientOptions } from '@microsoft/sp-http';
import { WebPartContext } from '@microsoft/sp-webpart-base';

/********** 画面から呼び出し用関数 **********/
/********** TodoList GET **********/
const getTodoListOptions = "?$select=ID,Title,LimitDate,Note,Modified,Status&$filter=Status eq 'Run'&$orderby=LimitDate asc";

export const GetTodoListItems =
    async (setState: any, targetListName: string, context: WebPartContext) => {
        setState({ loading: true });
        const todoListItems: Array<Object> = await GetListItems(context, targetListName, getTodoListOptions);
        setState({ loading: false, todoListItems });
    };

/********** TodoDetail GET **********/

/********** TodoDetail POST **********/


/********** リストアイテムの操作用共通関数 **********/
const defHeaders: HeadersInit = { "Content-type": "application/json", "Accept": "application/json" };

/********** 検索 **********/
const GetListItems =
    async (context: WebPartContext, listName: string, options: string) => {

        if (!options) {
            options = "";
        }
        const restUri: string = `${context.pageContext.web.absoluteUrl}/_api/web/lists/getbytitle('${listName}')/Items${options}`;
        const res: SPHttpClientResponse = await SpRestGet(context, restUri);
        const resJson: any = await res.json();
        const resJsonArray: Array<Object> = resJson.value;
        return resJsonArray;
    };

/********** 作成 **********/

/********** 更新 **********/

/********** 削除 **********/


/********** Spへのアクセス用共通関数 **********/
/********** GET Request **********/
const SpRestGet =
    async (context: WebPartContext, RestUri: string) : Promise<SPHttpClientResponse>  => {

        const res: SPHttpClientResponse = await context.spHttpClient.get(RestUri, SPHttpClient.configurations.v1);
        //エラーチェックは他のサイトが詳しいので省きます
        return res;
  };

/********** POST Request **********/
util.ts
/********** 見た目調整用共通関数 **********/
export const DateFormatJa = (datestring: string): string  => {
    const TempDate = new Date(datestring);
    return TempDate.toLocaleDateString("ja");
};
export const DateTimeFormatJa = (datestring: string) : string => {
    const TempDate = new Date(datestring);
    return TempDate.toLocaleString("ja");
};

/********** JSON調整用共通関数 **********/
export const ObjectMerge = <T extends Object>(copyToObj: T, copyFromObj: Object): T => {
    Object.keys(copyToObj).forEach(key => {
        console.log(key);
        if (key in copyFromObj) {
            copyToObj[key] = copyFromObj[key];
        }
    });
    return copyToObj;
};

3. 一覧画面用コンポーネントからの関数の呼び出し

呼び出す処理ができたので、画面から呼び出して結果を確認していきたいと思います。

まず画面の中で共通的に使うものを定義します。
SharePoint Online へのアクセスは取得するデータ量にもよりますが、待ち時間が発生します。
その間の演出として表示するコンポーネントを用意します。

molecules/Loading.tsx
import * as React from 'react';

import { Spinner, SpinnerSize } from 'office-ui-fabric-react';

const Loading = () => (
  <div>
    <Spinner size={SpinnerSize.large} label="loading..." ariaLive="assertive" labelPosition="top" />
  </div>
);
export default Loading;

あと、作成したカスタムリストもリストアイテムの扱う値だけを定義した型を作成します。

molecules/ITodoItem.ts
export interface ITodoItem {
  Title: string;
  LimitDate: string| null;
  Note: string;
  Status: string;
  ID?: string;
  Created?: string | null;
  Modified?: string | null;
}

次に以下のファイルを更新します。
import の部分では以下を読み込みます。
- 先ほど作成したコンポーネントや、関数の読み込み
- SharePoint っぽいUIを勝手にやってくれる office-ui-fabric-react
- 今回扱うリストアイテムの型

次に、State としてこのコンポーネントが持つ状態を定義します。
- loading データ読み込み中かどうか
- todoListItems 一覧に表示中のアイテムそのもの

画面に出力する箇所では、更新ボタンを表示する部分と、一覧の取得結果を表示する部分を縦に並べています。

pages/TodoList.tsx データ取得用関数の動作確認用
import * as React from 'react';

import {
  PrimaryButton, Stack, IStackTokens
} from 'office-ui-fabric-react';

import Loading from '../molecules/Loading';
import * as api from '../../api';

import { IPageProps } from './IPageProps';
import { ITodoItem } from '../molecules/ITodoItem';

interface State {
  loading: boolean;
  todoListItems: Array<Object>;
}

class TodoList extends React.Component<IPageProps, State> {

  constructor(props: IPageProps) {
    super(props);

    this.state = {
      loading: false, todoListItems: []
    };
  }

  private GetTodoListItems = async () =>
    api.GetTodoListItems(this.setState.bind(this), this.props.todoListName, this.props.webPartContext)

  public componentDidMount() {
    this.GetTodoListItems();
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 5 };
    const { loading, todoListItems } = this.state;
    return (
      <div>
        <Stack tokens={stackTokens}>
          <Stack.Item align='end'>
            <PrimaryButton text='Reload' onClick={this.GetTodoListItems} />
          </Stack.Item>
          <Stack.Item align='auto'>
            {loading ? (
              <Loading />
            ) : (
                todoListItems.length > 0 ? (
                todoListItems.map((item : ITodoItem) => <div>{item.Title}</div>)
                ) : (
                    <><p>Good work! No more Todos.</p></>
                  )
              )}
          </Stack.Item>
        </Stack>
      </div>
    );
  }
}
export default TodoList;

Hosted workbench の実行でリストアイテムのタイトルが以下のように表示できていれば成功です。
Reloadボタンを押すと、Loding... と一瞬表示され、画面が更新されているのがわかります。

image.png

4. 一覧画面用コンポーネントの見た目の作成

最後に 先ほど取得したデータの見た目を整えるため、ファイルを更新していきます。
大きな変更は以下の2点です。
- State に todoColumns を追加し、初期値として一覧表示する列を定義
- 一覧の取得結果を表示する部分に DetailList を使用し、一覧表示する。

SharePoint Online っぽい一覧表示をやってくれるコンポーネントとして、 office-ui-fabric-react に DetailsList というものがあるので、それを使います。

https://developer.microsoft.com/ja-JP/fluentui#/controls/web/detailslist

DetailList の プロパティはいろいろありますが、とりあえず、items に、カスタムリストから取得してきたリストアイテムの配列を、columns に、IColumn の配列で、表示したい列だけを定義したもの(fieldName と Object の key で一致させる)を渡しせばいい感じに表示してくれます。
また、表示されたアイテムをクリックした際に、詳細画面に繊維する処理も追記します。

pages/TodoList.tsx
import * as React from 'react';

import {
  PrimaryButton, Stack, IStackTokens, DetailsList, IColumn, CheckboxVisibility
} from 'office-ui-fabric-react';

import Loading from '../molecules/Loading';
import * as api from '../../api';
import * as util from '../util';

import { IPageProps } from './IPageProps';

interface State {
  loading: boolean;
  todoColumns: IColumn[];
  todoListItems: Array<Object>;
}

class TodoList extends React.Component<IPageProps, State> {

  constructor(props: IPageProps) {
    super(props);
    const todoColumns: IColumn[] = [
      {
        key: 'col0',
        name: 'ID',
        fieldName: 'ID',
        minWidth: 20,
        maxWidth: 20,
        data: 'string',
        isPadded: true,
      },
      {
        key: 'col1',
        name: '件名',
        fieldName: 'Title',
        minWidth: 150,
        maxWidth: 150,
        isRowHeader: true,
        isResizable: true,
        data: 'string',
        isPadded: true,
      },
      {
        key: 'col2',
        name: '期限',
        fieldName: 'LimitDate',
        minWidth: 75,
        maxWidth: 75,
        isResizable: true,
        data: 'number',
        isPadded: true,
        isSorted: true,
        isSortedDescending: false,
        onRender: (item) => {
          return <span>{util.DateFormatJa(item.LimitDate)}</span>;
        },
      },
      {
        key: 'col3',
        name: 'メモ',
        fieldName: 'Note',
        minWidth: 200,
        maxWidth: 200,
        isResizable: true,
        data: 'string',
        isPadded: true,
        isMultiline: true
      },
      {
        key: 'col4',
        name: '最終更新',
        fieldName: 'Modified',
        minWidth: 75,
        maxWidth: 75,
        isResizable: true,
        data: 'string',
        isPadded: true,
        onRender: (item) => {
          return <span>{util.DateFormatJa(item.Modified)}</span>;
        },
      }
    ];

    this.state = {
      loading: false, todoColumns, todoListItems: []
    };
  }

  private GetTodoListItems = async () =>
    api.GetTodoListItems(this.setState.bind(this), this.props.todoListName, this.props.webPartContext)

  public componentDidMount() {
    this.GetTodoListItems();
  }

  public render() {
    const stackTokens: IStackTokens = { childrenGap: 5 };
    const { routeProps } = this.props;
    const { history } = routeProps;
    const { loading, todoColumns, todoListItems } = this.state;
    return (
      <div>
        <Stack tokens={stackTokens}>
          <Stack.Item align='end'>
            <PrimaryButton text='Reload' onClick={this.GetTodoListItems} />
          </Stack.Item>
          <Stack.Item align='auto'>
            {loading ? (
              <Loading />
            ) : (
                todoListItems.length > 0 ? (
                  <DetailsList items={todoListItems} columns={todoColumns} checkboxVisibility={CheckboxVisibility.hidden}
                    onActiveItemChanged={(item, i, e) =>
                      history.push({ pathname: "/TodoDetail/" + item.ID })} />
                ) : (
                    <><p>Good work! No more Todos.</p></>
                  )
              )}
          </Stack.Item>
        </Stack>
      </div>
    );
  }
}
export default TodoList;

Hosted workbench の実行で以下のように表示できていれば成功です。
アイテムをクリックすれば、別画面に遷移し、IDが表示されるようになっています。

image.png

以下に実際に動作させたファイルを置いておきますので、うまくいかない場合は参考にしてください。

https://github.com/7o83/SpfxTodo3to4

次回は作成、更新、削除機能付きの更新画面を作ります。

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

(Next.js) TypescriptファイルでCSS/SCSSファイルを読み込む方法

1. 問題

Next.jsプロジェクト内のtypescriptファイルでCSSファイルをimportで読み込もうとすると、エラーになります。

sample.ts
import style from "./styles.css"

//下記のようなエラーが出て読み込めない
TS2307: Cannot find module './styles.css

2.解決策

「next-env.d.ts」というファイルがあるはずなので、そこに下記のように追加で記述。
無事Next.jsプロジェクト内のtypescriptファイルでcss/scssが読み込めるようになります。

next-env.d.ts
declare module '*.css';
// or scss
declare module '*.scss';

参考
How to fix TypeScript cannot find module CSS/SCSS in Next.js

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

React入門 未経験から1週間でReactをマスターする #01. プロジェクトの作成と立ち上げ

この入門は、 Reactの技術要素を圧倒的効率で学ぶ ことを目的にしています。

Reactの技術要素を散りばめたWebアプリを1から作っていくことでReactを学んでいきます。
すべての動くコードを公開しています。

目次

1. Reactの新規プロジェクトの立ち上げ ←今ここ
2. コンポーネントの書き方とイベントハンドラ
3.Class Components と Function Components (近日公開予定)
4. 条件分岐 (if) と繰り返し (loop) (準備中)
5. フォームと親子間のデータのやり取り (準備中)
6. コンポーネントのライフサイクル (準備中)
7. スタイル (準備中)
8. Higher-Order Component (準備中)
9. Portalを利用したモーダル (準備中)
10. refによるエレメントの取得 (準備中)
11. Contextを利用したテーマの変更 (準備中)

この入門の特徴

  • ソースコードはすべてGitHubで公開
  • コミットがすべて入門にそっている
  • 文章と動画の同時解説
    • Qiita(文章)、YouTube(動画)、GitHub(ソースコード) と参考にできるものが充実
  • 動くコードを作り上げる入門
    • 機能ごとではなく、一つのWebサイトを作り上げながら入門

YouTubeでの解説動画

【YouTube動画】未経験から1週間でマスターするReact入門 #01 Reactの新規プロジェクトの作成と立ち上げ
Collation

この入門で学べること

Reactに関する機能を一通り学ぶことができます。
ただし、Next.js のようなReactを利用したフレームワークや API通信などReactのライブラリとは関係ない部分はこの入門には入っていません。

  1. Reactの新規プロジェクトの立ち上げ
  2. コンポーネントの書き方とイベントハンドラ
  3. Class Components と Function Components
  4. 条件分岐 (if)と繰り返し(loop)
  5. フォームと親子間のデータのやり取り
  6. コンポーネントのライフサイクル
  7. スタイル
  8. Higher-Order Component
  9. Portalを利用したモーダル
  10. refによるエレメントの取得
  11. Contextを利用したテーマの変更

この入門の最終作成物

この入門で作成する最終的な作成物は、Netlifyで公開しています。

https://nifty-bardeen-fa5775.netlify.app/

どういう方に役立つか

対象となる方

  • JavaScript, HTML, CSSがだいたいわかっている方
  • Reactを学んでみたい方
  • Reactを学びなおしたい方
  • Class Componentsしか使ったことがない方(Function Componentsを学びたい方)

対象とならない方

  • すでに「React完全に理解した」方
  • JavaScript, HTML, CSSの基礎がわからない方

Reactとは

  • Facebookが開発したJavaScriptのライブラリ
  • Component指向のライブラリ
  • React, Vue, Angularでは一番人気
  • JavaScriptでコードを書くのが好きな方におすすめ
  • Function Componentsのおかげでスッキリ書けるようになった

スクリーンショット 2020-10-31 1.23.23.png

2019年のnpmダウンロード数

開発前に必要なもの

本入門では下記コマンドを利用します。

  • node
  • yarn

mac なら下記でインストール可能。

brew install node
brew install yarn

ただし、nodeは nvm でバージョン管理するのがおすすめです。

注意

このあとプロジェクトを作っていきますが、Reactのバージョンアップなどにより、生成されるコードの内容が変更されることがあります。
生成されたコードが入門と違うコードになっている場合は、GitHubから clone してみてください。

https://github.com/yassun-youtube/ReactTutorial

Reactのプロジェクトの作成と立ち上げ

プロジェクトの作成

npxで簡単にプロジェクトを作成可能(nodeをインストールすれば入っているはず(npmバージョン5以上がインストールされていれば使える))

npx create-react-app react-tutorial

立ち上げ

プロジェクトのフォルダで下記コマンドを実行

yarn start

http://localhost:3000/ でブラウザが立ち上がる。

スクリーンショット 2020-10-31 1.41.03.png

ビルド

プロジェクトのフォルダで下記コマンドを実行

yarn build

build フォルダにビルドしたファイルが格納されます。

ここまでのコミット

ここまでのコミットの状態をGitHubでコミットにしています。

https://github.com/yassun-youtube/ReactTutorial/commit/be5ebc908f7861222c2c39f0f43300bc9c15d35e

フォルダ構成

作成されたフォルダを見ていきます。

スクリーンショット 2020-10-31 2.01.45.png

publicsrc フォルダがありますね。
public はビルド時にそのままコピーされるもの(index.htmlを除く)。
src はビルド対象になります。

srcフォルダの中身

今回の入門で触るファイルは、下記の4つです。

index.js
index.css
App.js
App.css

これらのファイルの関係性はこちらです。

index.js <-style- index.css
↓(呼ぶ)
App.js <-style- App.css

index.jsがこのプロジェクトのルートのファイルになっていて、その中からApp.jsが呼ばれています。

index.jsの中身

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

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root')
);

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

このファイルは、htmlとReactのコンポーネントをつなぐ部分を記述しています。
今回の入門ではこのファイルはそのまま利用しますので、修正する必要はありません。

内容としては、 root IDを持つエレメント (index.htmlに記載)にReactをレンダリングしているという内容です。

ReactDOM.render というのは、 Reactのコンポーネントを指定されたエレメントに埋め込むものです。
この render 関数の引数にHTMLのような何かが埋まっていて、始めて見る方は気持ち悪いと思ったかもしれません。

この記法を JSX といいます。Reactでは、JSXの記法を利用します。
JSXでは、JSの中にHTMLのような木構造を埋め込むことができます。

AngularやVueはHTMLの中にロジックを書くようにしているのに対し、Reactはこの文法を使ってJSの中にHTMLを埋め込む方向に行ってるんですね。

React.StrictMode は、開発用に deprecated な書き方などをしていると警告を出してくれるものだそうです。
その中で App コンポーネントを入れていて、そこがメインの表示になります。

ここで難しいと思った方、この当たりのコードは実際の開発ではほとんど触ることはないので、この当たりは理解する必要はありません!
実際にはこのあとから出てくるコードが一番開発で使うことになります。

App.jsの中身

ここから本格的にReactのコードを見ていきます。

src/App.js
import logo from './logo.svg';
import './App.css';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

こちらがApp.jsの中身になります。
Class Components のコードしか見たことない人は、あれ?なにこれ、と思っている方もいるかもしれません。
これは Function Components の書き方で、関数がReactのコンポーネントになっています。

このコードは非常にシンプルで、ただ HTMLのような何かを返している関数を定義するだけで、このHTMLを表示してくれる、というものになります。

HTMLとの違いを見てみると、 classclassName になっていますね。
あと src={logo} のように属性が {} で値を入力されているものがあることがわかります。

Reactの書き方では、 classclassName という属性になります。
また、基本的に onclick , tabindex などの2つの文字がくっついているような属性要素は onClicktabIndex のように camelCase に変換されています。
属性を扱うときは、この当たりを意識して利用してください。

HTMLとJSX内でのHTMLの違い

  • class属性が className となる
  • {} で JavaScriptの要素を埋め込むことができる
  • onclickなどのように2word(on click)の属性は onClick とcamelCaseになる

TIPS {} と ""

src={logo} のように {} を利用する部分は、 {} の中にJSの値を直接入れることができます。
この場合は、 logosvgimport しているので svg のデータをソースに入れているということになります。
文字列を入れる場合は、どちらでも同じように動きますが、 {} 内で文字列を入れると自動的にエスケープされるようです。

<MyComponent message="&lt;3" />
// 同じ↓
<MyComponent message={'<3'} />

少し変更してみる

yarn start で立ち上げたWebページは、コードを変更すると自動で変更を検知して更新してくれます。
実際にやってみましょう。

Learn React

の部分を

Learn React from やっすん

としてみます。

src/App.js
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React from やっすん
        </a>

すると、変更されました。

スクリーンショット 2020-10-31 2.56.35.png

ここまでのコミット

この変更をした際のコミットは以下です。参考にしてください。

https://github.com/yassun-youtube/ReactTutorial/commit/d40a8e08631a2febbfe7d3bdda2cc1f5d71cc6a4

次に備えて現状のコードを綺麗にする

今のコードは不要なので、コードを消して綺麗な状態にしようと思います。
まず、App.jsを変更します。

src/App.js
function App() {
  return (
    <div>
      ゼロから始めるReact入門
    </div>
  );
}

export default App;

また、下記CSSも使わないので削除します。

src/App.css # 削除

Webページからロゴが消えて文字列のみになったことがわかります。

スクリーンショット 2020-10-31 3.07.43.png

これで自分で作っていく準備が整いました。
次回は、ここにコードを追加していきます。

今日のまとめ

Reactのプロジェクトの作成と立ち上げ

npx create-react-app <directory>

yarn start

フォルダ構成

  • publicはビルド時にコピーされる
  • srcはビルド時にまとめられる => 重要なのはsrcフォルダ

JSXの基礎

JavaScriptにHTMLのようなものを入れ込んだ記法

  • class => className
  • {} を使うことでJavaScriptの要素を代入できる
  • onclick のような2つの言葉の属性は onClick のようにcamelCaseになる。

コードを少し修正

  • 修正はリアルタイムに反映される
  • 最初からある記述を削除してキレイにした

おわりに

今回はプロジェクトの作成、立ち上げをして、簡単な修正をしました。
次から本格的にReactのコードを書いていきます。

ではまた!

追記

続きはこちらです。
2. コンポーネントの書き方とイベントハンドラ

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

Gatsbyスターターを使ってリンク集をつくる

前回に引き続きGatsbyの報告です

今回完成したサイトはこちら「パソコンのべんきょうのためのリンク」

前回作ったサイト

前回もリンク集を作りました。その時の記事が次です。
「Gatsby -> github -> netlifyでリンク集をデプロイしました」
その続きで、さらにちょっといいやつを作れそうだったので、
お助けを受けながらやってみることにしました。

前回オッケー?のところ

  • 私の初デプロイ!(railsチュートリアルは除く)
  • HTML CSSだけでつくったことはあるが、素人感満載でもやもやだったが、今回はちょっと動きのあるものになっていい感じ!

次の課題

  • 学校の環境で開くと、どうやってもレイアウト崩れる
  • 画像がないので、できたら入れたい。

over40web club勉強会

で、@pitangさんのアドバイスで、画像もあつかえる新たなスターターを使用して、内容はそのままで新しいサイトを作ることにしました。そしてまた、over40WebClub で勉強会をしてもらい、サイトデプロイに向けてスタートしました。

手順

1. Gatsbyを使えるようにするための準備

  1. node.jsを環境にインストールしておく必要もあります。こちらを参考にしました。
  2. GatsbyCLIのインストール

2. スターターを選んでインストール

今回使うスターターはこれ。
gatsby-airtable-listing

スクリーンショット 2020-10-27 19.31.40.png

AirTableというデータベース的なウェブサービスと連携して、サイトを構築するスターターです。初心者に優しいGUI操作ができて、しかも、リレーショナルなものも作れて、無料という素敵なサービスです。もちろん、ある程度の規模で使うなら有料プランということになりますが、ぼくが作ろうとするくらいなら無料プランで十分な感じです。

コマンドラインからインストールします!

$ gatsby new my-gatsby-project https://github.com/wkocjan/gatsby-airtable-listing

my-gatsby-projectのところは自分のプロジェクト名にします。

前回のスターターはこの段階でサーバーを起こせば、サンプルのサイトが表示されたので、
ここで、調子に乗って gatsby develop をして、localhost:8000を表示しましたが、エラー・・・。

3. AirTableとの連携設定

AirTableとの連携の設定をしていなかったので、データベースにつなげませんというエラーでした。スターターのREADMEに説明が書かれていたので、それを読みつつ(英語で時間がかかる)すすめました。

1. AirTableアカウントを作成します

2. 用意してくれているデータベースをコピーして自分のアカウントにいただきます

  1. README.md内 ### Create Airtable baseのところにあるリンクにとびます
  2. 画面右上の’CopyBase’をクリック
  3. これで、自分のAirTableアカウントのところに、データベースができました

3. .envファイル API設定編集

1.アプリのルートフォルダにある .env.example をコピーしてファイル名を.envに変更
2. API_KEYとBASED_IDは、データベースを表示している画面右上のHelpからAPIDocumentationのところへ行くと見ることができます。ちょっとさがすのに苦労をしました。
3. BASED_ID API DocumentationーINTRODUCTIONのところにありました
4. API_KEY  API DocumentationーAUTHENTICATION さらに、accountページへとびました。結局、自分のアカウントページへ行くと見ることができるということでした。

env.example
# Airtable API Key
AIRTABLE_API_KEY=""
AIRTABLE_BASE_ID=""
AIRTABLE_TABLE_NAME="Destinations" 

とりあえずこれでAirTableとの連携が完了しました。
gatsby develop して、localhost:8000でめでたくサンプルサイトと同じように表示されました!

4. 学習リンク集用にカスタマイズ

編集したところは以下の通り。
reactのことやjavascriptを十分理解していないので、
コードを見ながら「ここかなあ」と予想したり、
over40WebClub勉強会やその後もslackで教えてもらったりしながら、
なんとかカスタマイズしました。

  1. タイトルなどのメタデータ
  2. アイコン、ヘッダー画像などの変更
  3. カード、カードからとんでいくモーダルウィンドウのラベル
  4. カードからサイトへの直接リンク作成

1. タイトルなどのメタデータ

src/pages/index.jsxを触ります。

      <SiteMetadata
        title="Travel destinations"
        description="Check the most popular travel destinations in Europe."
        image={data.hero.url}
      />

      <Hero
        image={data.hero}
        tag="#travel"
        title="Travel destinations"
        description="Check the most popular travel locations in Europe."
      />

title,description,tagなどの中身を書き換えました。

2. アイコン、ヘッダー画像などの変更

アイコン画像は、imagesフォルダ内においておきます

gatsby-config.jsの中、gatsby-plugin-manifestのoptions内、

icon:の値を、自分のアイコン画像のパス・ファイル名にします。
nameとか、shotr_nameとかも、自分の目的にあった名前にかえました。
この辺はなんとなくです。

{
      resolve: `gatsby-plugin-manifest`,
      options: {
        name: `Son Hiroki Hong-su`,
        short_name: `Son Hiroki Hong-su`, 
        start_url: `/`,
        background_color: `#fff`,
        theme_color: `#4299e1`,
        display: `standalone`,
        icon: `src/images/icon-son.png`,
      },
    },

ヘッダー画像は、pages/index.isxHero.jsxをさわりました。

pages/index.jsx の最後の方
export const query = graphql`
  query IndexQuery($tableName: String!) {
    hero: file(relativePath: { eq: "tm-image.jpg" }) {
      ...HeroImageFragment
    }

imagesフォルダ内に使いたいヘッダ画像を入れておいて、
貼り付けた部分の3行目あたりのファイル名を変更します。
hero: file(relativePath: { eq: "tm-image.jpg" }) {

実際のコンポーネントはHero.jsxで、そこでgraphqlをimportして、この画像を使っているという感じでしょうか。

Hero.jsx
  • 実際の表示部分
<Img
  alt={title}
  //className="grayscale-1 blend-multiply"
  fadeIn={false}
  fixed={[
  { ...image.childImageSharp.desktop, media: `(min-width: 768px)` },
  { ...image.childImageSharp.mobile, media: `(max-width: 767px)` },
  ]}
/>

この部分のclassName="grayscale-1 blend-multiply"のところは無効にしました。
たぶん、tailwindcssというやつで、画像に暗い加工が施されていたと思われます。

  • export constのところ

ヘッダー画像がどうしても勝手にトリミングされてしまって
上の部分がきれてしまうという問題があって、なかなかわかりませんでした。
どうやらGatsby-imageというプラグインの具合で
勝手にそうなることまではわかったのですが、自力ではそこまで。
いつものように@pitangさんに教えてもらって、
画像のトリミング具合を以下の部分で調整。desktop: の heightを変更しました。

export const query = graphql`
  fragment HeroImageFragment on File {
    url: publicURL
    childImageSharp {
      mobile: fixed(width: 768, height: 240, quality: 80, cropFocus: CENTER) {
        ...GatsbyImageSharpFixed_withWebp
      }
      desktop: fixed(width: 1248, height: 500, quality: 85, cropFocus: SOUTH) {
        ...GatsbyImageSharpFixed_withWebp
      }
    }
  }
`

カード一覧のラベル変更

card.jsx
もとになったサイトで旅行先の国名表示のところには
リンクの紹介者名を入れていたので、
そのラベルを「しょうかいした人」に変更しました。

カードが一覧表示されているところから、サイトへのリンクを作りました。

こちらも、まずは card.jsx
「データベースのこのカラムをとってきて」という命令が書いているのだと思いますが
もとのサイトではカード一覧にurlカラムは必要ないので、書かれていなかったところへurlを追加。

export const Card = props => {
  const {

    image: {
      localFiles: [cover],
    },
    name,
    navigation,
    slug,
    summary,
    country,
    url,
  } = props

この変更だけではエラーとなってしまいました。
pages/index.jsxの
export const query = graphqlの設定にも
urlカラムを追加しました。
これも、自力で解決したのではなく
Over40WebClubの勉強会(という名の個別指導)で
解決!

5 できあがり

カカスタマイズしたのはたぶんこのくらいのはず・・・。
なにか、認識が違うことや、まちがいがあったらコメントなどくれるとうれしいです!

deployはnetlifyを使いました。
githubとの連携で、git pushすれば変更が反映されます。
ただ、データベースを更新したときには、git status に変更がなくcommit,pushできなくてどうするか困りました。
netlifyCLIをインストールして、ターミナルからnetlify deploy --prodでOKとわかりました。

前回に続いて2つ目のデプロイは自分的にも満足度の高いものでした!

プログラミング学習をすすめるにあたってオンラインコミュニティは強い味方だと実感しています!

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