20210128のReactに関する記事は8件です。

mapについて

javascriptのmapについて理解不足だったので、まとめてみます。

mapとは

配列内の要素をコールバックで処理(加工)して、配列としてreturnするメソッド。

index.js
const names = ["田中", "山田", "佐藤", "鈴木"];

const members = map((secondName, index) => {
return `${index + 1}番目は${secondName}です`
});

console.log(members);

出力結果
["1番目は田中です。","2番目は山田です。","3番目は佐藤です。","4番目は鈴木です。"]

第一引数secondNameはnameで定義した配列。
第二引数はindexは配列membersの各要素のインデックス番号となります。

終わりに

filterも後々まとめたいと思います。

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

React入門!!の前に知っておきたいNode.jsとJavaScriptの知識

対象読者とこの記事から何が得られるのか

そろそろReactの勉強をはじめてみたい、勉強したいと思っているけど、以下のようなことを考えて前に進めない方向けの記事です。

  • JavaScriptの知識レベルはこれくらいで足るのか?
  • 他に知っておくことはないのかな?

この記事を読むことでReact入門の前に知っておきたいNode.jsとJavaScriptの知識を得ることができると思います。

Node.jsをざっと知る

Node.jsを簡単に言うと

JavaScriptをブラウザではなくPythonやRubyなどと同じようにターミナル上で動かすことができるようにするための実行環境のことであり、バックエンド言語としてJavaScriptを使うことができ、ファイル操作などのOSの機能にアクセスできます。

公式ページだと以下のように記載されています。

Node.js はスケーラブルなネットワークアプリケーションを構築するために設計された非同期型のイベント駆動の JavaScript 環境です。

Node.jsが注目を浴びたのはC10K問題の解決策である(イベント駆動により大量のリクエストを同時に処理できるスケーラビリティを備えている・ノンブロッキングI/Oモデルを採用しており、I/Oの処理を待たずに次の処理を始めることができるので、大量のデータ処理が可能)というのが理由です。またバックエンドも同じ言語(JavaScript)で書けたら効率的ではないかというのも理由の一つです。フロントエンド開発はJavaScriptほぼ一択ということもあり、こういう流れになったのではないかと思います。

なぜReactアプリ開発でNode.jsが必要なのか

ではなぜバックエンド開発のために開発されたNode.jsがReactアプリ(フロントエンド)開発で必要になってきたのでしょうか。

Reactのような大規模なアプリケーションを開発するとなると、様々なパッケージが必要になり、そのパッケージたちが特定のバージョンで依存しあっています。Node.jsはそのパッケージのインストールと整合性の管理を解決するために必要であり、npm(Node Package Manager)がその解決を担っています。

もともとはバックエンド開発のためにnpmは使われるパッケージ管理システムであったが、フロントエンド用のパッケージを提供するのにも使われるようになり、最近では使用用途としてはフロントエンドのほうが多くなってきているそうです。

その他以下のような用途でも使われます。

  • JavaScriptやCSSファイルをバンドルできる
  • ブラウザ実行時にpolyfill※するのではなく 最初からコンパイルしておける
  • ローカル環境で開発用のHTTPサーバを起動してアプリケーションを稼働させることができる
  • ユニットテストやE2Eテストなどのテストを実行できる
  • ローカル環境で構文解析等実行できる

※polyfillとは最近の機能をサポートしていない古いブラウザーで、その機能を使えるようにするためのコードです。

Node.jsをインストール

公式サイトからインストールしたり、MacならHomebrew・Windowsならwingetなどでインストールできますが、プロジェクトごとに異なるバージョンの環境を共存させる必要があるので、バージョンマネージャー(nvmやnodenv)を使ってインストールしましょう。

Node.jsでプロジェクト作成

作成したいディレクトリに移動してターミナルで以下コマンドでプロジェクトを作成できます。

対話形式でプロジェクト名など聞かれますので、全て入力するとpackage.jsonが作成されます。

$ npm init

-yをつければ、全てデフォルトのままプロジェクト作成できます。

$ npm init -y

npm-scripts

package.jsonのscriptsに記載されているコマンドを以下で実行できます。

$ npm run xxx

では試しにnpm-scriptsでプログラムを実行してみましょう。
まずindex.jsを作成する。

index.js
console.log("Hello World!!");

package.jsonにstartでのコマンドを追加する。
node.jsではnode+ファイル名 でそのファイルのプログラムを実行できます。

"scripts": {
  "start": "node index.js"
}

以下コマンドで実行する。

$ npm run start

以下出力されれば成功です。

Hello World!!

start,stop,restart,testの場合はrunを省略して実行できます。

$ npm start

Reactアプリ開発ではcreate-react-appというコマンドで新規プロジェクトが作成できますが、こちらで作られたプロジェクトだと以下が記載されています。たとえばnpm run startを実行するとreact-scripts startが実行され、ローカル環境でReactアプリが起動できます。react-scriptsはcreate-react-appで作られたプロジェクトの中でBabelやwebpackなどが裏で動かすことができます。

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "react-scripts test",
  "eject": "react-scripts eject"
},

Yarnとは

npmコマンドのFacebookによる改良版で記述が短い・実行が速いなどのメリットがある。
Yarnがグローバルインストールされていた場合はCreate React App使用時にyarnがデフォルトになるので、npmコマンドを使いたいなら--use-npmオプションを指定しましょう。

Yarnのコマンド

  • yarn (install) はプロジェクトのルートディレクトリに存在するpackage.jsonの内容を参照して、依存関係のあるパッケージをすべてインストールする
  • yarn add xxx で指定したパッケージをインストールする
  • yarn remove xxx で指定したパッケージをアンインストールする
  • yarn upgrade xxx で指定したパッケージを最新バージョンに更新する
  • yarn info xxx で指定したパッケージの情報を表示する

上記のnpm run xxxは以下コマンドで同じことができます。

yarn xxx

JavaScriptをざっと知る

変数宣言について

  • varは再宣言も再代入も可。スコープはブロックをすり抜ける。
  • letは再代入のみ可。スコープはブロック内。
  • constはどちらも不可。スコープはブロック内。

constを第一選択肢として、どうしても仕方ない場合のみletを使う。
varは使用しないようにしましょう。

var name1 = "瀬戸熊";
name1 = "佐々木"      // 再代入可
var name1 = "滝沢"    // 再宣言可

let name2 = "瀬戸熊";
name2 = "佐々木"      // 再代入可
let name2 = "滝沢"    // 再宣言不可:'name2' has already been declared

const name3 = "瀬戸熊";
name3 = "佐々木"      // 再代入不可:"name3" is read-only
const name3 = "滝沢"  // 再宣言不可:'name3' has already been declared

スコープに関してもvarを使うと以下のようになり、安全ではないのでletやconstを使うべきである。

var name1 = "高宮";
if (true) {
  var name1 = "二階堂";
  var name2 = "魚谷";
  console.log(name1); // 二階堂
  console.log(name2); // 魚谷
}
console.log(name1); // 二階堂(ブロック内でも書き換え可能なので危険)
console.log(name2); // 魚谷(ブロック内で定義したものがブロック外でも参照できる)

letだとブロック内で書き換えや定義しても、ブロック外に影響を与えない。

let name1 = "高宮";
if (true) {
  let name1 = "二階堂";
  let name2 = "魚谷";
  console.log(name1); // 二階堂
  console.log(name2); // 魚谷
}
console.log(name1); // 高宮
console.log(name2); // name2 is not defined

データ型について

プリミティブ型

以下7種類ある。

  • Boolean型
  • Number型
  • BigInt型
  • String型
  • Symbol型
  • Null型
  • Undefined型

falsyな値

MDNによると、falsyとは偽値 (falsy または falsey な値) は、 Boolean コンテキストに現れたときに偽とみなされる値です。

以下8種類あります。

  • false
  • 0
  • -0
  • 0n
  • Nan
  • ""(空文字)
  • null
  • undefined

以下はifブロックを実行しません。

if (false){}
if (null){}
if (undefined){}
if (0){}
if (-0){}
if (0n){}
if (NaN){}
if (""){}

関数

関数宣言文と関数式

