[JS] diff 알고리즘을 구현하면서 발생한 문제점2

jiseong·2022년 5월 27일
0

T I Learned

목록 보기
256/291

발생했던 문제점

diff 알고리즘에서 컴포넌트를 태그로 한번 감싸야지만 정상적으로 동작하는 문제가 있었다.

  • 문제가 발생하지 않는 예시코드
render() {
  const {books} = this.state;

  return (
    <div>
      <Todo books={books} addItem={this.addItem} checkItem={this.checkItem} removeItem={this.removeItem} />
    </div>
  )
}
  • 문제가 발생하는 예시코드
render() {
  const {books} = this.state;

  return (
    <div>
      <h2>투두리스트</h2>
      <Todo books={books} addItem={this.addItem} checkItem={this.checkItem} removeItem={this.removeItem} />
    </div>
  )
}

원인

상태가 재정의되어 리렌더링되는 과정에서 해당 Node의 idx 즉, 부모노드로 부터 몇번째 위치한 Node인지를 정의해주지 않았기 때문에 태그로 한번감싸면 항상 첫번째에 위치함으로 문제가 없었지만 자식노드가 여러개 존재하는 부모노드의 경우에는 문제가 발생했던 것이였다.

setState(newState: Record<string, any>){
  this.state = newState;
  const realDOM = this._DOM;    
  const componentVDOM = this.render();
  // 어디서 시작되는지 알기 위해 현재 컴포넌트 정보를 저장
  injectComponentToVDOM(componentVDOM, this);

  nodeCompare(componentVDOM, realDOM.parentNode, realDOM);
}

// nodeCompare()함수
function nodeCompare(vDOM: IDom, container: Node | null , realDOM?: INode , idx: number = 0){}

해결방법

nodeCompare()함수에서는 이미 idx만 알고 있다면 부모노드의 몇번째 자식을 업데이트 해야하는지 알 수 있기 때문에 전달인지로 해당 노드가 부모로부터 몇번째 노드인지 idx를 알려주기만 하면 된다.

nodeCompare(componentVDOM, realDOM.parentNode, realDOM, 
            [...realDOM.parentNode.childNodes].indexOf(realDOM as ChildNode));

기타

동작에 맞는 함수명

다음 함수는 생성된 또는 기존의 Element의 속성을 업데이트하는 함수로 항상 전달인자로 Node가 아닌 Element가 전달되기 때문에 updateNode 대신 updateElement로 함수명을 수정하였고 매개변수 또한 newNode에서 ele로 수정하였다.

export function updateNode(newNode: Element, vDOM: IDom, oldDOM?: IDom) {
  const newProps = vDOM.attributes || {};
  const oldProps = oldDOM && oldDOM.attributes || {};

  // 새로운 이벤트나 속성 처리
  Object.entries(newProps).forEach(([key, value]) => {
    const newProp = newProps[key];
    const oldProp = oldProps[key];

    if(newProp === oldProp) return;
    if(!isSpecialAttribute(key) && !value) return;
    
    if(key.startsWith('on')){
      const eventType = key.slice(2).toLowerCase();
      newNode.addEventListener(eventType, value);

      if(oldProp) newNode.removeEventListener(eventType, oldProp);
      
      return;
    }

    newNode.setAttribute(key, value);

    if(isBooleanAttribute(key) && !value){
      newNode.removeAttribute(key);
    }
  });

  // 기존 이벤트나 속성 삭제 처리
  Object.entries(oldProps).forEach(([key, value]) => {
    const newProp = newProps[key];

    if(newProp != null) return;
    
    if(key.startsWith('on')){
      const eventType = key.slice(2).toLowerCase();
      newNode.removeEventListener(eventType, value);
      
      return;
    }

    newNode.removeAttribute(key);
  });
}

vscode Tip!
cmd + shift + H: 전체 리플레이스 가능


Reference

0개의 댓글