관리 메뉴

Dev Blog

20210307 19일차 그림판/일기장 로직 구현(85% 완성) 본문

BootCamp_Codestates/Final Project

20210307 19일차 그림판/일기장 로직 구현(85% 완성)

Nomad Kim 2021. 3. 7. 20:23

수도 로직

Paint Page: 그림그리기 완료 여부를 상태끌어올리기로 app 으로 올리고 diary 페이지에 props로 전달.

- Diaryinfo component: 날씨 정보 저장 및 상태끌어올리기로 page 로 전달

- Title: isLogin 상태에 따라 (손님) 의 행복한 하루 또는 (유저이름) 의 행복한 하루 로 구현

- Cpaint component: 그림의 좌표 데이터/url 을 저장하고 이를 diary 에 전달.

 

실제 구현

1. 로그인, 로그아웃, 회원가입 코드 리팩토링. 로그인 상태를 최상위의 app.tsx 에서 관리

로그인, 로그아웃 로직과 회원가입 로직은 이미 끝낸 상태였지만,

회원가입 후 자동 로그인된 상황에서 로그아웃을 했을때 메인페이지의 작동이 잘 안되는 현상이 발생했다. (로그아웃 했는데 로그아웃 버튼이 존재하고 모든 네이게이션 버튼이 존재하는 상황)

결론적으로는, 부모를 app.tsx 로 두고 로그인, 로그아웃, 회원가입 시 변화하는 로그인 상태를 관리하도록 구현했다.

회원가입을 하면 로그인 상태가 변화하는데 changeSignin 이라는 함수를 이용하여 상태끌어올리기를 한다.

로그인, 로그아웃도 마찬가지로 이 함수를 사용하는데, isSignin 상태를 자식에게 전달하는 점이 다르다. 이는 자식 컴포넌트에서 로그인 유무에 따라 보여줘야 하는 엘리먼트가 다르기 때문에 이 isSignin 이 true 냐 false 냐가 기준이 된다.

