본문으로 바로가기

10장은 실습이라 따라하는 과정을 전부 기록하는 것 보단 그 과정에서 깨닫게된 점 내가 잘못 알고 있던 점들만 빼내어서 기록하려고 한다

그렇게 하기 위해선 우선 책에서 ~~~한 기능을 구현할 것이라고 소개하면 내가 먼저 그러한 기능을 만들어보고 난 뒤에 내 코드와 책의 코드를 비교하면서 진행했다

10-1. props 전달하기


아래는 책 10-3의 기능구현하기 부분에서 props를 전달하는 과정이다

const App = () => {
    const [todos, setTodos] = useState([
        { id:1,text : '리액트의 기초 알아보기',checked: true},
        { id:2,text : '컴포넌트 스타일링 해보기',checked: true},
        { id:3,text : '일정 관리 앱 만들어 보기',checked: false},
    ]);

    return (
        <TodoTemplate>
            <TodoInsert/>
            <TodoList todos={todos} exp={ex1} ex2={ex2}/>
        </TodoTemplate>
    )
};

위와 같이 id, text, checked 객체를 가진 todos 배열을 TodoList에게 전달해줬고

// in TodoList.js
const TodoList = (todos)=>{
    const forRender = todos.map(todo=><TodoListItem key={todo.id} todo={todo}/>);
    return(
        <div className="Todolist">
            {forRender}
        </div>
    );
}

이렇게 props를 받아서 데이터에 접근하려고 했으나..

어째서인지 todos.map은 함수가 아니라는 에러를 뱉어대고 있었다

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/520a0aae-23dd-427b-ab9f-8ffdde09efc2/Untitled.png

그래서 todos를 콘솔로 찍어봤는데 배열이 넘어가는 것이 아니라 객체형태로 wrapping되어서 넘어간다는 걸 다시 한번깨달았고

const forRender = todos.todos.map(todo=><TodoListItem key={todo.id} todo={todo}/>);

이런식으로 접근하니까 제대로 데이터에 접근이 가능했지만 코드가 매우 더러워서 보기 너무 싫었다..

해결


해결법은 매우 간단했는데 구조분해식으로 props값을 받아오면 wrapping된 객체를 벗기고 간편하게 접근하는게 가능하다

// in TodoList.js
const TodoList = ({todos})=>{
    const forRender = todos.map(todo=><TodoListItem key={todo.id} todo={todo}/>);
    return(
        <div className="Todolist">
            {forRender}
        </div>
    );
}

여러개의 props?


너무 당연할 수도 있지만 나는 코드를 짜다가 막혀서 남겨둔다..

    return (
        <TodoTemplate>
            <TodoInsert/>
            <TodoList todos={todos} exp={exp} ex2={ex2}/>
        </TodoTemplate>
    )

이처럼 여러개의 props를 넘겨준다면 어떻게 받아야할까?

// in TodoList.js
const TodoList = ({todos}, {exp}, {ex2})=>{
        ...
}

이렇게 받아서 에러를 뱉었다.. props로 넘어오는 것들을 하나의 객체로 묶어 받는다 라고 생각했어야 했는데 다르게 생각하고 있었다..

// in TodoList.js
const TodoList = ({todos, exp, ex2})=>{
        ...
}

이렇게 받는게 맞다

10-2. classnames 라이브러리


다음은 classnames 라이브러리 사용에 관해선데

이 전장인 스타일링 파트를 내가 제대로 듣지 않아서 그런지 제대로 사용하지 못했다

const TodoListItem = ({todo})=>{
    const {text, checked} = todo;
    return(
        <div className='TodoListItem'>
            **<div className={cn('checkbox',checked?'checked':'')}>**
                {checked ? <MdCheckBox/> : <MdCheckBoxOutlineBlank/>}
                <div className="text">{text}</div>
            </div>
            <div className="remove">
                <MdRemoveCircleOutline/>
            </div>
        </div>
    );
}

저런식으로 cheked가 트루면 checked 클래스를 추가 아니면 없게 코드를 짰었는데

const TodoListItem = ({todo})=>{
    const {text, checked} = todo;
    return(
        <div className='TodoListItem'>
            **<div className={cn('checkbox', {checked})}>**
                {checked ? <MdCheckBox/> : <MdCheckBoxOutlineBlank/>}
                <div className="text">{text}</div>
            </div>
            <div className="remove">
                <MdRemoveCircleOutline/>
            </div>
        </div>
    );
}

내 생각에는 {}안에는 js코드가 들어가고 checked는 true값이니까 true가 클래스 네임으로 들어갈 것 같은데

html 태그를 확인해 보면 "checkbox checked"가 클래스 네임이다

10-3. 추가 기능 구현하기


추가 기능을 구현하는 단계였는데 시작부터 막막했다

const TodoInsert = ({onInsert})=>{
    const [value, setValue] = useState('');
    const handleSubmit = (e) => {
        e.preventDefault();

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

    return(
        <form className="TodoInsert" onSubmit={handleSubmit}>
            <input type="text" name={'todoText'} value={value} onChange={handleChange}/>
            <button type={"submit"}>
                <MdAdd/>
            </button>
        </form>
    );
}

input의 value를 useState로 관리하는 것까진 알겠는데 이 value를 어떻게 상위 컴포넌트로 보내서 todos 배열에 추가시킬까 고민을 계속했다

리액트의 데이터는 상위에서 하위 컴포넌트로 흐른다 는 말을 들어본 적이있는데 아래의 코드를 보고 그 말이 다시 한번 떠올랐다

데이터를 바꿔서 위로 올려줄 순 없기 때문에 데이터를 바꿔주는 함수를 밑으로 내려서 위의 데이터를 수정하는 방식의 코드이다

const App = () => {
    const [todos, setTodos] = useState([
        { id:1,text : '리액트의 기초 알아보기',checked: true},
        { id:2,text : '컴포넌트 스타일링 해보기',checked: true},
        { id:3,text : '일정 관리 앱 만들어 보기',checked: false},
    ]);
    const nextId = useRef(todos.length+1);

    const onInsert = useCallback(
        text=>{
            const todo = {
                id:nextId.current,
                text,
                checked:false
            };
            setTodos(todos.concat(todo));
            nextId.current++;
        },
        [todos]
    );
    return (
        <TodoTemplate>
            <TodoInsert onInsert={onInsert}/>
            <TodoList todos={todos}/>
        </TodoTemplate>
    )
};

onIsert함수는 text값을 받아서 todo 객체를 만들고 todos에 추가시켜주는 함수이고 그 함수를 다시 TodoInsert에 props로 보낸다

const TodoInsert = ({onInsert})=>{
    const [value, setValue] = useState('');
    const handleSubmit = (e) => {
        e.preventDefault();
        onInsert(value);
        setValue('');
    };
        ...
}

받아온 onInsert를 사용해 주는 모습

nextId?


nextId는 렌더링되는 데이터가 아니기 때문에 ref로 선언해주고 사용하고 있다

useCallback


본문에서는 useCallback을 마구마구 사용해주고 있는데 나는 아직 어떤 때에 구분해서 써야하는지 잘 모르겠다..

조금 더 많은 코드를 짜봐야 최적화를 이해할 수 있을 듯 하다

'React > 리액트를 다루는 기술' 카테고리의 다른 글

12. immer  (0) 2021.02.05
11. 컴포넌트 성능 최적화  (0) 2021.02.01
9. 컴포넌트 스타일링  (0) 2021.01.25
8. Hooks  (0) 2021.01.22
7. 라이프사이클 메서드  (0) 2021.01.20