React-2 (23/02/22)

nazzzo·2023년 2월 22일
0
post-custom-banner

리액트 2번째



1. component에 대해서


화면을 구성할 때 모듈 방식을 활용하여 만들어진 단위를 컴포넌트라고 합니다
일반적으로 <div><ul> 등으로 이루어진 형태를 사용하며
그 안에도 여러 자식 컴포넌트가 포함될 수 있습니다

그런데 div라는 이름의 컴포넌트를 만들 수 있을까요?
정답은 No입니다
컴포넌트 이름은 첫글자를 대문자로 지어야 한다는 규칙이 있기 때문입니다
리액트는 소문자로 시작하는 컴포넌트를 DOM 태그로 처리합니다

예를 들어 <div />는 HTML의 div 태그를 나타내지만,
(엄밀히는 리액트의 내장 컴포넌트입니다)
<Welcome />은 컴포넌트(사용자정의 컴포넌트)를 나타내며 범위 안에 Welcome이 있어야 합니다

*추가로 우리가 흔히 써온 <div id=wrap>과 같이
모든 컴포넌트를 감싸는 역할을 하는 부모 컴포넌트는 보통 App이라고 이름을 짓습니다

  • 컴포넌트의 상태에 대해

    상태(state)는 기본적으로 상태를 선언한 해당 컴포넌트 안에서만 사용할 수 있습니다
    상위 컴포넌트의 상태를 하위 컴포넌트로 넘기는 것은 간단하지만,
    특정 컴포넌트의 상태를 전달하려면 상태 끌어올리기 기법을 사용해야 할 경우가 생깁니다
    (예를 들어 A 컴포넌트 아래에 B, C 컴포넌트가 있고 B의 상태를 C에 전달하려면
    B > A > C 순으로 전달해야 합니다)

*상태 끌어올리기에 대해서는 4번의 댓글 제작 예제 코드를 통해 알아보는 걸로...



2. props에 대해서


  1. 먼저 App 컴포넌트가 있다고 가정합시다
React.createElement(App, null, "안녕하세요")
  1. div 엘리먼트에 안녕하세요라는 텍스트를 넣어봅니다
React.createElement("div", null, "안녕하세요")
  1. Babel의 도움을 받아서 위 코드를 JSX 문법으로 치환하면 아래와 같이 쓸 수 있습니다
<App>안녕하세요</App>
<div>안녕하세요</div>

그렇다면 여기서 props는 어떻게 될까요?
위 예제에서 두번째 인자(속성)와 세번째 인자(innerHTML)는 모두 props로 전달할 수 있습니다


<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
    <script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
</head>
<body>
    <div id="root"></div>
    <script type="text/babel">
        // 컴포넌트명은 첫글자가 대문자이어야 합니다
        class App extends React.Component {
            constructor(props) {
                super(props);
                // children
                // property

                //props.children
                console.log(props)
            }

            render() {
                return <div>Hello world!</div>
            }

        }
        const root = ReactDOM.createRoot(document.querySelector('#root'));

        // render 함수 실행은 인스턴스 생성과 유사한 역할을 합니다
        root.render(<App id='hello'>안녕하세요</App>)
    </script>
</body>
</html>

결과는 아래와 같습니다

// console.log(props)
{id: 'hello', children: '안녕하세요'}

  • 내장 컴포넌트(<div>, <span> 등)에서 id를 사용하면 실제 엘리먼트의 id로 사용되도록 전달됩니다
  • 반면 사용자정의 컴포넌트(<App>)에서 사용한 id='hello'는 실제 html의 엘리먼트에 id 속성을 부여하는 것이 아닙니다
    단지 객체의 key, value의 역할을 할 뿐... 이 부분은 헷갈리면 안됩니다



3. class 컴포넌트 이해



지난 포스트에 이어서 클릭에 따라 상태(로그인/로그아웃)가 바뀌는 버튼을 만들어보겠습니다


<body>
    <div id="root"></div>
    <script type="text/babel">
        class Word extends React.Component {
            constructor(props) {
                super(props);
            }
            render() {
                return <h2>{this.props.text}</h2>
            }
        }

        class LoginBtn extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    isLogin: false,
                }
            }
            render() {
                return <button onClick={() => this.setState({ isLogin: !this.state.isLogin })}>
                            {this.state.isLogin ? '로그아웃' : "로그인"}
                       </button>
            }
        }

        class App extends React.Component {
            constructor(props) {
                super(props);
            }

            render() {
                return (
                    <div>
                        <Word text="Hello World!1" />
                        <Word text="Hello World!2" />
                        <Word text="Hello World!3" />
                        <LoginBtn />
                    </div>
                )
            }

        }
        const root = ReactDOM.createRoot(document.querySelector('#root'));
        root.render(<App></App>)
    </script>
