案件でPOSTする際、
『Content-type: form-data』で送信する機会があったので、まとめます。
ボタン部分はマテリアルUIを使っています、初見の方は細かく気にしなくても大丈夫です
環境
react 16.12.0
typescript 3.7.3
material-ui/core 4.8.0(Buttonに使用)
##したいこと
inputで選択したファイルをstateにセット、セットしたファイルを POSTする
全体
const IconUpload: FC = () => {
const [userIconFormData, setUserIconFormData] = useState<File>()
const handleSetImage = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return
const iconFile:File = e.target.files[0]
setUserIconFormData(iconFile)
}
const handleSubmitProfileIcon = () => {
const iconPram = new FormData()
if (!userIconFormData) return
iconPram.append('user[icon]', userIconFormData)
axios
.post(
'https://api/update',
iconPram,
{
headers: {
'content-type': 'multipart/form-data',
},
}
)
}
return (
<form>
<p>アイコンアップロード</p>
<input
type="file"
accept="image/*,.png,.jpg,.jpeg,.gif"
onChange={(e: ChangeEvent<HTMLInputElement>) => handleSetImage(e)}
/>
<Button
text="変更する"
variant="contained"
color="primary"
type="button"
onClick={handleSubmitProfileIcon}
disabled={userIconPreview === undefined}
/>
</form>
)
}
export default IconUpload
##切り分けて解説
###form周り、ボタン部分
<form>
<p>アイコンアップロード</p>
<input
type="file"
accept="image/*,.png,.jpg,.jpeg,.gif"
onChange={(e: ChangeEvent<HTMLInputElement>) => handleSetImage(e)}
/>
<Button
text="変更する"
variant="contained"
color="primary"
type="button"
onClick={handleSubmitProfileIcon}
disabled={userIconPreview === undefined}
/>
</form>
####input
- type:fileにすることによってアップロードが可能になる
- accept:アップロード画面で、ここに指定された拡張子のファイルのみ選択可能になる
- onChange:通常のinputと異なり、valueにはFileを入れることはできない、なので別の場所に取得したFileを保持するfunctionを呼び出す
####Button
- onClick:onChangeでセットしたstateをaxiosでPOSTするfunctionを呼び出す
###ファイルを取得するfunction
const handleSetImage = (e: ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return
const iconFile:File = e.target.files[0]
setUserIconFormData(iconFile)
}
if (!e.target.files) return
TypeScriptでは、undefindになる可能性のある値に関してはエラーがでるので先に無い場合はreturnすることを明示的にしている
const iconFile:File = e.target.files[0]
これがファイル本体。
filesは複数選択可能の場合を備え、配列になっていて、[0]としてあげないと取得できない。
型はFile。
setUserIconFormData(iconFile)
stateにセット
####ちなみに、e.target.files[0]
をURLにしてimgに入れたいとなると
const blobUrl = URL.createObjectURL(iconFile)
blob:http://パス
に変換され、URLをしてとして扱うことができます。
###ファイルをaxiosでPOSTするfunction
const createProfileIcon = () => {
const iconPram = new FormData()
if (!userIconFormData) return
iconPram.append('user[icon]', userIconFormData)
axios
.post(
'https://api/update',
iconPram,
{
headers: {
'content-type': 'multipart/form-data',
},
}
)
}
ようやくAPI送信部分
content-type: multipart/form-data
で送信する際、気をつけること2つ
####1. FormData
という形式で送ってあげなければいけない。
const iconPram = new FormData()
FormDataオブジェクトを作成
型はそのまんまで、FormData
iconPram.append('user[icon]', userIconFormData)
作成したFormDataオブジェクトにパラメーターを追加
append(キー, 送信したいファイル)
####2. headersに'content-type': 'multipart/form-data'
を指定
基本API通信する時はjsonが多いかと思いますが、jsonでは正しく送れず、空になるので注意
_axios.create
を使って、Classでまとめている場合にも、
className.defaults.headers = { 'Content-Type': 'multipart/form-data' }
で、上書きしてあげましょう
##送信内容
こんな感じです。
###確認ポイント
-
RequestHeadersの
content-type
が**multipart/form-data
**になっている - FormDataのvalueが**
(binary)
**になっている
##まとめ
これで正常にmultipart/form-dataでファイルをPOSTできるようになりました!
WEBサービスを作成する際、画像のアップロードはよく出でくるシチュエーションだとおもいますので、参考になれば幸いです
初めてこの機能を実装する際には迷う部分がたくさんあって、ファイルのPOSTする形も何種かあると思っていましたが、2種類のみのようです。
- Fileのまま送信(multipart/form-data)
- Base64にエンコードして送信(application/json)
エンコードするとjsonのまま送れるメリットがあるのですが、ファイルサイズが20~30%あがるそうです。
やればやるほど奥が深く、正解がなくて迷いますが、楽しいですね