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

React + ReduxでTodoアプリを作ってみよう!『Filter Todo編』

概要

前回の記事までは、Todoを追加する『Add Todo』と、Todoの未・済を切り替える『Toggle Todo』の機能を実装して参りました。今回は、選択したフィルタによって表示するTodoを変更する『Filter Todo』の機能を実装していきたいと思います!

『Add Todo』に関してはこちら
『Toggle Todo』に関してはこちら

完成品

『All』,『Active』,『Completed』のリンクを押すことで、ページに表示されるTodoが変わります!
ezgif.com-optimize (3).gif

Action Creatorの作成

新たにsetVisibilityFilterを追加します。ここでは、フィルター(SHOW_COMPLETEDなど)を受け取り、typeとfilterを返します。

src/actions/index.js
export const ADD_TODO = 'ADD_TODO';
export const TOGGLE_TODO = 'TOGGLE_TODO';
export const SET_VISIBILITY_FILTER = 'SET_VISIBILITY_FILTER';

export const VisibilityFilters = {
  SHOW_ALL: 'SHOW_ALL',
  SHOW_COMPLETED: 'SHOW_COMPLETED',
  SHOW_ACTIVE: 'SHOW_ACTIVE',
};

let nextTodoId = 0;
export const addTodo = text => {
  return {
    type: ADD_TODO,
    id: nextTodoId++,
    text,
    //text: text,
  };
};

export const toggleTodo = id => {
  return {
    type: TOGGLE_TODO,
    id,
    //index: index
  };
};

export const setVisibilityFilter = filter => {
  return {
    type: SET_VISIBILITY_FILTER,
    filter,
    // filter: filter
  };
};

reducerの作成

初期stateをSHOW_ALLとし、action.typeSET_VISIBILITY_FILTERの際にaction.filterを新しいstateとして返します。

src/reducers/visibilityFilter.js
import {VisibilityFilters} from '../actions';

const visibilityFilter = (state = VisibilityFilters.SHOW_ALL, action) => {
  switch (action.type) {
    case 'SET_VISIBILITY_FILTER':
      return action.filter;
    default:
      return state;
  }
};

export default visibilityFilter;

新しくreducerを作ったので、src/reducers/index.jsにてcombineReducers関数に加えましょう!

src/reducers/index.js
import {combineReducers} from 'redux';
import todos from './todos';
import visibilityFilter from './visibilityFilter';

const todoApp = combineReducers({todos, visibilityFilter});

export default todoApp;

動作確認

src/index.jsにて、正しくデータが格納されるか手動で確認してみましょう!

src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

console.log(store.getState()); /// "SHOW_ALL"
store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));
console.log(store.getState()); /// "SHOW_COMPLETED"

スクリーンショット 2019-05-06 20.35.44.png

ここまでで、Action Creatorとreducerを作成し、フィルターの値をstoreに格納することができました。

次からは、フィルターの値によってviewの表示を変更できるようにしましょう!

VisibleTodoListを修正する

todos.filter()のように配列のメソッドのフィルタを用いることで、todoのcompleted属性によって、新たに作成した配列を返します。

src/containers/VisibleTodoList.js
import {connect} from 'react-redux';
import TodoList from '../components/TodoList';
import {toggleTodo, VisibilityFilters} from '../actions';

const getVisibleTodos = (todos, filter) => {
  switch (filter) {
    case VisibilityFilters.SHOW_ALL:
      return todos;
    case VisibilityFilters.SHOW_COMPLETED:
      return todos.filter(todo => todo.completed);  //todos.filter()は配列のメソッドのフィルタ 
    case VisibilityFilters.SHOW_ACTIVE:
      return todos.filter(todo => !todo.completed); //todos.filter()は配列のメソッドのフィルタ
  }
};

const mapStateToPorops = state => {
  return {todos: getVisibleTodos(state.todos, state.visibilityFilter)};
};

const mapDispatchToProps = dispatch => {
  return {
    toggleTodo: id => {
      dispatch(toggleTodo(id));
    },
  };
};

const VisibleTodoList = connect(
  mapStateToPorops,
  mapDispatchToProps
)(TodoList);

export default VisibleTodoList;

先ほどと同様にsrc/index.jsにて手動で動作確認をしましょう!

