미친 react key

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • MyrinNew
    Senior Member
    • Feb 2024
    • 5175

    #1

    미친 react key

    map을 통한 렌더링






    export function Parent() {
    const [array, setArray] = useState([1, 2, 3, 4, 5]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => [6,7,8,9,10,...prev]);
    }, 3000);
    }, []);

    return (

    {array.map((item) => (
    Child key={item} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    console.log(item);

    return {item}>
    }







    실험 결과: 1 2 3 4 5






    export function Parent() {
    const [array, setArray] = useState([1, 2, 3, 4, 5]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => [6,7,8,9,10,...prev]);
    }, 3000);
    }, []);

    return (

    {array.map((_, index) => (
    Child key={index} item={item} />
    )}
    >
    );
    }







    실험 결과: 6, 7, 8, 9, 10, 1, 2, 3, 4, 5






    export function Parent() {
    const [array, setArray] = useState([1, 2, 3, 4, 5]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => [...prev.slice(0, 2), 10, ...prev.slice(3,prev.length)]);
    }, 3000);
    }, []);

    return (

    {array.map((item) => (
    Child key={item} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    return {item}>
    }







    실험 결과: 10






    export function Parent() {
    const [array, setArray] = useState([1, 2, 3, 4, 5]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => [...prev.slice(0, 2), 10, ...prev.slice(3,prev.length)]);
    }, 3000);
    }, []);

    return (

    {array.map((_, index) => (
    Child key={index} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    return {item}>
    }







    실험 결과: 10


    실험 1

    상태: [1, 2, 3, 4, 5] => [6, 7, 8, 9, 10, 1, 2, 3, 4, 5]

    결과:
    • react.memo로 감싸지 않았을 경우:
      • key가 item인 경우: 리렌더링시 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력
      • key가 index인 경우: 리렌더링시 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력
    • react.memo로 감쌌을 경우:
      • key가 item인 경우: 리렌더링시 6, 7, 8, 9, 10 출력
      • key가 index인 경우: 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 출력


    실험 2

    상태: [1, 2, 3, 4, 5] => [1, 2, 10, 4, 5]

    결과:
    • react.memo로 감싸지 않았을 경우:
      • key가 item인 경우: 리렌더링시 1, 2, 10, 4, 5 출력
      • key가 index인 경우: 리렌더링시 1, 2, 10, 4, 5 출력
    • react.memo로 감쌌을 경우:
      • key가 item인 경우: 리렌더링시 10 출력
      • key가 index인 경우: 1, 2, 10, 4, 5 출력


    결론:
    • react memo를 사용하면 부모 리렌더링시, 동일한 key에 대해 prop이 변경되지 않는다면 자식의 리렌더링이 발생하지 않는다. 다만 prop이 변경되면 리렌더링이 발생한다.
    • react memo를 사용하지 않는다면 부모 리렌더링시 항상 자식의 리렌더링이 발생한다.


    실험 2

    부모 컴포넌트 상태: [1, 2, 3] => [3, 2, 1]

    자식 컴포넌트 상태: text






    export function Parent() {
    const [array, setArray] = useState([1,2,3]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => prev.toReversed());
    }, 3000);
    });

    return (

    {array.map((item) => (
    Child key={item} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    const [text, setText] = useState("");

    return (
    {item}th textfield:
    input value={text} onChange={(event) => setText(event.target.value)}/>
    >
    );
    }







    결과:


    실험 2

    부모 컴포넌트 상태: [1, 2, 3] => [3, 2, 1]

    자식 컴포넌트 상태: text






    export function Parent() {
    const [array, setArray] = useState([1,2,3]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => prev.toReversed());
    }, 3000);
    });

    return (

    {array.map((item) => (
    Child key={item} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    const [text, setText] = useState("");

    return (
    {item}th textfield:
    input value={text} onChange={(event) => setText(event.target.value)}/>
    >
    );
    }











    export function Parent() {
    const [array, setArray] = useState([1,2,3]);

    useEffect(() => {
    setTimeout(() => {
    setArray((prev) => prev.toReversed());
    }, 3000);
    });

    return (

    {array.map((item) => (
    Child key={index} item={item} />
    )}
    >
    );
    }

    export function Child({ item }: { item: number }) {
    const [text, setText] = useState("");

    return (
    {item}th textfield:
    input value={text} onChange={(event) => setText(event.target.value)}/>
    >
    );
    }







    결과:


    원인:
    • 이전렌더 key의 컴포넌트 종류 === 이후 렌더 컴포넌트 종류일 경우 unmount/mount하지 않는다. 실험 같은 경우 Child로 이전 렌더와 이후 렌더의 컴포넌트 종류 같아서 unmount/mount가 발생하지 않는다.
    • 이전 렌더 컴포넌트와 이후 렌더 컴포넌트에서 같은 종류의 컴포넌트 instance에 대해서는, state가 유지된다. 즉, key와 state가 매핑된다. 따라서 index를 key로 사용하였을 경우, array 순서 변경이 일어날 때 index와 자식 상태(text)가 매핑되어서 순서 변경이 제대로 일어나지 않는다. 반면 item을 key로 사용하였을 경우에는 array 순서 변경이 일어날 때 item와 자식 상태(text)가 매핑되어서 순서 변경이 정상적으로 같이 일어난다.


    결론

    • index를 key로 사용해도 괜찮다. 다만 요소 순서의 변경이 일어날 때는 index를 key로 사용하면 안 된다.
    • 자식 컴포넌트의 상태가 key와 매핑되는데, index와 매핑되는 경우 요소 순서 변경이 일어나도 상태 순서 변경이 일어나지 않기 때문이다.




    More...
Working...