개발 낙서장

[TIL] 내일배움캠프 70일차 - 리액트 본문

Java/Sparta

[TIL] 내일배움캠프 70일차 - 리액트

권승준 2024. 4. 5. 22:59

 

오늘의 학습 키워드📚

리액트란?

메타에서 개발한 오픈 소스 자바스크립트 라이브러리.

프론트엔드 개발자 사이에서 AngularJS, Vue.js와 더불어 많은 인기를 얻고 있다. GitHub Star 수와 npm 패키지 다운로드 수는 React가 가장 많다.

SPA을 전제로 하고 있으며, Dirty checking과 Virtual DOM을 활용하여 업데이트 해야하는 DOM 요소를 찾아서 해당 부분만 업데이트하기 때문에, 리렌더링이 잦은 동적인 모던 웹에서 엄청나게 빠른 퍼포먼스를 내는게 가능하다. 기본적으로 모듈형 개발이기 때문에 생산성 또한 상당히 높은 라이브러리인지라 순식간에 대세로 떠올랐다. 거기에 기본적으로 프레임워크가 아니라 라이브러리인지라 다른 프레임워크에 간편하게 붙여서 사용하는 것도 가능하며, React Hooks라는 강력한 메소드들을 지원하면서 사실상 웹 프론트엔드 개발의 표준으로 자리잡았다. 이와 더불어 타입스크립트나 Sass 같은 언어도 지원한다. 또한 Next.js 등의 등장으로 인해 SSG, SSR등을 할 수 있게 되면서 사용 범위 또한 기하급수적으로 늘어났다.

출처 : 나무위키

쉽게 말해 자바 스크립트에서 화면을 구성하는 로직들을 객체, 메소드화하여 보다 사용하기 쉽게 해주는 라이브러리이다.
현재 팀 프로젝트를 진행 중인데 화면 구성을 Vue나 리액트 등을 활용해서 볼만하게 구성했으면 좋겠다는 피드백이 들어와서 고민하다 리액트로 결정하게 됐다.

설치

우선 리액트를 설치하고 사용하는 과정에서 node와 npm 명령어가 필요하기에 설치해야 한다.

npm -v
node -v

설치가 됐으면 위의 명령어로 버전을 확인할 수 있다.

cd src/main
npx create-react-app {프로젝트명} (ex. npx create-react-app frontend)

이후 main 디렉토리로 이동한 후에 리액트를 설치하면 리액트를 실행할 수 있게 된다!

cd frontend
npm start

Proxy 설정

리액트는 localhost:3000에서, 스프링은 localhost:8000에서 동작한다.
서로 다른 도메인을 사용하기에 스프링 서버에 요청을 할 경우 CORS 이슈가 발생한다.
따라서 리액트의 Proxy 설정을 통해 도메인을 맞춰줘야 한다.

리액트를 설치한 폴더에서 프록시 관련 모듈을 설치한다.

npm install http-proxy-middleware --save

리액트를 설치한 폴더에 있는 package.json 파일에서 "proxy": "http://localhost:8080" 속성을 추가한다.

리액트 MUI 설치

리액트 MUI(Material-UI)는 부트 스트랩과 비슷하게 버튼, 드롭다운 등 주로 사용되는 UI들을 컴포넌트로 제공해주는 라이브러리이다.
아래 링크에서 가이드와 사용 방법, 템플릿, 컴포넌트 등을 확인할 수 있다.

https://mui.com/material-ui/getting-started/installation/

 

Installation - Material UI

Install Material UI, the world's most popular React UI framework.

mui.com

먼저 MUI를 리액트에 설치해준다.

npm install @mui/material @emotion/react @emotion/styled @mui/icons-material

템플릿이나 컴포넌트 등 원하는 UI의 소스 코드를 가져와 편집해주면 된다.
나는 간단한 로그인 창을 구현하기 위해 Sign-in 템플릿을 가져왔다.

import './App.css';
import SignIn from "./SignIn";

function App() {
    return (
        <div className="App">
            <SignIn />
        </div>
    );
}

export default App;

App.js 에서 SignIn 페이지를 메인 페이지로 설정해준다.

😁

build.gradle 수정

먼저 스프링과 리액트를 빌드해 한번에 실행하려면 build.gradle에 추가할 것들이 있다.
스프링 부트 프로젝트가 빌드될 때 리액트가 먼저 빌드되고 해당 내용을 스프링 부트 빌드 결과물에 포함한다는 내용이라고 한다.

def frontendDir = "$projectDir/src/main/frontend"

sourceSets {
	main {
		resources { srcDirs = ["$projectDir/src/main/resources"]
		}
	}
}

processResources { dependsOn "copyReactBuildFiles" }

task installReact(type: Exec) {
	workingDir "$frontendDir"
	inputs.dir "$frontendDir"
	group = BasePlugin.BUILD_GROUP
	if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
		commandLine "npm.cmd", "audit", "fix"
		commandLine 'npm.cmd', 'install' }
	else {
		commandLine "npm", "audit", "fix" commandLine 'npm', 'install'
	}
}

task buildReact(type: Exec) {
	dependsOn "installReact"
	workingDir "$frontendDir"
	inputs.dir "$frontendDir"
	group = BasePlugin.BUILD_GROUP
	if (System.getProperty('os.name').toLowerCase(Locale.ROOT).contains('windows')) {
		commandLine "npm.cmd", "run-script", "build"
	} else {
		commandLine "npm", "run-script", "build"
	}
}

