20190728のReactに関する記事は4件です。

VirtualDOMのメリット

ReactやVueで採用されている仮想DOM(Virtual DOM)の何が良いの?って話。

そもそもDOMとは?

DOMはXML形式の文書をツリー構造で表現したものです。このDOMを参照することで、各要素にアクセスすることができ、見た目やコンテンツを変更することができます。

とりあえず、「WEBページを表示するのに必要な設計書みたいなもの」と思ってもらえると良いと思います。

仮想DOMの説明の前に、ブラウザのレンダリングの流れについて

仮想DOMのメリットを知るためには、ブラウザのレンダリングの流れを知る必要があります。

ブラウザはページを描画するまでに以下の処理を行っています。

  • Parse HTML/CSSを解析
  • DOM, CSSOM DOMツリーとCSSOMツリーを組み合わせて、レンダーツリー作成
  • Layout 描画する位置、サイズの決定
  • Paint 描画処理
  • Composite 各レイヤーを重ねる

DOM操作を行うと、レンダーツリーの更新が発生し、再度レイアウトとペイント処理が走ります。
これら「レイアウト」と「ペイント」処理が負荷が高い処理と言われています。
そのため、描画速度を上げるためには、一般的に不要なDOM操作を減らすという手段が取られています。

レンダリングの流れについて、より詳しく知りたい方は、以下の記事を参考にしてください。
- ブラウザの仕組み: 最新ウェブブラウザの内部構造
- ブラウザレンダリング入門〜知ることで見える世界〜

仮想DOMとは?

実際のDOMと対となる、構造体。

先程書いたように、DOM操作を行うたびにレンダーツリーの更新が発生し、再度レイアウトとペイント処理が走ります。

この描画時の負荷を抑えてくれる仕組みが、仮想DOMです。

【本題】仮想DOMのメリット

仮想DOMを使うことで、以下のようにDOM操作を最小限に抑えることができます。

  • DOMと対になる仮想DOMを定義し、ページが変化するときには、まず仮想DOMを変更
  • 仮想DOMはブラウザレンダリングとは切り離されているため、変更しても影響はない
  • 仮想DOMの差分をチェックし、変更部分のDOMのみを変更
  • 最小限のDOM操作でページを変化させることができる

image.png

まとめ

仮想DOMとは、DOMと対になる構造体で、ReactやVueは、仮想DOMの差分をチェックし、DOMの再構築を最小限に抑えてくれる!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

redux-saga で編集フォームのフローを実装する

ふとしです。

最近 flux と redux と vuex を行ったり来たりして脳みそが大変です。

redux-saga

redux-saga は redux で非同期処理を取りあつかう際の選択肢としてあげられる middleware です。

しかし以下のような処理のみを redux-saga で行う場合、redux-thunk に対しても同様ですが、導入するには大げさすぎるという意見もあるようです。

function* save (payload) {
  try {
    yield call(Registry.api.saveProfile, payload);
    yield put({ type: SAVE_PROFILE_SUCCEEDED });
  } catch (e) {
    yield put({ type: SAVE_PROFILE_FAILED, payload: e.errors });
  }
}

function* fetch () {
  try {
    const payload = yield call(Registry.api.fetchProfile);
    yield put({ type: FETCH_PROFILE_SUCCEEDED, payload });
  } catch (e) {
    yield put({ type: FETCH_PROFILE_FAILED });
  }
}

わたしも多少そのように思っていました。

フロー全体を一つの Saga として取りあつかう

機能を知ろうとドキュメントを読んでいたところ redux-saga は pull 型のアプローチであるという記述に目がとまりました。

redux-saga では redux の Action フローからターゲットとして定めた Action を取りだすことにより一連の処理を行えます。以下の例では take が LOGIN LOGOUT の Action が来るまで処理をブロックします。

function* loginFlow() {
  while (true) {
    yield take('LOGIN')
    // ... perform the login logic
    yield take('LOGOUT')
    // ... perform the logout logic
  }
}

このアプローチによりフロー全体を把握しやすい形でコードにできます。ただ LOGIN などは比較的大きな粒度なので、実際の実装にどの程度の影響があるかわかりづらいと思いました。

そこで SPA で典型的に登場する編集フォームを考えてみました。今回はかんたんなプロフィール編集フォームを想定しています。

プロフィール編集フロー

