#author("2024-03-01T21:26:57+09:00","default:shota","shota") #author("2024-03-01T22:06:57+09:00;2024-03-01T21:26:57+09:00","default:shota","shota") [[FrontPage]] *目次 [#d92fec5f] #contents *useState [#s98aff1c] -関数コンポーネントでStateを使用するためにはuseState関数で初期化とセッター作成を行う const [stateを格納する変数, stateの値を更新する関数] = useState(初期値) Counter.js #highlightjs(javascript) import { useState } from 'react'; export default function Counter({ init }) { const [count, setCount] = useState(init); return ( <> <p>{ count }</p> <button onClick={ () => setCount( c => c + 1 ) }>++</button> </> ); } -Form --onChange属性を使用すると値の変更がされたときにハンドラを呼び出すことができる #highlightjs(javascript) export default function UserForm() { const [form, setForm] = useState({ name: '', email: '' }); const formHandler = e => { setForm({ ...form, [e.target.name]: e.target.value }) }; return ( <form> <p>User: {form.name}</p> <p>Email: {form.email}</p> <div> <label htmlFor="name">Name</label> <input id="name" name="name" type="text" onChange={formHandler} value={form.name} /> </div> <div> <label htmlFor='email'>E-mail</label> <input id="email" name="email" type="email" onChange={formHandler} value={form.email} /> </div> <div> <button type="button" onClick={ () => alert(`Hello ${form.name}`)}> 送信 </button> </div> </form> ); } *useRef [#jd31ce52] -Stateは更新時にDOMの再描画が行われるが,再描画を行いたくない場合はRefを使う -Refは`useRef(初期値)`で宣言可能で,`ref変数.current = xxx`で値の更新を行う -Refで宣言された変数は要素が再描画された場合でも変数の中身を保持することができる #highlightjs(javascript) export default function Counter() { // countRefの値が変更されても再描画はされない const countRef = useRef(0); const handleClick = () => { // Ref変数.currentで値にアクセスできる countRef.current = countRef.current + 1; }; return ( <> <button onClick={handleClick}>Count++</button> <button onClick={() => alert(countRef.current)}>Show</button> </> ); } *useReducer [#wac78543] -State同様に値を保持する -Actionを用いたセッターを使用 -複雑なセッターが複数個ある場合にロジックを1箇所にまとめておくことができる -useReducer関数を使用してStateとReducerを呼び出すためのdispatch関数を作成する -Reducer --Stateの更新の際に呼ばれる関数 --保持されているStateと更新時のdispatch関数に渡すActionオブジェクトが渡され,それを元に更新後のStateを返却する --Reducerに渡されるStateは読み取り専用であるため更新をしてはいけない -Dispatch関数 --typeプロパティとReducerに渡したい値を含んだActionオブジェクトを渡して,Reducerを呼び出す #highlightjs(javascript) useReducer( reducer, initialArg, init? ) -initialArg: Stateの初期値になる値(3つ目の引数[関数]を指定する場合はその引数になる値) -init?: オプション.Stateの初期値を関数によって決定したい場合に指定する #highlightjs(javascript) export default function Counter({init = 0}) { const [count, dispatch] = useReducer( (count, action) => { switch (action.type) { case 'increment': return count + 1; case 'reset': return init; default: throw Error('Unknown action: ' + action.type); } }, init ); return ( <> <p>{count}</p> <button onClick={() => dispatch({type: 'increment'})}>++</button> <button onClick={() => dispatch({type: 'reset'})}>reset</button> </> ); } 少し複雑な例~ App.js #highlightjs(javascript) import {useReducer} from "react"; import AddNote from "./AddNote"; import NoteList from "./NoteList"; let initialNotes = []; function notesReducer(notes, action) { switch (action.type) { case 'added': { return [...notes, { id: crypto.randomUUID(), text: action.text, }]; } case 'edited': { return notes.map( e => { return e.id === action.note.id ? action.note : e; }); } case 'deleted': { return notes.filter( e => e.id !== action.id); } default: { throw Error('Unknown action: ' + action.type); } } } export default function App() { const [notes, dispatch] = useReducer( notesReducer, initialNotes ); // ノートを追加 function handleAddNote(text) { dispatch({ type: 'added', text: text, }); } // ノートを編集 function handleEditNote(note) { dispatch({ type: 'edited', note: note, }); } // ノートを削除 function handleDeleteNote(id) { dispatch({ type: "deleted", id: id, }); } return ( <> <h1>Notes</h1> <AddNote onAddNote={handleAddNote}/> <NoteList notes={notes} onEditNote={handleEditNote} onDeleteNote={handleDeleteNote} /> </> ); } AddNote.js #highlightjs(javascript) import {useState} from "react"; export default function AddNote({onAddNote}) { const [text, setText] = useState(''); return ( <> <input placeholder="ノートを追加" value={text} onChange={e => setText(e.target.value)} /> <button onClick={() => { onAddNote(text); setText(''); }}>追加</button> </> ); } NoteList.js #highlightjs(javascript) import {useState} from "react"; export default function NoteList({ notes, onEditNote, onDeleteNote, }) { return ( <> {notes.map(e => ( <div key={e.id}> <NoteRow note={e} onEdit={onEditNote} onDelete={onDeleteNote} /> </div> ))} </> ); } function NoteRow({ note, onEdit, onDelete }) { const [isEditing, setIsEditing] = useState(false); if (isEditing) { return ( <> <input value={note.text} onChange={e => { onEdit({ ...note, text: e.target.value }); }} /> <button onClick={() => setIsEditing(false)}> 保存 </button> </> ); } else { return ( <> {note.text} <button onClick={() => setIsEditing(true)}> 編集 </button> <button onClick={() => onDelete(note.id)}> 削除 </button> </> ); } } *useContext [#sb873fdb] -上位のコンポーネントの値にアクセスするためのフック -createContext関数でContextオブジェクトを定義 -useContext関数で上位コンポーネントで定義されたContextオブジェクトを参照可能 -`Contextオブジェクト.Provider`コンポーネント以下の子コンポーネントではContextオブジェクトのdefaultValueではなくvalue属性で指定した値が参照される const Contextオブジェクト = createContext(defaultValue); const 値 = useContext(Contextオブジェクト); #highlightjs(javascript) import {createContext, useContext} from "react"; export const MessageContext = createContext('hello'); export default function Context() { return ( <> {/* Context.Provider以下のコンポーネントではvalueの値になる */} <MessageContext.Provider value='hello world'> <Label /> {/* 'hello world'が描画される */} </MessageContext.Provider> {/* Context.Provider以下のコンポーネントでない場合はdefaultValueが使用される */} <Label /> {/* defaultValueの'hello'が描画される */} </> ); } function Label() { return <Message /> } function Message() { const message = useContext(MessageContext); return <p>{message}</p> } *useEffect [#a0bc88a8] -主に外部のAPI等を呼び出す処理を記述する -コンポーネントがレンダリングされたあとに処理を実行する -useEffect関数の返り値はundefinedを返す useEffect(setup, dependencies?) -setupに呼び出したい処理(関数)を指定する -dependenciesは省略可能 --省略した場合はコンポーネントの再レンダリングのたびにsetupの処理を実行する --空配列にした場合はコンポーネントの初期描画時にのみsetupの処理を実行する --変数群を指定(1つの場合でも配列で指定する)した場合は,それらの変数の値が変化した場合にsetupの処理が実行される #highlightjs(javascript) import {useEffect, useState} from "react"; export default function Weather({latitude='35.662422', longitude='139.590247'}) { const [data, setData] = useState(null); useEffect(() => { fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m&timezone=Asia%2FTokyo&forecast_days=1`) .then(response => response.json()) .then(json => setData(`time:${json.current.time}, temperature:${json.current.temperature_2m}`)); }, []); if (!data) { return <p>Loading...</p> } return <p>{data}</p> } **アンマウント時の処理 [#fd0696e5] -第一引数の関数の戻り値として関数を返すようにすると,アンマウント時にその関数が実行される useEffect(() => { 起動と再描画時に実行 return () => { アンマウント時に実行 } }, [トリガー変数]); **更新処理 [#aaf30169] -dependenciesに更新用のフラグを使用することで,APIからデータ再取得等の処理が行える #highlightjs(javascript) import {useEffect, useState} from "react"; export default function Weather({latitude='35.662422', longitude='139.590247'}) { const [data, setData] = useState(null); const [refresh, setRefresh] = useState(false); useEffect(() => { fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t=temperature_2m&timezone=Asia%2FTokyo&forecast_days=1`) .then(response => response.json()) .then(json => { console.log('update'); setRefresh(false); setData(`time:${json.current.time}, temperature:${json.current.temperature_2m}`); }); }, [refresh]); if (!data) { return <p>Loading...</p> } return ( <> <p>{data}</p> <button onClick={() => setRefresh(true)}>更新</button> </> ); }