src/index.js
import {addTodo, toggleTodo, setVisibilityFilter} from './actions';

store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));

Linkを作成する

表示したいTodoの種類をLinkをクリックすることによって表示できるようにします。

まずは、とりあえずLinkを表示させましょう!

props.childrenはコンポーネントの中身を取得できます。Linkコンポーネントを使うときの、<Link>xxx</Link>xxxです。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({children, onClick}) => {
  return (
    <a href="#">{children}</a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
};

export default Link;

LinkコンポーネントはFooterコンポーネントで使用します。

src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <Link>All</Link>
      {', '}
      <Link>Active</Link>
      {', '}
      <Link>Completed</Link>
    </p>
  );
};

export default Footer;

FooterコンポーネントはAppコンポーネントで表示します!

src/component/App.js
import React from 'react';
import VisibleTodoList from '../containers/VisibleTodoList';
import AddTodo from '../containers/AddTodo';
import Footer from './Footer';

const App = () => {
  return (
    <div className="App">
      <AddTodo />
      <VisibleTodoList />
      <Footer />
    </div>
  );
};

export default App;

これでリンクの表示が完了です。

リンクをクリックしたときにフィルターの値を変える

リンクをクリックした際にフィルターの値(SHOW_ALLなど)を変えるには、リンクをクリックした際に、dispatch(setVisibilityFilter())を呼び出せるようにします。
connect関数を使って、propsにdispatchを渡せるようにしましょう!

src/containers/FilterLink.js
import {connect} from 'react-redux';
import {setVisibilityFilter} from '../actions';
import Link from '../components/Link';

const mapStateToProps = (state, ownProps) => {
  return {state: state};
};

const mapDispatchToProps = (dispatch, ownProps) => {
  return {
    onClick: () => {
      dispatch(setVisibilityFilter(ownProps.filter));
    },
  };
};

const FilterLink = connect(
  mapStateToProps,
  mapDispatchToProps
)(Link);

export default FilterLink;

src/components/Footer.jsでLinkとmapStateToProps,mapDispatchToProps
をconnectさせた『FilterLink』を使います。
この際に、mapDispatchToPropsのonClick関数にフィルターの値を渡すためfilter="SHOW_ALL"のように記述します。

src/components/Footer.js
import React from 'react';
import FilterLink from '../containers/FilterLink';

const Footer = () => {
  return (
    <p>
      Show: <FilterLink filter="SHOW_ALL">All</FilterLink>
      {', '}
      <FilterLink filter="SHOW_ACTIVE">Active</FilterLink>
      {', '}
      <FilterLink filter="SHOW_COMPLETED">Completed</FilterLink>
    </p>
  );
};

export default Footer;

Linkをクリックした際にonClick関数を呼ぶ

『preventDefault』は『デフォルトの動作を発生させない』という意味です。今回はaタグの中で使っているのですが、これは『aタグのherfで指定されたURLへ遷移する動作を発生させない』という意味を持っております。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({children, onClick}) => {
  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;

これで、Linkを押すとviewの表示が切り替わる動作を実装することができました。
(Linkクリック→onClick関数が呼び出される→dispatch(setVisibilityFilter())が呼び出される→storeに保存されているフィルターの値が更新→viewが書き換わる)

現在activeなリンクを押せなくする

現在activeなリンクを押せなくするために、activeなリンクをただのテキストに変更する機能を実装いたします。

ここでは,Linkコンポーネントの現在の状態を知るためにprops.activeとしてデータを渡します。

src/containers/FilterLink.js
const mapStateToProps = (state, ownProps) => {
  return {active: ownProps.filter === state.visibilityFilter};
};

そして、activeの状態によってテキストを返すか、リンクを返すかをsrc/components/Link.jsにて判断いたします。

src/components/Link.js
import React from 'react';
import PropTypes from 'prop-types';

const Link = ({active, children, onClick}) => {
  if (active) {
    return <span>{children}</span>;
  }

  return (
    <a
      href="#"
      onClick={e => {
        e.preventDefault();
        onClick();
      }}>
      {children}
    </a>
  );
};

Link.propTypes = {
  children: PropTypes.node.isRequired,
  onClick: PropTypes.func.isRequired,
};

export default Link;

以上で、『Filter Todo』の実装は完了です!

公式のBasicTutorial完了

これで公式のBasicTutorialと同じ機能が実装できたように思います。

ezgif.com-optimize (4).gif

リファレンス

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

NEXT.jsとReact Hooksを使ってTodoアプリを10分で作る

Reactのフレームワークであり、かつ爆速でReact環境を構築できるNEXT.jsを使って、定番のTodoアプリを作ってみます。

そこにReact Hooksを使えばTodoアプリくらいなら10分もあれば作れるので、NEXT.jsまたはReact Hooksを使った事のない方は、気軽に取り組んでみてください。

作るもの

こんな感じの簡単なTodoアプリを作ります。
next-react-app.gif

環境設定

NEXT.jsが動く環境を作ります。

まずはプロジェクトの作成。

$ mkdir next-todo-app
$ cd next-todo-app
$ npm init -y

次にNEXT.jsおよびReactを入れる。

$npm install --save react react-dom next

そこに pages/index.jsx を作成して、以下のReactコンポーネントのコードを書きます。

pages/index.jsx
const App = () => {
  return <h1>Hello World!</h1>;
};

export default App;

package.json のscriptsも書き換えます。

package.json
"scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
}

