- 投稿日:2020-11-08T23:28:28+09:00
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') );実行結果
おまけ
名前の入力が空白だった場合のクラッシュ湯婆婆は入室しませんでした!
SPA なので名前を言い終える前に湯婆婆は名前を奪い始めるという。早とちり湯婆婆降臨。最後に
ここまで読んでいただきありがとうございました!
- 投稿日:2020-11-08T15:38:02+09:00
Reactでログイン機能を実装する
はじめに
react-router-domでPriateRouteを実現します。今回は以下の機能を実装していきたいと思います。
- ログイン済みユーザーしかアクセスできないページを作る
- ↑のページに、ログインせずにアクセスしようとするとログインページにリダイレクトされる。
- 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-domPrivateRouteを実装する
今回の肝となるPrivateRouteの実装です。(
useAuthUser
,AuthUserProvider
については別ファイルで定義しています。詳しい実装はGithubをご覧ください)App.tsximport 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.tsximport 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`へリダイレクトされるべきです。そこで、
PrivateRoute
、UnAuthRoute
を以下のように修正します。App.tsxconst 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
を追加するなどで応用できます。
- 投稿日:2020-11-08T12:52:12+09:00
SharePoint Framework で TODO管理アプリを作る (3/4) 一覧画面の作成
続きです。
今回は SharePoint Online のデータへのアクセスが含まれるので、SPFxっぽいかもです。
毎度ながら内容に関して React も TypeScript もあまり上手に使えるわけではないですので、そのあたりご容赦ください。。。完成イメージ(4/4)
下は画像が荒いですね。
来週の Global Microsoft 365 Developer Bootcamp 前に SharePoint Framework を予習しよう!Teams のタブにもなるよ!
— nan_oka (@nanoka7o8) November 6, 2020
Qiita用にツイート。#OfficeDev #SharePointOnline #SharePointFramework #MicrosoftTeams pic.twitter.com/m4oSyYCAP3もちろん SharePoint Online のページとしても使えます。
関連記事
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 でデータソースの作成
今回は一覧画面を作成するのですが、そこに表示するデータを用意します。
以下のようなカスタムリストを作成し、適当なデータを準備します。
- 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 とのやり取りで発生するエラーチェックなどもここで一元的に行います。
引用ばかりで恐縮ですが、以下を参考にされるといいと思います。api/index.tsimport { 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.tsximport * 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.tsexport 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... と一瞬表示され、画面が更新されているのがわかります。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.tsximport * 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が表示されるようになっています。以下に実際に動作させたファイルを置いておきますので、うまくいかない場合は参考にしてください。
次回は作成、更新、削除機能付きの更新画面を作ります。
- 投稿日:2020-11-08T12:21:15+09:00
(Next.js) TypescriptファイルでCSS/SCSSファイルを読み込む方法
1. 問題
Next.jsプロジェクト内のtypescriptファイルでCSSファイルをimportで読み込もうとすると、エラーになります。
sample.tsimport style from "./styles.css" //下記のようなエラーが出て読み込めない TS2307: Cannot find module './styles.css2.解決策
「next-env.d.ts」というファイルがあるはずなので、そこに下記のように追加で記述。
無事Next.jsプロジェクト内のtypescriptファイルでcss/scssが読み込めるようになります。next-env.d.tsdeclare module '*.css'; // or scss declare module '*.scss';参考
How to fix TypeScript cannot find module CSS/SCSS in Next.js
- 投稿日:2020-11-08T08:24:50+09:00
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で公開
- GitHubですべてのコードを公開しているので、動かなくなっても比較しながら学べます。
- https://github.com/yassun-youtube/ReactTutorial
- コミットがすべて入門にそっている
- 文章と動画の同時解説
- Qiita(文章)、YouTube(動画)、GitHub(ソースコード) と参考にできるものが充実
- 動くコードを作り上げる入門
- 機能ごとではなく、一つのWebサイトを作り上げながら入門
YouTubeでの解説動画
【YouTube動画】未経験から1週間でマスターするReact入門 #01 Reactの新規プロジェクトの作成と立ち上げ
この入門で学べること
Reactに関する機能を一通り学ぶことができます。
ただし、Next.js のようなReactを利用したフレームワークや API通信などReactのライブラリとは関係ない部分はこの入門には入っていません。
- Reactの新規プロジェクトの立ち上げ
- コンポーネントの書き方とイベントハンドラ
- Class Components と Function Components
- 条件分岐 (if)と繰り返し(loop)
- フォームと親子間のデータのやり取り
- コンポーネントのライフサイクル
- スタイル
- Higher-Order Component
- Portalを利用したモーダル
- refによるエレメントの取得
- 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のおかげでスッキリ書けるようになった
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/
でブラウザが立ち上がる。ビルド
プロジェクトのフォルダで下記コマンドを実行
yarn build
build
フォルダにビルドしたファイルが格納されます。ここまでのコミット
ここまでのコミットの状態をGitHubでコミットにしています。
https://github.com/yassun-youtube/ReactTutorial/commit/be5ebc908f7861222c2c39f0f43300bc9c15d35e
フォルダ構成
作成されたフォルダを見ていきます。
public
とsrc
フォルダがありますね。
public
はビルド時にそのままコピーされるもの(index.htmlを除く)。
src
はビルド対象になります。srcフォルダの中身
今回の入門で触るファイルは、下記の4つです。
index.js index.css App.js App.cssこれらのファイルの関係性はこちらです。
index.js <-style- index.css ↓(呼ぶ) App.js <-style- App.cssindex.jsがこのプロジェクトのルートのファイルになっていて、その中からApp.jsが呼ばれています。
index.jsの中身
src/index.jsimport 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.jsimport 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との違いを見てみると、
class
がclassName
になっていますね。
あとsrc={logo}
のように属性が{}
で値を入力されているものがあることがわかります。Reactの書き方では、
class
はclassName
という属性になります。
また、基本的にonclick
,tabindex
などの2つの文字がくっついているような属性要素はonClick
やtabIndex
のようにcamelCase
に変換されています。
属性を扱うときは、この当たりを意識して利用してください。HTMLとJSX内でのHTMLの違い
- class属性が className となる
- {} で JavaScriptの要素を埋め込むことができる
- onclickなどのように2word(on click)の属性は onClick とcamelCaseになる
TIPS {} と ""
src={logo}
のように{}
を利用する部分は、{}
の中にJSの値を直接入れることができます。
この場合は、logo
はsvg
をimport
しているのでsvg
のデータをソースに入れているということになります。
文字列を入れる場合は、どちらでも同じように動きますが、{}
内で文字列を入れると自動的にエスケープされるようです。<MyComponent message="<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>すると、変更されました。
ここまでのコミット
この変更をした際のコミットは以下です。参考にしてください。
https://github.com/yassun-youtube/ReactTutorial/commit/d40a8e08631a2febbfe7d3bdda2cc1f5d71cc6a4
次に備えて現状のコードを綺麗にする
今のコードは不要なので、コードを消して綺麗な状態にしようと思います。
まず、App.jsを変更します。src/App.jsfunction App() { return ( <div> ゼロから始めるReact入門 </div> ); } export default App;また、下記CSSも使わないので削除します。
src/App.css # 削除Webページからロゴが消えて文字列のみになったことがわかります。
これで自分で作っていく準備が整いました。
次回は、ここにコードを追加していきます。今日のまとめ
Reactのプロジェクトの作成と立ち上げ
npx create-react-app <directory> yarn startフォルダ構成
- publicはビルド時にコピーされる
- srcはビルド時にまとめられる => 重要なのはsrcフォルダ
JSXの基礎
JavaScriptにHTMLのようなものを入れ込んだ記法
- class => className
- {} を使うことでJavaScriptの要素を代入できる
- onclick のような2つの言葉の属性は onClick のようにcamelCaseになる。
コードを少し修正
- 修正はリアルタイムに反映される
- 最初からある記述を削除してキレイにした
おわりに
今回はプロジェクトの作成、立ち上げをして、簡単な修正をしました。
次から本格的にReactのコードを書いていきます。ではまた!
追記
続きはこちらです。
2. コンポーネントの書き方とイベントハンドラ
- 投稿日:2020-11-08T07:50:31+09:00
Gatsbyスターターを使ってリンク集をつくる
前回に引き続きGatsbyの報告です
今回完成したサイトはこちら「パソコンのべんきょうのためのリンク」
前回作ったサイト
前回もリンク集を作りました。その時の記事が次です。
「Gatsby -> github -> netlifyでリンク集をデプロイしました」
その続きで、さらにちょっといいやつを作れそうだったので、
お助けを受けながらやってみることにしました。前回オッケー?のところ
- 私の初デプロイ!(railsチュートリアルは除く)
- HTML CSSだけでつくったことはあるが、素人感満載でもやもやだったが、今回はちょっと動きのあるものになっていい感じ!
次の課題
- 学校の環境で開くと、どうやってもレイアウト崩れる
- 画像がないので、できたら入れたい。
over40web club勉強会
で、@pitangさんのアドバイスで、画像もあつかえる新たなスターターを使用して、内容はそのままで新しいサイトを作ることにしました。そしてまた、over40WebClub で勉強会をしてもらい、サイトデプロイに向けてスタートしました。
手順
1. Gatsbyを使えるようにするための準備
- node.jsを環境にインストールしておく必要もあります。こちらを参考にしました。
- GatsbyCLIのインストール
2. スターターを選んでインストール
今回使うスターターはこれ。
gatsby-airtable-listingAirTableというデータベース的なウェブサービスと連携して、サイトを構築するスターターです。初心者に優しいGUI操作ができて、しかも、リレーショナルなものも作れて、無料という素敵なサービスです。もちろん、ある程度の規模で使うなら有料プランということになりますが、ぼくが作ろうとするくらいなら無料プランで十分な感じです。
コマンドラインからインストールします!
$ gatsby new my-gatsby-project https://github.com/wkocjan/gatsby-airtable-listingmy-gatsby-projectのところは自分のプロジェクト名にします。
前回のスターターはこの段階でサーバーを起こせば、サンプルのサイトが表示されたので、
ここで、調子に乗って gatsby develop をして、localhost:8000を表示しましたが、エラー・・・。3. AirTableとの連携設定
AirTableとの連携の設定をしていなかったので、データベースにつなげませんというエラーでした。スターターのREADMEに説明が書かれていたので、それを読みつつ(英語で時間がかかる)すすめました。
1. AirTableアカウントを作成します
2. 用意してくれているデータベースをコピーして自分のアカウントにいただきます
- README.md内 ### Create Airtable baseのところにあるリンクにとびます
- 画面右上の’CopyBase’をクリック
- これで、自分の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. タイトルなどのメタデータ
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.isx
とHero.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つ目のデプロイは自分的にも満足度の高いものでした!
プログラミング学習をすすめるにあたってオンラインコミュニティは強い味方だと実感しています!