1. 완성화면 보러가기

[완성화면-링크]

[완성화면-영상캡처]

[소스코드]


2. 개발 기간 및 기여

개발 기간:

  • 2022년 9월 28일(수) ~ 10월 26일(수) ※ 현재 운영 중에 있음.

기여:

  • 프론트 메인 개발

3. 프로젝트 제목 및 설명

제목:

  • FromC - 위탁 교육, 도입 문의 웹

설명:

  • 초, 중, 고 학생 방과 후 코딩 교육 지원
    • root > src: 프론트를 구성하는 소스코드

4. 기술 스택 및 사용된 도구

언어 :

  • JavaScript(ES6+): 최신 ECMAScript 표준을 사용한 프로그래밍.

프레임워크 및 도구:

  • react: 프레임워크
  • react-router: SPA(Single Page Application) 라우팅 관리
  • react-redux: 전역 상태 관리
  • react-calendar: 도입 문의 시, date input 노출
  • react-daum-post: 도입 문의 시, 학교명/기관명 주소 검색
  • react-scroll: 스크롤 관리

백엔드 통신:

  • axios: HTTP 클라이언트 라이브러리

5. 다음 프로젝트에서 알아야 할 것들

1. 마우스 스크롤의 위치에 따라 반응하고 싶다면?

  • useEffect 에 window.addEventListener 에 params 로 콜백함수를 추가하고, 콜백함수에 마우스 스크롤에 따른 액션을 정의하면 된다.
// useEffect로 state 변화될 때마다, changeHeader함수를 콜백
useEffect(() => {
    window.addEventListener('scroll', changeHeader, { capture: true });
}, []);

// 스크롤 작동 중 내리거나 올리는 중인지 체크 변수
let prevScrollTop = 0;
let nowScrollTop = 0;

let wheelDelta = () => {
	return prevScrollTop - nowScrollTop > 0 ? 'up' : 'down';
}


const changeHeader = () => {
	nowScrollTop = window.scrollY;
    // 스크롤 작동 중 내리거나 올리는 중인지 체크
	if (wheelDelta() === 'down') {
		if (window.scrollY >= 1000) {
			setChangeHeaderTransfrom('translateY(-100px)');
		}
	}
	if (wheelDelta() === 'up') {
			setChangeHeaderTransfrom('translateY(0px)');
        }
	prevScrollTop = nowScrollTop;

    // 스크롤의 위치에 따라 헤더의 로고의 feature 변경
	if (window.scrollY <= 0) {
		setChangeLogo(`url(${logo_light})`);
		setChangeColor('');
		setChangeFontColor('#FFFFFF');
	}

	else {
		setChangeLogo(`url(${logo_dark})`);
		setChangeColor('#FFFFFF')
		setChangeFontColor('#6F3AC1');
	}
}

2. 리액트 라우트를 어떻게 사용했지?

  • 가장 먼저 index.js 에서 BrowserRouter 로 사이트맵이 담긴 상위 컴포넌트(App)을 그룹화한다. 여기서, 사이트 맵의 모든 사이트로 이동할 때마다 스크롤이 최상단에서 보여지길 원하는 경우, 아래와 같이 ScrollToTop 컴포넌트를 상위에 그룹핑해야한다.
import { BrowserRouter } from 'react-router-dom';
import ScrollToTop from './Components/ScrollToTop';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
    <BrowserRouter>
    	// ScrollToTop 컴포넌트는 렌더될 때마다, 화면의 가장 상단으로 이동하게 하는 컴포넌트
        <ScrollToTop />
    	// App 컴포넌트는 사이트맵이 담긴 상위 컴포넌트
        <App />
    </BrowserRouter>
);
  • ScrollToTop 코드
import { useEffect } from "react";
import { useLocation } from "react-router-dom";

export default function ScrollToTop() {
    const { pathname } = useLocation();

    useEffect(() => {
        window.scrollTo(0, 0);
    }, [pathname]);

    return null;
}
  • App.js 에서 사이트맵에 따라 Routes > Route 를 묶었다. 여기서 가장 위에 있는 Route 가 첫 랜딩 시 보여지는 페이지가 된다.
import { Routes, Route } from 'react-router-dom';
import MainPage from './MainPage';
import ProgramPage1 from './ProgramPage1';
import ProgramPage2 from './ProgramPage2';