あとは、nextを起動させて、http://localhost:3000 でHello Worldが表示されているのを確認できれば完了です。

$ npm run dev

Todoアプリを作る

NEXT.jsで一瞬でReact環境を作ることができたので、次にTodoアプリを実装していきます。

Todoアプリの基本的なコードは以下の通り

pages/index.tsx
import { useState } from "react";

const App = () => {
  // 作成したtodoを入れておくためのstate
  const [todos, setTodos] = useState([]);
  // フォームに入力された値をtodoに登録するまでに入れておくためのstate
  const [tmpTodo, setTmpTodo] = useState("");

  const addTodo = () => {
    setTodos([...todos, tmpTodo]);
    setTmpTodo("");
  };

  return (
    <>
      <h1>Todo App</h1>
      <div className="form">
        <input
          type="text"
          name="todo"
          // formの入力値をtmpTodoで持っておく
          onChange={e => setTmpTodo(e.target.value)}
          value={tmpTodo}
        />
        <button onClick={addTodo}>Add</button>
      </div>
      <ul>
        {todos.map((todo, index) => {
          return <li key={index}>{todo}</li>;
        })}
      </ul>
      <style>{`
        h1 {
          text-align: center;
        }
        .form {
          display: flex;
          justify-content: center;
        }
        ul {
          width: 200px;
          margin: 10px auto;
        }
      `}</style>
    </>
  );
};

export default App;

これで、Todoアプリに登録する処理が書けました。
React Hooksの1つであるuseStateを使うことで、stateの管理が一気に楽になります。

あとは、空白の状態でTodoが登録されないようにしつつ、削除処理のコードを加えて、Todoアプリは完成です。

pages/index.tsx
import { useState } from "react";

const App = () => {
  const [todos, setTodos] = useState([]);
  const [tmpTodo, setTmpTodo] = useState("");

  const addTodo = () => {
    // formの内容が空白の場合はalertを出す
    if (tmpTodo === "") {
      alert("文字を入力してください");
      return;
    }
    setTodos([...todos, tmpTodo]);
    setTmpTodo("");
  };

  // todoを削除する処理
  const deleteTodo = index => {
    const newTodos = todos.filter((todo, todoIndex) => {
      return index !== todoIndex;
    });
    setTodos(newTodos);
  };

  return (
    <>
      <h1>Todo App</h1>
      <div className="form">
        <input
          type="text"
          name="todo"
          onChange={e => setTmpTodo(e.target.value)}
          value={tmpTodo}
        />
        <button onClick={addTodo}>Add</button>
      </div>
      <ul>
        {todos.map((todo, index) => {
          return (
            <li key={index}>
              {todo}
              {/* 削除ボタンを追加 */}
              <button onClick={() => deleteTodo(index)}>x</button>
            </li>
          );
        })}
      </ul>
      <style>{`
        h1 {
          text-align: center;
        }
        .form {
          display: flex;
          justify-content: center;
        }
        ul {
          width: 200px;
          margin: 10px auto;
        }
      `}</style>
    </>
  );
};

export default App;

