4. Appbar, 메인 페이지 작업하기

4. Appbar, 메인 페이지 작업하기

백엔드와 어떻게 통신해야 할까?

나름 로고도 만들었으니, 로고가 들어갈 자리가 있어야 한다.

현재 스크린에서 가장 간단 명료하게 정보를 보여줄 수 있는 App bar를 이용한다.



Appbar 커스터마이징

Material-UI에서 기본 제공해주는 Appbar도 좋지만, 너무 모바일스러워서 커스터마이징하기로 했다

이곳에 로고를 넣고, 이후 필요한 버튼이 있다면 추가할 생각이다.


일단 레이아웃부터 변경하도록 하자.

// file: 'AppbarLayout.tsx'
import MuiAppBar, { AppBarProps } from '@material-ui/core/AppBar';


export default function AppBar(children: AppBarProps) {

    return <MuiAppBar elevation={0} position="static" {...children} />;
}


propsAppBarProps 타입의 매개변수를 받는다.

spread operator를 사용해 여러 개의 props를 받아, MuiAppbar의 속성을 오버라이드할 수 있도록 했다.

elevation={0}은 말 그대로 고도가 0인 상태로, z축이 부모와 상대적으로 얼마나 떨어져 있어야 하는지를 나타낸다.

앞으로 튀어나와 있을 필요는 없으니까 0으로!


다음은 레이아웃을 바탕으로 AppBar를 디자인해보자.


// file: 'Appbar.tsx'
interface Props extends WithStyles<typeof astyles> {}


const astyles = (theme: Theme) => ({
    placeholder: toolbarStyles(theme).root,
    toolbar: {
        justifyContent: 'space-between',
    },
    right: {
        flex: 1,
        display: 'flex',
        justifyContent: 'flex-end',
    },
    langIcon: {
        marginLeft: theme.spacing(2),
        maxWidth: "48px",
    },
    left: {
        flex: 1,
    },
    logo: {
        marginTop: theme.spacing(1),
        maxWidth: "300px",
    }
});


...

저번에 배웠던 것처럼, 함수형 컴포넌트에 styles을 주기 위해 인터페이스를 선언했다.

선언한 astyles는 CSS와 굉장히 비슷하게 보이는데, 부모의 스타일을 오버라이드해서 적용시켰다.


// file: 'Appbar.tsx'
function AppBarView (props: Props) {
    const { classes } = props;

    return (
        <div>
            <AppBar position="fixed">
                <Toolbar className={classes.toolbar}>
                    <div className={classes.left} />
                    <Link
                        variant="h3"
                        underline="none"
                        color="inherit"
                        href="/jchecker"
                    >
                        <img src="/assets/logo.png" alt="logo" className={classes.logo} />

                    </Link>

                    <div className={classes.right}>
                        <Button className={classes.langIcon} onClick={() => handleChangeLang('ko')}>
                            <img src="/assets/kor.svg" alt="kor" />
                        </Button>
                        <Button className={classes.langIcon} onClick={() => handleChangeLang('en')}>
                            <img src="/assets/eng.svg" alt="eng" />
                        </Button>
                    </div>
                    
                </Toolbar>
            </AppBar>
            <div className={classes.placeholder} />
        </div>
    );
}

Toolbar 컴포넌트는 Material-UI에서 기본 제공하는 컴포넌트에 반응형 코드만 조금 첨가해서 고대로 사용했다.

스타일을 주고싶은 컴포넌트에 className에 대한 값으로 넘겨줬으며,

로고에는 Link 컴포넌트를 이용해 홈으로 이동하도록 만들었다.



메인 페이지 완성하기

이전에 레이아웃을 만들었으니, 이제 이를 이용해서 각 컴포넌트를 조합해 넣어주면 될 것 같다.

내용은 최대한 보기 쉽게, 시각적으로 이해할 수 있도록 목표를 잡았다.

설계대로 일단 3개의 컴포넌트를 각각 따로 만들었다.



첫 번째 페이지부터!

// file: 'SectionMain.tsx'

// 스타일 주는 것은 생략

function SectionMain(props: Props) {
    const { classes } = props;

    const handleBottom = () => {
        window.scrollTo({top: 1800, left: 0, behavior: 'smooth'});
    }

    return (
        <SectionLayout backgroundClassName={classes.background}>
            {}
            <img style= src={backgroundImage} alt="prioirty" />
            <Typographic color="inherit" align="center" variant="h2" marked="center">
                JUDGE YOUR ASSIGNMENTS
            </Typographic>
            <Typographic color="inherit" align="center" variant="h5" className={classes.h5}>
                We provide OOP-based Java program scoring service through static analysis.
            </Typographic>
            <Button
                color="secondary"
                variant="contained"
                size="medium"
                className={classes.button}
                onClick={handleBottom}
            >
                {t('start_button')}
            </Button>
            <Typographic variant="body2" color="inherit" className={classes.more}>
                with ISEL, HGU.
            </Typographic>
            
        </SectionLayout>
        
    );
}


export default withStyles(styles)(SectionMain);


원래 버튼의 목적은 바로 토큰을 넣는 페이지로 이동시키는 것이었으나,

사용자가 메인 페이지를 스크롤 다운 하면서 서비스가 어떻게 동작하고

어떻게 이용해야 하는지 간단하게 봐 줬으면 좋겠다는 생각에 자동 스크롤로 변경했다.