プロフィール編集フォームで必要な処理をかんたんに整理しましょう。

プロフィール編集は明確な開始と終了があるフローです。フローは初期化処理・入力受付・終了処理で構成されています。

初期化処理

フロー開始時に一度だけ実行されます。

  • 既存のプロフィールデータを取得します
  • 取得したデータをフォームの値として設定します

入力受付

終了条件のあるループです。

  • ユーザーからの値入力を受けつけます
  • ユーザーからの保存要求を受けつけます

終了条件は以下のとおりです。

  • 保存が成功した
  • フォームから離脱した
  • フローを進行できない問題がある

終了処理

フロー終了時に一度だけ実行されます。処理内容は上記ループの入力受付が終了に至った理由によりかわります。

  • 保存成功していればプロフィール画面へ
  • フォームから離脱した場合は何もしない
  • いずれの場合でもプロフィール編集に関する state を撤去する

プロフィール編集フロー Saga

整理したフローを Saga として実装します。

  • 入力受付whiletake を用いたループとして扱います。終了条件に至った場合は while を抜けます
  • 初期化 はそのループ以前に記述します
  • 終了処理 はそのループ以後に記述します

SET_PROFILE_EDIT_STATE のみ Reducer が処理します。

export default function* profileEditFlow () {
  const chan = yield actionChannel([
    FETCH_PROFILE,
    FETCH_PROFILE_SUCCEEDED,
    FETCH_PROFILE_FAILED,

    INPUT_PROFILE_VALUE,
    RESET_PROFILE_VALUE,

    SAVE_PROFILE,
    SAVE_PROFILE_SUCCEEDED,
    SAVE_PROFILE_FAILED,

    LEAVE_PROFILE_EDIT_FLOW,
    END_PROFILE_EDIT_FLOW,
  ]);

  // # 初期化処理

  let reason = '';
  let isLeft = false;
  yield put({ type: FETCH_PROFILE });

  // フォームのロック処理
  // 同期的に API リクエストを行っている場合、同じループに含めると take がその call 後になる
  yield fork(function* () {
    const chan = yield actionChannel([
      LOCK_PROFILE_EDIT,
      UNLOCK_PROFILE_EDIT,
      CLEAN_PROFILE_EDIT_FLOW,
    ]);

    flow: while (true) {
      const action = yield take(chan);
      const state = (yield select()).profileEditState;

      switch (action.type) {
      case LOCK_PROFILE_EDIT:
        yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, isLocked: true } });
        continue;
      case UNLOCK_PROFILE_EDIT:
        yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, isLocked: false } });
        continue;
      case ENDED_PROFILE_EDIT_FLOW:
        break flow;
      default:
        continue;
      }
    }
  });

  // # 入力受付

  flow: while (true) {
    const action = yield take(chan);
    const state = (yield select()).profileEditState;

    switch (action.type) {
    case FETCH_PROFILE:
      yield put({ type: LOCK_PROFILE_EDIT });
      yield call(fetch);
      continue flow;
    case FETCH_PROFILE_SUCCEEDED:
      yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, values: { ...action.payload }, defaultValues: { ...action.payload } } });
      yield put({ type: UNLOCK_PROFILE_EDIT });
      continue flow;
    case FETCH_PROFILE_FAILED:
      // 終了条件
      reason = 'server error';
      break flow;

    case INPUT_PROFILE_VALUE:
      yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, values: { ...state.values, ...action.payload } } });
      continue flow;
    case RESET_PROFILE_VALUE:
      yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, values: { ...state.defaultValues } } });
      continue flow;

    case SAVE_PROFILE:
      yield put({ type: LOCK_PROFILE_EDIT });
      yield call(save, state.values);
      continue flow;
    case SAVE_PROFILE_SUCCEEDED:
      // 終了条件
      reason = 'succeeded';
      break flow;
    case SAVE_PROFILE_FAILED:
      yield put({ type: SET_PROFILE_EDIT_STATE, payload: { ...state, errors: action.payload } });
      yield put({ type: UNLOCK_PROFILE_EDIT });
      continue flow;

    case LEAVE_PROFILE_EDIT_FLOW:
      // 終了条件
      reason = 'left';
      isLeft = true;
      yield put({ type: END_PROFILE_EDIT_FLOW });
      continue flow;
    case END_PROFILE_EDIT_FLOW:
      yield call(chan.close);
      break flow;

    default:
      continue flow;
    }
  }

  // # 終了処理

  switch (reason) {
  case 'server error':
    yield put({ type: NOTIFY, payload: 'server error' });
    yield call(Registry.history.push, '/@');
    break;
  case 'succeeded':
    yield put({ type: NOTIFY, payload:'saved' });
    isLeft || (yield call(Registry.history.push, '/@/profiles'));
    break;
  case 'left':
    // do nothing
    break;
  default:
    yield call(Registry.logger.log, '?');
    yield call(Registry.history.push, '/@');
    break;
  }

  yield put({ type: CLEAN_PROFILE_EDIT_FLOW });
}