NEXT.jsを使ってReact環境の構築が簡単になったこと、React Hooksを使ってStateの管理が簡単になったことで、Todoアプリくらいなら10分くらいで作ることができるようになりました。

他にもNEXT.jsを使うことで、

  • デフォルトでサーバーサイドレンダリングされる
  • デフォルトでコードスプリッティングされる
  • デフォルトでSPAになっている
  • ページごとのシンプルなページルーティング

などの利点があります。

まあ、複雑なことをやろうとすると厳しいところもあるNEXT.jsですが、Reactの環境構築としては秀逸なので触ってみる価値はあると思います。

今回作ったコードはこちらから
https://github.com/hiraike32/next-todo-app

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

React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す

Image 5.jpg

こんにちは、はが@mxcn3と申します。GW中にPlayCanvasを触ってみるという事を予定として入れていたのでその消化をしたことの備忘録です。

※前提としてなのですが、普段は、フロントエンジニアとして働いており、似たようなツールにUnityやUE等があると思うのですが、そちらの存在は名前は知っているという程度で3Dゲームの知識は殆ど無いためおかしい部分がありましたら申し訳ございません。

※React Nativeに関しましては、あまりトンチンカンなことは書いていないはずなのでどうかよろしくおねがい致します。

そもそもPlayCanvasって何?

PlayCanvasとは?
PlayCanvasは、インタラクティブなウェブコンテンツ用のビジュアル開発プラットフォームです。作成するツール> > とウェブアプリは、どちらもHTML5を使用しています。 プラットフォームはウェブでホストされているため、インストールするものは何もなく、対応されているウェブブラウザを実行する任意のデバイスからアクセスできます。
https://developer.playcanvas.com/ja/user-manual/introduction/

導入 | Learn PlayCanvas

React Nativeとは

React Nativeは、Reactを使ってネイティブアプリを開発するためのオープンソースのフレームワークです。
実践Expo

1.実際に10分くらいでアプリに落とし込んで見る

PlayCanvas側のソースを作る

こちらから登録をして使ってみます。
https://playcanvas.com/

登録をして新規プロジェクトを開くとこのような画面になるのでこちら今回、ほぼそのまま使ってみたいと思います。
ただこのままだと少しさみしいのでサイドバーから物理演算をさせるためにCollisionと、RigidBodyを追加してあげます。

Image 8.jpg

今回はとりあえずどんな感じに動くかを知りたかっただけなので、左横のアイコンからこのままパブリッシュをしてしまいましょう。

Image 12.jpg

2.WebView

スマートフォンで表示をさせるために今回は、SnackというReact Nativeのコードを面倒事をしないでビルド、表示をしてくれるとても便利な物を使います。

こちらが実際にのReact Nativeのコードなのですが、殆どWebViewで表示をしているだ毛のものとなっております。
Image 13.jpg

import React, { Component } from 'react';
import { WebView, SafeAreaView, Button } from 'react-native';

export default class App extends Component {
  render() {
    return (
      <SafeAreaView style={{ flex: 1 }}>
        <WebView
          ref={ref => {
            this.playCanvas = ref;
        }}
          style={{ marginTop: 20 }}
          source={{ uri: 'https://playcanv.as/p/nBzxd5Gq/' }}
        />
        <Button onPress={() => {this.playCanvas.reload()}} title="Restart" />
      </SafeAreaView>
    );
  }
}

3.実機で確認

実機で確認をしたいと思います。

3-1. Expo Clientのインストール

Expoで作成をした、React Nativeのコードなのですが、Expo Clientというアプリを入れることで簡単に実行ることができます。
App StoreでExpo Clientと検索していただければ簡単に出てきます。
Image 17.jpg

3-2. SnackからQRコードを読み込む

https://snack.expo.io/@yutten/playcanvas

Snackはとても便利でURLを共有するだけでソースコードの共有ができるので上記のURLへアクセスしていただければすぐに確認することができます。上記のURLは先程のReact Nativeのコードとなります。
Image 18.jpg

右のiPhoneのマークからRun on your deviceを押していただいてQRをスマートフォンで読み込んでいただくと実機で実行することができます.

3-3 録画したものがこちら

実際に読み込むとこのようなものが表示されているかと思います。(読み込めない場合にはSnackのアカウントを作ることで読み込めるかとおもいます)
output.gif

結論