</body>

</html>

위 코드를 좀 더 직관적으로 작성하려면 어떻게 해야 할까요

우선 기능별로 함수를 쪼갤 필요가 있어보이네요

        class LoginBtn extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    isLogin: false,
                }
                // this bind
                this.handleClick = this.handleClick.bind(this)
            }

            handleClick() {
                // 얼핏 문제가 없어보이지만 this 이슈가 발생하는 코드... 생성자 함수에서 this 바인드 처리가 필요합니다
                // console.log(this) // undefined ~ 일반 함수에서 this는 동적으로 결정됩니다
                this.setState({ isLogin: !this.state.isLogin });
            }

            render() {
                return <button onClick={this.handleClick}>
                            {this.state.isLogin ? '로그아웃' : "로그인"}
                       </button>
            }
        }

위와 같이 this로 인한 문제는 항상 this가 가리키는 대상이 함수를 호출할 때 결정된다는 것에서 발생합니다


        class LoginBtn extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    isLogin: false,
                }
                // this.handleClick = this.handleClick.bind(this)
            }

            handleClick() {
                // console.log(this) // undefined
                this.setState({ isLogin: !this.state.isLogin });
            }

            render() {
                return <button onClick={() => this.handleClick()}>
                            {this.state.isLogin ? '로그아웃' : "로그인"}
                       </button>
            }
        }

↑ 이런 식의 해결도 가능합니다 (추천하는 방법은 아닙니다)


혹은 아래와 같이 일반 함수 대신 화살표 함수를 사용하는 것도 하나의 방법입니다
화살표 함수에서는 this를 정의한 컨텍스트의 this를 참조하기 때문입니다

            handleClick = () => {
                // console.log(this) // LoginBtn
                this.setState({ isLogin: !this.state.isLogin });
            }



다음은 완성 코드입니다

<body>
    <div id="root"></div>
    <script type="text/babel">
        class Word extends React.Component {
            constructor(props) {
                super(props);
            }
            render() {
                return <h2>{this.props.text}</h2>
            }
        }

        class LoginText extends React.Component {
            constructor(props) {
                super(props);
            }
            render() {
                // 조건부 렌더링 (if문을 통한 렌더링)
                return this.props.flag ? <Word text="로그아웃" /> : <Word text="로그인" />;
            }
        }

        class LoginBtn extends React.Component {
            constructor(props) {
                super(props);
                this.state = {
                    isLogin: false,
                }
                this.handleClick = this.handleClick.bind(this)
            }

            handleClick() {
                this.setState({ isLogin: !this.state.isLogin });
            }

            render() {
                return <button onClick={this.handleClick}>
                            <LoginText flag={this.state.isLogin} />
                       </button>
            }
        }

        class App extends React.Component {
            constructor(props) {
                super(props);
            }

            render() {
                return (
                    <div>
                        <Word text="Hello World!1" />
                        <Word text="Hello World!2" />
                        <Word text="Hello World!3" />
                        <LoginBtn />
                    </div>
                )
            }

        }
        const root = ReactDOM.createRoot(document.querySelector('#root'));
        root.render(<App></App>)
    </script>
</body>



4. 댓글창 만들기 실습


배운 내용을 바탕으로 리액트를 통해 간단한 댓글창을 구현해보려 합니다
(CRUD 전체기능 구현은 다음 포스트에서...)