take を用いることで入力中に起こりうる出来事を一元管理できました。また初期化処理と終了処理で何が起こるかも一目瞭然です。

本人以外が処理内容を把握するために断片化されたコードを手繰る困難さが軽減されたと思います。

フォームのロック処理用のループについて

非同期処理を同期的に行っている call(fetch) call(save, state.values)fork で行うことによりロック処理も同ループに含められます。

今回は保存中にページ離脱され、その後に発生した SAVE_PROFILE_SUCCEEDED を捉えて通知するというケースを考えたため call を使用しています。

call について

call は非同期関数を同期的に扱う (返り値が必要であればそれを得る) ために使用されますが、同期関数を呼び出すことも可能です。

同期非同期に関わらず call を用いることでフロー内で起こる出来事として観測可能になります。そうすることによりテストを有利に進められるため、フロー内での関数呼び出しは call で行うことをおすすめします。

Saga のテスト方法について詳細はドキュメントを参照してください。
https://redux-saga.js.org/docs/advanced/Testing.html

プロフィール編集フローの起動と終了

プロフィール編集フローはプロフィール編集フォームに至った時点で起動されます。また、フォームから離脱した場合は終了されます。そこで今回は didMount willUnmount に相当する時点でそれぞれの Action を発行します。

また入力や保存に際しては、プロフィール編集フロー Saga が take する Action 以外は発行しないようにすることで、コードを手繰る負荷を低減できます。

function ProfileEditForm () {
  const dispatch = useDispatch();
  const {
    isLocked,
    errors,
    values: {
      name,
      email,
      message,
    },
  } = useSelector(s => s.profileEditState);

  useEffect(() => {
    dispatch({ type: START_PROFILE_EDIT_FLOW });
    return () => { dispatch({ type: LEAVE_PROFILE_EDIT_FLOW }); };
  }, []);

  const submit = e => e.preventDefault() || dispatch({ type: SAVE_PROFILE });
  const reset = () => dispatch({ type: RESET_PROFILE_VALUE });
  const setter = k => e => dispatch({ type: INPUT_PROFILE_VALUE, payload: { [k]: e.target.value } });
  const errorMessages = errors.length ? <>{errors.map(m => <p key={m}>{m}</p>)}</> : null;

  return (
    <form onSubmit={submit}>
      <h1>Profile</h1>
      {errorMessages}
      <div>
        <label htmlFor="name">Name:</label>
        <input type="text" disabled={isLocked} id="name" value={name} onChange={setter('name')} />
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input type="text" disabled={isLocked} id="email" value={email} onChange={setter('email')} />
      </div>
      <div>
        <label htmlFor="message">Message:</label>
        <input type="text" disabled={isLocked} id="message" value={message} onChange={setter('message')} />
      </div>
      <button type="submit" disabled={isLocked}>Save</button>
      <button type="button" disabled={isLocked} onClick={reset}>Reset</button>
    </form>
  );
}

余談: 上記のコードと実際に使っているコードのちがい

  • ActionCreator を使っています
  • TypeScript 使っています。Action に適切な型を与えることにより switch がタイプガードとして働きます

まとめ

redux-saga を最初に導入した時にはこのエントリーの最初に書いてるような単純な使い方しかしていませんでした。

ドキュメントを読んだところ pull 型を実現する take こそが redux-saga のパワーではないのかと思い、そのように実装してみたところこのようになりました。そして好ましいと感じました。

より複雑なフォーム、例えばショッピングカートではいくつかのフォームを進んでいく高レベルなフローが存在する場合があります。(ドキュメントに記載されていた LOGIN LOGOUT のフローも高レベルですね) その場合も同じように一つのフローとして考え記述することにより、全体で何が起こるのかがわかりやすくなると思います。

