일상적인 횡설수설에 반응: 캡슐화된 목록 항목

13734 단어 reactjavascript
이것은 간단해야 하지만 실제로 어제 잠시 동안 저를 강타한 것입니다.

할 일 앱을 만들고 싶다고 가정해 보겠습니다. 또는 목록이 있는 다른 항목.



목록이 있으므로 작업 목록 데이터는 상위 구성 요소에 저장된 상태가 된 다음 하위 구성 요소로 확산됩니다. 이 같은:

function Root() {
  const [tasks, setTasks] = useState([INITIAL_TASK])

  return <main>
    <h1>my to do</h1>
    <ul>
      {tasks.map(task => (<TaskView value={task} setValue={...}/>))}
    </ul>
  </main>
}


여기에는 두 가지 주요 목표가 있습니다.
  • <TaskView />는 적절하게 캡슐화되어야 합니다. 응용 프로그램에 있는 WHERE에 대해 신경쓰지 않아야 합니다. 따라서 배열의 인덱스에 대해 알면 안 됩니다.
  • 성능을 향상시키기 위해 <TaskView />memo()로 래핑됩니다. memo()가 작동하려면 기본 데이터가 변경되지 않은 경우 소품이 변경되지 않도록 해야 합니다.

  • 접근법 1: Setter 콜백



    우리는 다음과 같이 TaskView를 씁니다.

    (PS: 이 문서의 코드는 테스트되지 않았거나 보풀이 없습니다)

    const TaskView = memo((
      { value, setValue }:
      { value: Task, setValue: (cb: (arg: (old: Task) => Task) => void }
    ) => {
      const handleChangeName = useCallback((event) => {
        const newName = event.target.value
        setValue(old => ({ ...old, name: newName }))
      }, [setValue])
      return ...
    })
    


    이것은 적절하게 캡슐화되지만 소비자를 작성할 때 몇 가지 문제가 발생합니다.

    function Root() {
      const [tasks, setTasks] = useState([INITIAL_TASK])
    
      const setTaskAtIndex = useCallback((value: Task, index: number) => {
        setTasks(previous => {
          // ...
        })
      }, [])
    
      return <main>
        <h1>my to do</h1>
        <ul>
          {tasks.map((task, idx) => {
            const setValue = callback => {
              const newValue = callback(task)
              setTaskAtIndex(newValue, idx)
            }
            return <TaskView value={task} setValue={setValue}/>
          })}
        </ul>
      </main>
    }
    


    따라서 여기서 문제는 setValue가 모든 렌더링에서 항상 새로운 참조를 갖게 되어 memo()를 쓸모 없게 "렌더링"한다는 것입니다. 동적 크기의 루프 내부에 있기 때문에 적용할 수 없습니다useCallback.

    순진한 접근 방식은 index 에 추가 소품 TaskView 을 추가하는 것이지만 캡슐화가 깨지기 때문에 해킹이 될 수 있습니다.
    useCallback를 사용할 수 있도록 "어댑터 구성 요소"를 만들어 이 문제를 해결했습니다. 이제TaskView는 데이터가 변경될 때만 다시 렌더링해야 합니다.

    function TaskViewAdapter(props: {
      value: Task,
      setValueAtIndex: (value: Task, index: number) => void ,
      index: number
    }) {
      const setValue = useCallback((callback) => {
        const newValue = callback(value)
        setValueAtIndex(newValue, index)
      }, [value, setValueAtIndex, index])
      return <TaskView value={props.value} setValue={setValue} />
    }
    


    HTML 이벤트와 다른 점은 무엇입니까?



    목록을 처리하는 오래되고 일반적인 접근 방식은 데이터 태그(또는 다른 속성)를 사용하는 것입니다. 이 접근 방식을 사용하면 중간 구성 요소의 도움 없이 효율적인 렌더링에 도달할 수 있습니다.

    function Main() {
      const handleClick = useCallback((ev) => {
        console.log('index', ev.target.dataset.index)
      }, [])
      return <ul>
        <li><button data-index="1" onClick={handleClick}>Button 1</button></li>
        <li><button data-index="2" onClick={handleClick}>Button 2</button></li>
      </ul>
    }
    


    이것은 데이터가 HTML 이벤트에서 방출되기 때문에 작동합니다.

    여기서 변경된 사항은 무엇입니까? setValue 콜백과 달리 HTML 이벤트는 데이터와 함께 컨텍스트를 가져옵니다. 단순히 값이 아닌 전체 요소를 가져옵니다.

    이것은 부모가 요소에 데이터를 첨부하고 이벤트를 처리할 때 해당 데이터를 다시 읽을 수 있음을 의미합니다. 그리고 <button>의 내부 구현은 여전히 ​​부모가 첨부된 추가 정보가 무엇인지 알 필요가 없습니다.

    단순히 데이터를 방출하는 대신 구성 요소에 대한 추가 컨텍스트 데이터가 있는 이벤트 같은 것을 방출하여 복제를 시도할 수 있습니다. 사용자 정의 이벤트 방출은 React "표준"에 포함되지 않기 때문에 특정 프로젝트에 대한 표준 이벤트 형식을 정확히 지정해야 합니다.

    const event = createEvent({
      component: getSelfRef(),
      data,
    })
    onChange(event)
    


    또한 (후크 구성 요소를 사용하는 경우) 래퍼 "어댑터"구성 요소를 만들지 않고는 현재 구성 요소 참조를 가져올 수 있는 방법이 없습니다. 결국 우리는 어댑터가 필요한 동일한 경우에 다시 빠지게 됩니다.

    좋은 웹페이지 즐겨찾기