Reactのガイド殴り書きメモ

実践的チュートリアルとは別のやつです。

# 要素とコンポーネント
## 要素
要素は読み取り専用。

```jsx
const elem1 = <h1>Hello, {name}</h1>;
const elem2 = <img src={user.avatarUrl}></img>;
```

## コンポーネント
`props`は読み取り専用。
更新したいなら`state`を使う。

```jsx
// 関数コンポーネント
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

// クラスコンポーネント
class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
```



# コンポーネントの再利用

```jsx
function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

function App() {
  return (
    <div>
      <Welcome name="Sara" />
      <Welcome name="Cahal" />
      <Welcome name="Edite" />
    </div>
  );
}
```



# state
クラスコンポーネントで「状態」を持つ。
`state`への直接代入するのはコンストラクタのみ。
`state`を更新する時は`setState`を使う。
`props`は`super`で親コンストラクタに渡す必要がある。

```jsx
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  // stateの更新
  update(){
    this.setState({
      date: new Date()
    });
  }

  render() {
    return (
      <div>
        <h1>Hello, world!</h1>
        <h2>It is {this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}
```

## state の更新は非同期に行われる可能性がある
よくわかってない
https://ja.reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous

>React はパフォーマンスのために、複数の setState() 呼び出しを 1 度の更新にまとめて処理することがあります。

>this.props と this.state は非同期に更新されるため、次の state を求める際に、それらの値に依存するべきではありません。

>例えば、以下のコードはカウンターの更新に失敗することがあります：

```jsx
// Wrong
this.setState({
  counter: this.state.counter + this.props.increment,
});
```

>これを修正するために、オブジェクトではなく関数を受け取る setState() の 2 つ目の形を使用します。その関数は前の state を最初の引数として受け取り、更新が適用される時点での props を第 2 引数として受け取ります：

```jsx
// Correct
this.setState((state, props) => ({
  counter: state.counter + props.increment
}));
```

## stateの値はそれぞれ個別に更新できる

```jsx
class Post extends React.Component {
 constructor(props) {
    super(props);
    this.state = {
      date: [],
      comment: []
    };
  }

  update() {
    this.setState({
      date: new Date()
    });

    this.setState({
      comment: "new comment."
    });
  }
}
```




# ライフサイクル
クラスコンポーネントで利用可能。

```jsx
class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  componentDidMount() {
    // ...
  }

  componentWillUnmount() {
    // ...
  }

  // ...
}
```


# TypeScriptでクラスコンポーネントを作成する場合
クラス宣言の型引数に`props`の型と`state`の型を指定する。

```jsx

type User = {
}
class Comment extends React.Component</* propsの型 */, /* stateの型 */> {
  // ...
}
```




# イベントハンドラ
onClickに関数を渡す。
イベントのデフォルト動作をキャンセルしたい場合、`preventDefault`を呼び出す。
`return false`ではキャンセルされないので注意。

```jsx
function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('Clicked.');
  }

  return (
    <a href="#" onClick={handleClick}>Click me</a>
  );
}
```

## コールバック内で`this`を使う場合
https://ja.reactjs.org/docs/handling-events.html

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Function/bind

コールバック内（下記コードの`handleClick`）で`this`を使う場合はバインドしておく必要がある。

```jsx
class Toggle extends React.Component {
  constructor(props) {
    super(props);
    this.state = {isToggleOn: true};

    // This binding is necessary to make `this` work in the callback
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    this.setState(state => ({
      isToggleOn: !state.isToggleOn
    }));
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.isToggleOn ? 'ON' : 'OFF'}
      </button>
    );
  }
}
```

または関数ではなく関数型を変数で宣言する。
執筆時の2020.10.18では試験的な構文のため仕様変更の可能性あり。

```jsx
class LoggingButton extends React.Component {
  // This syntax ensures `this` is bound within handleClick.
  // Warning: this is *experimental* syntax.
  handleClick = () => {
    console.log('this is:', this);
  }

  render() {
    return (
      <button onClick={this.handleClick}>
        Click me
      </button>
    );
  }
}
```

