2차 공부/TIL

24.06.12 리액트 css 적용하기 + 컴포넌트 분리하기 + export default

공대탈출 2024. 6. 12. 16:00

기존에 리액트를 배울 때 처음에 했던 방식이 생각나지 않았다.

styled-components를 사용하여 오랜기간 학습했고, 최종 프로젝트 때에도 같은 라이브러리를 사용하여 스타일 작업을 하였기 때문이다. 다시 해보면서 느낀점은 매우 불편하다는 것이다.

각 컴포넌트마다 return하기 전 style을 미리 객체로 만들어두고, 태그에서 style값을 JSX형태로 준다.

 

 여기서 불편했던 점은 각 스타일 요소마다 자동완성이 되지 않는다는 점이다. 예를들어 styled-components나 html의 style태그에서 작성할 때에는 padding, border-radius 등등 다양한 속성값을 입력할 때에도 자동완성 기능이 제공되어 빠르게 코드를 입력하거나 원하는 속성을 쉽게 얻어낼 수 있었는데, 리액트에서는 객체 속에 저장을 해두고, 자동완성이 되지 않아 어려움이 있었다.

 또한 속성의 값도 자동완성이 되지 않아 불편했다. 예를들어 border의 값을 지정할 때 vscode에서 어떤 값을 필요로하는지, display를 지정할 때에는 어떤 값을 사용할 수 있는지 알려주었는데, 그게 없어서 불편했다.

//App.js

import './App.css';

function App() {
    const wrap = {
        padding: '100px',
        display: 'flex',
        gap: '10px',
    };
    const square = {
        width: '100px',
        height: '100px',
        border: '1px solid green',
        borderRadius: '10px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
    };
    return (
        <div style={wrap}>
            <div style={square}>감자</div>
            <div style={square}>고구마</div>
            <div style={square}>오이</div>
            <div style={square}>가지</div>
            <div style={square}>옥수수</div>
        </div>
    );
}

export default App;

 

 

두번째로 css파일을 분리하여 저장하는 방식이다.

기존 html에서는 class로 사용하던 것이 className으로 리액트에서 사용된다.

여기선 그다지 어려움은 없었다. import하는 방식과 className을 지정하는 방법만 안다면 html에서 style태그를 작성할 때와 동일하기 때문이다.

 

물론 프로젝트의 크기가 커지고 코드가 길어질 수록 특정 컴포넌트에 사용된 style을 찾기 어려울 것 같다는 생각도 들었다. 또한 css파일이 분리가되어 저장했기 때문에 컴포넌트의 양이 많아질수록, 페이지의 수가 많아질 수록 동일하게 css파일도 늘어나 조금 복잡하게 보일 수 있다는 생각을 하였다.

//App.js

import './App.css';

function App() {
    return (
        <div className="app-style">
            <div className="square-style">감자</div>
            <div className="square-style">고구마</div>
            <div className="square-style">오이</div>
            <div className="square-style">가지</div>
            <div className="square-style">옥수수</div>
        </div>
    );
}

export default App;
//App.css
.app-style {
    padding: 100px;
    display: flex;
    gap: 10px;
}

.square-style {
    width: 100px;
    height: 100px;
    border: 1px solid green;
    border-radius: 10px;
    display: flex;
    align-items: center;
    justify-content: center;
}

 


 

App.js에 3가지 컴포넌트를 만들다보니 코드가 길어졌다.

import React, { useState } from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.

function CustomButton(props) {
    const { color, onClick, children } = props;
    if (color) {
        return (
            <button style={{ backgroundColor: color, color: 'white' }} onClick={onClick}>
                {children}
            </button>
        );
    }
    return <button onClick={props.onClick}>{props.children}</button>;
}

function User(props) {
    return (
        <div className="square-style">
            <div>{props.user.age}살 - </div>
            <div>{props.user.name}</div>
            <CustomButton
                color="red"
                onClick={() => {
                    props.handleDelete(props.user.id);
                }}
            >
                삭제하기
            </CustomButton>
        </div>
    );
}

