- Published on
React基礎
- Authors
 - Name
- Kikusan
 
 
- Refs
- shell
- eslint
- VS Code拡張
- チュートリアル
- レンダーとJSX
- コンポーネントとprops
- 関数コンポーネント
- クラスコンポーネント
- Component Lifecycle
- if
- for
- 組み込みコンポーネント
- Reack Hooks
- useState
- useEffect
- useMemo
- useRef
- useContext
- useReducer
- 独自フック
- React Router
- Redux
Refs
- チュートリアル: https://ja.reactjs.org/tutorial/tutorial.html
- ドキュメント: https://ja.reactjs.org/docs/getting-started.html
shell
npx create-react-app hello-world --template=typescript
cd hello-world
# eslint グローバルインストールでパスを通す 他にプロジェクトがあれば -> https://qiita.com/mysticatea/items/6bd56ff691d3a1577321
npm install -g eslint
npm i -D eslint-config-airbnb eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react
eslint --init # 質問に答える .eslintrc.[js/yaml]が生成されるのを書き換えて自分好みにする
eslint src # コードチェック
eslint
読み込みファイルを限定するため.eslintrc.jsのparseOptionsにproject: "./tsconfig.eslint.json"を設定し、ファイルを以下の内容で作成する。
{
  "extends": "./tsconfig.json",
  "include": [
    "src/**/*.js",
    "src/**/*.jsx",
    "src/**/*.ts",
    "src/**/*.tsx"
  ], 
  "exclude": [ "node_modules" ]
}
VS Code拡張
- ESLint: 静的コード解析ツールLintの統合. https://qiita.com/Mount/items/5f8196b891444575b7db
- Prettier: コード整形ツールPrettierの統合. CMD + Shift + P -> Format Documenthttps://qiita.com/takeshisakuma/items/bbb2cd2f1c65de70e363
チュートリアル
github: https://github.com/kikusan-16/react-tutorial
このチュートリアルはクラスコンポーネントを利用したstateを使用している。
この管理方法ではstateを子孫にpropsとして渡しているが、
state管理の問題としてpropがバケツリレーされるprop drilling問題があり、これを解決するためにreduxがあった。
ただ、今はReact関数コンポーネントにReact Hooksが導入され、状態管理を簡潔に行えるようになった。
今は関数コンポーネントを基本的に使用して、できないことがある場合にクラスコンポーネントをつかうといい。
レンダーとJSX
React要素はrenderによりDOMにレンダリングされる。
JSXの特性
- コンパイルによってReact.createElement()関数に変換される。(帰ってくるのはObject=React要素)
- タグを生成できる。{}で変数を代入できる。const element = <h1>Hello, {name}</h1>;
- デフォルトでXSSを防ぐ。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
root.render(
  // strictモードは本番ビルドには影響を与えない
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
ReactDOMは要素とその子要素に必要な分だけDOMの更新を行う。
コードでJSX全体を塗り替えるように書いていても、差分がある部分だけ更新される。
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
const tick = () => {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2> {/* 更新されるのはここだけ*/}
    </div>
  );
  root.render(element);
}
setInterval(tick, 1000);
import React from 'react';はJSXがコンパイルされた後必要になるので必ず記載が必要だったが、
TypeScriptではtsconfig.jsonのcompilerOptions.jsx=react-jsxとするとimportしなくても動作するようになる。
コンポーネントとprops
関数コンポーネント
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
// propsの型定義
type Props = {name: String, children?: React.ReactNode};
// TypeScriptでジェネリクス使用
const Welcome: React.FunctionComponent<Props> = (props: Props) => {
  // 分割代入
  const { name, children } = props;
  return <>
           <h1>Hello, {name}</h1>
           {/* childrenは暗黙的に呼び出し時の中身のタグを受け取れる */}
           {children}
         </>;
}
root.render(
  // propsに属性を渡せる
  <Welcome name="Kikusan">
    {/* childrenに子要素を渡せる */}
    <p>this is children</p>
  </Welcome>
);
クラスコンポーネント
クラスコンポーネントではstateとライフサイクルメソッドを使用できる。
Hooksがある今は率先して使う必要はない。
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
type Props = {date?: Date};
type State = {date?: Date};
// <props, state>で型指定
class Clock extends React.Component<Props, State> {
  