定義の方法は関数宣言文による定義と関数式による定義があるがconstを使った関数式による定義が推奨されています。
無名関数は定義時に名前を与えられない関数のことで、関数式は変数に無名関数を入れているようなもの。
変数に関数式を代入することになるので、関数宣言文と違って先に定義しておかないと使えません。

// 関数宣言文による定義
function Introduce1(name) {
  return `私の名前は${name}です。`
} 
// 関数式による定義 
const Introduce2 = function (name) {
  return `私の名前は${name}です。`
};

アロー関数

アロー関数は引数が一つだと括弧の省略ができる。(推奨はされていないらしいが・・・)
また、retern文が1行だとreturnも省略できます。

// アロー関数式
const Introduce3 = (name) => {
  return `私の名前は${name}です。`
};
// アロー関数式、さらに省略記法
const Introduce4 = name => `私の名前は${name}です。`;

デフォルト引数

デフォルト値が設定された引数は省略が可能。
以下の例では第二引数を省略するとageが18で代用される。

const Introduce5 = (name, age = 18) => `私の名前は${name}です。${age}歳です。`;
console.log(Introduce5("白鳥", 32)); // 私の名前は白鳥です。32歳です。
console.log(Introduce5("松本")); // 私の名前は松本です。18歳です。

Rest Parameters

最後の引数に...を付けることで残りの引数を配列として受け取れる。

const Names = (name1, name2, ...rest) => {
  console.log(name1);   // 瀬戸熊
  console.log(name2);   // 佐々木
  console.log(rest);    // ["滝沢","二階堂","高宮"]
};
Names('瀬戸熊', '佐々木', '滝沢', '二階堂', '高宮');

Reactで開発する上で良く使う構文

プロパティ名のショートハンド

プロパティのキー名と値を同じにする。

const name = "瀬戸熊";
const obj = { name };  // = const obj = { name: name };
console.log(name); // { name: "瀬戸熊" }

分割代入

配列とオブジェクトの値を分割して代入する。

const data = [180, 70]
const [height, weight] = data;
console.log(`私の身長は${height}cmで、体重は${weight}kgです。 `); // 私の身長は180cmで、体重は70kgです。

const user = { name: "瀬戸熊", age: 50 };
const { name, age } = user;
console.log(`私の名前は${name}です。${age}歳です。`); // 私の名前は瀬戸熊です。50歳です。

スプレッド構文

const names1 = ["瀬戸熊", "佐々木", "滝沢"];
const names2 = [...names1, "二階堂", "高宮"];
console.log(names2); // [ "瀬戸熊","佐々木","滝沢","二階堂","高宮" ]

const users1 = { name: "瀬戸熊", age: 48, sex: "" };
const users2 = { ...users1, group: "renmei", grade: "A1" };
console.log(users2); // { name: "瀬戸熊", age: 50, sex: "男", group: "renmei", grade: "A1" }

オブジェクトのコピー

分割代入を用いてオブジェクトをコピーできます。

しかし、こちらはシャローコピーと言ってオブジェクトの深さが1段階までしか有効ではない。

オブジェクトの深さが2段階目の値を変更すると、コピー元の値まで変更されてしまう。

const user1 = { name: "瀬戸熊", age: 50, sex: "" };
const user2 = { ...user1 };
console.log(user2); // { name: "瀬戸熊", age: 50, sex: "男" }
console.log(user2 === users1); // false

const user3 = { name: "瀬戸熊", age: 50, sex: "", group: { group: "連盟", grade: "A1" } };
const user4 = { ...user3 };
console.log(user4);  // user4:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }
user4.group.group = "協会";
console.log(user3);  // user3:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }
console.log(user4);  // user4:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }

完全なコピー(ディープコピー)の方法はいくつかあるが、JSONパースを用いる方法は以下の通り。

プロパティにDateオブジェクトや関数が入ってた場合はうまく動かないので注意。

const user3 = { name: "瀬戸熊", age: 50, sex: "", group: { group: "連盟", grade: "A1" } };
const user4 = JSON.parse(JSON.stringify(user3));
console.log(user4);  // user4:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }
user4.group.group = "協会";
console.log(user3);  // user3:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '連盟', grade: 'A1' } }
console.log(user4);  // user4:  { name: '瀬戸熊', age: 50, sex: '男', group: { group: '協会', grade: 'A1' } }

他にはLodashのcloneDeep()を使う方法などある。

ショートサーキット評価(短絡評価)

  • || は左辺がfalsyな値だと評価が右辺に渡される。
  • ?? は左辺がnullかundefinedだと評価が右辺に渡される。
  • && は左辺がtruthyな値だと評価が右辺に渡される

|| はちょっと前に賛否ありましたが、一応よくでてくるので記載しておきます・・・。

const name1 = '瀬戸熊';
const name2 = '佐々木';

true && console.log(name1); // 瀬戸熊
false && console.log(name1); // 出力なし
true || console.log(name2); // 出力なし
false || console.log(name2); // 佐々木
null ?? console.log(name2); // 佐々木
undifined ?? console.log(name2); // 佐々木

配列・オブジェクトの処理

map,filter,find,findIndex,every,some

const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(dataset.map((data) => data * 3)); // [ 3, 6, 9, 12, 15, 18, 21, 24, 27 ]
console.log(dataset.filter((data) => data > 5)); // [ 6, 7, 8, 9 ]
console.log(dataset.find((data) => data > 5)); // 6
console.log(dataset.findIndex((data) => data > 5)); // 5
console.log(dataset.every((data) => data > 5)); // false
console.log(dataset.some((data) => data > 5)); // true
  • map():与えられた関数を配列のすべての要素に対して呼び出し、その結果からなる新しい配列を生成する。
  • filter():与えられた関数によって実装されたテストに合格したすべての配列からなる新しい配列を生成する。
  • find():提供されたテスト関数を満たす配列内の 最初の要素の値を返します。見つからなかった場合はundefinedを返す。
  • findIndex():配列内の指定されたテスト関数を満たす最初の要素の位置を返します。テスト関数を満たす要素がない場合を含め、それ以外の場合は-1 を返します。
  • every():配列内のすべての要素が指定された関数で実装されたテストに合格するかどうかを真偽値で返します。
  • some():配列の少なくとも一つの要素が、指定された関数で実装されたテストに合格するかどうかを真偽値で返します。

reduce,sort

  • reduce():配列の各要素に対して (引数で与えられた) reducer関数を実行して、単一の出力値を生成します。
  • sort(): 配列の要素をソートします。既定のソート順は昇順で、要素を文字列に変換してから、UTF-16コード単位の値の並びとして比較します。
const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9];
console.log(dataset.reduce((a, b) => a + b)); // 45
console.log(dataset.sort((a, b) => a > b ? -1 : 1)); // [ 9, 8, 7, 6, 5, 4, 3, 2, 1 ]

forEach,for...of

  • forEach():メソッドは与えられた関数を、配列の各要素に対して一度ずつ実行します。
  • for...of:反復可能オブジェクトなどに対して、反復的な処理をするループを作成します。

本来なら使わないほうがいいが、どうしても使う場合はforEach推奨。

const dataset = [1, 2, 3, 4, 5, 6, 7, 8, 9];
dataset.forEach((data) => {
  if (data % 2 === 0) {
    console.log(`${data}は偶数です。`);
  }
});
for (let data of dataset) {
  if (data % 2 === 0) {
    console.log(`${data}は偶数です`);
  }
}

includes

特定の要素が配列に含まれているかどうかを true または false で返します。

const dataset = [1, 2, 3, 4, 5];
console.log(dataset.includes(1)); // true
console.log(dataset.includes(7)); // false

また、以下のような||演算子が繰り返し使われるコードを完結に書けます。

if ( x === 'a' || x === 'b' || x === 'c' ) {
  console.log('ok')
}
if (['a', 'b', 'c'].includes(x)) {
  console.log('ok')
}

Object.keys,values,entries

  • Object.keys():プロパティのキーのリストを配列で取得できる
  • Object.values():プロパティ値のリストを配列で取得できる
  • Object.entries():プロパティのキーと値が対になった2次元配列を取得できる
const user = {
  id: 1,
  name: '瀬戸熊',
  age: 50,
};
console.log(Object.keys(user));
// [ 'id', 'name', 'age']

console.log(Object.values(user));
// [ 1, '瀬戸熊', 50 ]