const App = () => {
    const [users, setUsers] = useState([
        { id: 1, age: 30, name: '송중기' },
        { id: 2, age: 24, name: '송강' },
        { id: 3, age: 21, name: '김유정' },
        { id: 4, age: 29, name: '구교환' },
    ]);
    const [name, setName] = useState('');
    const [age, setAge] = useState('');

    const addUserHandler = () => {
        const newUser = {
            id: users.length + 1,
            age: age,
            name: name,
        };
        setUsers([...users, newUser]);
    };
    const deleteUserHandler = (id) => {
        const newUserList = users.filter((user) => user.id !== id);
        setUsers(newUserList);
    };
    return (
        <div className="app-style">
            {users.map((user) => {
                return user.age > 25 ? <User handleDelete={deleteUserHandler} user={user} key={user.id}></User> : null;
            })}
            <div>
                <input
                    value={name}
                    placeholder="이름을 입력해주세요"
                    // 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
                    onChange={(e) => setName(e.target.value)}
                />
                <input
                    value={age}
                    placeholder="나이를 입력해주세요"
                    // 인풋 이벤트로 들어온 입력 값을 age의 값으로 업데이트
                    onChange={(e) => setAge(e.target.value)}
                />
                <CustomButton color="green" onClick={addUserHandler}>
                    추가하기
                </CustomButton>
            </div>
        </div>
    );
};

export default App;

 

코드가 길어지면 여럿이서 한 프로젝트를 진행할 때 코드의 가독성이 떨어지는 단점이 있다.

코드의 가독성이 떨어지면 다른 사람이 작성한 함수가 어떤 곳에서 사용되는 함수인지, 어떤 역할을 하는 state인지, 어떤 것을 나타내는 컴포넌트인지 이해하기 어려워진다. 또, 코드의 흐름과 데이터의 이동을 예측하기 어려워진다.

따라서 반복되어 사용되는 컴포넌트를 분리하여 작성해야한다.

function CustomButton(props) {
    const { color, onClick, children } = props;
    if (color) {
        return (
            <button style={{ backgroundColor: color, color: 'white' }} onClick={onClick}>
                {children}
            </button>
        );
    }
    return <button onClick={props.onClick}>{props.children}</button>;
}

export default CustomButton;

커스텀버튼을 분리하였다. export default로 CustomButton을 내보내 App.js에서 불러들여 사용할 것이다.

 

import CustomButton from './CustomButton';

function User(props) {
    return (
        <div className="square-style">
            <div>{props.user.age}살 - </div>
            <div>{props.user.name}</div>
            <CustomButton
                color="red"
                onClick={() => {
                    props.handleDelete(props.user.id);
                }}
            >
                삭제하기
            </CustomButton>
        </div>
    );
}

export default User;

User 컴포넌트를 분리하였다. User컴포넌트에는 이전에 분리한 커스텀버튼 컴포넌트가 사용되어 제일 윗단에서 import한 것을 볼 수 있다.

User컴포넌트 또한 export  default로 내보내 App.js에서 사용한다.

 

import React, { useState } from 'react';
import './App.css'; // 🔥 반드시 App.css 파일을 import 해줘야 합니다.
import CustomButton from './components/CustomButton';
import User from './components/User';