## TypeScriptでイベントハンドラを作成する場合
Reactのイベントを受け取る型があるので引数にはそれを指定する。

```tsx
  function handleClick(e: React.MouseEvent){
    e.preventDefault();
    console.log("Clicked");
  }
```




# 条件付きレンダー
`render()`で`return`する前に描画の中身を決めておく。

```jsx
class LoginControl extends React.Component {
  constructor(props) {
    super(props);
    this.handleLoginClick = this.handleLoginClick.bind(this);
    this.handleLogoutClick = this.handleLogoutClick.bind(this);
    this.state = {isLoggedIn: false};
  }

  handleLoginClick() {
    this.setState({isLoggedIn: true});
  }

  handleLogoutClick() {
    this.setState({isLoggedIn: false});
  }

  render() {
    const isLoggedIn = this.state.isLoggedIn;
    let button;
    if (isLoggedIn) {
      button = <LogoutButton onClick={this.handleLogoutClick} />;
    } else {
      button = <LoginButton onClick={this.handleLoginClick} />;
    }

    return (
      <div>
        <Greeting isLoggedIn={isLoggedIn} />
        {button}
      </div>
    );
  }
}
```

三項演算子でも条件分けできる。

```jsx
render() {
  const isLoggedIn = this.state.isLoggedIn;
  return (
    <div>
      The user is <b>{isLoggedIn ? 'currently' : 'not'}</b> logged in.
    </div>
  );
}
```

`render()`で`null`を返すと描画されない。

```jsx
function WarningBanner(props) {
  if (!props.warn) {
    return null;
  }

  return (
    <div className="warning">
      Warning!
    </div>
  );
}
```

# リスト表示
配列を描画することで複数コンポーネントをリストで表示できる。
keyはどのようさが変更、追加、削除されたのかをReactが識別するためのもので、要素同士の間で一意になる値を設定する。
配列が複数あっても、自身の配列内で一意であればよい。

```jsx
function NumberList() {
  const numbers = [1, 2, 3, 4, 5];
  const listItems = numbers.map((number) =>
    <li key={number.toString()}>
      {number}
    </li>
  );
  return (
    <ul>{listItems}</ul>
  );
}
```

一意になる安定した値がない場合はindexで代用できる。
ただし、並び順が変更される可能性がある場合、indexを用いることは推奨されない。

```jsx
const todoItems = todos.map((todo, index) =>
  // Only do this if items have no stable IDs
  <li key={index}>
    {todo.text}
  </li>
);
```

keyはコンポーネントの引数として渡されない。
コンポーネント内でその値を使いたいときは別の名前のpropsとして渡す必要がある。
下記のコードで、Postコンポーネントは`props.id`を読み取ることはできるが`props.key`は読み取ることができない。

```jsx
const content = posts.map((post) =>
  <Post
    key={post.id}
    id={post.id}
    title={post.title} />
);
```

リスト化はJSX内で式として埋め込むこともできる。

```jsx
function NumberList(props) {
  const numbers = props.numbers;
  return (
    <ul>
      {numbers.map((number) =>
        <ListItem
          key={number.toString()}
          value={number} />
      )}
    </ul>
  );
}
```



# フォームのバインディングとイベントハンドラ
stateの値と紐付ける。
イベントはhtmlのイベントに関数（かjsの式）を紐付ければいい。

```tsx
class NameForm extends React.Component {
  // ...
  handleSubmit(event){
    event.preventDefault();
    alert('A name was submitted: ' + this.state.value);
  }

  resetInput(event){
    event.preventDefault();
    this.setState({value: ''});
  }

  render(){
    return (
      <form onSubmit={this.handleSubmit}>
        <label>
          Name:
          <input type="text" value={this.state.value} onChange={this.handleChange} />
        </label>
        <input type="submit" value="Submit" />
      </form>
    );
  }
}
```

## textarea
テキストエリアだけ書き方が異なる。
閉じタグなしでvalueをプロパティとして持たせる。

```tsx
<textarea value={this.state.value} onChange={this.handleChange} />
```

## select
`selected`の代わりに`value`を指定する。

