React 編
同じ Docker の環境を使って、React を使おうかと思います。
今回も、Pacel-bundler をそのまま使います。
React にしても特に Parcel の設定はいらないです。
では、ライブラリを読み込んで始めましょう
Docker コンテナを起動して、uname
で linux が出るようにしてください。
このあたりは、最初の投稿を参照ねがいます。
docker-compose run --service-ports node js
が書いているあたりです。
一応 yml ファイルを載せておきます
dcoker-compose.yml
version: "3"
services:
node:
container_name: node
image: atoris1192/node:0.1.5
# build: .
# volumes は上書きに注意
volumes:
- .:/app
ports:
- "1234:1234"
- "1235:1235"
working_dir: /app
command: cp -rp /tmp/src /app
# command: npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
tty: true
docker-compose up # 同じディレクトリでするなら必要ないです
docker-compose ps
docker-compose run --service-ports node bash
# コンテナ linux
uname
cd src
yarn add react react-dom
npx parcel --hmr-port 1235 --hmr-hostname localhost index.pug
index.pug
<!DOCTYPE html>
html(lang="en")
head
meta(charset="UTF-8")
meta(name="viewport", content="width=, initial-scale=1.0")
meta(http-equiv="X-UA-Compatible", content="ie=edge")
title Document
body
#app
script(src="./index.tsx")
index.tsx
import * as React from 'react'
import * as ReactDOM from 'react-dom';
import App from './App';
import './style.stylus'
ReactDOM.render(
<App />,
document.getElementById('app')
)
App.tsx
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
class App extends React.Component {
private state: any;
constructor() {
super();
// 仮データを直接いれてますが、Reactでは禁止行為です
this.state = {
todos: todos,
}
}
render() {
const todos = this.state.todos.map ( todo => {
return (<li key={ todo.id }>{ todo.title }</li>)
})
return(
<div className="container">
<ul>
{ todos }
</ul>
</div>
)
}
}
export default App;
addTodo
input 処理が面倒くさい。
Att.tsx
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: todos,
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
}
addTodo(e) {
e.preventDefault();
interface Item {
id: number;
title: string;
isDone: boolean;
}
const newTodos = this.state.todos.slice();
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false
}
newTodos.push(item);
this.setState({
todos: newTodos
item: ''
})
}
updateItem(e) {
this.setState({
item: e.target.value,
})
}
render() {
const todos = this.state.todos.map ( todo => {
return (<li key={ todo.id }>{ todo.title } : { todo.id } : { String(todo.isDone) }</li>)
})
return(
<div className="container">
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<ul>
{ todos }
</ul>
</div>
)
}
}
export default App;
todoList 関数化
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
function TodoList (props) {
const todos = props.todos.map( todo => {
return (<li key={ todo.id }>
{ todo.title } : { todo.id } : { String(todo.isDone) }
</li>)
});
return(
<ul>
{ todos }
</ul>
)
};
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: todos,
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
}
addTodo(e) {
e.preventDefault();
interface Item {
id: number;
title: string;
isDone: boolean;
}
const newTodos = this.state.todos.slice();
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false
}
newTodos.push(item);
this.setState({
todos: newTodos
item: ''
})
}
updateItem(e) {
this.setState({
item: e.target.value,
})
}
render() {
// const todos = this.state.todos.map ( todo => {
// return (<li key={ todo.id }>{ todo.title } : { todo.id } : { String(todo.isDone) }</li>)
// })
return(
<div className="container">
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<TodoList todos={ this.state.todos } />
{/* <ul>
{ todos }
</ul> */}
</div>
)
}
}
export default App;
checkbox, deleteTodo 処理
App.tsx
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
function TodoList (props) {
const todos = props.todos.map( todo => {
return (<li key={ todo.id } className={ todo.isDone ? 'done' : ''}>
<input type="checkbox"
onChange={ () => {props.changeCheckbox(todo)} }
checked={ todo.isDone ? "chacked" : ""}
/>
{ todo.id } : { todo.title } : { String(todo.isDone) }
<span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>
</li>)
});
return(
<ul>
{ todos }
</ul>
)
};
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: todos,
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
this.changeCheckbox = this.changeCheckbox.bind(this);
}
changeCheckbox(todo) {
console.log("changeCheckbox ...");
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(todo.id)
newTodos[pos].isDone = !newTodos[pos].isDone
this.setState({
todos: newTodos
})
}
deleteTodo(id) {
// console.log("deleteBtn");
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(id)
// console.log(pos);
newTodos.splice(pos,1)
this.setState({
todos: newTodos
})
}
addTodo(e) {
e.preventDefault();
interface Item {
id: number;
title: string;
isDone: boolean;
}
const newTodos = this.state.todos.slice();
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false
}
newTodos.push(item);
this.setState({
todos: newTodos
item: ''
})
}
updateItem(e) {
this.setState({
item: e.target.value,
})
}
render() {
return(
<div className="container">
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<TodoList
todos={ this.state.todos }
deleteTodo={ this.deleteTodo }
changeCheckbox={ this.changeCheckbox }
/>
</div>
)
}
}
export default App;
todoList を todoItem でさらに分割
todoList
の分割は好みです。 関数の見通しが悪くなりそうだったら、分割したらいいかと思います。
バケツリレーがもれなくついてくるので、子要素は程々に
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
function TodoItem(props) {
const todo = props.todo;
return (<li className={ todo.isDone ? 'done' : ''}>
<input type="checkbox"
onChange={ () => {props.changeCheckbox(todo)} }
checked={ todo.isDone ? "chacked" : ""}
/>
{ todo.id } : { todo.title } : { String(todo.isDone) }
<span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>
</li>)
}
function TodoList (props) {
const todos = props.todos.map( todo => {
return <TodoItem
key={ todo.id }
todo={ todo }
changeCheckbox={ props.changeCheckbox }
deleteTodo={ props.deleteTodo }
/>
});
return(
<ul>
{ todos }
</ul>
)
};
// function TodoList (props) {
// const todos = props.todos.map( todo => {
// return (<li key={ todo.id } className={ todo.isDone ? 'done' : ''}>
// <input type="checkbox"
// onChange={ () => {props.changeCheckbox(todo)} }
// checked={ todo.isDone ? "chacked" : ""}
// />
// { todo.id } : { todo.title } : { String(todo.isDone) }
// <span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>
// </li>)
// });
// return(
// <ul>
// { todos }
// </ul>
// )
// };
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: todos,
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
this.changeCheckbox = this.changeCheckbox.bind(this);
}
changeCheckbox(todo) {
console.log("changeCheckbox ...");
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(todo.id)
newTodos[pos].isDone = !newTodos[pos].isDone
this.setState({
todos: newTodos
})
}
deleteTodo(id) {
// console.log("deleteBtn");
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(id)
// console.log(pos);
newTodos.splice(pos,1)
this.setState({
todos: newTodos
})
}
addTodo(e) {
e.preventDefault();
interface Item {
id: number;
title: string;
isDone: boolean;
}
const newTodos = this.state.todos.slice();
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false
}
newTodos.push(item);
this.setState({
todos: newTodos
item: ''
})
}
updateItem(e) {
this.setState({
item: e.target.value,
})
}
render() {
return(
<div className="container">
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<TodoList
todos={ this.state.todos }
deleteTodo={ this.deleteTodo }
changeCheckbox={ this.changeCheckbox }
/>
</div>
)
}
}
export default App;
一括削除 purge
import * as React from 'react';
const todos = [
{id: 0, title: "Task 0", isDone: false },
{id: 1, title: "Task 1", isDone: true },
{id: 2, title: "Task 2", isDone: false },
]
function TodoItem(props) {
const todo = props.todo;
return (<li className={ todo.isDone ? 'done' : ''}>
<input type="checkbox"
onChange={ () => {props.changeCheckbox(todo)} }
checked={ todo.isDone ? "chacked" : ""}
/>
{ todo.id } : { todo.title } : { String(todo.isDone) }
<span className="delete" onClick={ () => {props.deleteTodo(todo.id)} } >[x]</span>
</li>)
}
function TodoList (props) {
const todos = props.todos.map( todo => {
return <TodoItem
key={ todo.id }
todo={ todo }
changeCheckbox={ props.changeCheckbox }
deleteTodo={ props.deleteTodo }
/>
});
return(
<ul>
{ todos }
</ul>
)
};
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
function TodoTitle(props) {
return (
<h1>
Todo
<button className="purge" onClick={ props.todoPurge }>Purge</button>
</h1>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: todos,
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
this.changeCheckbox = this.changeCheckbox.bind(this);
this.todoPurge = this.todoPurge.bind(this);
}
todoPurge() {
const todos = this.state.todos.slice();
const newTodos = todos.filter( todo => {
return !todo.isDone;
})
this.setState({
todos: newTodos
})
}
changeCheckbox(todo) {
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(todo.id)
newTodos[pos].isDone = !newTodos[pos].isDone
this.setState({
todos: newTodos
})
}
deleteTodo(id) {
// console.log("deleteBtn");
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(id)
// console.log(pos);
newTodos.splice(pos,1)
this.setState({
todos: newTodos
})
}
addTodo(e) {
e.preventDefault();
interface Item {
id: number;
title: string;
isDone: boolean;
}
const newTodos = this.state.todos.slice();
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false
}
newTodos.push(item);
this.setState({
todos: newTodos
item: ''
})
}
updateItem(e) {
this.setState({
item: e.target.value,
})
}
render() {
return(
<div className="container">
<TodoTitle todoPurge={ this.todoPurge } />
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<TodoList
todos={ this.state.todos }
deleteTodo={ this.deleteTodo }
changeCheckbox={ this.changeCheckbox }
/>
</div>
)
}
}
export default App;
firebase / fireStore
fireStore を 組み込んでいます。
Vue.js編 と似ているので、同じように組み込んで見てください.
ただ, FireStore の組み込み方が React の方法に遵守できていないように思います。
setState
が からんでいない。。。
ここは、また更新するかも。
- コンテナ linux
uname
cd src
yarn add firebase
parcel --hmr-port 1235 --hmr-hostname localhost index.pug
firebase.js
export const config = {
apiKey: "",
authDomain: "",
databaseURL: "",
projectId: "",
storageBucket: "",
messagingSenderId: "",
appId: "",
measurementId: ""
};
React の 一方通行ルールは、 Vue.js をした後では、辛いですね。
input
処理書くだけでも、正直面倒くさい(笑)
setState
を必ず使う必要があるのも、初心者キラーでコードも増えがちです。
個人でちょこちょこ書くなら, Vue.js の方が楽しいかと思います。 チームプレイで他のコードも見ないとだめなら、ルールの厳しいReact のほうがコードを追いやすいのかなと思います。
src/App.tsx
import * as React from 'react';
import * as firebase from 'firebase/app';
import 'firebase/firestore';
import { config } from '../firebase';
firebase.initializeApp(config);
const firestore = firebase.firestore()
const collection = firestore.collection('reactTodo');
function TodoItem(props) {
const todo = props.todo;
return (<li className={ todo.isDone ? 'done' : ''}>
<input type="checkbox"
onChange={ () => {props.changeCheckbox(todo)} }
checked={ todo.isDone ? "chacked" : ""}
/>
{ todo.title } : { String(todo.isDone) } : { todo.id } : { todo.dbId }
<span className="delete" onClick={ () => {props.deleteTodo(todo)} } >[x]</span>
</li>)
}
function TodoList (props) {
const todos = props.todos.map( todo => {
return <TodoItem
key={ todo.id }
todo={ todo }
changeCheckbox={ props.changeCheckbox }
deleteTodo={ props.deleteTodo }
/>
});
return(
<ul>
{ todos }
</ul>
)
};
function TodoForm(props) {
return(
<form onSubmit={ props.addTodo }>
<input type="text"
value={ props.item }
onChange={ props.updateItem }
/>
<input type="submit" value="Add"/>
</form>
)
}
function TodoTitle(props) {
return (
<h1>
Todo
<button className="purge" onClick={ props.todoPurge }>Purge</button>
</h1>
)
}
class App extends React.Component {
private state: any;
private setState: any;
constructor() {
super();
this.state = {
todos: [],
itemsId: [],
item: '',
}
this.addTodo = this.addTodo.bind(this);
this.updateItem = this.updateItem.bind(this);
this.deleteTodo = this.deleteTodo.bind(this);
this.changeCheckbox = this.changeCheckbox.bind(this);
this.todoPurge = this.todoPurge.bind(this);
}
async todoPurge() {
console.log("purge...");
const todos = this.state.todos.slice();
const newTodos = todos.filter( todo => {
return !todo.isDone;
})
this.setState({
todos: newTodos
})
const itemsQuery = await collection.where("isDone", "==" , true)
.get()
.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
const item = collection.doc(doc.id).delete();
console.log(item);
});
})
.catch(function(error) {
console.log("Error getting documents: ", error);
})
}
async changeCheckbox(todo) {
// const newTodos = this.state.todos.slice();
// const pos = newTodos.map( todo => {
// return todo.id
// }).indexOf(todo.id)
// newTodos[pos].isDone = !newTodos[pos].isDone
// this.setState({
// todos: newTodos
// })
// setStateを使っていないので、ルール違反だが、簡潔に処理する方法が思いつかなかったよ
await collection.doc(todo.dbId).update({
isDone: !todo.isDone
});
}
async deleteTodo(deleteTodo) {
const newTodos = this.state.todos.slice();
const pos = newTodos.map( todo => {
return todo.id
}).indexOf(deleteTodo.id)
// なくても動くが、React ルール上付けている
newTodos.splice(pos,1)
this.setState({
todos: newTodos,
})
await collection.doc(deleteTodo.dbId).delete();
}
async addTodo(e) {
e.preventDefault();
if (!this.state.item.trim()) return
interface Item {
id: number;
title: string;
isDone: boolean;
created_at: any;
}
const newTodos = this.state.todos.slice(); // todos Deep Copy
const item: Item = {
id: new Date().getTime(),
title: this.state.item,
isDone: false,
created_at: firebase.firestore.FieldValue.serverTimestamp()
}
newTodos.push(item);
this.setState({
todos: newTodos,
item: '',
})
await collection.add(item)
.then( (doc) => {
console.log('Writed: ',doc.id);
})
.catch( err => {
console.log(err);
})
}
async componentDidUpdate() { // 更新用
const query = await collection.orderBy("created_at", "desc").get()
const itemsData = query.docs.map( item => {
return ({
dbId: item.id,
id: item.data().id,
title: item.data().title,
isDone: item.data().isDone,
created_at: item.data().created_at,
date: item.data().date,
})
})
this.setState({
todos: itemsData,
})
}
async componentDidMount() { // リロード用
const query = await collection.orderBy("created_at", "desc").get()
const itemsData = query.docs.map( item => {
return ({
dbId: item.id,
id: item.data().id,
title: item.data().title,
isDone: item.data().isDone,
created_at: item.data().created_at,
date: item.data().date,
})
})
this.setState({
todos: itemsData,
})
}
updateItem(e) { // input用
this.setState({
item: e.target.value,
})
}
render() {
return(
<div className="container">
<TodoTitle todoPurge={ this.todoPurge } />
<TodoForm
item={ this.state.item }
updateItem={ this.updateItem }
addTodo={ this.addTodo }
/>
<TodoList
todos={ this.state.todos }
deleteTodo={ this.deleteTodo }
changeCheckbox={ this.changeCheckbox }
/>
</div>
)
}
}
export default App;
React 編 終了です。
React って便利! を実感するために、VanillaJS(素のJS) も予定しています。