task copyReactBuildFiles(type: Copy) {
	dependsOn "buildReact"
	from "$frontendDir/build"
	into "$projectDir/src/main/resources/static"
}

이렇게 하면 빌드해서 나온 jar 파일을 실행해주면 스프링 서버와 리액트 서버를 한 번에 구동시킬 수 있다.

스프링 API 연동

리액트에서는 useState라는 특이한 형식의 함수가 있는데 컴포넌트(인풋 필드, 드롭다운 등)의 변경 사항을 적용하는 함수이다.

import {useState} from 'react';

...

const [변수 이름, 함수 이름] = useState(기본값);

형식은 이러하며 함수를 통해 값을 설정하고 해당 변수로 바뀐 값을 가져올 수 있는 방식이다.
이를 이용해 입력 값을 가져와 필요한 요청 데이터에 넣어주면 된다.

                        <TextField
                            margin="normal"
                            required
                            fullWidth
                            id="username"
                            label="아이디"
                            name="username"
                            autoComplete="username"
                            autoFocus
                            onChange={event => setUsername(e.target.value)}
                        />

이런 식으로 입력 컴포넌트에서 onChange를 통해 값을 설정해주면 된다.

// ... 생략

export default function SignIn() {

    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');

    const handleSubmit = async (event) => {
        event.preventDefault();
        try {
            const result = await axios.post('/api/login', {username, password});
            console.log(result.data);
            alert('로그인 완료되었습니다.');
        } catch (error) {
            alert(error.response.data);
        }
    };

    return (
        <ThemeProvider theme={defaultTheme}>
            <Container component="main" maxWidth="xs">
                <CssBaseline/>
                <Box
                    sx={{
                        marginTop: 8,
                        display: 'flex',
                        flexDirection: 'column',
                        alignItems: 'center',
                    }}
                >
                    <Avatar sx={{m: 1, bgcolor: 'secondary.main'}}>
                        <LockOutlinedIcon/>
                    </Avatar>
                    <Typography component="h1" variant="h5">
                        Sign in
                    </Typography>
                    <Box component="form" onSubmit={handleSubmit} noValidate
                         sx={{mt: 1}}>
                        <TextField
                            margin="normal"
                            required
                            fullWidth
                            id="username"
                            label="아이디"
                            name="username"
                            autoComplete="username"
                            autoFocus
                            onChange={e => setUsername(e.target.value)}
                        />
                        <TextField
                            margin="normal"
                            required
                            fullWidth
                            name="password"
                            label="비밀번호"
                            type="password"
                            id="password"
                            autoComplete="current-password"
                            onChange={e => setPassword(e.target.value)}
                        />
                        <FormControlLabel
                            control={<Checkbox value="remember"
                                               color="primary"/>}
                            label="Remember me"
                        />
                        <Button
                            type="submit"
                            fullWidth
                            variant="contained"
                            sx={{mt: 3, mb: 2}}
                        >
                            Sign In
                        </Button>
                        <Grid container>
                            <Grid item xs>
                                <Link href="#" variant="body2">
                                    Forgot password?
                                </Link>
                            </Grid>
                            <Grid item>
                                <Link href="#" variant="body2">
                                    {"Don't have an account? Sign Up"}
                                </Link>
                            </Grid>
                        </Grid>
                    </Box>
                </Box>
            </Container>
        </ThemeProvider>
    );
}

기존에 JQuery를 사용할 때는 ajax로 API 호출을 했지만 리액트에서는 비동기 통신을 통해 API를 호출하고 값을 받을 수 있다.
입력 폼에 submit 함수를 달아서 요청을 보내는 방식이다.

로그인 API가 잘 호출돼서 데이터도 잘 넘어오는 모습이다.

주의할 점

정말 어이없게 고생했던 부분인데 자바 버전 맞추고 IDE 세팅도 잘 해놓은 상태인데 빌드가 계속 실패한다면 js 파일을 꼼꼼히 살펴보자.
IntelliJ를 쓰고 있는데 js 문법을 검사하는 기능이 없어서 오타나 잘못된 변수를 사용하고 있더라도 컴파일에 문제가 생기지 않는다.
나는 event라고 선언하고 e.~~ 이런 식으로 사용해서 빌드가 안 됐는데 이유를 몰랐어서 시간을 낭비했다.

또 빌드는 잘 되는데 bootRun이 안 되는 경우에는 IDE의 도움을 받기 보다는 차근차근 명령어로 빌드부터 jar 파일 실행, gradle 클린 등을 시도해보자.
내 경우에는 서버 포트를 여러번 껐다 키고 다른 프로젝트도 껐다 키다보니 꼬였는지 모든 프로젝트에서 실행 중이 아님에도 포트가 이미 실행 중이라 부트 실행이 되지 않았다.

참고

https://velog.io/@sians0209/Spring-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%97%B0%EA%B2%B0

 

[Spring] 리액트 스프링 연결

리액트 스프링부트 localhost:8080으로 연결하기

velog.io


오늘의 회고💬

생각보다 구현할 화면이 많아져서 마음이 조금 무거워지긴 했지만 리액트가 입문 단계에서는 배우기 어렵지 않다고 해서 어떻게든 잘 할 수 있을 거라 생각한다.

 

내일의 계획📜

주말 동안 시간이 나면 리액트 공부도 좀 하고 CI/CD에 대해 공부도 해볼 생각이다.

Comments