FullStackOpen Part7-b Custom hooks メモ
Hooks
useStateやuseEffectなど。
使う上で以下のルールを守る
フックは:
ループ中・IF文・入れ子の関数中で呼ばない
Reactコンポーネント中・カスタムフック中から呼ぶこと
Custom hooks
カスタムフックとはコンポーネントのロジックを別の場所でも再利用するための機能
上記のルールに加えて、フックの名前はuseから始まる必要がある
例えば以下のようなカウンターがあるとする
import { useState } from 'react'
const App = () => {
const [counter, setCounter] = useState(0)
return (
<div>
<div>{counter}</div>
<button onClick={() => setCounter(counter + 1)}>
plus
</button>
<button onClick={() => setCounter(counter - 1)}>
minus
</button>
<button onClick={() => setCounter(0)}>
zero
</button>
</div>
)
}
これをカスタムフックuseCounterにすると以下のようになる
const useCounter = () => {
const [value, setValue] = useState(0)
const increase = () => {
setValue(value + 1)
}
const decrease = () => {
setValue(value - 1)
}
const zero = () => {
setValue(0)
}
return {
value,
increase,
decrease,
zero
}
}
Reactコンポーネントをカスタムフックを使って書き直すと以下のようになる
const App = () => {
const counter = useCounter()
return (
<div>
<div>{counter.value}</div>
<button onClick={counter.increase}>
plus
</button>
<button onClick={counter.decrease}>
minus
</button>
<button onClick={counter.zero}>
zero
</button>
</div>
)
}
カウンター機能をカスタムフックに独立させたので、以下のように使いまわしすることもできる
const App = () => {
const left = useCounter()
const right = useCounter()
return (
<div>
{left.value}
<button onClick={left.increase}>
left
</button>
<button onClick={right.increase}>
right
</button>
{right.value}
</div>
)
}
その他にはフォームの入力項目をすべて一つ一つuseStateしていた以下のコードであれば
const App = () => {
const [name, setName] = useState('')
const [born, setBorn] = useState('')
const [height, setHeight] = useState('')
return (
<div>
<form>
name:
<input
type='text'
value={name}
onChange={(event) => setName(event.target.value)}
/>
<br/>
birthdate:
<input
type='date'
value={born}
onChange={(event) => setBorn(event.target.value)}
/>
<br />
height:
<input
type='number'
value={height}
onChange={(event) => setHeight(event.target.value)}
/>
</form>
<div>
{name} {born} {height}
</div>
</div>
)
}
以下のようにカスタムフックとコンポーネントを書き換えられる
const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
return {
type,
value,
onChange
}
}
const App = () => {
const name = useField('text')
// ...
return (
<div>
<form>
<input
type={name.type}
value={name.value}
onChange={name.onChange}
/>
// ...
</form>
</div>
)
}
Spread attributes
先ほどのnameとuseFieldの例で考えると、input部分はスプレッド構文{…name}で記述できる
const App = () => {
const name = useField('text')
const born = useField('date')
const height = useField('number')
return (
<div>
<form>
name:
<input {...name} />
<br/>
birthdate:
<input {...born} />
<br />
height:
<input {...height} />
</form>
<div>
{name.value} {born.value} {height.value}
</div>
</div>
)
}
可読性においても機能の独立という面でもカスタムフックは優れている
More about hooks
Ready-madeなカスタムフックはいろんなところに転がっているので活用しよう
Awesome React Hooks Resources on Github
演習の気づき
モジュールのエクスポートにはデフォルトエクスポートとネームドエクスポートの二種類ある
デフォルトエクスポート:
export default App
ネームドエクスポート:
export const useField = (type) => {
//…
}
カスタムフックの中身にアクセスするときはこんな感じ。
あくまでauthorはオブジェクトなので注意
props.addNew({
content: content.value,
author: author.value,
info: info.value,
votes: 0
})
Spread Syntaxで必要ないプロパティを除きたいときは以下のようにすればよい。
例えばresetまでInputタグに渡したくない場合は、useFieldを呼ぶ時点で分けておく
const CreateNew = (props) => {
const {reset: resetContent, ...content} = useField('text')
const {reset: resetAuthor, ...author} = useField('text')
const {reset: resetInfo, ...info} = useField('text')
const handleSubmit = (e) => {
e.preventDefault()
props.addNew({
content: content.value,
author: author.value,
info: info.value,
votes: 0
})
}
const resetField = (e) => {
e.preventDefault()
resetContent()
resetAuthor()
resetInfo()
}
演習7.7はこんな感じ
useEffect中は直接asyncできないので、一度asycn関数を定義してから呼び出す
const useCountry = (name) => {
const [country, setCountry] = useState(null)
const [found, setFound] = useState(false)
useEffect(() => {
const fetchCountry = async () => {
try {
const response = await axios.get(`https://studies.cs.helsinki.fi/restcountries/api/name/${name}`)
setCountry(response)
setFound(true)
} catch (exception) {
setCountry(null)
setFound(false)
}
}
fetchCountry()
}, [name])
return {
found,
...country
}
演習7.8はこんな感じ。
新しいカスタムフックuseResourceを設定して、noteServiceとpersonServiceを一つにまとめている
import { useState, useEffect } from 'react'
import axios from 'axios'
const useField = (type) => {
const [value, setValue] = useState('')
const onChange = (event) => {
setValue(event.target.value)
}
return {
type,
value,
onChange
}
}
const useResource = (baseUrl) => {
const [resources, setResources] = useState([])
const getAll = async () => {
const response = await axios.get(baseUrl)
setResources(response.data)
}
const create = async (resource) => {
try {
const response = await axios.post(baseUrl, resource)
setResources(resources.concat(response.data))
}
catch (exception) {
console.log(exception)
}
}
const service = {
getAll,
create
}
return [
resources, service
]
}
const App = () => {
const content = useField('text')
const name = useField('text')
const number = useField('text')
const [notes, noteService] = useResource('http://localhost:3005/notes')
const [persons, personService] = useResource('http://localhost:3005/persons')
const handleNoteSubmit = (event) => {
event.preventDefault()
noteService.create({ content: content.value })
}
const handlePersonSubmit = (event) => {
event.preventDefault()
personService.create({ name: name.value, number: number.value })
}
useEffect(() => {
noteService.getAll()
},)
useEffect(() => {
personService.getAll()
},)
return (
<div>
<h2>notes</h2>
<form onSubmit={handleNoteSubmit}>
<input {...content} />
<button>create</button>
</form>
{notes.map(n => <p key={n.id}>{n.content}</p>)}
<h2>persons</h2>
<form onSubmit={handlePersonSubmit}>
name <input {...name} /> <br />
number <input {...number} />
<button>create</button>
</form>
{persons.map(n => <p key={n.id}>{n.name} {n.number}</p>)}
</div>
)
}
export default App
この記事が気に入ったらサポートをしてみませんか?