console.log(Object.entries(user));
// [
//   [ 'id', 1 ],
//   [ 'name', '瀬戸熊' ],
//   [ 'age', 50 ],
// ]

非同期処理

Promise

PromiseはES2015から導入されたJavaScriptの組み込みオブジェクトで、非同期処理の最終的な完了処理 (もしくは失敗) およびその結果の値を表現するものであり、Promiseを使うことによって、非同期処理の完了を待って次の処理を行うというのがJavaScriptでも簡単(それ以前はコールバック関数で書くしかなかったので、連続して非同期処理を行う場合にコールバック地獄となる)にできるようになります。

以下例文

  • 最初のresolve()に渡したものが.then()の引数のvalueになり、その.then()内でreturnしたものが次の.thenのvalueになる。
  • reject()に渡したものが、errorとしてcatch()で受け取れる。
  • finally()に渡された関数は必ず最後に実行される。(ES2018から)
const promise = new Promise((resolve, reject) => {
  if (isSuccess) {
    resolve('成功1');
  } else {
    reject(new Error('失敗'));
  }
});
promise.then((value) => {
    console.log(value);
    return '成功2';
  })
  .then((value) => {
    console.log(value);
  })
  .catch((error) => {
    console.error(error);
  })
  .finally(() => {
    console.log('完了');
  });

axiosを使ってAPIをたたいてみる
async/awaitを使わない場合

const getUser = (userId) => {
  return new Promise(function (resolve, reject) {
    axios
      .get(`https://jsonplaceholder.typicode.com/users/${userId}`)
      .then((response) => resolve(response.data))
      .catch((error) => reject(error.response.status));
  });
};

getUser(1)
  .then((user) => {
    console.log(user);
  })
  .catch((error) => {
    console.log(error);
  })
  .finally(() => {
  });

async/awaitを使った場合

関数宣言時にasyncキーワードを付与するとその関数は、返される値がPromise.resolve()によってラップされたものになる。

asyncをつけた非同期関数内では他の非同期関数をawaitをつけて呼び出すことができる。

const getUser2 = async (userId) => {
  try {
    const response = await axios.get(
      `https://jsonplaceholder.typicode.com/users/${userId}`
    );
    return response.data;
  } catch (error) {
    throw error.response.status;
  }
};
const main = async () => {
  try {
    const user = await getUser2(1);
    console.log(user);
  } catch (error) {
    console.error(error);
  } finally {
  }
};
main();

モジュール

名前付きエクスポート

1ファイルでいくらでもエクスポートできる。

src/export.js
const NAME = "瀬戸熊";
const AGE = 50;
const Introduce = (name) => `私の名前は${name}です`
export { NAME, AGE, Introduce };

or

export const NAME = "瀬戸熊";
export const AGE = 50;
export const Introduce = (name) => `私の名前は${name}です`

名前付きエクスポートの場合は{}をつけてインポートする

src/inport.js
import { NAME, AGE, Introduce } from "./export"

デフォルトエクスポート

1ファイル1回までしかエクスポートできない

src/export.js
export const NAME = "瀬戸熊";
export const AGE = 50;
const Introduce = (name) => `私の名前は${name}です`
export default Introduce;

デフォルトエクスポートの場合は{}はつけないでインポートする。名前も自由に命名できる。

src/inport.js
import Introduce, { NAME, AGE } from "./export"

まとめ

React入門の前に知っておきたいNode.jsとJavaScriptの知識を簡単にまとめました。

今回記載した項目が大体頭に入っていれば、Reactに入門してもよいのではないかと思います。

このほかにもクラスやthisの扱いなど学ぶべきものはいっぱいありますが、Reactが関数コンポーネント主体になってきたのであまり使わない印象があるので、一旦は飛ばしてもよいかと思い省略しました。(理解はしておいたほうがいいですが)

あとTypeScriptはReactでの開発においてマストなものになりつつあるので、早めにTypeScriptでReactを書けるように学んでいったほうがいいと思います。

間違っていたり、これも理解しておくべきなどありましたら、指摘していただけると幸いです。

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

参考

MDN

Polyfill (ポリフィル)
Falsy (偽値)
Array.prototype.map()
Array.prototype.filter()
Array.prototype.find()
Array.prototype.findIndex()
Array.prototype.every()
Array.prototype.some()
Array.prototype.reduce()
Array.prototype.sort()
Array.prototype.includes()
Promise

Qiita

Node.jsとはなにか?なぜみんな使っているのか?
Promiseの使い方、それに代わるasync/awaitの使い方

りあクト

りあクト! TypeScriptで始めるつらくないReact開発 第3.1版【Ⅰ. 言語・環境編】

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

プロジェクトでReact.jsコンポーネントを使う7つの理由

(原文) 7 Best Reasons To Use React.js Components In Your Project

image.png

React.js は UXPin で使用できる Javascript コンポーネントのライブラリで、プロジェクトをより速く、より簡単に作成することができます。多くのReactの利点があるため、プロダクトデザイナーや、独自のページやアプリを作成することに興味がある人に人気があります。

Facebookによって2013年に設計されたReact.jsを使えば、モバイルアプリやシンプルなウェブページを簡単に構築できます。しかし、そのシンプルさに騙されてはいけません。このライブラリには、デザインプロセスをスピードアップさせる強力なオプションが満載です。利用可能なコンポーネントを使えば、新しい情報やデータが収集されると即座に更新されるアプリを作成することができます。

1. ページをリロードせずに瞬時に更新できる

おそらくReactの利点の中で最も大きいのは、ページ全体をリロードすることなく、ウェブページやアプリの個々の要素を更新できることです。Facebookでは、人々が「いいね!」をクリックしたり、コメントを書いたりすると、すぐに表示されるこの機能を実際に見ることができます。過度な読み込みは不要で、この機能を使えば、情報が入ってきたときにすぐに更新することができます。

2. JSX内で条件文を使う

JSXはReact.jsのコンポーネントをより効率的にするためのJavascriptの拡張機能です。これを使うことで、マークアップとロジックの両方を1つのファイルで作成することができるので、複数のファイルで作業する必要がなくなります。JSXを使うと、条件文を簡単に作成して、デザインやアプリの機能性を向上させることができます。実際には、HTML と JSX をブレンドして、希望通りの結果を得ることができます。また、JSX は Javascript よりもはるかに高速なので、プロジェクトをより早く完了させることができます。独自のテンプレートを構築するのも非常に簡単です。

3. 同じコンポーネントを再利用する

image.png

モバイルアプリをデザインする際に、同じアセットを使用することができます。Reactはもともとコードコンポーネントを再利用することができなかったため、多くの時間が無駄になりました。しかし、現在ではそのオプションが利用できるようになっており、同じコードやアセットを使用してニーズに合わせて調整することで、作業をさらにスピードアップさせることができるようになりました。

4. 1つのコンポーネントだけを更新する

すべてのReact.jsコンポーネントは別々に動作するので、すべてを更新しなくてもアプリの1つのセクションを変更することができます。これはまた、アプリの各エリアで同じコンポーネントを使用して、個々の部分を変更することができることを意味します。更新する量が少なくて済むので、全体のプロセスがはるかに効率的になります。アプリにエラーやアップデートがある場合は、これを簡単に処理することができます。多くのアプリやプログラムでは、コードが複雑なため、アップデートが簡単ではありません。ある領域の単純な変更が、アプリ全体の問題を引き起こしてしまうことも多々あります。しかし、React.jsコンポーネントを使えば、そんなことを心配することなく好きな場所で素早く調整を行うことができます。

5. コードは素晴らしく安定している

image.png

他の多くのオプションとは対照的に、React.jsは安定したコードを提供します。これは、下向きのデータフローを利用することで実現されています。実際には、コードが子構造の変更から親構造を保護する方法です。これまで不安定なコード、バグ、長く長引くメンテナンス手順などの問題を抱えていた人は、React.jsを気に入ることでしょう。そのコンポーネントだけに集中してバグを修正できるので、より多くの時間を節約することができます。

6. React.jsは習得しやすい

React.jsのコンポーネントは非常にユーザーフレンドリーなので、誰でも使いこなせるようになります。Reactの最大のメリットは、Javascriptを知っていればReactの使い方がわかるということです。Javascriptを知らなくても、コンポーネントを覚えるのに問題はないでしょう。コンポーネントは非常によく設定され、設計されているので、それらを理解するのにほとんど時間がかかりません。コードも非常に絞られているので、少し学ぶだけでもいろんなことができるようになります。

