💡

AngularユーザーのためのReactチートシート

2020/10/03に公開

普段はAngularで書いていて、Reactも勉強し始めたのでAngularユーザー視点でのReactの書き方をまとめてみました。
もう少し詳しくなったら項目を増やしていく予定です。

ng new my-app

TypeScriptで書きたい場合は--template typescriptを追加する。

npx create-react-app my-app --template typescript

[foo]="bar"

Angularにおける@Input()プロパティ。
親から値を渡すにはpropsに値を渡す。propsはイミュータブルなのでコンポーネント内からは値を弄れないようになっている。

const MyButton: React.FC<{ label: string }> = (props) => {
  return <button>{props.label}</button>;
};

const SaveButton = () => {
  const label = 'Save';
  retrun <MyButton label={label} />;
};

(click)="onClick()"

イベントハンドラ関数の参照を合成イベントに渡すことでイベントが発生したときの処理を実行することができる。

const MyButton: React.FC = () => {
  const handleClick = () => {
    console.log('clicked!');
  };
  return <button onClick={handleClick}>My button</button>;
};

ブラウザのイベントを受け取りたい場合

インラインで書くとイベントの型を推論してくれる。

const MyButton: React.FC = () => {
  return <button onClick={(e) => console.log(e)}>My button</button>;
};

イベントを伝播させる

親コンポーネントにイベントの変更を伝えるにはpropsにイベントハンドラ関数を定義し、親コンポーネントから渡す。

type Props = {
  value: string;
  onChange: (value: string) => void
};

const MyInput: React.FC<Props> = (props) => {
  return (
    <input
      value={props.value}
      onChange={(e) => props.onChange(e.target.value)}
    />
  );
};

ReactではAngularと異なり、時間とともに変更されうる値はstateで管理する必要がある。

// ステートフックを用いて関数でコンポーネントを作成する場合
const SearchBox: React.FC = () => {
  const [keyword, onChangeKeyword] = useState('');
  return (
    <fieldset>
      <legend>Search:</legend>
      <MyInput value={keyword} onChange={(v) => onChangeKeyword(v)} />
    </fieldset>
  );
};

// クラスを用いてコンポーネントを作成する場合
class SearchBox extends React.Component<{}, { value: string }> {
  state = {
    value: ''
  };
  render() {
    return (
      <fieldset>
        <legend>Search:</legend>
        <MyInput
	  value={value}
	  onChange={(value) => this.setState({ value })}
	/>
      </fieldset>
    );
  }
}

*ngIf

{}の式埋め込みと&&を使った方法

// <></>で囲うことでng-containerと同様に余分なDOMを生成しない
<>
  {somethingList.length > 0 && <ListContainer items={somethingList} />}
</>

三項演算子を使ったif-else

<>
  {loggedIn ? <LogoutButton /> : <LoginButton />}
</>

*ngFor

mapでReactDOMに変換する。
Reactが要素を一意に識別するためのkeyを渡すのを忘れないように。

<div>
  {users.map((user) => <User key={user.id} user={user} />)}
</div>

コメントアウト

<div>
  {/* <MyComponent /> */}
<div>

<ng-content></ng-content>

コンポーネントの子要素はprops.childrenで取得できる。

const ScrollableContainer: React.FC = (props) => {
  return (
    <div className="ScrollableContainer">
      {props.children}
    </div>
  );
};
<ScrollableContainer>
  <h1>Orders</h1>
  <ul>
    {orderList.map(order => <li>{order.name}</li>)}
  </ul>
</ScrollableContainer>

コンポーネントの型にReact.FC<P>を指定すればchildrenをPropsの型定義入れなくても追加される

// Reactの型定義ファイルを覗くとchildrenプロパティが追加されてるのがわかる
type PropsWithChildren<P> = P & { children?: ReactNode };

childrenの型からもわかるように、埋め込みたいコンポーネントを(childrenとは別に)propsに入れて渡す事もできる。

const Dialog: React.FC<{ header: ReactNode }> = (props) => {
  return (
    <div className="Dialog">
      <div class="Dialog-header">
        {props.header}
      </div>
      <div class="Dialog-content">
        {props.children}
      </div>
    </div>
  );
};

ngOnInit / ngOnChanges / ngOnDestroy

Angularのコンポーネントにライフサイクルがあるのと同様にReactのコンポーネントにもライフサイクルがある。

class UserProfile extends React.Component<Props, State> {
  componentDidMount() {
    // コンポーネントがDOMツリーに挿入された後に呼び出される
    // HTTPリクエストの呼び出しに適した場所
    fetchUser(this.props.userId).then((user) => this.setState(user));
  }
  componentDidUpdate() {
    // propsが更新されたときに呼び出される
    fetchUser(this.props.userId).then((user) => this.setState(user));
  }
  render() {
    // renderにはstateを書き換えるなど副作用をもたせてはいけない
    return /* ... */;
  }
}

注意点として、AngularではngOnInitが呼び出されるのは最初のngOnChangesの後であり、初回もngOnChangesが呼び出される。
一方で、ReactのcomponentDitUpdateは初回のDOMレンダリング時には呼び出されない。

コンポーネントがDOMツリーから削除されたときに処理を実行したい場合はcomponentWillUnmount内に記述する

class UserProfile extends React.Component<Props, State> {
  componentDidMount() {
    subscribeUser(this.props.userId, (user) => this.setState(user));
  }
  componentWillUnmount() {
    unsubscribeUser(this.props.userId, (user) => this.setState(user));
  }
  render() {
    return /* ... */;
  }
}

関数コンポーネントでReactのライフサイクルを利用するには副作用フックを使う

const UserProfile: React.FC<Props> = (props) => {
  const [user, setUser] = useState<User | null>(null);
  useEffect(() => {
    subscribeUser(props.userId, (user) => setUser(user));
    return () => unsubscribeUser(props.userId, (user) => setUser(user));
  });
  return /* ... */;
};

参考

Discussion