JavaScriptとReactのお勉強

参考にした本

初心者向けの以下の本でお勉強。あと少しで終わりそう。

公式のReactチュートリアルもあるのだが、JavaScript自体の経験もないのでJavaScriptのお勉強も兼ねて買ってみた

内容自体はReactに入る前に多用されるJavaScriptの機能について使い方を説明してくれているのが良かった。

その一方でReactのローカルでの環境構築方法についてはほぼ触れていないので、そこは自分で補う必要がある。

内容については正直薄いので、他の勉強も多く必要だろう。

JavaScript

変数宣言

constとlet

const a = 0; // 書き換え不可
let b = 1; // 書き換え可

テンプレート文字列

バッククオートで囲まれた文字列中に変数や式を直接埋め込んで文字列にできる。シェルスクリプト的な感じ。 テンプレートリテラル (テンプレート文字列) - JavaScript | MDN

const a = 5;
const b = 10;

console.log(`a=${a}, b=${b}, a+b=${a+b}`)

アロー関数

慣れると楽な書き方。 アロー関数式 - JavaScript | MDN

const onClickSwitch = (a, b) => {
	let result = a + b;
	return result;
};

オブジェクトのデフォルト値

const myProfile = {
	age: 24,
};
const {name = "Sato"} = myProfile;

const message = `Hello, ${name}.`;
console.log(message); // Hello, Sato.

スプレッド構文/残余構文

... で配列やオブジェクトの要素を展開する。

let numberStore = [0, 1,2];
const newNum = 12;
numberStore = [...numberStore, newNum];

const myFunction = (a, b, c, d) => a+b+c+d;

// 関数呼び出し時に展開することで引数を一つづつ書かないようにする(残余構文)
myFunction(...numberStore);

分割代入

分割代入 - JavaScript | MDN

let a, b, rest;
// 配列の分割代入
[a, b] = [10, 20]; // a=10, b=20
[a, b, ...rest] = [10, 20, 30, 40, 50] // a=10, b=20, rest=Array [30, 40, 50]

// オブジェクトの分割代入
({a, b, ..rest}) = {a:10, b:20, c:30, d:40, e:50});// a=10, b=20, rest=Object {c:30, d:40, e:50}

// 戻り値の受け取り(要らない戻り値は無視できる)
function f() {
	return [1, 2, 3];
}
const [a, ,c] = f();
console.log(a); // 1
console.log(c); // 3

map

Array.prototype.map() - JavaScript | MDN

map(関数)の形で与えられた関数を配列の要素に対して実行し 新しい配列 を生成する。

const array1 = [1, 4, 9, 16];
const map1 = array1.map(x => x * 2);

console.log(map1); // Array [2, 8, 18, 32]

filter

Array.prototype.filter() - JavaScript | MDN

filter(関数)の形で与えられた関数によって実装されたテストに対して条件が一致する(trueになる)要素のみからなる 新しい配列 を生成する。

const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];

const result = words.filter(word => word.length > 6);

console.log(result); // Array ["exuberant", "destruction", "present"]

JSXのルール

return以降は一つのタグで囲われている必要がある

import {Fragment} from "react";
<Fragment></Fragment> // Fragmentを利用
<></> // Fragmentの省略記法
// 単なるdivで囲っても良い
import ReactDOM from "react-dom";

const App = () => {
	return(
		<>
			<h1>Text</h1>
		</>
	);
};

HTMLの(ような)タグの中では {} で囲うことで JavaScriptを記述できる。

import ReactDOM from "react-dom";

const App = () => {
	return(
		<>
			{console.log("test")} // OK
			<h1>Text</h1>
		</>
	);
};

コンポーネント

exportすればOK

App.jsx

export const App = () => {
	return(
		<>
			<h1>Text</h1>
		</>
	);

}

index.js

import ReactDOM from "react-dom";
import { App } from "./App";
ReactDOM.render(<App />), document.getElementById("root");

イベント

Reactの場合はイベント等がキャメルケースで書かれることに注意

onClick

Propsとchildren

コンポーネントにわたす引数。特別な扱いのpropsとしてchildrenがある。childrenにはcomponent名に囲まれたあらゆる要素が入るので、タグの塊を渡すこともできる。

MyComp.jsx

export const MyComp = (props) => {
    console.log(props);
    // children: "child"
    // p1: "props1"
    // p2: "props2"
    return (
        <>
        <p>hello, {props.p1}, {props.p2}, {props.children}</p>
        </>
    );
};

App.jsx

import {MyComp} from "./components/MyComp";

export const App = () => {
  return (
    <>
		  <MyComp p1="props1" p2="props2">child</MyComp>
	  </>
  )
};

Stateとhook

State

コンポーネントの状態を表す値。

useState

hookの一つでStateを使う場合に再レンダリングしても状態を保持する変数とその値を更新するための関数を戻り値とする。useStateは複数回呼ぶことができる。

