- 投稿日:2019-05-06T22:36:23+09:00
React + ReduxでTodoアプリを作ってみよう!『Filter Todo編』
概要
前回の記事までは、Todoを追加する『Add Todo』と、Todoの未・済を切り替える『Toggle Todo』の機能を実装して参りました。今回は、選択したフィルタによって表示するTodoを変更する『Filter Todo』の機能を実装していきたいと思います!
『Add Todo』に関してはこちら
『Toggle Todo』に関してはこちら完成品
『All』,『Active』,『Completed』のリンクを押すことで、ページに表示されるTodoが変わります!
Action Creatorの作成
新たに
setVisibilityFilter
を追加します。ここでは、フィルター(SHOW_COMPLETEDなど)を受け取り、typeとfilterを返します。src/actions/index.jsexport 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.type
がSET_VISIBILITY_FILTER
の際にaction.filter
を新しいstateとして返します。src/reducers/visibilityFilter.jsimport {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.jsimport {combineReducers} from 'redux'; import todos from './todos'; import visibilityFilter from './visibilityFilter'; const todoApp = combineReducers({todos, visibilityFilter}); export default todoApp;動作確認
src/index.js
にて、正しくデータが格納されるか手動で確認してみましょう!src/index.jsimport {addTodo, toggleTodo, setVisibilityFilter} from './actions'; console.log(store.getState()); /// "SHOW_ALL" store.dispatch(setVisibilityFilter('SHOW_COMPLETED')); console.log(store.getState()); /// "SHOW_COMPLETED"ここまでで、Action Creatorとreducerを作成し、フィルターの値をstoreに格納することができました。
次からは、フィルターの値によってviewの表示を変更できるようにしましょう!
VisibleTodoListを修正する
todos.filter()のように配列のメソッドのフィルタを用いることで、todoのcompleted属性によって、新たに作成した配列を返します。
src/containers/VisibleTodoList.jsimport {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.jsimport {addTodo, toggleTodo, setVisibilityFilter} from './actions'; store.dispatch(setVisibilityFilter('SHOW_COMPLETED'));Linkを作成する
表示したいTodoの種類をLinkをクリックすることによって表示できるようにします。
まずは、とりあえずLinkを表示させましょう!
props.childrenはコンポーネントの中身を取得できます。Linkコンポーネントを使うときの、
<Link>xxx</Link>
のxxx
です。src/components/Link.jsimport 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.jsimport 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.jsimport 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.jsimport {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.jsimport 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.jsimport 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.jsconst mapStateToProps = (state, ownProps) => { return {active: ownProps.filter === state.visibilityFilter}; };そして、
active
の状態によってテキストを返すか、リンクを返すかをsrc/components/Link.js
にて判断いたします。src/components/Link.jsimport 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と同じ機能が実装できたように思います。
リファレンス
- 投稿日:2019-05-06T21:51:34+09:00
NEXT.jsとReact Hooksを使ってTodoアプリを10分で作る
Reactのフレームワークであり、かつ爆速でReact環境を構築できるNEXT.jsを使って、定番のTodoアプリを作ってみます。
そこにReact Hooksを使えばTodoアプリくらいなら10分もあれば作れるので、NEXT.jsまたはReact Hooksを使った事のない方は、気軽に取り組んでみてください。
作るもの
環境設定
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.jsxconst 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.tsximport { 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.tsximport { 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
- 投稿日:2019-05-06T18:20:56+09:00
React Native + PlayCanvasを使ってスマートフォンゲームを爆速で生み出す
こんにちは、はが@mxcn3と申します。GW中にPlayCanvasを触ってみるという事を予定として入れていたのでその消化をしたことの備忘録です。
※前提としてなのですが、普段は、フロントエンジニアとして働いており、似たようなツールにUnityやUE等があると思うのですが、そちらの存在は名前は知っているという程度で3Dゲームの知識は殆ど無いためおかしい部分がありましたら申し訳ございません。
※React Nativeに関しましては、あまりトンチンカンなことは書いていないはずなのでどうかよろしくおねがい致します。
そもそもPlayCanvasって何?
PlayCanvasとは?
PlayCanvasは、インタラクティブなウェブコンテンツ用のビジュアル開発プラットフォームです。作成するツール> > とウェブアプリは、どちらもHTML5を使用しています。 プラットフォームはウェブでホストされているため、インストールするものは何もなく、対応されているウェブブラウザを実行する任意のデバイスからアクセスできます。
https://developer.playcanvas.com/ja/user-manual/introduction/React Nativeとは
React Nativeは、Reactを使ってネイティブアプリを開発するためのオープンソースのフレームワークです。
実践Expo1.実際に10分くらいでアプリに落とし込んで見る
PlayCanvas側のソースを作る
こちらから登録をして使ってみます。
https://playcanvas.com/登録をして新規プロジェクトを開くとこのような画面になるのでこちら今回、ほぼそのまま使ってみたいと思います。
ただこのままだと少しさみしいのでサイドバーから物理演算をさせるためにCollisionと、RigidBodyを追加してあげます。今回はとりあえずどんな感じに動くかを知りたかっただけなので、左横のアイコンからこのままパブリッシュをしてしまいましょう。
2.WebView
スマートフォンで表示をさせるために今回は、SnackというReact Nativeのコードを面倒事をしないでビルド、表示をしてくれるとても便利な物を使います。
こちらが実際にのReact Nativeのコードなのですが、殆どWebViewで表示をしているだ毛のものとなっております。
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と検索していただければ簡単に出てきます。
3-2. SnackからQRコードを読み込む
https://snack.expo.io/@yutten/playcanvas
Snackはとても便利でURLを共有するだけでソースコードの共有ができるので上記のURLへアクセスしていただければすぐに確認することができます。上記のURLは先程のReact Nativeのコードとなります。
右のiPhoneのマークからRun on your deviceを押していただいてQRをスマートフォンで読み込んでいただくと実機で実行することができます.
3-3 録画したものがこちら
実際に読み込むとこのようなものが表示されているかと思います。(読み込めない場合にはSnackのアカウントを作ることで読み込めるかとおもいます)
結論
PlayCanva製のゲームを、アプリ化するにあたり、トラッキングや広告の部分をReact Nativeに持たすことが出来るのでWebの知識だけしかない場合においても、切り分けをして使うことで面白そうなことはできそうだなと言う印象を持ちました。
あとがき
かなり雑に書きなぐってしまって申し訳ございません、
今日やりたかったこととしては、近年PWAなどが流行っているので、ウェブ向けのゲームエンジンで作ったゲームをWebViewで表示したらどれくらいのパフォーマンスが出るのかなというのが知りたかったこと、最終的にはReact Nativeでアニメーションの描画などで苦労している部分の解決につながる糸口が見つかるかもしれないという期待を込めた検証を込めたものでした。
参考
- 投稿日:2019-05-06T14:18:52+09:00
[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は、
text
とwindows
というステートを持っている。
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>