要素のレンダー

要素のレンダー

要素のレンダー

要素とは React アプリケーションの最小単位の構成ブロックです。

要素は画面に表示したいものの説明書き。

<h1>Hello, world</h1>

React 要素はプレーンなオブジェクト、安価に作成できる。安価というのは、素早くハードの省資源で作れるという意味。

描画する

React だけで構築されたアプリケーションは、ルート DOM ノードは1つ。

既存アプリケーションと React の併用の場合は、ルート DOM ノードは複数持てる。

React 要素とルート DOM ノードをレンダーに渡す。

    <div id="root"></div>
    <script type="text/babel">
      const element = <h1>Hello, world</h1>;
      ReactDOM.render(element, document.getElementById('root'));

レンダリングされたページを inspect で調べると <div></div> の間に <h1>Hello, world</h1> が挿入されています。

要素の更新

React 要素はイミュータブル。生成後は、子要素、属性を変更できない。特定の時点のUIを表す。

更新するには、新しい要素を作成して ReactDOM.render() に渡す必要がある。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(element, document.getElementById('root'));
}

setInterval(tick, 1000);

秒刻みで表示する時分秒のみが更新される。

任意の時点で UI がどのように見えるべきかを考える。バグを排除することができる。

コンポーネントと props

コンポーネントと props

props を受け取り、React 要素を返す。

関数コンポーネントとクラスコンポーネント

コンポーネント定義の最もシンプルな方法は、関数を書くこと。

function Welcome(props) {
  return <h1>Hellom, {props.name}</h1>
}

データの入ったprops オブジェクトを引数に取り、 React 要素を返す。

ES6クラスを使用してコンポーネントを定義することもできる。

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>
  }
}

コンポーネントのレンダー

ユーザ定義のコンポーネントもReact 要素です。

const element = <Welcome name="Sara" />

属性と子要素を単一のオブジェクトとしてコンポーネントに渡す。このオブジェクトを props と呼ぶ。

const Welcome = ({ firstName, lastName }) => {
  return (
    <h1>
      Hello, {firstName} {lastName}
    </h1>
  );
};
const element = <Welcome firstName="Sara" lastName="Conner" />;
ReactDOM.render(element, document.getElementById('root'));

Hello, Sara Conner と表示される。

  1. 要素 <Welcome firstName="Sara" lastName="Conner" /> を引数として ReactDOM.render() を呼び出す
  2. React は Welcome コンポーネントを呼び出し、 props として {firstName: 'Sara', lastName: 'Conner'} を渡す
  3. コンポーネントは出力 <h1>Hello, Sara Conner</h1> を返す
  4. ReactDOM はコンポーネント出力に一致するように、DOM を更新

組み合わせる

コンポーネントは他のコンポーネントを参照できる。

Welcome コンポーネントを複数回参照し、レンダーする App コンポーネントを作成する。

const Welcome = ({ firstName, lastName }) => {
  return (
    <p>
      {firstName} {lastName}
    </p>
  );
};
const App = () => {
  return (
    <div>
      <Welcome firstName="Elijah" lastName="Wood" />
      <Welcome firstName="Ian" lastName="McKellen" />
      <Welcome firstName=" Orlando" lastName="Bloom" />
      <Welcome firstName="Alan" lastName="Howard" />
    </div>
  );
};
ReactDOM.render(<App />, document.getElementById('root'));

抽出

コンポーネントを分割する。

function Comment(props) {
  return (
    <div className="Comment">
      <div className="UserInfo">
        <img className="Avatar" src={props.author.avatarUrl} 
alt={props.author.name} />
        <div className="UserInfo-name">{props.author.name}</
div>
      </div>
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</
div>
    </div>
  );

Avator を抽出する。

function Avator(props) {
  return (
    <img className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

UserInfo を抽出する。

function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">
        {props.user.name}
      </div>
    </div>
  );
}

すっきりした 'Commnet` コンポーネント

function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">
        {props.text}
      </div>
      <div className="Comment-date">
        {formatDate(props.date)}
      </div>
    </div>
  );
}

コード全体