redux-saga の使い方のヒントになれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CSS modulesを使ってCSSで名前空間を実現する

CSSは、勉強してないのをごまかすためにイキって強がる人の風評被害を受けやすく、実際よりも低く評価されてしまうことがよくあります。
でも現代のCSSは意外に良いものですし、依然として残る課題に対しても「名前空間がないからCSSマジつかえねぇわ〜」と知ったかぶりして書かない理由を探すのではなく、やはり生産的に解決したいものです。

そこで、この記事ではCSS modulesを使って お手軽に CSSに名前空間を擬似的に導入します。
また、ParcelでCSS modulesを使う時に遭遇するバグの回避方法もあわせてお見せします。

CSSに名前空間がないことで起きる問題

CSSには名前空間がないため、たとえばこれからご紹介する例のように複数のCSSファイルを読み込んだ際に意図せぬ見た目になってしまうことがあります。

まず、以下の yagi.css だけを読み込んでみましょう。

yagi.css
.yagi {
  width: 10em;
}

.big {
  width: 50em;
}

.yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
yagi.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
</head>
<body>
  <div class="yagi"></div>
  <div class="yagi big"></div>
</body>
</html>

yagi0.png

yagi.css に設定した .big クラスの設定が効いて $\huge{大きなヤギ}$ の画像になっています。

次に、以下の button.css だけを読み込んでみましょう。

button.css
.button {
  padding: 0.6em;
  font-size: 1em;
}

.big {
  font-size: 3em;
}
button.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button">Button</button>
  <button type="button" class="button big">Big Button</button>
</body>
</html>

button0.png

button.css に設定した .big クラスが効いてボタンが大きくなっています。

では、yagi.cssbutton.css を両方読み込むとどうなるでしょうか。

mix.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button">Button</button>
  <button type="button" class="button big">Big Button</button>
  <div class="yagi"></div>
  <div class="yagi big"></div>
</body>
</html>

mix.png

ぶっこわれました。

ヤギ画像にもボタンにも以下の 両方の CSSが適用されてしまったためです。

yagi.css
.big {
  width: 50em;
}
button.css
.big {
  font-size: 3em;
}

このように、一般的過ぎるクラス名を使うと、意図しない要素にそのCSSが影響してしまいます。

BEM記法

技術ではなく運用でこの問題を解決しようとするのがBEM記法です。
先ほどの例では、以下のようにクラス名をつけなおします。

yagi-bem.css
.yagi {
  width: 10em;
}

.yagi--big {
  width: 50em;
}

.yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}
button-bem.css
.button {
  padding: 0.6em;
  font-size: 1em;
}

.button--big {
  font-size: 3em;
}

手作業でクラス名の頭に識別子をちまちまくっつける感じですね!
悪くはないんですが、識別子を付けないやつがチームにいると困ったことになります。

また、いくらベラのキャラデザインを現代的な若い女の子にしても、同時期に放映されているゲゲゲの鬼太郎もネコ娘のキャラデザインが一新されており、脚本も攻めまくったおもしろい内容になっているので「まぁ鬼太郎があるし、妖怪人間の方は見なくていいか」となってしまいます。

CSS modules で解決する

そこで、人間の手にたよらずに、ヤギの手も妖怪の手も借りずに自動で名前の衝突を避けてくれる技術の1つが CSS modules です。

では、お手軽にためしてみましょう。
nodeをインストールしていない方は先にインストールしておいてください。
以下はLinux OS上での操作を想定していますが、Macでもだいたい一緒だと思います。
うぃんどうずを使っている人はまずデュアルブートでUbuntuとかを入れましょう。

css-modules-sample みたいなディレクトリを作り、そのディレクトリの中で

$ npm i postcss-cli postcss-modules

を実行して必要なライブラリをローカルインストールしましょう。
あとは、CSSファイルと以下の設定ファイルをこのディレクトリに置いておきます。

(設定ファイルの詳しい仕様を知りたくなったら公式ドキュメントをご参照ください)

postcssrc.js
module.exports =
  {
    "modules": true,
    "plugins": {
      "postcss-modules": {},
    }
  }

これで準備は完了です。
このディレクトリ内に yagi.css を用意して、以下のコマンドを実行してみましょう。

$ npx postcss yagi.css -o dist/yagi.css