import { useState } from "react";
export const App = () => {
  const [count, setCount] = useState(0);
  // 0はnumの初期値、numは保持したい値、setNumはnumを変更するときに呼ぶ関数名
  
  return (
	  <>
		  <p>You clicked {count} times</p>
		  <button onClick={()=> setCount(count+1)}
		</>
  );
  
}

useEffect

副作用フックの利用法 – React

レンダリング後、処理を実行するhook。 以下の例では、useStateで指定されたsetCountによってcountが変化した場合再レンダリングされるが、この時useEffectの引数として渡された関数(ここではdocument.titleを変更する関数)が毎度呼び出されるようになる。useEffectは 初回レンダリング毎回の更新時 に呼ばれる(つまりレンダリング後には毎回呼ばれる)

useEffectは複数回呼ぶことができる。

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

毎回レンダリング後にuseEffectを呼ぶ必要がない場合もある。上記の例ではcountが変わっていない場合はtitleを変更する必要がないはずなので、それ以外の理由で再レンダリングされたとしてもuseEffectで指定した関数を実行する必要がない。これを回避するためにuseEffectの第2引数に依存する値を指定することができ、依存する値が変更された時のみ実行するようになる。

import React, { useState, useEffect } from 'react';

function Example() {
  const [count, setCount] = useState(0);

  // Similar to componentDidMount and componentDidUpdate:
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`;
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

なお、useEffectで指定した関数を1度だけ(マウント時/アンマウント時)実行したい場合、空の配列を第2引数として渡せばよい(いずれの値にも依存していないことを明示する)

useContext

コンテクスト – React

Reactでのデータ受け渡しはpropsを通じて行われるが、多くのコンポーネントから参照されるプロパティ(例:ロケール設定、UIテーマ)を毎度propsで受け渡ししてしまうとコンポーネント自体の再利用性が下がってしまう。毎度propsを受け取る前提でコンポーネントを書かなくてはならない。それを避けるためにコンテキストはコンポーネント間で共通で値を利用するための方法を提供する。(グローバル変数みたいなものかな…??)

// コンテクストを使用すると、全てのコンポーネントを明示的に通すことなしに
// コンポーネントツリーの深くまで値を渡すことができます。
// 現在のテーマ(デフォルトは "light")の為のコンテクストを作成します。
const ThemeContext = React.createContext('light'); // = テーマ変数を入れるための変数

class App extends React.Component {
  render() {
    // 以下のツリーへ現在のテーマを渡すためにプロバイダを使用します。
    // どんな深さでも、どのようなコンポーネントでも読み取ることができます。
    // このサンプルでは、"dark" を現在の値として渡しています。
    return (
      <ThemeContext.Provider value="dark"> // Providerで変数設定する
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

// 中間のコンポーネントはもう明示的にテーマを
// 下に渡す必要はありません。
function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  // 現在のテーマのコンテクストを読むために、contextType に指定します。
  // React は上位の最も近いテーマプロバイダを見つけ、その値を使用します。
  // この例では、現在のテーマは "dark" です。
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

メモ化

メモ化 - Wikipedia

計算結果を再利用できるように保持し、関数の呼び出しごとの再計算を防ぐ。

再レンダリングがおきる条件

  1. Stateが更新されたコンポーネント
  2. Propsが変更されたコンポーネント
  3. 再レンダリングされたコンポーネント配下のコンポーネント全て

3番のパターンではメモ化を意識しないと再レンダリング時の処理が無駄に多くなってしまう。

React.memo

React の最上位 API – React.memo

コンポーネント自体のメモ化を行う。 React.memoはpropsの変化があった時のみそのコンポーネントを再レンダリングする。

ただしpropsに コールバック関数 を渡している場合、その関数自体に変更がなかったとしても呼び出し元のコンポーネント自体が再レンダリングされている場合、関数の参照が変わってしまう(オブジェクトが変わる)ので、結果として再レンダリングされてしまう。そこで次の useCallback を組み合わせ使うことになる。

const MyComponent = React.memo(function MyComponent(props) {
  /* render using props */
});

useCallback

フック API リファレンス – React

メモ化された関数を返す。このメモ化された関数を React.memoでメモ化したコンポーネントのpropsとして渡してやれば、再レンダリングを防ぐことができる。※React.memoでメモ化していないコンポーネントに対して、useCallbackでメモ化した関数を渡しても再レンダリングされてしまい意味がないので気をつける。またメモ化した関数自体を、それを生成したコンポーネント自身で利用しても同じように意味がない(オブジェクトが変わるので再レンダリングされる)

const memorizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
	[a, b],
)

useMemo

フック API リファレンス – React

メモ化された値を返す。以下の例では第一引数である computeExpensiveValue 関数自体が変数として設定され、題二引数としてcomputeExpensiveValueが依存する変数 a, bを渡している。 a, bが変化した場合に computeExpensiveValueが再計算されることになる(変化しなかった場合は計算されない)

useMemoでは第一引数にコンポーネントを指定することもできる(レンダリング結果をメモ化することもできる)

const memorizedValue = useMemo(() => computeExpensiveValue(a+b), [a, b]);

React.memoとuseMemoの違いがわからん

useMemoでもコンポーネントを指定できるので、コンポーネント自体をメモ化することについては同じ機能を持つように思える。 ただ、React.memoは 高階 (Higher-Order) コンポーネント – React であり、useMemoはあくまでもhookの一つということで、それぞれ制約が異なる。

高階コンポーネント

高階 (Higher-Order) コンポーネント – React

上記ページより引用

高階コンポーネントとは、あるコンポーネントを受け取って新規のコンポーネントを返すような関数です。 コンポーネントが props を UI に変換するのに対して、高階コンポーネントはコンポーネントを別のコンポーネントに変換します。

フックのルール

フックのルール – React

上記ページより引用

フックを呼び出すのはトップレベルのみ

フックをループや条件分岐、あるいはネストされた関数内で呼び出してはいけません。代わりに、あなたの React の関数のトップレベルでのみ、あらゆる早期 return 文よりも前の場所で呼び出してください。これを守ることで、コンポーネントがレンダーされる際に毎回同じ順番で呼び出されるということが保証されます。これが、複数回 useState や useEffect が呼び出された場合でも React がフックの状態を正しく保持するための仕組みです(興味がある場合はページ下部で詳しく説明しています)。

フックを呼び出すのは React の関数内のみ

フックを通常の JavaScript 関数から呼び出さないでください。代わりに以下のようにします。

✅ React の関数コンポーネント内から呼び出す。 ✅ カスタムフック内(次のページで説明します)から呼び出す。 このルールを守ることで、コンポーネント内のすべての state を使うロジックがソースコードから間違いなく参照可能になります。

CSSの適用方法

Inline Styles

DOM 要素 – React#style

注意点

  • プロパティ名はキャメルケースにする
  • 値は文字列 or 数字

上記より引用

style 属性は CSS 文字列ではなく、キャメルケースのプロパティを持った JavaScript オブジェクトを受け取ります。これは JavaScript での DOM の style プロパティとの一貫性があり、より効率的で、XSS 攻撃の対象となるセキュリティホールを防ぎます。

パフォーマンスについてはインラインスタイルはあまり良くないらしい。 CSS とスタイルの使用 – React

パフォーマンス観点から言えば、基本的に CSS クラスを使う方が、インラインスタイルを用いるよりも優れています。

従来のCSSファイルの読み込み

cssやscssファイルを別途定義する従来のWeb開発と同じ方法。 importして利用する。このやり方でcssをimportすると「コード全体に」スタイルが適用されることに注意する。

import "./path/to/styles.css";

CSS Modules

全体に適用するのではなく、特定のコンポーネントだけに適用するなどimportしたファイルだけに適用したい場合はモジュール化して適用する。

ファイル名を styles.module.css というように module という文字列をつけてやる。

import "./path/to/styles.module.css";

Sassの場合も同様に使うことができる。

Styled JSX

Next.jsに標準で組み込まれているライブラリ。CSS-in-JSと呼ばれる、コンポーネントファイルにCSSを記述していくライブラリ。

npm install --save styled-jsx

インストールして以下のように使う。

export default () => (
  <>
    <p>Hello World!</p>
    <style jsx>{`
    p {
      color: red;
    }
    `}</style>
  </>
);

styled components

styled components というライブラリを使うパターン。 これも CSS-in-JS と呼ばれるコンポーネントファイルにCSSを記述していくライブラリ。

npm install styled-components

インストールして以下のように使う。

import styled from "styled-components";
const StyledDiv = styled.div`
  color: red;
`;

export const StyledComponents = () => {
	return (
		<StyledDiv>
			<p>Hello world</p>
		</StyledDiv>
	);
};

メリットとしてはコンポーネント単位でCSSも決まるので複数のCSSをたどる必要もなく楽、というメリットがある。 一方で複数人で開発する場合には命名規則等を設けないとバラバラの名前の付け方になってしまうといったデメリットもある。

コンポーネントライブラリ

統一されたデザインを作るのが大変なので、先にスタイルが決まったコンポーネントを定義して、そのコンポーネントを使うことでデザインが統一されたUIを作ることができる。 Material UIやChakra UIなどの有名なコンポーネントライブラリがある。

MUI: The React component library you always wanted

ReactのUIコンポーネントライブラリ「Chakra UI」とは? カスタマイズ性と生産性を両立しよう【前編】 (1/3):CodeZine(コードジン)

Headless UI – Unstyled, fully accessible UI components