0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Reactのガイド殴り書きメモ

0
Posted at

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

要素とコンポーネント

要素

要素は読み取り専用。

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

コンポーネント

propsは読み取り専用。
更新したいならstateを使う。

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

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

コンポーネントの再利用

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を使う。
propssuperで親コンストラクタに渡す必要がある。

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 を求める際に、それらの値に依存するべきではありません。

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

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

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

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

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

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

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

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

ライフサイクル

クラスコンポーネントで利用可能。

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

  componentDidMount() {
    // ...
  }

  componentWillUnmount() {
    // ...
  }

  // ...
}

TypeScriptでクラスコンポーネントを作成する場合

クラス宣言の型引数にpropsの型とstateの型を指定する。


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

イベントハンドラ

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

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

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

コールバック内でthisを使う場合

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

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では試験的な構文のため仕様変更の可能性あり。

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のイベントを受け取る型があるので引数にはそれを指定する。

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

条件付きレンダー

render()returnする前に描画の中身を決めておく。

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>
    );
  }
}

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

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

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

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

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

リスト表示

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

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を用いることは推奨されない。

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は読み取ることができない。

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

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

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

フォームのバインディングとイベントハンドラ

stateの値と紐付ける。
イベントはhtmlのイベントに関数(かjsの式)を紐付ければいい。

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をプロパティとして持たせる。

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

select

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

<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>

複数選択する場合。

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

nameで更新するプロパティを指定

nameプロパティをstateで用いて複数のイベントを処理できる。

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が公式おすすめのライブラリっぽい。

stateのリフトアップ

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

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

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

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>
    );
  }
}
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>
    )
  }
}

子要素を引数として渡す

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

function WelcomeDialog() {
  return (
    <FancyBorder color="blue">
      <h1 className="Dialog-title">
        Welcome
      </h1>
      <p className="Dialog-message">
        Thank you for visiting our spacecraft!
      </p>
    </FancyBorder>
  );
}
function FancyBorder(props) {
  return (
    <div className={'FancyBorder FancyBorder-' + props.color}>
      {props.children}
    </div>
  );
}

複数要素を引数として渡す

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

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 />
      } />
  );
}

ガイドのサンプルプロダクト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?