7. React.jsの背後には非常に頑丈なエンジニアリングチームがある

image.png

元々はFacebookが社内で使うために設計されたものでしたが、React.jsのライブラリは欲しい人がいれば誰でも利用できるようになりました。とはいえ、設計を支えているエンジニアリングチームの存在は大きいです。その中には、FacebookやInstagramのチームや、その分野に精通した外部の開発者も含まれています。

アプリにReact.jsのコンポーネントを使う場合は、専門家の手に委ねられているということになります。また、世界で最も大きなアプリのいくつかがReactを使用しており、Facebookが今後何年もReactをサポートする可能性が高いことを覚えておくと便利です。Netflix、Paypal、Dropbox、Khan Academy、Lyft、Reddit、Coursera、BBCなどは、React.jsコンポーネントを使って構築された数多くのサイトのほんの一握りです。普及が進むにつれ、Fortune 500企業のサイトでもReact.jsフレームワークを目にするようになるでしょう。

React.jsは世界中で重要視されているため、今後も既存の要素を改良し、構築していくことが期待できます。React.jsは完璧なユーザーインターフェースの作成をこれまで以上にシンプルにし、プロダクト、ウェブサイト、アプリをこれまで以上に早く市場に出すことを可能にしてくれます。UXPinに無料で参加してみませんか?

UXPinは、世界中の製品開発チームがアイデアをより早く製品に変えるのに役立つデザインプロセスツールです。

UXPinの革新的なテクノロジーであるMergeを活用することでPayPalなどの企業はDesignOpsの課題を簡単に解決するができました。UXPin Mergeは、Reactコンポーネントを利用することで、最終完成品と完全な一貫性を持つデザインを実現します。

本記事は2021/1/5投稿の英語版を翻訳したものです。

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

Next.jsのgetServerSidePropsでJWT認証処理を行う

この記事について

Next.jsのgetServerSideProps(SSR)で、JWTを使った認証処理を行う方法をご紹介したいと思います。

注意として、説明の簡略化のため具体的な認証処理はFirebase Authenticationにやってもらう事とします。そのため、サーバー側のJWT生成処理部分はこの記事では解説しませんので、予めご了承ください?

※ この記事では JWT を使った認証処理を実装していますが、他の認証フローでも同じような方法で実装することが出来ると思います(タブン)。

後、この記事は以下のリポジトリと記事を参考にしています。

https://github.com/colinhacks/next-firebase-ssr

Authenticated server-side rendering with Next.js and Firebase

環境情報

環境情報は以下の通りです。

ライブラリ名 バージョン
next v10.0.5
react v17.0.1
firebase v8.2.3
firebase-admin v9.4.2
nookies v2.5.1

※ この記事ではredirect functionalityという機能を使っているため、nextのバージョンがv9.5.3以下だとエラーが発生しますので、ご注意ください。

前準備

今回はFirebase Authenticationを使うため、FirebaseのKey情報を環境変数に登録します。

※ 値の所は、自身のFirebaseアプリのKey情報を入れて下さい

.env.local
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_DATABASE_URL=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=

FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=

ここでの注意点として、ブラウザ側で使用する環境変数は NEXT_PUBLIC_を付ける必要があります。

Step 1: JWTをCookieにセットする

JWTをCookieに保存する処理は、本来はサーバー側などでやった方が良いのですが、今回は Firebase Auth を使ってJWTを生成するので、Cookieへの保存はブラウザ側でやります。ブラウザ側でCookieを扱うのは少し気持ち悪いですが、Firebase Authだとこのような方法しかなさそうです。サーバー側で、Firebaseを使ってJWTを生成出来る方法知っている方は、教えて頂けると嬉しいです。

という事で、AuthProvider と言うコンポーネントを作り、その中でFirebase AuthからJWTを取得してCookieに保存する処理を実装します。

Hooksによる実装でも良いのですが、コンポーネントの方が何かと扱いやすかったりするので、今回はReact Context APIを使ってコンポーネントで実装します。

components/AuthProvider.tsx
import nookies from "nookies";
import React, { useState, useEffect, createContext } from "react";

import * as firebase from "firebase/app";
import "firebase/auth";

// firebaseの設定キー
const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID,
};

// firebaseを初期化
if (typeof window !== "undefined" && !firebase.apps.length) {
  firebase.initializeApp(firebaseConfig);
}

interface IAuthContextData {
  firebaseUser: FirebaseUserType | null;
}

// AuthContextを作成
export const AuthContext = createContext<IAuthContextData>({
  firebaseUser: null,
});

/**
 * @description 認証処理を提供するコンポーネント
 */
export const AuthProvider: React.FC = ({ children }) => {
  const [firebaseUser, setFirebaseUser] = useState<firebase.User | null>(
    null
  );

  useEffect(() => {
    // FirebaseのJWT更新イベントを受け取る
    return firebase.auth().onIdTokenChanged(async (user) => {
      if (!user) {
        nookies.destroy(null, "token"); // cookiesを削除
        nookies.set(null, "token", "", {}); // ユーザーがログアウトしたらcookieを破棄
        setFirebaseUser(null); // Stateを更新
      } else {
        const token = await user.getIdToken(); // jwtを取得する

        nookies.set(null, "token", token, {}); // jwtをcookieに保存
        setFirebaseUser(user); // StateにFirebaseのUser情報を保存
      }
    });
  }, []);

  // FirestoreやRealtime Databaseを使ってない場合は、以下の処理を実行する必要があります。
  // 10分ごとにトークンをリフレッシュ
  // useEffect(() => {
  //   const handle = setInterval(async () => {
  //
  //    const user = firebase.auth().currentUser;
  //
  //    if (user) await user.getIdToken(true);
  //
  //   }, 10 * 60 * 1000);
  //
  //  // clean up setInterval
  //  return () => clearInterval(handle);
  // }, []);

  return (
    <AuthContext.Provider value={{ firebaseUser }}>
      {children}
    </AuthContext.Provider>
  );
};

上記のソースコードでは、nookiesというブラウザとサーバーどちらでもCookieを使う事が出来るライブラリを使用しています。今回の実装では、Cookieをサーバー・ブラウザ両方で実行する必要があるため、とても便利なライブラリとなっています。

そして、onIdTokenChangedという関数にコールバックを渡して、そのコールバック内でFirebaseのJWTを取得し、nookiesを使ってCookieにJWTを保存しています。

この際の注意点として、FirebaseのJWTは、デフォルトで一時間ごとに更新されます! そのため、更新されるたびにJWTを取得してCookieに保存する必要があります。 Firebaseの認証状態を取得する関数として onAuthStateChanged がありますが、こちらの関数はJWTの更新イベントを受け取る事が出来ない為、 onIdTokenChanged を使う必要があります!

また、ソースコードのコメントでも書いていますがFirestoreやRealtime Databaseを使ってない場合は、Firebaseはトークンを自動で更新してくれないようです。 1 そのため、自前でトークンの更新をする必要があります。今回はコメントアウトしていますが、該当する人は適時コメントアウトを外して頂ければ、動作するようになると思います。

Step 2: 作ったAuthProviderを_app.tsxで使う

次に、上記で作った AuthProviderpages/_app.tsx で使います。

pages/_app.tsx
import type { AppProps } from 'next/app';
import { AuthProvider } from 'components/AuthProvider';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <AuthProvider>
      <Component {...pageProps} />
    </AuthProvider>
  );
}

export default MyApp;

<Component />AuthProvider で囲ってあげる事で、全てのコンポーネントで上記で実装したJWTの取得&Cookeへの保存処理を実行する事が出来ます。

また、もし一部のページにのみ AuthProvider の処理を実行したい場合は、以下のようにします。

一部のページにのみJWT取得処理を実装する
import { useEffect } from "react";
import type { AppProps } from 'next/app';
import { AuthProvider } from 'components/AuthProvider';