PlayCanva製のゲームを、アプリ化するにあたり、トラッキングや広告の部分をReact Nativeに持たすことが出来るのでWebの知識だけしかない場合においても、切り分けをして使うことで面白そうなことはできそうだなと言う印象を持ちました。

あとがき

かなり雑に書きなぐってしまって申し訳ございません、

今日やりたかったこととしては、近年PWAなどが流行っているので、ウェブ向けのゲームエンジンで作ったゲームをWebViewで表示したらどれくらいのパフォーマンスが出るのかなというのが知りたかったこと、最終的にはReact Nativeでアニメーションの描画などで苦労している部分の解決につながる糸口が見つかるかもしれないという期待を込めた検証を込めたものでした。

参考

導入 | Learn PlayCanvas

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

[React] 動的に追加したコンポーネントのパラメータを変更しても更新されない

note

React初心者です。

環境

  "dependencies": {
    "react": "^16.8.6",
    "react-dom": "^16.8.6",
    "react-scripts": "3.0.0"
  },

概要

期待する動作

ボタンをクリックすると、時計が1つ増える。時計に表示する文字列は、各コンポーネントではなく、Appが管理する。時計は1秒ごとに更新される。

実装

下にあるコードを見たほうが早いと思いますが、簡単に説明。

Windowという名前のコンポーネントがある。props.textを表示する以外のことは何もしない。

Appは、textwindowsというステートを持っている。
textには、時刻を文字列にしたものが格納されていて、setIntervalで1秒ごとに更新される。
windowsは、Windowコンポーネントが格納された配列である。

画面上のボタンをクリックすると、state.windows に Windowコンポーネント を追加する。
Appのrenderの中にthis.state.windowsを表示する部分があるので、state.windows に要素を追加した時点で画面が更新されるはず。

起きる問題

動的に追加されたWindowたちの文字列が変化しない。

Appのrenderに直接書いたWindowは更新されているのが分かる。

codesandbox

https://codesandbox.io/s/oq6nk3wn16?fontsize=14

念のため、ブログにもコードを記載します。

import React from "react";
import ReactDOM from "react-dom";

class Window extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <pre>{this.props.text}</pre>;
  }
}

class App extends React.Component {
  constructor(prop) {
    super(prop);
    this.state = {
      text: "",
      windows: []
    };
    setInterval(() => {
      this.updateText();
    }, 1000);
  }
  updateText() {
    this.setState({
      text: new Date(Date.now()).toString()
    });
  }
  addWindow() {
    this.setState({
      windows: this.state.windows.concat([<Window text={this.state.text} />])
      //windows: this.state.windows.concat([() => <Window text={this.state.text} />])
    });
  }
  render() {
    return (
      <div className="App">
        <Window text={this.state.text} />
        <hr />
        <div>
          {
            this.state.windows
            //this.state.windows.map(e => e())
          }
        </div>
        <hr />
        <div>
          <button
            onClick={() => {
              this.addWindow();
            }}
          >
            addWindow
          </button>
        </div>
      </div>
    );
  }
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

原因と解決策

上記のコードの、コメントアウト部分が、自分なりの解決策です。

下のように、domを直接配列に入れてしまっているのが間違い。おそらく、現時点でのthis.state.textを含むレンダリング済みのデータが格納されてしまっている。

    this.state.windows.concat([<Window text={this.state.text} />])

this.state.text が変化したら再構築されて欲しいはずなので、配列にはdomを返す無名関数を格納するようにして、

    this.state.windows.concat([() => {return (<Window text={this.state.text} />);}])

render() 側で無名関数を呼んで、domをレンダリングする。

        <div>
          { this.state.windows.map(e => e()) }
        </div>

これで期待通りの動作はするようになった。

原因と解決策(追記:2019/05/06)

stateにはコンポーネントを入れず、コンポーネントに渡す引数を保持させた方が良いとのコメントがありました。

この方法ならば、stateも最小限の大きさになり、可読性も上がりそうです。
配列にはキーワードや連想配列を格納するようにして、

    this.state.windows.concat(["Window"])

render() 側でキーワードに応じてレンダリングするdomを分岐する。

        <div>
          { this.state.windows.map(type => 
              (type == "Window") ? (<Window text={this.state.text} />) :
                                   (<p>{this.state.text}</p>))
          }
        </div>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む