あらたに yagi.css.jsondist/yagi.css が生成されているはずです。
dist/yagi.css をひらいてみると、以下のような内容になっています。

dist/yagi.css
._yagi_1d9sa_1 {
  width: 10em;
}

._big_1d9sa_5 {
  width: 50em;
}

._yagi_1d9sa_1::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}

なんかランダムっぽい識別子がくっついて名前の衝突を防げるようになっていますね?

yagi.css.json は、オリジナルのクラス名がCSS modulesによってどのように変換されたかをJSON形式で保持しています。

yagi.css.json
{"yagi":"_yagi_1d9sa_1","big":"_big_1d9sa_5"}

なんらかの工夫をして、HTML側のタグにつけるクラス名もこのJSONファイルを見て変換しないといけません。
Reactとかだと何かそういうライブラリとかがあるみたいです。
でも、ちょっとそれってめんどくさいですよね?
そこでそんなめんどくさがりの人のために、ずるいやり方 があります。

設定ファイル postcssrc.js を以下のように書き換えてしまってください。

postcssrc.js
module.exports =
  {
    "modules": true,
    "plugins": {
      "postcss-modules": {
        "generateScopedName": "[name]__[local]",
        "getJSON": () => null
      }
    }
  }

postcss-modulesの説明にあるとおり、

  • generateScopedName は「どのようにクラス名を変換するか」のルールを指定し
  • getJSON は変換の対応表の書き出し方を指定します

ここでは generateScopedNameファイル名__元のクラス名 という形式を指定し、getJSON には対応表は特に生成しないように指定しています。
実行して結果を見てみましょう。

$ npx postcss yagi.css -o dist/yagi.css
dist/yagi.css
.yagi__yagi {
  width: 10em;
}

.yagi__big {
  width: 50em;
}

.yagi__yagi::after {
  content: '';
  display: block;
  background-image: url('https://user-images.githubusercontent.com/1481749/56465716-251ebf00-643f-11e9-8c66-8d0de8953663.jpg');
  background-size: contain;
  background-repeat: no-repeat;
  padding-top: calc(100% * 225 / 400);
  width: 100%;
}

これなら事前にどのようなクラス名に変換されるか予測できるため、HTML側を書く時に

mix2.html
<!DOCTYPE HTML>
<html>
<head>
  <meta charset="UTF-8">
  <link rel="stylesheet" href="yagi.css">
  <link rel="stylesheet" href="button.css">
</head>
<body>
  <button type="button" class="button__button">Button</button>
  <button type="button" class="button__button button__big">Big Button</button>
  <div class="yagi__yagi"></div>
  <div class="yagi__yagi yagi__big"></div>
</body>
</html>

としておけばいいだけです。

Elmを使っている方は、「Elmでもっと気軽にCSS modules」で紹介した方法を使うと簡単です。

Parcelで使う

ファイルを結合したり、Babelを使ったり、Minifyしてくれたり、そういう面倒なタスクをお手軽にいろいろ勝手にやってくれる技術の1つにParcelがあります。
Parcelを使う場合は先ほどの .postcssrc.js をディレクトリ内に置いた状態で

$ npm i parcel https://github.com/arowM/parcel/archive/parcel-bundler@1.12.4.tar.gz
$ parcel build mix2.html --public-url ./

とすれば、dist にコンパイル後のファイルが生成されます。

サンプルリポジトリ を作っておいたので、このリポジトリをcloneして

$ npm i && npm run build

としても同じことができます。

mix2.png

この通り、うまくいきました!

なお、Parcel には postcss-modules の generateScopedName が効かないという問題があり、これを修正したものがまだリリースされていません。
そこで、上記のコマンドやサンプルリポジトリでは、parcelをフォークして独自に修正した parcel-bundler@1.12.4 をインストールしています。

さくらちゃんにご飯をあげる
さくらちゃんをもっと見る
他の記事を見る
sakura.jpg

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「2019年にReact Nativeを使い始める:初めてのアプリケーションを構築する」をやってみた

今までReact Nativeをやってみたいと思っていたのですが、なかなか機会がなく、今回↓のチュートリアルがとてもわかりやすそうでしたのでやってみました。

Getting Started with React Native in 2019: Build Your First App