function MyApp({ Component, pageProps, router }: AppProps) {

  // ログインページとダッシュボードページにのみJWT取得処理を実装する
  if( ["/login", "/dashboard"].includes(router.pathname) ) {
    return (
      <AuthProvider>
        <Component {...pageProps} />
      </AuthProvider>
    );
  }

  // ページコンポーネントをそのまま表示
  return (
    <Component {...pageProps} />
  );
}

export default MyApp;

Step 3: JWTを検証する関数を作る

次は、サーバー側の処理を実装します。

Firebase AuthのJWTは、firebase-adminと言うライブラリで検証することが出来ますので、そのライブラリを使って以下のように実装します。

utils/firebaseAdmin.ts
import * as firebaseAdmin from "firebase-admin";

// .env.localで設定したFirebaseの秘密鍵情報を取得する
const firebasePrivateKey = process.env.FIREBASE_PRIVATE_KEY;

if (!firebasePrivateKey) console.error("権限がありません");

// firebaseAdminが初期化されてなければ、初期化する
if(!firebaseAdmin.apps.length) {
  firebaseAdmin.initializeApp({
    credential: firebaseAdmin.credential.cert({
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
      privateKey: firebasePrivateKey.replace(/\\n/g, "\n"), // 参照: https://stackoverflow.com/a/41044630/1332513
    }),
  });
}

/**
 * @description JWTを解析し、有効なモノかどうか検証します
 * @param JWT文字列
 */
export const verifyIdToken = async (
  token: string
): Promise<firebaseAdmin.auth.DecodedIdToken> => {
  return firebaseAdmin
    .auth()
    .verifyIdToken(token)
    .catch((error) => {
      console.error(error);

      throw new Error("有効なトークンではありません");
    });
};

verifyIdToken が、JWTを検証するための関数となっています。
認証に成功するとuid文字列などを含んだ認証情報を返し、失敗するとエラーを投げます。

また、必ずサーバー側で実行するようにしてください。 じゃないとエラーが発生します。
Next.jsの getInitialProps はブラウザ側で実行される可能性があるので、verifyIdToken を使うことが出来ない事に注意です!

Step 4: getServerSidePropsで認証処理を行う

上記で実装した verifyIdToken を使って、getServerSidePropsで認証処理を実装すると以下のようなソースコードになります。

pages/dashboard.tsx
import nookies from "nookies";
import { NextPage, GetServerSideProps } from "next";

import { verifyIdToken } from "utils/firebaseAdmin";

// firestoreのヘルパー関数を定義したファイル( 今回は定義を省略しています )
import { getUser } from "utils/helpers"; 

interface IDashboardPageProps {
  currentUser: { 
    id: string;
    displayName: string;
  }
}

/**
 * @description ダッシュボード画面
 */
const DashboardPage: NextPage<IDashboardPageProps> = ({ currentUser }) => {
  return (
    <div>
      <h1>{currentUser.displayName} さんのダッシュボード画面です</h1>

      {/* -- 以下、認証情報を使った内容を表示 -- */}
    </div>
  );
};

/**
 * @description DashboardPageをSSRで実行するようにする
 */
export const getServerSideProps: GetServerSideProps<IDashboardPageProps> = async (ctx) => {
  try {
    const cookies = nookies.get(ctx); // ブラウザ側で設定したCookieを取得
    const { uid } = await verifyIdToken(cookies["token"]); // CookieからJWTを取得し検証する

    const currentUser = await getUser(uid); // ユーザー情報を取得する

    return { props: { currentUser } }; // DashboardPageにpropsを渡して遷移する

  } catch (error) {
    console.error(error); // エラーをコンソールに表示しておく

    return {
      // 認証に失敗したら、ログイン画面へリダイレクト
      redirect: {
        permanent: false,
        destination: "/login",
      },

      props: {} as never,
    };
  }
}

export default DashboardPage;

getServerSidePropsの引数に渡されるContext(上記のソースコードではctx)からnookiesを使ってCookieを取り出し、Cookieに保存されているJWT(cookies["token"])を取り出して検証しています。

この検証が失敗すると verifyIdToken はエラーを吐くので、try...catch文 で囲って対処していますが、Promiseを使っているのでcatch関数を使っても同じような事が出来ます。しかし、今回は try...catch文 の方がニュアンスが伝わりやすいので、そちらの方を使っています。

検証が成功すると uid が取得できるので、それを使ってユーザー情報を取得して、PropsとしてDashboardPageに渡して/dashboardへと遷移させています。このようにする事で、DashboardPageでユーザー情報を取得する処理をしなくていいので、ローディングを挟まずにユーザー情報を画面に表示させることが出来ます。

逆に検証が失敗してエラーをキャッチすると、redirect functionalityという機能を使って/login ページへリダイレクトさせています。( /loginに対応するコンポーネントが定義されている必要があります! )

また、リダイレクトさせる時 props の値を定義する必要がありますが、そのまま {} を渡すとTypeErrorが発生してしまうので、as never を付けて渡しています。

上記の実装によって、ログインしているユーザーはダッシュボードページ(/dashboard)を見ることが出来ますが、ログインしてないユーザーはログインページ(/login)へリダイレクトされるようになります。

/login の実装は本題では無いので省略させて貰っていますが、Firebase Authenticationを使った認証をしている事を想定しています。

以上で、getServerSidePropsでJWT認証処理を実装することが出来ました。
お疲れ様です?

あとがき

Firebase Authenticationではどうしてもブラウザ側でCookieを設定する必要があるので、サービスによっては導入は難しいかもしれません。しかし、Firebase Authenticationを使わなければ、そんなことせずとも同じような感じで実装できると思うので、そこら辺は上手く対応して頂ければと思います!

あっあと、最近知ったのですが、getServerSidePropsで返すPropsと言うのは __NEXT_DATA__ と言う変数に埋め込まれてしまうので、今回のようにgetServerSidePropsで認証処理を行っている場合では特に注意が必要です! 詳しくは、以下の記事を参考にして下さい。

next-authとgetServerSide(Static)Props使うときの__NEXT_DATA__に注意

ここまで読んでくれてありがとうございます。
何か不備などがありましたら、コメントなどで教えて頂けると幸いです?

それではまた?

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

【VSCode 拡張機能】Reactに便利な拡張機能。他にもAIコード補完、フォルダやファイルにiconを付ける拡張機能など。

Reactを使っているとき、ブラケットやカーリーブラケットがたくさん出てきて見づらくなることがあったので、自動で色分けしてくれてわかりやすくなる便利な拡張機能を入れてみました。

ブラケットなどを自動で色分けして見やすくする拡張機能

Bracket Pair Colorizer

Reactコード補完拡張機能

ES7 React/Redux/GraphQL/React-Native snippets

rafce(reactArrowFunctionExportComponent)と入力すると補完で下記テンプレートが作れたりする。

import React from 'react'

const App = () => {
  return (
    <div>

    </div>
  )
}

export default App

その他

AIコード補完
Tabnine Autocomplete AI

コード品質 : コードのスタイルを自動で綺麗にしてくれる
Prettier - Code formatter

視認性向上 : ファイルにわかりやすいiconを付けてくれる
vscode-icons

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

React.memo / useCallback / useMemo を書きながら学ぶ

Reactの組み込みフックであるuseCallbackuseMemoの説明をします。

また、useCallbackは、React.memoという Reactの API と併用するので、React.memoの解説もします。

useCallback とは

一言で言うと、パフォーマンス向上のためのフックです。

具体的に言うと、コールバック関数をメモ化して、不要な関数インスタンスの作成を抑制します。

これによってパフォーマンスを向上させています(不要な関数インスタンスの作成が重い時は特に)。

ちなみに、メモ化とはプログラム高速化のための最適化の1つです。関数の定義や実行の結果を再利用するために一時的に保持しています。

依存配列の要素(deps)が変化した場合にのみメモ化した値を再計算します。

const callback = useCallback(コールバック関数, [依存配列])

依存している値がなければ、空の配列OKです。

const callback = useCallback(コールバック関数, [])

依存している値が更新されれば、関数が再生成されます。

例を1つ

const callback = useCallback(() => console.log(count), [count])

上記の場合、countが更新されない限り、関数は再生成されません。

サンプルコード

こんなん作るとします。

スクリーンショット 2021-01-28 0.17.11.png

各ボタンクリック時の結果は、なんとなくわかりますよね?

スクリーンショット 2021-01-28 0.49.04.png