  timerID?: number;
  // stateの更新はコンストラクタで
  constructor(props: Props) {
    super(props);
    this.state = {date: new Date()};
  }
  // レンダリングはrenderメソッドで
  render() {
    return <h2>It is {this.state.date && this.state.date.toLocaleTimeString()}.</h2>
  }
  // マウント時一度だけ呼ばれる。データロードはcomponentDidMountで
  componentDidMount = () => {
    this.timerID = window.setInterval(
      () => this.tick(),
      1000
    );
  }
  // 更新があると呼ばれる。stateとpropsの変更はここで。
  componentDidUpdate = () => {
    console.log(this.state);
  }
  // 削除時処理はcomponentWillUnmountで
  componentWillUnmount = () => {
    clearInterval(this.timerID);
  }
  tick = () => {
    // stateの更新はsetStateで行う
    this.setState({
      date: new Date()
    });
  };
}
root.render(<Clock />);
Component Lifecycle
- constructor: 1度だけの初期化
- render: JSXの返却以外は行わない
- (表示される)
- componentDidMount: マウントされた時1度だけ呼ばれる。データロードにちょうどいい
- (更新を待つ)
- componentDidUpdate: 親か自分の要素に変更があった時に呼ばれる。state/propsの変更をするのにちょうどいい
- (コンポーネントが表示されなくなるのを待つ)
- componentWillUnmount: 通常は使用せずなにかcleanupが必要な場合使用する
if
JavaScriptのifを使用するか、jsxでは{}で真偽値を使用する。
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
const n = Math.floor(Math.random()*10);
root.render(
    <>
      {/* 偽の場合はレンダリングされない */}
      {n > 5 && <p>{n} is larger than 5 </p>}
      <p>{n} is {n % 2 === 0 ? 'even' : 'odd'}</p>
    </>
);
for
配列を使用する。
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
);
const numbers = [1, 2, 3, 4, 5];
root.render(
  <>
    <ul>
      {/* keyはReactの識別性のために必要。並び替えが変わる場合を考慮して、index以外を使用すること */}
      {numbers.map((number) =>
        <li key={number.toString()}>{number}</li>
      )}
    </ul>
  </>
);
組み込みコンポーネント
予め用意されたタグがJSXでは使える。
基本的にhtmlと同じだが、JavaScriptの命名規則に合わせて属性名が変わっている。
(ユーザ定義コンポーネントでも同じだが)boolean値は値を省略するとtrueになる。
https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts#L3140
Reack Hooks
関数コンポーネントでのみ、使える。
ループや条件分岐内ではなく、React関数内トップレベルで使用する。
Hooksにおけるpropsやstateの値はレンダリング毎に固定される。
useState
オブジェクトの状態を保つ。
import React, { useState } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
  // count=変数
  // setCount=関数, setCount(処理)
  // useState(初期値)
  const [count, setCount] = useState(0)
  // hooks関数には関数を渡せる。引数にはstateが入る
  // count+1でも書けるが、hooksではstateの値が常に最新とは限らないので関数を渡す
  const increment = (): void => setCount((c) => c + 1)
  return (
    <div>
      <p>You clicked { count } times</p>
      <button onClick={ increment }>
        Click me
      </button>
    </div>
  )
}
root.render(<Example />)
useEffect
副作用がある場合のオブジェクトの書き換え。componentDidMount and componentDidUpdateに似ている。
import React, { useState, useEffect } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
  const [count, setCount] = useState(0)
  /* useEffect
   * 第二引数は依存配列といい、配列内の変数に差分があると第一引数が実行される。
   * 省略するとレンダリング毎に第一引数が実行される。
   * [] のときはcomponentDidMountと同じように実行される。
   * [count] のときはcomponentDidUpdateと同じように実行される。
   *
   * 正確にはcomponentDidMountは描画前に実行されるが、useEffectは先に初期値で描画される
   * (これを利用してロード中を簡単に表せる)
   */
  useEffect(() => {
    // Update the document title using the browser API
    document.title = `You clicked ${count} times`
    // return () => clearSomething() 戻り値に関数を入れるとunMount時に実行
  }, [count])
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}
root.render(<Example />)
useMemo
計算量の多い処理を分けておき、依存配列に変化があった場合だけ再計算するようにできる。
import React, { useState, useMemo } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const Example = (): React.ReactElement => {
  const calc = (n: number): number => {
    return n ** 2
  }
  const [count, setCount] = useState(0)
  // 依存配列の中身に変更があった場合だけ再計算
  const memoizedValue = useMemo(() => calc(count), [count])
  return (
    <div>
      <p>{`You clicked ${count} times => ${memoizedValue}`}</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  )
}
root.render(<Example />)
useRef
Refはレンダリングではなくて要素の変更を行いたい場合に使用する。
リアルDOMの要素を参照できる。
以下の場合に使用できる。
- フォーカス、テキストの選択およびメディアの再生の管理
- アニメーションの発火
- サードパーティの DOM ライブラリとの統合
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
const TextInput: React.FunctionComponent = () => {
  // HooksのuseRef(初期化値)
  const inputRef = React.useRef<HTMLInputElement>(null);
  const onButtonClick = (): void => {
    // current はrefが渡されたリアルDOMオブジェクトになる
    if (inputRef.current) inputRef.current.focus();
  };
  return (
    <div>
      {/* refを渡す */}
      <input type="text" ref={inputRef} />
      <input type="button" value="Focus" onClick={onButtonClick} />
    </div>
  );
};
root.render(<TextInput />);
useContext
コンポーネントへの値の受け渡しを可能にする。useContext呼び出しオブジェクトから、直近の<ContextName.Provider>が記載された値を取り出す。
import React, { useContext } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const themes = {
  light: {
    foreground: '#000000',
    background: '#eeeeee'
  },
  dark: {
    foreground: '#ffffff',
    background: '#222222'
  }
}
// createContext(デフォルト値)でcontextを作成する
const ThemeContext = React.createContext(themes.light)
const App = (): React.ReactElement => {
  return (
    // タグにcontext.Provider value= を使用してツリーの子孫に値を差し替えられる。
    // 子供は直近の親のcontext.Providerに作用される
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  )
}
const Toolbar = (): React.ReactElement => {
  return (
    <div>
      <ThemedButton />
    </div>
  )
}
const ThemedButton = (): React.ReactElement => {
  // useContextでcontextを使用する。
  const theme = useContext(ThemeContext)
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  )
}
root.render(<App />)
useReducer
React Hooksで個別のコンポーネントにおいてReduxの機能を再現したもの。 useStateの代替品として使用され、複数の値にまたがる複雑なstateロジックがある時に使う。
import React, { useReducer } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const initialState = { count: 0 }
const CounterActionType = {
  increment: 'counter/increment',
  decrement: 'counter/decrement'
} as const // as const で全ての値をreadonlyにする
type ValueOf<T> = T[keyof T] // Tのvalueのユニオン型
type CounterAction = {
  type: ValueOf<typeof CounterActionType> // counter/increment | counter/decrement
}
type CounterState = { count: number }
// reducer (state, action): newState
const reducer = (state: CounterState, action: CounterAction): CounterState => {
  switch (action.type) {
    case 'counter/increment':
      return { count: state.count + 1 }
    case 'counter/decrement':
      return { count: state.count - 1 }
    default:
      throw new Error()
  }
}
const Counter = (): React.ReactElement => {
  // 現在のstateとreducerを呼ぶためのdispatcherを受け取る。
  const [state, dispatch] = useReducer(reducer, initialState)
  return (
    <>
      Count: { state.count }
      <button onClick={() => dispatch({ type: 'counter/decrement' })}>-</button>
      <button onClick={() => dispatch({ type: 'counter/increment' })}>+</button>
    </>
  )
}
root.render(
  <Counter />
)
独自フック
再利用可能な処理を切り出して定義しておくこと。
名前はuse~をつける。
やってることは関数分離だが、呼び出し元のstateやeffectを使用できる。
import React, { useState } from 'react'
import ReactDOM from 'react-dom/client'
const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
const useCount = (): Array<number | (() => void)> => {
  const [count, setCount] = useState(0)
  const increment = (): void => setCount((c) => c + 1)
  return [count, increment]
}
const Example = (): React.ReactElement => {
  const [count, increment] = useCount()
  return (
    <div>
      <p>You clicked { count as number } times</p>
      <button onClick={ increment as () => void }>
        Click me
      </button>
    </div>
  )
}
root.render(
  <>
    <Example />
    <Example />
  </>
)
React Router
公式: https://reactrouter.com/en/main
- install
npm install react-router-dom
チュートリアルから動作を見ることができる。
- Routerが履歴管理をしてくれることで戻る動作などに対応できる(aでなくLinkタグを使うのもそのため)
- HooksAPIを使用することで履歴やURLパラメータなどを使用できる
- ReactGAライブラリを使用してSPAでもGoogleAnalyticsの機能を使える
- ReactHelmetライブラリを使用することでSPAでもHeader情報を書き換えることができる
Redux
Fluxアーキテクチャを採用した状態管理ライブラリ FluxではMVCでのmodel ⇄ view の複雑な関係性をよしとせず、単方向データフローを提供する。
- Store: アプリケーションの状態データの集合
- Action: イベントそのもの
- Dispatcher: Actionに応じてStoreの更新処理を行うもの
- View: Reactなど
reduxではここにreducer: 唯一storeのデータを変更できる純粋関数を追加する。
参考画像: https://redux.js.org/tutorials/essentials/part-1-overview-concepts#redux-application-data-flow
Viewからは状態データの変更は行わず、Actionを起動する。
- install
npm install redux react-redux