この投稿で書いてある内容は、素人の私が参考サイトの通りやってみただけの内容です。
参考サイトの方では詳しい解説などもあるため、私の投稿は読まずに参考サイトを見れば大丈夫です。
ですが一応やってみたので投稿させていただきました。

私が試してみたコード

バージョン

  • macOS Mojave 10.14.5
  • react 16.8.6
  • react-native 0.60.4
  • Xcode 10.2
  • Android Studio 3.3.2

React Native CLI のインストール

$ yarn add -D react-native-cli
$ yarn react-native --version
yarn run v1.17.3
warning package.json: No license field
$ /Users/kengookumura/dev/tmp/rn/node_modules/.bin/react-native --version
react-native-cli: 2.0.1 # ←react-native-cliのバージョンを確認
react-native: n/a - not inside a React Native project directory
✨  Done in 0.19s.

React Native Appを実行

yarn react-native init EmojiDictRN
cd EmojiDictRN/

Screen Shot 2019-07-27 at 19.44.32.png

アプリを起動

yarn start

iOSで起動

yarn startで実行しているのとは別のターミナルのタブで↓を実行する

yarn react-native run-ios

iOSシミュレータを起動することができました

Screen Shot 2019-07-27 at 21.17.42.png


※最初Xcode 11.0 beta 3がインストールされている状態で行ったのですが、以下のようにエラーが出ました。
Xcode 10.2をインストールして動かせました

error Could not find "iPhone X" simulator.

↓からXcode 10.2をダウンロードしました

Androidで起動

yarn startで実行しているのとは別のターミナルのタブで↓を実行する

bash
yarn react-native run-android #※私の環境ではエラーになりました

エラーになってしまったので、自分がやってみことです

まずこちらを参考にし、ANDROID_HOMEのパスを設定しました

export ANDROID_HOME=/Users/kengookumura/Library/Android/sdk

私の環境では↑だけではうまくいかず、

↑こちらのリンクで、「アプリケーションを実行するには、Androidシミュレータを起動するか、デバイスを接続する必要がある」とあっため、↓を参考にエミュレータを起動した上でyarn react-native run-androidを実行するとうまくいきました

Screen Shot 2019-07-27 at 23.17.54.png

App.jsを変更してみる

参考サイトの通りにApp.jsを変更して、iOSシミュレータの場合はcommand + Rでリロードすると、

App.js
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';

export default class App extends Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.instructions}>Hello World!</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF'
    },
    instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5
    }
});

表示を変更することができました

Screen Shot 2019-07-27 at 23.32.55.png

Hot Reloadする

iOSの場合ですと、iOSエミュレータのメニューで[Hardware] > [Shake Gesture]とするとメニューが表示されるので、
「Enable Hot Reloading」を選択するとHot Reloadしてくれました

Screen Shot 2019-07-27 at 23.49.59.png

lj4uTkCLT2.gif

簡単にコンポーネントを作成する

src/components/EmojiDict.jsファイルを作成する

src/components/EmojiDict.js
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';

class EmojiDict extends Component {
    state = {
        '?': '? Smiley',
        '?': '? Rocket',
        '⚛️': '⚛️ Atom Symbol'
    };

    render() {
        return (
            <View style={styles.container}>
                <Text>{this.state['?']}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    }
});

export default EmojiDict;
App.js
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import EmojiDict from './src/components/EmojiDict';

export default class App extends Component {
  render() {
    return <EmojiDict />;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF'
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5
  }
});

Screen Shot 2019-07-28 at 0.16.46.png


emojiのリストをFlatListで表示するする

src/components/EmojiDict.js
import React, { Component } from 'react';
import { Text, StyleSheet, FlatList } from 'react-native';

class EmojiDict extends Component {
    state = {
        '?': '? Smiley',
        '?': '? Rocket',
        '⚛️': '⚛️ Atom Symbol'
    };

    render() {
        return (
            <FlatList
                contentContainerStyle={styles.container}
                data={[
                    { key: '?', value: '? Smiley' },
                    { key: '?', value: '? Rocket' },
                    { key: '⚛️', value: '⚛️ Atom Symbol' }
                ]}
                renderItem={({ item }) => <Text>{item.value}</Text>}
            />
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    }
});

export default EmojiDict;

Screen Shot 2019-07-28 at 0.40.40.png


React Nativeについてはわからないことだらけですが、とりあえず動かすことができたとてもありがたかったです。

読んでいただいてありがとうございました。m(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む