// <Route path="주소규칙" element={보여줄 컴포넌트}
const App = () => {
    return (
        <Routes>
            <Route path="/" element={<MainPage />} />
            <Route path="/scratch_ai" element={<ProgramPage1 />} />
            <Route path="/appinventor_ai" element={<ProgramPage2 />} />
                ...
        <Routes>
  • App.js 에서 MainPage.js 로 이동할 수 있도록, 앞에서 설정한 주소규칙과 동일한 값으로 id를 입력해준다.
const MainPage = () => {
    return (
        // Route의 path 주소값과 보여줄 Component의 id가 서로 일치해야 연결됨
        <div className='MainPage' id='/'>
            <Header />
            <Contact />
        ...
	);
}

3. 어떠한 스코프의 state는 다른 스코프에서 변경할 수 없다.

useEffect 를 사용하면, 웹 페이지에 변화가 감지될 때마다 해당 실행문을 실행하게 되는데 여기에 입력과 관련된 기능을 정의하면, 비동기를 동기처리하는데에도 사용할 수 있다.

[가능한 경우]

const ContactToRegister = () => {
    // calendarHeight 의 스코프는 ContactToRegister 에 속해있음
	const [calendarHeight, setCalendarHeight] = useState('750px');
    
    // 조건식에 따라 calendarHeight 의 상태값을 변경
    useEffect(() => {
        if(pointer === 0) {
            setCalendarHeight('750px');
        } else if(pointer === 1) {
            setCalendarHeight('822px');
        } else {
            setCalendarHeight('890px');
        }
    }, [pointer])
    
    ...
    
    return(
        <div 
        	className="selectLessonDays"
        	style=
        >
    );
}

4. 카카오 주소 팝업창 사용하는 방법

import { useDaumPostcodePopup } from 'react-daum-postcode';

// 주소창에서 선택한 주소 상태값
const [address, setAddress] = useState("");

// 카카오 주소 팝업창 
const Postcode = () => {
	const open = useDaumPostcodePopup();

	const handleComplete = (data) => {
		let fullAddress = data.address;
		let extraAddress = '';

		if (data.addressType === 'R') {
			if (data.bname !== '') {
				extraAddress += data.bname;
			}
			if (data.buildingName !== '') {
				extraAddress += extraAddress !== '' ? `, ${data.buildingName}` : data.buildingName;
			}
			fullAddress += extraAddress !== '' ? ` (${extraAddress})` : '';
        }

        // 주소창에 입력한 값 상태 저장
        setAddress(fullAddress);

            // UserInfo에 주소 정보 전달
            setUserInfo((prevState) => ({
                ...prevState,
                inquirer: {
                    ...prevState.inquirer,
                    organizationAddress: fullAddress,
                }
            }));
        };

        const handleClick = () => {
            open({ onComplete: handleComplete });
        };

        return (
            <div className="wrap">
                <input
                    className="orgInfoInput"
                    type="text"
                    placeholder="학교/기관 주소"
            	    // 주소 입력창 클릭 시, Daum 주소창 팝업
                    onClick={handleClick}
                    defaultValue={address}
                    onFocus={e => (e.target.placeholder = "")}
                >
                </input>
            </div>
        );
    };

5. 유저가 인풋을 통해 제출한 값을 API로 전달하기 위한 객체 정보 관리

[객체의 프로퍼티 value가 하나인 경우]

// 유저가 인풋을 통해 제출한 값이 묶이게 될 userInfo 객체
const [userInfo, setUserInfo] = useState({
	classCount: 0,
	...
});
    
const onChangeClassesCount = (e) => {
	let tmp = parseInt(e.target.value);

	setUserInfo({
        // userInfo를 복사하고, 업데이트되는 프로퍼티의 항목만 수정
		...userInfo,
		classCount: tmp
	});
};

[객체의 프로퍼티 value가 객체인 경우]

const [userInfo, setUserInfo] = useState({
	inquirer : {
		name : "",
    }
});

const onChangeName = (e) => {
	if(e.target.value.length > 10) {
		return;
	}

    // 콜백함수(prevState)를 만들어 이전의 userInfo를 복사
	setUserInfo((prevState) => ({
		...prevState,
         // 복사된 userinfo의 inquirer 항목을 덮어 씌움 
		inquirer: {
			...prevState.inquirer,
			name: e.target.value,
		}
	}));
}

6. state를 객체로 관리하고 업데이트할 수 있다.

const Navigation = ({higlightLine}) => {
    const [fontWeight, setFontWeight] = useState(
		{
			_1: '400',
			_2: '400',
		});
    
const bottomLineShow = (e) => {
	if(e.target.className === 'scratchAI' || e.target.className === 'bottomLine1') {
		setFontWeight({...fontWeight, _1: 'bold' });

7. 스크롤을 이동시키는 방법

import { Link as LinkToRegsiter } from 'react-scroll';

const [ref, setRef] = useState('cityTown');

if (userInfo.inquirer.name === '') {
	setRef('applicant');
}

// 이동하고 싶은 컴포넌트에 id 지정
<div id="applicant"></div>
            
// LinkToRegister의 to에 이동하고 싶은 컴포넌트의 id값을 입력 
<LinkToRegsiter
	className="submitBtn"
	onClick={submitHandler}
	to={ref}
	smooth={true}
>
	<span>도입 문의 하기</span>
</LinkToRegsiter>

댓글남기기