今の状態だと、どのボタンを押しても、Titleコンポーネントとか(再レンダリングしても結果同じなやつ)も含め、全てのコンポネントが再レンダリングされます。

console.log仕込んでるので、それがわかります。

8de0ba44bab2230c72d3dddad8c8241a (1).gif

サンプルコード
App.js
import React, { useState } from "react";
import "./styles.css";
import Title from "./components/Title";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = () => setHeight(height + 1);
  const incrementWeight = () => setWeight(weight + 1);
  return (
    <>
      <Title />
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;
components/Title.js
import React from "react";

const Title = () => {
  console.log("Title");
  return <h1>身長と体重の入力</h1>;
};

export default Title;
components/Button.js
import React from "react";

const Button = ({ handleClick, children }) => {
  console.log(`Button - `, children);
  return <button onClick={handleClick}>{children}</button>;
};

export default Button;
components/Count.js
import React from "react";

const Count = ({ text, count }) => {
  console.log(`Count - `, text);
  return (
    <p>
      {text} : {count}
    </p>
  );
};

export default Count;

☝️のサンプルコードの問題点

どのボタンを押しても、全てのコンポネントが再レンダリングされることが、パフォーマンスが悪いとしましょう。

そこで、パフォーマンス改善に役立つReact.memouseCallbackを試してみましょう。

そこで、React.memoの出番

React.memo とはコンポーネントをメモ化(計算結果を再利用するために保持)するReactのAPIです。

キャッシュみたいなもんです。

もう少し細かい言い方をすると、HOCで、React.memoを使うことで、propsの値が変わらないなら、関数コンポネントのレンダリングを抑制することができるAPIです。

なので、以下のようなコンポネントをメモ化するとパフォーマンス上有効です。

  • レンダーコストが高いコンポネント
  • 頻繁に再レンダーされるコンポネントの子コンポネント

やってみましょう。変更点は、赤枠のみ。?

musing-almeida-g5ogr_-_CodeSandbox.png

身長+1ボタンをクリックしたら...

以下のコンポネントが再レンダリングされます。

  • 身長のCountコンポネント
  • 身長のButtonコンポネント
  • 体重のButtonコンポネント(? なんでこいつが再レンダリングされるんだ? してほしくない)

以下のコンポネントは再レンダリングされません。

  • 体重のCountコンポネント
  • Titleのコンポネント

musing-almeida-g5ogr_-_CodeSandbox.png

なぜ「身長+1ボタン」をクリックしたら体重のButtonコンポネントがレンダリングされるのか?

App コンポネントが再レンダリングされるたびに、関数も再生成され、再生成の前後で関数は等価ではありません。

なので、React.memoで子コンポネントをメモ化しても コールバック関数をpropsとして渡す場合は子コンポコンポネントは必ず再レンダリングされます。

この問題を解決するのがuseCallbackです。

次に、usecallbackの出番

変更点は、赤枠。?

musing-almeida-g5ogr_-_CodeSandbox.png

実行結果

399b7c1abfe524dedf2410cad071f1b0.gif

useCallbackを使うことでコンポネントの再レンダリングを最適化することができました。

このように、子コンポネントにコールバック関数をpropsとして渡す場合は使ってみるといいと思います。

useCallbackの注意点

前述の通り、useCallbackReact.memoと併用するものなので、次のような使い方をしても再レンダリングをスキップできません

  • React.memoでメモ化していないコンポネントにuseCallbackでメモ化したコールバック関数を渡すとき
  • useCallbackでメモ化したコールバック関数を、それを生成したコンポーネント自身で利用するとき

useCallbackの疑問点...依存関係の配列にsetState関数を含める必要があるか?

ない。

コード書いてると見かけるので、調べてみましたが、公式ドキュメント見ると不要だそうです。

Note

React guarantees that setState function identity is stable and won’t change on re-renders. This is why it’s safe to omit from the useEffect or useCallback dependency list.


React は再レンダリングでsetState関数の等価に保たれ、変化しないことを保証します。
従って useEffectuseCallbackの依存リスト(依存配列)にはsetState関数を含めなくてもいいです。

https://reactjs.org/docs/hooks-reference.html#basic-hooks

useMemo とは

useCallback同様、パフォーマンス最適化用のHookです。

useCallbackとの違いは以下です。

  • useCallbackは関数自体をメモ化
  • useMemoは関数の結果をメモ化(メモ化された値を返すHook)

useMemoは、値を算出するための不要な再計算をスキップすることでパフォーマンスを向上させます。

サンプルコード

こんなん作るとします。useCallbackの時とほとんど同じです。

身長だけ、奇数か偶数か判定されています。

スクリーンショット 2021-01-28 2.01.17.png

差異があるファイルだけ以下に記載しておきます。

サンプルコード
components/App.js
import React, { useState, useCallback } from "react";
import "./styles.css";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = useCallback(() => setHeight(height + 1), [height]);
  const incrementWeight = useCallback(() => setWeight(weight + 1), [weight]);

  const isEven = () => {
    console.log("身長");
    return height % 2 === 0;
  };

  return (
    <>
      <p>
        身長 {height}  {isEven() ? "偶数" : "奇数"}
      </p>
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;

コードを実行すると、どちらのボタンをクリックしても、発火します。

理由は、useCallbackの時と同様に、コンポネントが再生成されたタイミングでisEven関数も再作成、実行されるからです。

本来は、この関数は身長ボタンがクリックされたときだけ実行するべきですよね?

でも、体重ボタンをクリックしても発火してしまう。。

これを問題として、useMemoを使って、身長ボタンがクリックされたときだけisEvenが実行されるようにしましょう。

サンプルコード
components/App.js
import React, { useState, useCallback, useMemo } from "react";
import "./styles.css";
import Count from "./components/Count";
import Button from "./components/Button";

const App = () => {
  const [height, setHeight] = useState(150);
  const [weight, setWeight] = useState(50);

  const incrementHeight = useCallback(() => setHeight(height + 1), [height]);
  const incrementWeight = useCallback(() => setWeight(weight + 1), [weight]);

  const isEven = useMemo(() => {
    console.log("身長");
    return height % 2 === 0;
  }, [height]);

  return (
    <>
      <p>
        身長 {height}  {isEven ? "偶数" : "奇数"}
      </p>
      <Count text={"身長"} count={height} />
      <Count text={"体重"} count={weight} />
      <Button handleClick={incrementHeight}>身長+1</Button>
      <Button handleClick={incrementWeight}>体重+1</Button>
    </>
  );
};

export default App;

変更点は、赤枠。?

musing-almeida-g5ogr_-_CodeSandbox.png

useMemoは、値(今回は bool値)を算出するための不要な再計算(今回なら「体重+1」ボタンクリック時のisEven内の計算処理)をスキップさせています。

「体重+1」ボタンクリック時は、useMemoisEven処理を再実行させずに、結果(bool値)のみを返しています。

仮に、isEven処理が重い場合、「体重+1」ボタンクリック時は、結果(bool値)のみを返すので、パフォーマンス向上につながると思います。

今回は以上です。

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

react-hook-formで確認用パスワードの入力チェックを実装してみる

概要

Formである項目を入力した時に、別の項目の入力内容と比較してチェックをしたい時があると思います。例えばこちらで紹介されているような、確認用パスワードの入力です。
今回はreact-hook-formでこの確認用パスワードの入力チェックを実装してみたので、どのようになったのかというのを紹介してみます。

実装方針

・親コンポーネント、パスワードの入力用コンポーネント、確認用パスワードの入力用コンポーネントの3つでコンポーネント分けします。
・入力された値は親コンポーネントのformStateで管理します。formStateからはgetValuesを使用して、入力した値を取得します。
・確認用パスワードの入力欄では、getValuesで取得したパスワードの値とこちらの記事で紹介されているvalidateメソッドを使って比較します。

実装

【親コンポーネント】

ParentComponent.js
export default function ParentComponent() {
  const { register, handleSubmit, errors, formState, getValues } = useForm({
    mode: "onChange",
  });

  function onSubmit(data) {
    // submit処理は割愛・・
  }

  return (
    <div>
      <Form onSubmit={handleSubmit(onSubmit)}>
        <PasswordInputComponent register={register} errors={errors} />
        <br />
        <PasswordReInputComponent
          register={register}
          errors={errors}
          password={getValues("password")}
        />
        <br />
        <Button variant={"primary"} type="submit" disabled={!formState.isValid}>
          送信
        </Button>
      </Form>
    </div>
  );
}

【パスワードの入力用コンポーネント】

PasswordInputComponent.js
export default function PasswordInputComponent(prop) {
  return (
    <>
      <Form.Label>パスワード</Form.Label>
      <Form.Control
        id="password"
        type="password"
        name="password"
        placeholder="パスワード"
        isInvalid={prop.errors.password}
        style={{ width: "300px" }}
        ref={prop.register({
          required: "パスワードは必須項目です",
          minLength: {
            value: 6,
            message: "パスワードは6文字以上で入力してください",
          },
        })}
      />
      {prop.errors.password && (
        <Form.Control.Feedback type="invalid">
          {prop.errors.password.message}
        </Form.Control.Feedback>
      )}
    </>
  );
}

【確認用パスワードの入力用コンポーネント】

PasswordReInputComponent.js
export default function PasswordInputComponent(prop) {
  return (
    <>
      <Form.Label>パスワード確認用</Form.Label>
      <Form.Control
        id="passwordReInput"
        type="password"
        name="passwordReInput"
        placeholder="パスワード"
        isInvalid={prop.errors.passwordReInput}
        style={{ width: "300px" }}
        ref={prop.register({
          validate: (input) => input === prop.password,
        })}
      />
      {prop.errors.passwordReInput && (
        <Form.Control.Feedback type="invalid">
          <span>入力したパスワードと一致していません</span>
        </Form.Control.Feedback>
      )}
    </>
  );
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

10日間の学習でReactを使ったWebページをどの程度作れるか?に挑戦しました。

はじめに

前回の記事から2ヶ月ぶりの記事投稿となりました。月に1回程度は記事でアウトプットして、学習の成果を客観化しようと考えていたのですが、思いの外、転職活動に時間を取られてしまったので、それが出来ずじまいでした。今回は転職先が決まったので、1つの区切りとして記事を投稿したいと思います。

まず、私の説明をさせて頂きます。プログラミングの学習は1ヶ月半の独学と3ヶ月のプログラミングスクールの内訳で4ヶ月半しています。直近の10日間以外はPythonの勉強をしていましたが、フロントエンドエンジニアとして配属されることが決まったので今回はReactでwebページを作ってみました。

誰に向けた記事か?

これまでの記事と同様に「IT企業への転職を考えているプログラミング初学者の社会人」を想定して記事を書いています。上記の自己紹介の通り私も初学者の一人なので何かしらの参考にしていただければ非常に幸いです。

苦闘した10日間の内容

採用が決まって、転職先の方から色々とお話を聞いた内容と、Google Chromeに聞きまくったことを合わせた結果、HTML&CSSとJavascriptとReactが少なくとも必要そうだと分かったので、Reactでのアウトプットを最終目標にして、HTML&CSSの学習→HTML&CSSのアウトプット→Javascriptの学習→Reactの学習→Reactのアウトプットの順番で学習を進めました。学習教材はProgateと、残りはネット検索で補いました。

環境説明

MacBookAir
macOS : Catalina 10.15.5
Atom : 1.53.0
Node.js : 14.15.4
yarn : 1.22.10
create-react-app : 4.0.1

今回作るもの

学生時代で暇すぎたときにロシア文学を読んでいたことがあった経緯から、ロシアの小説家の名前と写真のリストが横並びになっており、写真をクリックするとその人物の代表作が表示されるWebページを作ろうと思い付きました。基本的なフォーマットはProgateで学習したものを使用しています。

本文

1.環境構築

環境構築は以下を参考にして行いました。

React開発環境構築

なぜNode.jsが必要なのか、yarnとは何者かについてはほとんど分からない(分かんなくてもWebページは作れるので勘弁してください!!)ので、それらの理解は後回しにしておき、今後の課題とさせていただきます。

2.構築した環境で作業する準備

環境構築が終わったあとはターミナル上で以下を順番に実行します。

Terminal.
npx create-react-app react_web_page

今回は「react_web_page」というディレクトリ名にしてますが、名前はご自由につけてください。

Terminal.
cd react_web_page

cd(change directory:現在いるディレクトリから移動する)で「react_web_page」に移動します。

Terminal.
yarn start

上を実行した後、ブラウザ上で以下の画面が出てきたら、今までの一連の作業が上手く行ったということです。

2021-01-27 6.38のイメージ.jpeg

3.テキストエディタで作業する

今回はAtomを使います。Atomで「react_web_page」を開きます。すると、いくつかのディレクトリと大量の記述されたファイルがあって、若干ビビりますが問題ないです。今回、関係のあるファイルは、「index.html」、「stylesheet.css」、「App.js」、「Main.js」、「index.js」、「Writer.js」と自分が挿入した画像群のディレクトリです。使用する画像の入ったディレクトリ名は「images」としています。書き換える必要のあるファイルは「index.html」、「App.js」、「index.js」です。

最初に今回必要となるディレクトリ及びファイルのみを抽出したディレクトリ構造図を以下に示します。

react_web_page/
 ├ public/
 │ ├ index.html
 │└ stylesheet.css
 └src/
  ├App.js(削除するかMain.jsに名前を変更)
   ├ Main.js
   ├ Writer.js
  ├ index.js
  └images1

4.index.htmlとindex.jsから

index.htmlとindex.jsはページ作成の過程で書き換えることがないものなので、先に済ませてしまいます。
既存のindex.htmlにはコメントを抜くと以下のようになっていると思います。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <link rel="stylesheet" href="stylesheet.css">
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

今回必要でない文はたくさんありますが、削除する必要もなく、下のコードをheadタグ内に貼っつけて、HTMLにCSSの適用をすることを可能にするだけで構いません。

index.html
<link rel="stylesheet" href="stylesheet.css">

次にindex.jsを変更しましょう。ブラウザ上に最初に現れた画面はApp.jsの内容をindex.jsがimportし、更にindex.htmlへ引き渡されて出力されています。しかし、今回は別の画面を出力したい、そしてファイル名を適切なものにしたいので、App.jsの内容を削除し、ページの形式をMain.jsの中に定めます。そして、index.jsがMain.jsをimportするように若干の変更を加えます。

index.js
import App from './App';

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

必要な部分だけ抜き出すと、コードは上のようになっています。上のコードでは1行目に2つ、そしてコードの4行目(空行を数えていません)の1つの合計3つの'App'が現れていることが分かります。この全ての'App'を'Main'へ変更します。2つ目の'App'を'Main'に変更するだけでも同じ出力を返してくれますが、全部変えたほうがキレイなので、そうします。

5.画像をimagesディレクトリへ

今回はドストエフスキー、トルストイ、ツルゲーネフの3人をピックアップしたいと思います。顔写真をテキトーにネットから収集してきます。その画像をimagesへ保存します。

6.Main.js

Main.jsでは、Webページに出力する画像とテキストを書いています。画像の出力の仕方はProgateのやり方では上手く行かなかったので下記を参考にしました。

Reactで画像を読み込む方法

Main.js
import React, {Component} from 'react';
import Writer from './Writer'
import logo1 from './images1/ドストエフスキー.jpeg';
import logo2 from './images1/トルストイ.jpeg';
import logo3 from './images1/ツルゲーネフ.jpeg';

const style1 = {
  width: '7rem',
  height: '10rem',
  backgroundImage: `url(${logo1})`
};
const style2 = {
  width: '7rem',
  height: '10rem',
  backgroundImage: `url(${logo2})`
};
const style3 = {
  width: '7rem',
  height: '10rem',
  backgroundImage: `url(${logo3})`
};



class Main extends Component{
  render(){


    return (
      <div className='main-wrapper'>
        <div className='main'>
          <div className='write-container'>
            <h1>Three great Russian writers</h1>
            <h2>Click the image to see the masterpiece</h2>
          </div>
          <div className="writer-container">
            <h3 className='section-title'>List of writers</h3>
            <Writer
              name='Dostoevsky'
              style={style1}
              masterpiece='The Brothers Karamazov'
              introduction='His father, Fyodor Karamazov, is an overwhelmingly crude, energetic, and amorous man. The conflict between the three brothers Micha, Ivan, and Alyosha returning home and their father over a bewitching beauty. Alyosha seeks help from the benevolent Elder Zosima ...'
            />
            <Writer
              name='Tolstoy'
              style={style2}
              masterpiece='War and peace'
              introduction='It started in the summer of 1805 with a night party in St. Petersburg. Set in the era of the war with the dictator Napoleon (homeland war) aiming to reorganize the entire European order, the masterpiece of Tolstoy depicts the appearance of Russian people confronting the national crisis from the rise and fall of Russian aristocrats to the peasants living on the earth'
            />
            <Writer
              name='Turgenev'
              style={style3}
              masterpiece='Father and son'
              introduction='A classic of Russian literature that depicts the ideological conflict between old aristocratic culture and new democratic culture before and after the liberation of farmers, and found the dawn of a new era there. The author gave the young protagonist Bazarov the new word nihilist and caused a stormy response, but in this Bazarov, which denies all old morals and religions and makes destruction the first step in construction. , The appearance of the radical intelligence at that time is artistically established.'
            />
          </div>
        </div>
      </div>


    );
  }
}

export default Main;

HTMLを学習された方は上記のコードがHTMLっぽいコードになっていることに気づくと思います。更にはJavascriptの要素も入っています。(ReactはJavascriptのライブラリなので)つまり、Reactやるなら、まずHTML&CSS、そしてJavascriptの段階を踏んだ方がいいです。この過程を通ることは面倒で、焦れったいですが、階段を5段一気に登るのが難しくても、1段ずつ合計で5段上がるのが簡単なように、学習においても一歩ずつ段階を経るのが効率の良い方法であることは、数ヶ月プログラミングをしていて感じたことです。(あるいは全ての学習について言えるかもしれません。)
もしHTML&CSSとJavascriptの部分が全く分からないようであれば、再学習あるいは新しく学習することをお勧めいたします。全てProgateにまとまっています。

Progate

7.Writer.js

次はWriter.jsの部分を書いていきます。コードは以下の通りです。

Writer.js
import React from 'react';


class Writer extends React.Component{
  constructor(props) {
    super(props);
    this.state={isModalOpen:false};

  }

  handleClickWriter() {
    this.setState({isModalOpen:true});
  }
  handleClickClose() {
    this.setState({isModalOpen:false});
  }
  render(){
    let modal;

    if(this.state.isModalOpen){
      modal=(
        <div className='modal'>
          <div className='modal-inner'>
            <div className='modal-header'></div>
            <div className='modal-introduction'>
              <h2>{this.props.masterpiece}</h2>
              <p>{this.props.introduction}</p>
            </div>
            <button
             className='modal-close-btn'
             onClick={() =>{this.handleClickClose()}}
             >
              Close
            </button>
          </div>
        </div>

      )

    }

  return(
    <div className='writer-card'>
      <div
       className='writer-item'
       onClick={() =>{this.handleClickWriter()}}
      >
        <div className='writer-name'>
          {this.props.name}
        </div>
        <img
         className='writer-image'
         style={ this.props.style } />
      </div>
      {modal}

    </div>

  );
}
}
export default Writer;

このファイルでは画像をクリックすると(厳密にはもう少し広い範囲に反応するのですが)、著者の代表作とその説明が表示され、再度クリックすると、元の画面に戻るようにプログラミングしています。
個々のコードの理解は私が説明するよりも、Progateで学習する方が良いと思われますので、説明は割愛させて頂きます。

8.stylesheet.css

stylesheet.css
h1 {
  padding-top: 40px;
  text-align:center;
}

.writer-item {
  width: 250px;
  height: 180px;
  background: #ffffff;
  float: left;
  box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 6px, rgba(0, 0, 0, 0.12) 0px 1px 4px;
  position: relative;
}

.writer-name {
  position: absolute;
  left: 0;
  right: 0;
  top: 35px;
  margin: auto;
  text-align: center;
}

.writer-image {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 40px;
  margin: auto;
  height: auto;
  width: 60%;
}

body {
  margin: 0;
  padding: 0;
  font-family: "Avenir Next";
  min-width: 1500px;
}

h1 {
  font-family: "Avenir Next";
  font-size: 50px;
  font-weight: 1000;
  color: #00CCFF;
}

h2, h3 {
  font-family: "Lato";
  color: #2b546a;
  font-weight: 300;

}

h2 {
  font-size: 24px;
  margin-top: 8px;
  padding-top: 100px;
}

h3 {
  font-size: 20px;
  border-bottom: 1px solid #e1e6ec;
  line-height: 100px;
  margin-bottom: 40px;
}

.main-wrapper {
  background-color: #f4fafa;
}

.main {
  max-width: 1100px;
  margin: 0 auto;
  padding: 96px 0;
}

.writer-container {
  margin-top: 100px;
}

.writer-card {
  display: inline-block;
  width: 22.5%;
  margin-right: 110px;
  text-align: center;
  cursor: pointer;
}

.writer-item {
  border-radius: 4px;
  padding: 30px 0 64px;
}

.modal {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.2);
}

.modal-inner {
  position: absolute;
  top: 2%;
  right: 0;
  left: 0;
  width: 480px;
  padding-bottom: 60px;
  margin: auto;
  background-color: rgb(255, 255, 255);
  border-radius: 8px;
}

.modal-header {
  height: 8px;
  width: 100%;
  margin-bottom: 60px;
  border-radius: 8px 8px 0 0;
}

.modal-introduction p {
  color: #5876a3;
  width: 384px;
  line-height: 30px;
  text-align: left;
  margin: 18px auto 40px;
}

.modal-close-btn {
  font-size: 13px;
  color: #8491a5;
  width: 200px;
  padding: 16px 0;
  border: 0;
  background-color: #f0f4f9;
  cursor: pointer;
}

.modal-close-btn:hover {
  color: #8491a5;
  background-color: #ccd9ea;
  transition: .3s ease-in-out;
}

9.最終的な出来栄え

さて、ここまで書くと最初の画面は下のようになります。

97086C7F-F224-49D8-AC9D-8306E0890B9F_1_201_a.jpeg

ツルゲーネフの画像をクリックすると

EC69A7AF-0DF8-445A-970A-66E28C4BC217_1_201_a.jpeg

のように表示されます。

英語で書いたのは、なんかその方が、それっぽいデザインになるかなと安易に考えた結果です。(恥)

10.課題点

課題点は2つあります。ただ、ツルゲーネフのおじさんが若干見切れているのはプログラムをする中での技術的失敗ではないので、無視します。写真を加工するのが面倒だっただけです。
課題点の1つめは,3人の画像が並んだ右側の余白が左側よりも大きくバランスが悪い点。これはstylesheet.cssを改良して直せそうです。
もう1つは下記の画像のように代表作と紹介文以外の部分が背景色になっておらず、真ん中の人物と右側の人物部分が明るくなってしまっている点です。原因はまだ分かりません、今後の課題とさせていただきます。

B0E02B56-12D5-47FF-A0D2-B47E330600F1_1_201_a.jpeg

おわりに

転職活動が終わったので、一つの区切りとして記事を書かせていただきました。仕事をしながらの身で、プログラミング学習をし、更にその空いた時間で転職活動を行うのは結構ハードでしたが、結論として諦めなくてよかったです。記事を読んでくださった方の中で転職活動をしている方がいらっしゃったら頑張ってください!応援してます!!良かったら、以前に記事を2つ書いているので、そちらを読んでみてください。アウトプットのレベルが低いので、「こんなやつが転職できたならオレも(ワタシも)!」と自分を奮起する材料にしていただけるだけでも、とても嬉しいです。
今は転職が決まり、自分が今一番したいことを仕事に出来たという喜びで腑抜けみたいになっていますが、今後の目標としてはNode.jsを使ってアプリを作ってみたいと考えています。そのために、もうすこしだけ戦いつづけたいと思います。ここまで読んでくださった方本当にありがとうございました!!

人生は苦闘だ
 ーー カール・マルクス

参考文献

Progate
React開発環境構築
Reactで画像を読み込む方法
プログラミング始めて2ヶ月の初心者が日本の実質GDPをSARIMAモデルで時系列分析してみた
プログラミング始めて2ヶ月半の初心者がFlaskを使ってWebアプリを作った日

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