function App(): ReactElement {
	const [isSignin, setSignin] = useState(false);
	const [weatherData, setWeatherData] = useState("");
	const [imgUrl, setImgUrl] = useState("");
	const [imgData, setImgData] = useState("");

	const changeSignin = (e: boolean) => {
		console.log(e);
		setSignin(e);
	};
	const changeWeather = (e: string) => {
		// console.log(e);
		setWeatherData(e);
	};
	const changeImgUrl = (e: string) => {
		// console.log(e);
		setImgUrl(e);
	};
	const changeImgData = (e: string) => {
		// console.log(e);
		setImgData(e);
	};

	return (
		<Router>
			<Main>
				<Switch>
					<Route exact path="/">
						<Manual />
						<SignIn changeSignin={changeSignin} isSignin={isSignin} />
					</Route>
					<Route exact path="/signup">
						<Manual />
						<Application changeSignin={changeSignin} />
					</Route>
					<Route exact path="/creatediary">
						<Paint changeWeather={changeWeather} changeImgUrl={changeImgUrl} changeImgData={changeImgData} />
						<Diary weatherData={weatherData} imgUrl={imgUrl} imgData={imgData} />
					</Route>

2. 그림판, 일기장 로직 구현

그림판 컴포넌트에서 날씨 데이터, 이미지 url, 이미지 좌표 데이터를 diary 페이지에 전달하는데, 이는 모든 데이터가 제출되었을 때 diary 의 완료버튼이 작동할 수 있도록 하기 위해서이다. 즉, 이 데이터들을 상태끌어올리기 및 diary 에 전달하고,

diary 에서의 제목, 내용 작성, 기분 까지 선택되었을 때 완료버튼이 작동하여 서버에 API 를 통해 제출하게 된다.

 

예) 다이어리에 그림판 데이터를 전달하는 코드

interface paintDataProps {
	weatherData: string;
	imgUrl: string;
	imgData: string;
}

export default function Diary(props: paintDataProps): ReactElement {
	const { weatherData, imgUrl, imgData } = props;
	const history = useHistory();
	const [isPublic, setIsPublic] = useState(false);
	const [title, setTitle] = useState("");
	const [content, setContent] = useState("");
	const [emotion, setEmotion] = useState("");

예) 완료버튼을 눌렀을때 작동하는 제출코드. 그림 완료 버튼을 눌러야 이미지 url 과 좌표데이터가 전달된다.

	const handleSubmit = async () => {
		if (weatherData === "") {
			console.log("날씨를 선택해주세요");
		} else if (imgUrl === "" || imgData === "") {
			console.log("그림 완료 버튼을 누르세요");
		} else if (title === "") {
			console.log("제목을 써주세요");
		} else if (content === "") {
			console.log("내용을 써주세요");
		} else if (emotion === "") {
			console.log("기분을 선택해주세요");
		}
		return;
		await axios.post("https://royal-diary.ml/contents/ccontent", {

3. 그림 PNG 이미지 multer 에 송부 및 이미지 URL 리턴 작동

관련코드는 아래와 같다. 비동기로 받아오기 때문에 하나하나 변수에 담아 리턴하는 방식으로 구현했다. 이해가 편하다.

diary 페이지에 전달하는 그림 데이터는 2가지이다.

  1. multer 로 S3 버켓에 그림을 업로드하고 리턴받는 Image Url => 일기보기 page 에서 사용

  예) royaldiarymulter.s3.ap-northeast-2.amazonaws.com/1616116451513

  2. 그림의 좌표데이터 => 그림 수정시 사용(되돌리기 기능이 가능)

  예)

	async function dataURLtoFile(dataurl: string) {
		const blobBin = atob(dataurl.split(",")[1]); // base64 데이터 디코딩
		const array = [];
		for (let i = 0; i < blobBin.length; i += 1) {
			array.push(blobBin.charCodeAt(i));
		}
		const u8arr = new Uint8Array(array);
		const file = new Blob([u8arr], { type: "image/png" }); // Blob 객체 생성
		const formdata = new FormData(); // formData 생성
		formdata.append("img", file); // formdata에 file data 추가
        // axios 로 서버에 img 파일 보내기
		// 유알엘을 리턴하여 saveAsPNG 에서 사용할 수 있도록!!
		const imgUrl = await axios
			.post("https://royal-diary.ml/image", formdata, {
				headers: { "content-Type": "multipart/form-data" },
			})
			.then((res) => {
				const returnedUrl = res.data.imgUrl;
				return returnedUrl;
			})
			.catch((err) => {
				console.log("server error occured");
			});
		return imgUrl;
	}
	const saveAsPNG = async () => {
		const canvas = document.querySelector(".CanvasDraw canvas:nth-child(2)") as HTMLCanvasElement;
		const imgUrl = canvas.toDataURL("image/png");//canvas Element 에 그려진 내용을 Data URL로 변환
		const returnedUrl = await dataURLtoFile(imgUrl);//S3버켓에 파일업로드 및 이미지URL을 받는 메소드로 Data URL 전달
		// 서버로부터 리턴받은 url 을 handleSaveClick 에 전달
		return returnedUrl;

		/* 이미지 데이터로 원하는 이미지 엘리먼트에 이미지를 만들 수 있다.
		const newImage = document.createElement("img");
		newImage.src = image;
		document.querySelector(Main)?.append(newImage);
		*/
		/* 이미지 파일을 다운받을수 있다. 
		 downloadImage(image, "my-canvas.png");
		*/
	};

	/* 이미지 파일 다운받는 함수 
	function downloadImage(data: string, filename: string) {
		const a = document.createElement("a");
		a.href = data;
		a.download = filename;
		document.body.appendChild(a);
		a.click();
	}
	*/
	const handleSaveClick = async () => {
		const data = firstCanvas.current.getSaveData();//그림의 좌표데이터
		setImgData(data);//상태(좌표데이터)끌어올리기
		const imgUrl = await saveAsPNG();
		setImgUrl(imgUrl);//상태()끌어올리기
		// multer 에 전송후 받은 url 과 좌표 데이터를 sessionStorage 에 저장해서 글쓰기 완료 버튼을 눌렀을때 사용한다..!
	};

시행착오

처음엔 기존에 있던 코드가 아깝다(?)는 마음이 들어서인지, signin page 에 있는 로그인 상태를 사용하여 구현을 시도했다.

하지만, 생각처럼 작동하지가 않았다. 특히, props 가 닿지 않은 곳은 sessionStorage 데이터를 사용하여(props 로 구현하기전, 그림판 데이터들을 이 세션스토리지에 담아 다이어리에 전달하고자 했다.) 해결하려고 했지만, 새로고침이 되어야 다이어리에서 이 데이터들을 읽을 수 있는 문제가 있었다. 그렇다고 새로고침을 하면 다이어리의 내용들이 모두 사라진다.

그래서 결국 최상위에 위치하는 app.tsx 에 로그인 상태를 두었고, 몇가지 함수들을 만들어 그림판 데이터를 다이어리에 전달해 줄 수 있었다.

세션 데이터는 참 좋은 것이지만, 실시간으로 반영하는 것이 불가하다. 관련하여 이 세션 데이터를 사용하는 useEffect 함수에 대해 더 공부가 필요하다.  코드 상 리뷰할 내용이 더욱 많지만, 너무 많아서 추후 정리하도록 한다.

 

도움 받은 사이트

multer 로 이미지 업로드하기: www.zerocho.com/category/NodeJS/post/5950a6c4f7934c001894ea83

useEffect 사용하기

1) deps 파라미터: react.vlpt.us/basic/16-useEffect.html

2) 공식 문서 번역: medium.com/@dayong/%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85-react-hooks-3-useeffect-6b781a6c6769

3) handle API calls using async await with the useEffect hook: javascript.plainenglish.io/handling-api-calls-using-async-await-in-useeffect-hook-990fb4ae423

4) 여러개의 input 상태관리 하기 (사용하진 않았지만.): velog.io/@zwonlala/Hook-setState-React%EC%97%90%EC%84%9C-%EC%97%AC%EB%9F%AC%EA%B0%9C%EC%9D%98-input-%EC%83%81%ED%83%9C-%EA%B4%80%EB%A6%AC%ED%95%98%EA%B8%B0

Specifying onClick event type with Typescript and React.Konva:

stackoverflow.com/questions/45089866/specifying-onclick-event-type-with-typescript-and-react-konva/45092365

Comments