
파일을 업로드, 다운로드 할 수 있는 Firebase의 기능중에 하나이다.

Build > Storage로 이동하여 시작하기 버튼을 클릭해서 데이터베이스와 마찬가지로 테스트 모드로 생성한다. 스토리지와 데이터베이스는 동일한 모드로 사용해야한다.

Firestore의 파일구조와 같은 형식으로 이뤄져있다.

공식문서에서는 위 문법을 사용하라고 나와있는데, 강의 영상에서는 그냥 진짜 심플하게
//기존에 있던 코드
import { initializeApp } from 'firebase/app';
const app = initializeApp(firebaseConfig);
//새로 추가한 storage활성화 코드
const storage = getStorage(app);
이렇게만 쓴다.
hoxy... app이 firebaseConfig니까... 거기에 추가한다는 뜻으로... 쓰는건가...?
왜 저 문법을 사용해도 되는건지 아는 분 계시다면 알려주세욥...🖤
Text는 tweet을 해보았고, 이제 img를 tweet해볼 차례다.
파일의 state를 관리하도록 useState를 생성했다.
const [hasFile, setFile] = useState<File | null>(null);
사실 위의 문법이 이해가 되지 않는데, JavaScript 문법으로는 useState(null)이렇게 하면 될 것이다. TypeScript 문법에서는 왜 useState<File | null>을 해야하는지 몰라서 답답했는데 몇시간 구글링해 본 결과 찾았다...!
const onFileChange = (
e: React.ChangeEvent<HTMLInputElement>
) => {
const { files } = e.target;
if (files && files.length === 1) {
setFile(files[0]);
}
};
const files = e.target.value로 해서 작동이 전혀 안되는걸 발견하고 콘솔을 찍어서 하나씩 뜯었다. 강의 영상을 보면서 구현하는 것이지만 이해를 하지않고 그냥 따라하기만 하는 것은 내 지식이라고 할 수 없기 때문에 최대한 찾아보면서 이해하려고 노력한다. 그래서 한개의 기능을 수행하기까지 시간이 정말 오래 걸린다.
if문의 경우 처음에는 files!=null, files.length!==0 이 두가지를 사용했었는데, 문법 에러가 출력되어서 이것저것 시도해보니 files가 들어있어야 실행이 된다...
여러 파일을 업로드 해보고싶었는데 파일은 하나만 올릴 수 있다.
그럼 굳이 인덱싱이 필요할까 싶어서 인덱스를 빼고 실행해보았다.
file은 어차피 한 개만 올려지니 files.length === 1는 뺐다.
if (files) {
//setFile(files[인덱스])에서 [인덱스]를 제거해봄
setFile(files);
}
//오류내용
Argument of type 'FileList' is not assignable to parameter of type 'SetStateAction<File | null>'.ts(2345)
구글링 해보니 타입불일치 할 때 나오는 에러라고 한다.
files의 타입을 찍어보면 오브젝트이다...?
오브젝트에 왜 인덱싱이 필요하지...?싶지만 인덱스도 같이 출력되고 있다. 오브젝트가 아니라 어레이 같다.


그렇다고 한다...
어쨌든 인덱스가 필요한 모양이니 넣어는 줘 본다...
submit함수에서 fileState가 변경됐을 때 실행되는 조건문을 추가해보자.

Firestore에서 제공하는 인스턴스 ref를 통해 파일이 저장될 경로를 지정할 수 있다.
//전달인수는 FirebaseSDK에 활성화 시킨 storage이다.
const locationRef = ref(storage, Firestore 저장경로);
내 경우는 tweets라는 스토리지 안에서 할당된 각 user별 폴더에 다시 문서별로(각 tweet별로) 분류해서 저장을 하고 싶으므로 아래와 같이 경로를 지정했다. uid는 userId로 역시 Firebase에서 제공하는 인스턴스이다.
문서별 id를 생성하기 위해서 공식문서를 참고했다.

const doc = await addDoc(collection(db, 'tweets'), {
tweet,
creatAt: Date.now(),
userName: user.displayName || 'Anonymous',
userId: user.uid,
});
if (hasFile) {
const locationRef = ref(
storage,
`tweets/${user.uid}-${user.displayName}/${doc.id}`
);
}

uploadBytes메소드를 사용하여 이미지를 업로드 해보려고 한다.
//문법
uploadBytes(저장경로, 파일);

전송한 이미지가 storage에 저장된 것을 볼 수 있다.

파일경로에 user.DisplayName으로 유저의 이름까지 추가했었는데 제대로 지정된 경로까지 있는 것을 볼 수 있었다.

입력했던 메세지는 Firestore에 저장되어있었다.
uploadByte는 promise로 업로드한 결과에 대한 참조를 반환한다.
const result = await uploadBytes(
locationRef,
hasFile
);
console.log('result', result);
콘솔을 찍어보면 아래와 같은 결과를 볼 수 있다.

이것을 이용해 데이터를 URL로 다운받을 수 있다.
result의 ref가 location과 path를 받아온 것을 볼 수 있으니 이것을 사용하는 것이다.

const url = getDownloadURL(result.ref);
getDownloadURL은 결괏값을 string으로 반환하기에 이것을 변수url에 담는다.
storage에 업로드된 file을 바로 화면에 바인딩하는 방법은 없는 것 같다.
따로 불러오는 방법을 쓰면 될 것 같은데 강의 영상에서는 없다고 하는데...🤔 이건 추후에 테스트 해보려고 한다.
getDownloadURL로 받아온 url을 doc에 삽입해보자.

const url = await getDownloadURL(result.ref);
//삽입할 doc이름, {삽입할 정보}
await updateDoc(doc, {imgUrl: url})

const onSubmit = async (
e: React.FormEvent<HTMLFormElement>
) => {
e.preventDefault();
try {
setLoading(true);
const doc = await addDoc(collection(db, 'tweets'), {
tweet,
creatAt: Date.now(),
userName: user.displayName || 'Anonymous',
userId: user.uid,
});
if (hasFile) {
const locationRef = ref(
storage,
`tweets/${user.uid}-${user.displayName}/${doc.id}`
);
const result = await uploadBytes(
locationRef,
hasFile
);
const url = await getDownloadURL(result.ref);
await updateDoc(doc, { imgUrl: url });
}
} catch (error) {
console.log('에러', error);
} finally {
setTweet('');
setFile(null);
setLoading(false);
}
};
TypeScript를 잘 모르는 상태에서 접하다보니 문법이 왜 이렇고 저런지 이해할 수 없어서 시간이 많이 걸렸다. 뜻밖의 결과이지만 타입스크립트 퇴출에 대한 기사와 포스트들을 정말 많이 보았는데 내가 타입스트립트를 몰라서 그런 것인지 모르겠지만 너무 번거롭다는 생각이 압도적으로 든다.
이번 클론이 끝나면 타입스크립트로 블록체인을 만들어보며 공부해볼 생각이긴 한데 현업에서 타입스크립트를 많이 사용하는지도 일단 의문이다. 자바스크립트보다 압도적으로 많이 사용하나...?
다들 타입스크립트가 좋다고 하는데 정작 내 주변에는 자바스크립트를 쓰는 사람이 더 많다.
명쾌함보다는 의문이 더 많이 드는 시간이었다.