const App = () => {
    const [users, setUsers] = useState([
        { id: 1, age: 30, name: '송중기' },
        { id: 2, age: 24, name: '송강' },
        { id: 3, age: 21, name: '김유정' },
        { id: 4, age: 29, name: '구교환' },
    ]);
    const [name, setName] = useState('');
    const [age, setAge] = useState('');

    const addUserHandler = () => {
        const newUser = {
            id: users.length + 1,
            age: age,
            name: name,
        };
        setUsers([...users, newUser]);
    };
    const deleteUserHandler = (id) => {
        const newUserList = users.filter((user) => user.id !== id);
        setUsers(newUserList);
    };
    return (
        <div className="app-style">
            {users.map((user) => {
                return user.age > 25 ? <User handleDelete={deleteUserHandler} user={user} key={user.id}></User> : null;
            })}
            <div>
                <input
                    value={name}
                    placeholder="이름을 입력해주세요"
                    // 인풋 이벤트로 들어온 입력 값을 name의 값으로 업데이트
                    onChange={(e) => setName(e.target.value)}
                />
                <input
                    value={age}
                    placeholder="나이를 입력해주세요"
                    // 인풋 이벤트로 들어온 입력 값을 age의 값으로 업데이트
                    onChange={(e) => setAge(e.target.value)}
                />
                <CustomButton color="green" onClick={addUserHandler}>
                    추가하기
                </CustomButton>
            </div>
        </div>
    );
};

export default App;

App.js 컴포넌트이다. 커스텀버튼과 User컴포넌트를 둘 다 사용하므로 윗단에서 import한 것을 볼 수 있다.

이렇게 반복되어 사용되는 컴포넌트를 분리하면, 해당 컴포넌트의 변경이 있을 시 작업하기에도 편하고, 데이터의 흐름을 파악하기에도 쉬워진다.

또한 메인인 App.js의 코드길이가 줄어들어 직관적이다.

 


export default / export 차이

위에서 우리는 export default로 어떤 컴포넌트를 내보내 사용할 수 있게 하였다.

 

하나의 모듈(파일)에는 보통 하나의 export default가 존재한다. export default로 컴포넌트를 내보내면 내보낸 일므과 상관없이 원하는 이름으로 import가 가능해진다.

function CustomButton(props) {
    const { color, onClick, children } = props;
    if (color) {
        return (
            <button style={{ backgroundColor: color, color: 'white' }} onClick={onClick}>
                {children}
            </button>
        );
    }
    return <button onClick={props.onClick}>{props.children}</button>;
}

export default CustomButton;

예를들어 위의 Custom Button을 test.js에서 사용한다면

//test.js
import CustomBtn from './components/CustomButton'

const Test = () => {
	retrun 
    	<div>
        	<CustomBtn>커스텀버튼</CustomBtn>
        </div>
}

이렇게 기존 export한 이름인 Custombutton과 다른 CustomBtn으로 불러들여 사용도 가능하다.

 

 

그렇다면 export만을 사용하여 named export한다면 어떻게 될까?

function CustomButton(props) {
    const { color, onClick, children } = props;
    if (color) {
        return (
            <button style={{ backgroundColor: color, color: 'white' }} onClick={onClick}>
                {children}
            </button>
        );
    }
    return <button onClick={props.onClick}>{props.children}</button>;
}

export {CustomButton};

이렇게 default를 사용하지 않고 중괄호에 넣어 이름을 지어준뒤 export하게되면

//test.js
import { CustomButton } from './components/CustomButton'

const Test = () => {
	retrun 
    	<div>
        	<CustomBtn>커스텀버튼</CustomBtn>
        </div>
}

export한 이름과 동일하게 중괄호에 넣어 import해주어야 정상으로 작동된다.

export default와 다르게 이름을 지어 같은 이름으로 import하니 더 정확히 무엇을 import하는지 알 수 있다.

 

하지만 협업을 할 때에 보통 직관적인 이름으로 export default하고, import할 때에도 동일한 이름으로 import하므로

팀의 그라운드 룰에 맞추어 적절히 사용하는것이 좋다고 생각한다.

'2차 공부 > TIL' 카테고리의 다른 글

24.06.18 할 일 목록 만들기  (0) 2024.06.18
24.06.17 한식 메뉴 렌더링하기  (0) 2024.06.17
24.06.11 리액트 시작하기  (1) 2024.06.11
24.06.10 숫자기억게임 페이지 만들기  (0) 2024.06.10
24.06.07 PostgreSQL  (0) 2024.06.07