<body>
    <div id="root"></div>
    <script type="text/babel">
        class CommentItem extends React.Component {
            render() {
                return (
                    <ul className="comment-row">
                        <li className="comment-id">{this.props.userid}</li>
                        <li className="comment-content">{this.props.content}</li>
                        <li className="comment-date">{this.props.date}</li>
                    </ul>
                )
            }
        }
        class CommentForm extends React.Component {
            constructor(props) {
                super(props)
                // console.log(this.props.create)
                this.submitHandler = this.submitHandler.bind(this)
                this.changeHandler = this.changeHandler.bind(this)

                // 리액트에서 input value는 전부 상태로 관리하는 것이 좋습니다
                // 입력값을 실시간으로 관리하기에도 용이하고, 데이터를 다른 컴포넌트에 전달하기도 쉽습니다
                this.state = {
                    value: ''
                }
            }

            changeHandler(e) {
                const { value } = e.target
                this.setState({
                    ...this.state,
                    value,
                })
            }

            submitHandler(e) {
                e.preventDefault()
                this.props.create(this.state.value)
                this.setState({ value: "" })
                e.target.commentInput.focus()
            }

            render() {
                return (
                    <li className="comment-form">
                        <form onSubmit={this.submitHandler}>
                            <h4>
                                댓글 쓰기 <span>({this.props.length})</span>
                            </h4>
                            <span className="ps_box">
                                <input type="text" onChange={this.changeHandler} className="int" id="commentInput" value={this.state.value} placeholder="댓글 내용을 입력해주세요" />
                            </span>
                            <input type="submit" value="등록" className="btn" />
                        </form>
                    </li>
                )
            }
        }

        class CommentList extends React.Component {
            // this.props.items는 배열입니다
            loop(val, key) {
                return <CommentItem key={key} userid="web7722" content={val.content} date="2023-02-22" />
            }

            render() {
                return (
                    <li>
                        {this.props.items.map(this.loop)}
                    </li>
                )
            }
        }

        class Comment extends React.Component {
            constructor(props) {
                super(props)
                this.state = {
                    comment: []
                }
                // this 바인드처리가 싫다면 화살표 함수를 사용할 수도 있습니다
                // this.create = this.create.bind(this)
            }

            // CommentForm > CommentList
            // 상태(state) 끌어올리기가 필요합니다. 이를 위해 먼저 Comment에서 상태를 바꾸는 함수를 만들어야 합니다
            // 상태를 바꿀 수 있는 메서드를 상위 컴포넌트인 Comment에서 만들어두고, 그것을 props로 CommentForm에 전달해야 합니다
            create = (content) => {
                this.setState({
                    comment: [
                        {
                            userid: 'web7722',
                            content,
                            date: '2023-02-22'
                        },
                        ...this.state.comment
                    ]
                })
            }

            render() {
                return (
                    // 자바스크립트이므로 이미 존재하는 class라는 예약어 대신 className으로 써야 합니다
                    <ul className="comment">
                        <CommentForm create={this.create} length={this.state.comment.length}/>
                        <CommentList items={this.state.comment} />
                    </ul>
                )
            }
        }

        class App extends React.Component {
            render() {
                return (
                    <div>
                        <Comment />
                        <Comment />
                        <Comment />
                    </div>
                )
            }
        }

        const root = ReactDOM.createRoot(document.querySelector("#root"))
        root.render(<App />)
    </script>
</body>

4번 예제는 많은 복습이 필요합니다

화이팅!!



+) 보다 더 직관적이고 간결한 함수 컴포넌트를 사용한 코드

<body>
    <div id="root"></div>
    <script type="text/babel">
        const { useState } = React;

        const CommentForm = (props) => {
            const [value, setValue] = useState("")

            const changeHandler = (e) => {
                setValue(e.target.value)
            }

            const submitHandler = (e) => {
                e.preventDefault()
                props.create(value)
                setValue("")
                e.target.commentInput.focus()
            }

            return (
                <li className="comment-form">
                    <form onSubmit={submitHandler}>
                        <h4>
                            댓글 쓰기 
                            <span>({props.length})</span>
                        </h4>
                        <span className="ps_box">
                            <input type="text" onChange={changeHandler} className='int' id='commentInput' value={value} placeholder="댓글 내용을 입력해주세요" />
                        </span>
                        <input type="submit" value="등록" className="btn" />
                    </form>
                </li>
            )
        }
    
        const CommentItem = (props) => {
            return (
                <ul className="comment-row">
                        <li className="comment-id">{props.userid}</li>
                        <li className="comment-content">{props.content}</li>
                        <li className="comment-date">{props.date}</li>
                    </ul>
            )
        }

        const CommentList = (props) => {
            const loop = (val, idx) => {
                return <CommentItem key={idx} userid="web7722" content={val.content} date="2023-02-22" />
            }

            return (
                <li>
                    {props.items.map(loop)}
                </li>
            )
        }

        const Comment = () => {
            const [value, setValue] = useState([])

            const create = (content) => {
                const newComment = {
                    userid: "web7722",
                    content,
                    date: "2023-02-22",
                }
                setValue([newComment, ...value])
            }

            return (
                <ul className="comment">
                    <CommentForm create={create} length={value.length}/>
                    <CommentList items={value} />
                </ul>
            )
        }
 
        const App = () => {
            return (
                <div>
                    <Comment />
                </div>
            )
        }

        const root = ReactDOM.createRoot(document.getElementById("root"))
        root.render(<App />)
    </script>
</body>
post-custom-banner

0개의 댓글