```tsx
<select value={this.state.value} onChange={this.handleChange}>
  <option value="grapefruit">Grapefruit</option>
  <option value="lime">Lime</option>
  <option value="coconut">Coconut</option>
  <option value="mango">Mango</option>
</select>
```

複数選択する場合。

```tsx
<select multiple={true} value={['B', 'C']}>
```

## nameで更新するプロパティを指定
`name`プロパティをstateで用いて複数のイベントを処理できる。

```tsx
class Reservation extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isGoing: true,
      numberOfGuests: 2
    };

    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleInputChange(event) {
    const target = event.target;
    const value = target.type === 'checkbox' ? target.checked : target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

  render() {
    return (
      <form>
        <label>
          Is going:
          <input
            name="isGoing"
            type="checkbox"
            checked={this.state.isGoing}
            onChange={this.handleInputChange} />
        </label>
        <br />
        <label>
          Number of guests:
          <input
            name="numberOfGuests"
            type="number"
            value={this.state.numberOfGuests}
            onChange={this.handleInputChange} />
        </label>
      </form>
    );
  }
}
```

## ライブラリ
Formikが公式おすすめのライブラリっぽい。

https://ja.reactjs.org/docs/forms.html#fully-fledged-solutions


# stateのリフトアップ
ふたつのコンポーネントで入力した値を同期してほしいなど、stateを共有したいとき、共有の親コンポーネントにstateを移動することによって実現する。
これをstateのリフトアップと呼ぶ。

下記コードはガイドのコードがすこぶるわかりにくかったのでtsにして修正したもの

ふたつのInputを表示し、片方が変更されたらもう片方も同時に変更するようにする。
TemperatureInputではstateを持たず、その親であるCalculatorでcelsiusとfahrenheitの値を管理している。

```tsx:Calculator
type CalculatorStateType = {
  celsius: string,
  fahrenheit: string
}
class Calculator extends React.Component<{}, CalculatorStateType> {
  constructor(props: {}){
    super(props);
    this.celsiusChanged = this.celsiusChanged.bind(this);
    this.fahrenheitChanged = this.fahrenheitChanged.bind(this);
    this.state = {celsius: '', fahrenheit: ''}
  }

  celsiusChanged(temperature: string) {
    this.setState({
      celsius: temperature,
      fahrenheit: tryConvert(temperature, toFahrenheit)
    });
  }

  fahrenheitChanged(temperature: string) {
    this.setState({
      fahrenheit: temperature,
      celsius: tryConvert(temperature, toCelsius)
    });
  }

  render() {
    return (
      <div>
        <p>My Calculator</p>
        <TemperatureInput scale="c" temperature={this.state.celsius}
          onTemperatureChange={this.celsiusChanged} />
        <TemperatureInput scale="f" temperature={this.state.fahrenheit}
          onTemperatureChange={this.fahrenheitChanged} />
        <BoilingVerdict celsius={parseFloat(this.state.celsius)} />
      </div>
    );
  }
}
```

```tsx:TemperatureInput
type TemperatureInputProps = {
  scale: keyof nameType,
  temperature: string,
  onTemperatureChange: ((arg0: string)=>void)
}
class TemperatureInput extends React.Component<TemperatureInputProps, {}> {
  constructor(props: TemperatureInputProps) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(event: React.ChangeEvent<HTMLInputElement>) {
    // this.setState({temperature: event.target.value});
    this.props.onTemperatureChange(event.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>
    )
  }
}
```




# 子要素を引数として渡す
親コンポーネントから子コンポーネントに要素を渡して描画できる。
ダイアログなど。

```tsx
function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}
```

```tsx
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}
```

## 複数要素を引数として渡す
デフォルトの引数名は`children`だが名前を付けて複数受け取ることもできる。

```tsx
function SplitPane(props) {
  return (
    <div className="SplitPane">
      <div className="SplitPane-left">
        {props.left}
      </div>
      <div className="SplitPane-right">
        {props.right}
      </div>
    </div>
  );
}

function App() {
  return (
    <SplitPane
      left={
        <Contacts />
      }
      right={
        <Chat />
      } />
  );
}
```


# ガイドのサンプルプロダクト
https://ja.reactjs.org/docs/thinking-in-react.html