generated at
CodePenを使ってReact.jsを学ぶ練習log
2020-11-16
18:18:10 ここで作業している
これcode保持されるのか?
上書きすると消えるのかな?
わからん
18:23:19 これで動いた
html
<div id="app"></div>
jsx
const element = <p id="the-text" className="text"> Hello world </p>; ReactDOM.render(element, document.getElementById('app'));
18:24:23 引き続きここを参考にいろいろやってみる
18:32:56 こうなった
jsx
const Element = () => <p id="the-text" className="text"> Hello world </p>; const Text = props => <p id={props.id} className="text"> {props.children} </p>; const App = <> <Text id="the-text">Hello, React</Text> <Element /> </>; ReactDOM.render(App, document.getElementById('app'));
18:35:39 2つの違うHTML風objectがある
↑正式名称わからん
jsx
const App = <h1>Hello, world</h1>; ReactDOM.render(App, document.getElementById('app'));
jsx
const App = () => <h1>Hello, world</h1>; ReactDOM.render(<App />, document.getElementById('app'));
後者がFunctionalComponent<>と呼ばれるやつ
前者はReact要素と呼べばいいみたい
仮想DOMとみなせばいいかな?
18:43:12 練習問題を解いてみる
問題1
>以下の HTML に表される DOM を出力する React コードを書いてください。
>(JSX を使用します。)
> <img src="https://media.giphy.com/media/33OrjzUFwkwEg/giphy.gif" alt="" />
18:46:06 簡単だった
jsx
const Text = props => <p id={props.id} className="text"> {props.children} </p>; const Gif = ({id, src}) => <img id={id} src={src} />; const App = () => <> <Text id="the-text">Hello, React</Text> <Gif id="the-gif" src="https://media.giphy.com/media/33OrjzUFwkwEg/giphy.gif" /> </>; ReactDOM.render(<App />, document.getElementById('app'));
問題2
>以下の HTML に表される DOM を出力する React コードを書いてください。
> (JSX を使用します。)
html
<section id="react" class="box"> <h1 class="title">React</h1> <dl class="definition"> <dt class="definition-title">Initial release</dt> <dd class="definition-content">2013/5</dd> <dt class="definition-title">Github stars</dt> <dd class="definition-content">147,940</dd> </dl> </section> <section id="vue" class="box"> <h1 class="title">Vue.js</h1> <dl class="definition"> <dt class="definition-title">Initial release</dt> <dd class="definition-content">2014/2</dd> <dt class="definition-title">Github stars</dt> <dd class="definition-content">163,165</dd> </dl> </section> <section id="angular" class="box"> <h1 class="title">Angular</h1> <dl class="definition"> <dt class="definition-title">Initial release</dt> <dd class="definition-content">2016/9</dd> <dt class="definition-title">Github stars</dt> <dd class="definition-content">60,571</dd> </dl> </section>
こうかな?
jsx
const items = [ { title: "react", info: [ { title: "Initial release", content: "2013/5" }, { title: "Github stars", content: "147,940" } ] }, { title: "vue", info: [ { title: "Initial release", content: "2014/2" }, { title: "Github stars", content: "163,165" } ] }, { title: "angular", info: [ { title: "Initial release", content: "2016/9" }, { title: "Github stars", content: "60,571" } ] } ]; const Title = ({ children }) => <h1 className="title">{children}</h1>; const Definition = (props) => ( <dl className="definition"> {props.info.map(({ title, content }) => ( <React.Fragment key={title}> <dt className="definition-title">{title}</dt> <dd className="definition-content">{content}</dd> </React.Fragment> ))} </dl> ); const Box = ({ id, title, info }) => ( <section id={title} className="box"> <Title>{title}</Title> <Definition info={info} /> </section> ); const App = () => items.map((item) => ( <Box id={item.title} title={item.title} info={item.info} /> )); ReactDOM.render(<App />, document.getElementById("app"));
19:06:52 時間かかった
20minか
scrapboxで書くとlinter効かないからつらい
19:09:15 問題
今度は最初からcodepenで書く
19:16:59 できた
jsx
// GIF 共有サイト GIPHY から持ってきた GIF ID const gifIds = [ '10dU7AN7xsi1I4', 'tBxyh2hbwMiqc', 'ICOgUNjpvO0PC', '33OrjzUFwkwEg', 'MCfhrrNN1goH6', 'rwCX06Y5XpbLG' ]; // 上記配列の要素をランダムに返す function getGifId() { const max = gifIds.length; const index = Math.floor(Math.random() * Math.floor(max)); return gifIds[index]; } const Gif = ({ id }) => <img id={id} src={`https://media.giphy.com/media/${id}/giphy.gif`} />; const App = () => { // ??? GIF ID を表す state を生成する // 初期値として gifIds[0]を渡す const [id, setId] = React.useState(gifIds[0]); // ??? ボタンが押されると GIF 画像が切り替わる const handleClick = () => setId(getGifId()); return ( <> <p> <button onClick={handleClick}>play</button> </p> <Gif id={id} /> </> ); }; ReactDOM.render(<App />, document.getElementById('app'));
GIPHYというサイトがあるのか
便利だな
使ったもの
20:18:36 CodePenより高機能なCodeSandboxを発見した。以降はこれを使うことにする
すっごい便利なweb serviceが作られるようになったなー
21:15:31 つくった
21:15:57 抜粋
tsx
import * as React from "react"; const input = document.createElement("input"); export const FormText = () => { const [name, setName] = React.useState("John");
EventHandlerの型がnative DOMのwrapperになっていることに注意
tsx
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setName(e.target.value); return ( <> <h1>Hello, {name}</h1> <input value={name} onChange={handleChange} /> </> ); };
21:24:53 問題1
logicが絡んできた
これは今度やるか
一度hintに沿ってやった後、もっといい感じにlogicを分離させたい
custom Hooksとか参考になるか。
21:56:41 sand box
2020-11-19 16:30:13 できた
questionaire.tsx
import * as React from "react"; import { options } from "./options"; export const Questionaire = ({ question }: { question: string }) => { const [val, setVal] = React.useState(options[0].value); const [text, setText] = React.useState(""); const getAnswer = () => (val === "" ? text : val); const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => setVal(e.target.value); const onTextChanged = (e: React.ChangeEvent<HTMLInputElement>) => setText(e.target.value); return ( <> <p>{question}</p> <p> {options.map((option) => ( <label> <input type="radio" value={option.value} checked={val === option.value} onChange={handleChange} /> {option.label} </label> ))} </p> <p hidden={val !== ""}> <label> 自由回答欄:<span> </span> <input type="text" value={text} onChange={onTextChanged} /> </label> </p> <hr /> <p> 回答: <span>{getAnswer()}</span> </p> </> ); };
options.ts
export const options = [ { value: 'js', label: 'JavaScript' }, { value: 'py', label: 'Python' }, { value: 'rb', label: 'Ruby' }, { value: '', label: 'その他' }, ];
refactoringしたほうが良い箇所はあるが、やらないで次に行く
refactoringするより先の問題をときたい
16:32:21 問題2
password入力欄とボタンをセットにするとよさそう
16:56:20 できた
PasswordForm.tsx
import * as React from "react"; export const PasswordForm = () => { const [password, setPassword] = React.useState(""); const [visible, setVisible] = React.useState(false); return ( <> <input type={visible ? "text" : "password"} value={password} onChange={(e) => setPassword(e.target.value)} /> <button type="button" children={"view"} onClick={() => setVisible(!visible)} /> </> ); };
event handlerに直接関数を書き込めば、 e: React.ChangeEvent<HTMLInputElement> と書かずに済む
2020-11-20 01:44:42 password情報を外部に置く
このままだとpasswordの取り出しがしにくい
てかViewとLogicが分離できていない
Logic: passwordの保持と変更処理
View: passwordの入力・表示
伏せ字に切り替えるか否かも
いわゆる依存性の注入をやって、ViewからLogicを分離する
単一責任の原則にも該当する?
依存性逆転の原則ではないか。
01:59:10 できた
PasswordForm.tsx
import * as React from "react";
lint errorを取り除くために、型定義を作る必要がある
PasswordForm.tsx
type Props = { password: string; onChange?: (event: React.ChangeEvent<HTMLInputElement>) => void; };
PasswordForm.tsx
export const PasswordForm = ({ password, onChange }: Props) => { const [visible, setVisible] = React.useState(false); return ( <> <input type={visible ? "text" : "password"} value={password} onChange={onChange} /> <button type="button" children={"view"} onClick={() => setVisible(!visible)} /> </> ); };
App.tsx
import * as React from "react"; import "./styles.css"; import { PasswordForm } from "./PasswordForm"; export default function App() { const [password, setPassword] = React.useState(""); return ( <div className="App"> <p>password</p> <PasswordForm password={password} onChange={(e) => setPassword(e.target.value)} /> <p>{password.length}characters</p> </div> ); }
02:31:27 入力欄はこれでいいかな?
Guess_old.tsx
// 入力欄 import * as React from "react"; type Props = { prediction: number; setPrediction?: (prediction: number) => void; };
これだと数値が変更されない
Guess_fail.tsx
export const Guess = ({ prediction, setPrediction }: Props) => { return ( <> <input type="number" value={prediction} /> <button type="button" children={"predict"} onClick={() => setPrediction(prediction)} />
常に prediction で固定されてしまう
onChange を指定する必要がある
とすると、 useState で状態を持つ必要が出てくるのか。
03:41:24 buttonを押すまで <App /> に値を伝えられないので、代わりに <Guess /> で保持しておく必要がある、とも考えられる
02:41:33 変えた
02:46:26 onChange に直接 setValue(e.target.valueAsNumber) を入れると、無効な値を入力したときに入力欄が空になってしまう。
それを防ぐために e.target.checkValidity() で入力値を検証し、無効な値のときは何もしないようにする
errorを表す赤枠が表示されなくなる
無効な値をそもそも入力できなくなるため
Guess.tsx
const [value, setValue] = React.useState(prediction); return ( <> <input type="number" value={value} onChange={(e) => e.target.checkValidity() ? setValue(e.target.valueAsNumber) : undefined } /> <button type="button" children={"predict"} onClick={() => setPrediction?.(value)} />

Guess_old.tsx
</> ); };
02:52:34 最小値と最大値を外から設定できるようにした
あと placefolder も設定した
Guess.tsx
// 入力欄 import * as React from "react"; type Props = { prediction: number; onSubmit?: (prediction: number) => void; max?: number; min?: number; }; export const Guess = ({ prediction, onSubmit: setPrediction, max, min }: Props) => { const [value, setValue] = React.useState(prediction); return ( <> <input type="number" value={value} max={max} min={min} placeholder={`from ${min} to ${max}`} onChange={(e) => e.target.checkValidity() ? setValue(e.target.valueAsNumber) : undefined } /> <button type="button" children={"predict"} onClick={() => setPrediction?.(value)} /> </> ); };
03:21:40 本体完成
要件から少し変えた
英語にした
勝敗が決まった後のメッセージを追加した
App.tsx
import * as React from "react"; import "./styles.css"; import { Guess } from "./Guess"; import { random } from "./random"; const max = 50; const initialCount = 5; export default function App() { const [prediction, setPrediction] = React.useState(0); const [answer, setAnswer] = React.useState(random(max)); const [message, setMessage] = React.useState(""); const [count, setCount] = React.useState(initialCount);
↓勝敗が確定していない状態を表現するために undefined を使った
本当は専用の型を使うのがbest
enum JudgeState = {Playing, Won, Lost}; など
App.tsx
const [isWinner, setIsWinner] = React.useState<boolean | undefined>( undefined ); const judge = (num: number) => { if (count === 0 || isWinner !== undefined) { setMessage( `You already ${ isWinner ? "won" : "lost" }. To replay, please click the "replay" button below.` ); return; } setCount(count - 1); if (num === answer) { setMessage("correct!"); setIsWinner(true); } else if (count === 1) { setMessage(`You lose! The answer is ${answer}`); setIsWinner(false); } else if (num > answer) { setMessage(`The answer is less than ${num}`); } else if (num < answer) { setMessage(`The answer is more than ${num}`); } }; const replay = () => { setAnswer(random(max)); setCount(initialCount); setMessage(""); setIsWinner(undefined); }; return ( <div className="App"> <Guess prediction={prediction} onSubmit={(value) => { setPrediction(value); judge(value);
ここで judge prediction を渡してはならない
この段階の prediction はまだ更新前の値が入っている
setXX で更新されるのは、次のrenderingのとき。すぐには更新されない
App.tsx
}} max={max} min={0} /> <p hidden={message === ''}>{message}</p> <p>You can challenge predict {count} times.</p> <button children={'replay'} onClick={replay} /> </div> ); }
一旦ご飯食べる
style.css でimportした
04:35:48 App.tsx つくった
小さくてもとにかくdeployできるMinimum Valuable Productを素早く作る
keyをSubtleCrypto.digest()で作ってみる
04:46:48 なぜかCodeSandboxだとString.prototype.padStart()が入力候補にでてこない
仕方ないので別な関数を使う
05:21:38 やめた
async/awaitが必要
async/awaitはReactの属性内で使えない
もろに副作用扱い
05:31:54 できた
Todo.tsx
import * as React from "react"; import { ToDoData } from "./TodoData"; export const Todo = () => { const [items, setItems] = React.useState([ new ToDoData("Learn JavaScript"), new ToDoData("Learn React"), new ToDoData("Get some good sleep") ]); return ( <div className="panel"> <div className="panel-heading"> <span>⚛</span>️ React ToDo </div> {items.map((item) => ( <label key={item.key} className="panel-block"> <input type="checkbox" /> {item.text} </label> ))} <div className="panel-block">{items.length} items</div> </div> ); };
TodoData.ts
export class ToDoData { constructor(text: string, done: boolean = false) { this._object = { text: text, done: done }; this._key = ""; this.updateKey(); } get text() { return this._object.text; } get done() { return this._object.done; } get key() { return this._key; } private updateKey() { this._key = Math.random().toString(32).substring(2); } private _key: string; private _object: { text: string; done: boolean }; }
これで最低限度を表示できた
05:34:27 componentsにばらしていく
まずはTodoを表示する項目から
05:55:19 できた
TodoItem.tsx
import * as React from "react"; import { TodoData } from "./TodoData"; type Props = { item: TodoData; onCheck: (item: TodoData, checked: boolean) => void; }; export const TodoItem = ({ item, onCheck }: Props) => { return ( <label className="panel-block"> <input type="checkbox" checked={item.done} onChange={(e) => onCheck(item, e.target.checked)} /> <span className={item.done ? "has-text-grey-light" : ""}> {item.text} </span> </label> ); };
Todo.tsx
import * as React from "react"; import { TodoData } from "./TodoData"; import { TodoItem } from "./TodoItem"; export const Todo = () => { const [items, setItems] = React.useState([ new TodoData("Learn JavaScript"), new TodoData("Learn React"), new TodoData("Get some good sleep") ]); const onCheck = (item: TodoData, checked: boolean) => { // 該当するitemを更新 const newItems = items.map((value) => { if (value.key !== item.key) return value; return new TodoData(value.text, checked); }); setItems(newItems); };
これ毎描画ごとにTodoDataを全部精査して値を変更することになるのか
dataが多くなったら更新が大変そうだ
まあ、実際には全部firestoreとかに任せるだろうから、そんなに気にすることでもないんだと思うけど。
Todo.tsx
return ( <div className="panel"> <div className="panel-heading"> <span>⚛</span>️ React ToDo </div> {items.map((item) => ( <TodoItem key={item.key} item={item} onCheck={onCheck} /> ))} <div className="panel-block">{items.length} items</div> </div> ); };
06:11:07 Todoの入力部分を実装する
06:22:18 できた
Input.tsx
import * as React from "react"; type Props = { onAdd: (text: string) => void }; export const Input = ({ onAdd }: Props) => { const [text, setText] = React.useState(""); const sendText = (e: React.KeyboardEvent<HTMLInputElement>) => { if (e.key !== "Enter") return; onAdd(text); setText(""); }; return ( <div className="panel-block"> <input className="input" type="text" placeholder="Enter to add a task" value={text} onChange={(e) => setText(e.target.value)} onKeyDown={sendText} /> </div> ); };
onChange はそとに出したほうが良いかも?
logicとUIを分離させる
まあそのへんはcustom Hooksとかをつかって考えるべきだろう。
06:23:30 もうこの時点で簡単なTodo appは完成している
すごいな
見た目はBulmaが全部やってくれている
あとtutorial形式だから、予め設計済みなのもあるかも
その設計通り、もしくはそこにアレンジを加えた形でコードを書いていけばいい
06:28:33 Filterを作る
href="#"ってなんだろう?
設計を変更する
Filter TodoList 内に TodoItem を表示するようにする
filter方法の切り替え欄はFilterToggle Filter に表示させる
07:13:21 できた
これで完成
Filter.tsx
import * as React from "react"; const filterState = ["All", "Done", "Todo"] as const; export type FilterState = typeof filterState[number]; type Props = { value: FilterState; onChange: (value: FilterState) => void; }; export const Filter = ({ value, onChange }: Props) => { const handleClick = ( key: FilterState, e: React.MouseEvent<HTMLAnchorElement> ) => { e.preventDefault(); onChange(key); }; return ( <div className="panel-tabs"> {filterState.map((state) => (
ここで何故かhref="#"が不正だと警告が出る
なぜだかはわからない
Filter.tsx
<a href="#" onClick={(e) => handleClick(state, e)} className={state === value ? "is-active" : ""} >
Filter.tsx
{state} </a> ))} </div> ); };
TodList.tsx
import * as React from "react"; import { TodoData } from "./TodoData"; import { TodoItem } from "./TodoItem"; import { Filter, FilterState } from "./Filter"; type Props = { items: TodoData[]; onCheck: (item: TodoData, checked: boolean) => void; }; export const TodoList = ({ items, onCheck }: Props) => { const [state, setState] = React.useState<FilterState>("All"); const displayItems = items.filter((item) => { if (state === "All") return true; if (state === "Done" && item.done) return true; if (state === "Todo" && !item.done) return true; return false; }); return ( <> <Filter value={state} onChange={(key) => setState(key)} /> {displayItems.map((item) => ( <TodoItem key={item.key} item={item} onCheck={onCheck} /> ))} <div className="panel-block">{displayItems.length} items</div> </> ); };
<Todo> <TodoList> <TodoItem> へと、 <TodoList> を経由して値と更新関数を渡している
更新関数はそのまま素通し
値は表示内容に応じてfilteringしている
つまり中抜き
Todo.tsx
import * as React from "react"; import { TodoList } from "./TodoList"; import { TodoData } from "./TodoData"; import { Input } from "./Input"; export const Todo = () => { const [items, setItems] = React.useState([ new TodoData("Learn JavaScript"), new TodoData("Learn React"), new TodoData("Get some good sleep") ]); const onCheck = (item: TodoData, checked: boolean) => { // 該当するitemを更新 const newItems = items.map((value) => { if (value.key !== item.key) return value; return new TodoData(value.text, checked); }); setItems(newItems); }; const onAdd = (text: string) => setItems([...items, new TodoData(text)]); return ( <div className="panel"> <div className="panel-heading"> <span>⚛</span>️ React ToDo </div> <Input onAdd={onAdd} /> <TodoList items={items} onCheck={onCheck} /> </div> ); };
useEffect()の話に入る
大量にコード書いているからだんだんページが重くなってきた……
分けるか?
07:31:49 分けた

#2020-11-19 16:32:18
#2020-11-16 18:18:46