function formatDate(date) {
  return date.toLocaleDateString();
}
/* Avatar Component */
function Avatar(props) {
  return <img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />;
}
/* UserInfo Component */
function UserInfo(props) {
  return (
    <div className="UserInfo">
      <Avatar user={props.user} />
      <div className="UserInfo-name">{props.user.name}</div>
    </div>
  );
}
/* Comment Component */
function Comment(props) {
  return (
    <div className="Comment">
      <UserInfo user={props.author} />
      <div className="Comment-text">{props.text}</div>
      <div className="Comment-date">{formatDate(props.date)}</div>
    </div>
  );
}
/* comment */
const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};
ReactDOM.render(
  <Comment date={comment.date} text={comment.text} author={comment.author} />,
  document.getElementById('root')
);

かわいいネコちゃんの画像とともに文字列と年月日が表示される。画像がかわいいとコードの凶悪さが倍増です。

コードを読み取りに挑戦!

function Avatar(props) {
  return (
    <img
      className="Avatar"
      src={props.user.avatarUrl}
      alt={props.user.name}
    />
  );
}

Avatar コンポーネントは、画像を表示する。画像 URLは {props.user.avatarUrl}alt属性{props.user.name}

ここの user は、UserInfo コンポーネント<Avatar user={props.user} /> としているから。

実際は, comment オブジェクト

const comment = {
  date: new Date(),
  text: 'I hope you enjoy learning React!',
  author: {
    name: 'Hello Kitty',
    avatarUrl: 'https://placekitten.com/g/64/64',
  },
};

author: { name: 'Hello Kitty', avatarUrl: 'https://placekitten.com/g/64/64'} が代入される。

ブラウザの inspect で確認すると <img class="Avatar" src="https://placekitten.com/g/64/64" alt="Hello Kitty"> となっている。

UserInfo コンポーネントuser={props.user} とすると comment オブジェクトの author と紐づくのか?

Comment コンポーネント<UserInfo user={props.author} /> としているからです。

そのまま author を使えば良いのにと考えますが、公式で全否定されます。

コンポーネントが使用される文脈ではなく、コンポーネント自身からの観点で props の名前を付けることをお勧めします。 https://ja.reactjs.org/docs/components-and-props.html#extracting-components

私なら、 <UserInfo avatar={props.author} /> とするだろうな。

度し難し!

comment オブジェクトの authoruser に置き換わるところを理解できれば、その他のコード理解は難しくない。めんどくさいだけ!

再利用できるコンポーネントをパレットとして持っておくことは、アプリケーションが大きくなれば努力に見合った利益を生みます。

逆に初心者が数時間で作れるようなアプリケーションならこだわることはないとも読めますね。

Props は読み取り専用

コンポーネントは、自身の props を変更してはいけない。

純粋関数 : 同じ入力に対し同じ結果を返す関数のこと

function sum(a, b) {
  return a + b;
}

以下の関数は純粋関数ではない。

function withdraw(account, amount) {
  account.total -= amount
}

React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。 https://ja.reactjs.org/docs/components-and-props.html#props-are-read-only

state とライフサイクル

秒刻みの時計の例では、 ReactDOM.render() を呼び出し、 UI を更新した。

function tick() {
  const element = (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {new Date().toLocaleTimeString()}.</h2>
    </div>
  );
  ReactDOM.render(    element,    document.getElementById('root')  );}

setInterval(tick, 1000);

再利用可かつカプセル化された Clock コンポーネントを定義する方法を学ぶ。

ます時計の見た目をカプセル化する。

function Clock(props) {
  return (
    <div>
      <h1>Hello, world!</h1>
      <h2>It is {props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

Clock がタイマーを設定して UI を更新するという処理は、カプセル化されるべき。

Clock コンポーネント自身に更新させたい

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
)

これを実装するには Clock コンポーネントstate を追加する必要がある。

state

state には、そのコンポーネント固有のデータが含まれており、これは時間の経過とともに変化する可能性があります。state はユーザ定義のものであり、プレーンな JavaScript オブジェクトでなければなりません