🔥 근데 window.scrollTo()를 이용해서 매뉴얼하게 코드를 작성한 것이 아쉬운 부분이다.

저렇게 하면 사용자의 브라우저 크기에 따라 footer까지 내려가버리기도 해서…

스프링은 되던데, 어떻게 변경할 수 있을지 고민해봐야겠다.



Main 1 다시 생각해도 글꼴 참 잘 골랐어

렌더링된 메인 페이지의 첫 번째 컴포넌트가 완성됐다!



다음은 두 번째, 간단하게 동작 방식을 적어놓을 컴포넌트다.

// file: 'SectionDetail.tsx'

// 스타일 주는 것은 생략

function SectionDetail(props: Props) {
    const { classes } = props;

    return (
        <section className={classes.root}>
            <Container className={classes.container}>
                <img
                    src="assets/hologram.png"
                    className={classes.hologram}
                    alt="back hologram"
                />
                <Typographic variant="h4" marked="center" className={classes.title} component="h2">
                    How the service works
                </Typographic>
                <div>
                    <Grid container spacing={5}>
                        <Grid item xs={12} md={4}>
                            <div className={classes.item}>
                                <div className={classes.number}>1.</div>
                                <img
                                    src="assets/file_add.svg"
                                    alt="file_add"
                                    className={classes.image}
                                />
                                <Typographic variant="h5" align="center">
                                    글 넣는 곳
                                </Typographic>
                            </div>
                        </Grid>
                        <Grid item xs={12} md={4}>
                            <div className={classes.item}>
                                <div className={classes.number}>2.</div>
                                <img
                                    src="assets/executing.svg"
                                    alt="execute"
                                    className={classes.image}
                                />
                                <Typographic variant="h5" align="center">
                                    글 넣는 곳
                                </Typographic>
                            </div>
                        </Grid>
                        <Grid item xs={12} md={4}>
                            <div className={classes.item}>
                                <div className={classes.number}>3.</div>
                                <img
                                    src="assets/diagram.svg"
                                    alt="file_add"
                                    className={classes.image}
                                />
                                <Typographic variant="h5" align="center">
                                    글 넣는 곳
                                </Typographic>
                            </div>
                        </Grid>
                    </Grid>
                </div>
            </Container>
        </section>
    )
}

export default withStyles(styles)(SectionDetail);


처음에는 첫 번째 페이지와 동일하게 코드를 작성했으나,

Grid로 구역을 나눠서 출력하니 위 페이지와 차별화되면서 한 눈에 와닿았다.

이 부분은 별로 어려울 게 없었지만, 구간을 보기 편하게 정렬하느라 여러 번 고친 컴포넌트 되시겠다.



Main 2


결과물은 이렇다.

목표는 채점 결과를 그래프 / 차트 라이브러리를 사용해 시각적으로 보여주는 것이지만,

아직까지는 유동적이다. (뭐가 더 알아보기 쉬울지 고민 중에 있다🤔)



마지막은 토큰 인증 페이지로 갈 수 있도록 해주는 컴포넌트여야 한다.


// file: 'SectionBegin.tsx'

// 스타일 주는 것은 생략

function SectionBegin(props: Props) {
    const { classes } = props;

    return (
        <SectionLayout backgroundClassName={classes.background}>
            <img style= src={backgroundImage} alt="priority" />
            <Typographic color="inherit" align="center" variant="h1" marked="center">
                {t('begin.getting start')}
            </Typographic>
            <Typographic color="inherit" align="center" variant="h5" className={classes.h5}>
                <Link href="/jchecker/classes" color="secondary">
                    인증 페이지로 가기
                </Link>
                글 넣는 곳
            </Typographic>
        </SectionLayout>
    )
}


export default withStyles(styles)(SectionBegin);


버튼을 넣을까 하다가 이미 첫 번째에 넣었으니까 링크로 대체했다. 더 깔끔한 것 같기도 하고.

Link 컴포넌트를 임포트해 사용했으며, 누르면 지정한 링크 /jchecker/classses로 이동하게 된다.



Main 3

이로써 Appbar와 메인 페이지, 즉 웹 서비스 시 가장 먼저 보이는 페이지를 완성했다.

아무래도 채점 서비스다 보니, 사용자에게 신뢰성을 줄 수 있게 만드려고 노력했던 것 같다..! 난 노력했다는 이야기



🔥 TroubleShooting & Review

Issue:

  • 저렇게 3개의 컴포넌트를 따로 만들어서, Appbar와 3개의 메인 컴포넌트, Footer까지 Home.tsx에서 합쳤다. 이렇게 만드는 게 효율적인지 잘 모르겠지만 문제가 생겼을 때 파악이 쉬웠다.

Review

  • Material-UI를 본격적으로 사용하게 되는 시점인데, 예제 코드를 그대로 갖다 쓰는 게 아니라 코드 리뷰에 꽤 많은 시간을 들였다
  • 시간 가는 줄 모르고 코드 작성했던 것 같다. 아무래도 예상한 대로 예쁘게 나오니까 너무 즐겁더라구…🥺
  • 구글링을 열심히 하다가 tsx vs. ts라는 글을 봤다. TSX 방식으로 코드를 작성하는 것이 좋은지, TS + HTML로 작성하는 것이 좋은지… 성능에 굉장히 관심이 많은 나로서는 정말 궁금한 주제다…! 결론이 모호해서 더 궁금하다