如何使用 JS or TS,实现 Vue 中的diff算法?

以下是 TS 实现

type PropValue = string | number | boolean;

interface Props {
  [key: string]: PropValue
}

interface VNode {
  tagName: string,
  props: Props,
  children: Array<VNode | string>,
  key?: string | number
}

interface Patch {
  type: string,
  node?: VNode,
  content?: string,
  props?: Array<{ key: string, value: PropValue }>,
  moves?: Array<{ index: number, type: string, from?: number, to?: number }>
}

function diff(oldTree: VNode, newTree: VNode): Patch[] {
  let index = 0;
  let patches: Patch[] = [];
  walk(oldTree, newTree, index, patches);
  return patches;
}

function walk(oldNode: VNode, newNode: VNode, index: number, patches: Patch[]) {
  let current: Patch[] = [];

  if (newNode == null) {
    // Node has been removed
  } else if (typeof oldNode === 'string' && typeof newNode === 'string') {
    // Text content has changed
    if (newNode !== oldNode) {
      current.push({ type: 'TEXT', content: newNode });
    }
  } else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
    // Node is the same, but properties may have changed
    let props = diffProps(oldNode.props, newNode.props);
    if (props.length > 0) {
      current.push({ type: 'PROPS', props: props });
    }
    diffChildren(oldNode.children, newNode.children, index, patches, current);
  } else {
    // Node is different, need to replace old node with new node
    current.push({ type: 'REPLACE', node: newNode });
  }

  if (current.length > 0) {
    patches[index] = current;
  }
}

function diffProps(oldProps: Props, newProps: Props): Array<{ key: string, value: PropValue }> {
  let props: Array<{ key: string, value: PropValue }> = [];
  for (let key in oldProps) {
    if (oldProps.hasOwnProperty(key) && !newProps.hasOwnProperty(key)) {
      props.push({ key: key, value: undefined });
    }
  }
  for (let key in newProps) {
    if (newProps.hasOwnProperty(key)) {
      let oldValue = oldProps[key];
      let newValue = newProps[key];
      if (oldValue !== newValue) {
        props.push({ key: key, value: newValue });
      }
    }
  }
  return props;
}

function diffChildren(oldChildren: Array<VNode | string>, newChildren: Array<VNode | string>, index: number, patches: Patch[], current: Patch[]) {
  let diffs = listDiff(oldChildren, newChildren, 'key');
  newChildren = diffs.children;

  if (diffs.moves.length > 0) {
    let reorderPatch = { type: 'REORDER', moves: diffs.moves };
    current.push(reorderPatch);
  }

  let leftNode = null;
  let currentNodeIndex = index;
  oldChildren.forEach(function(oldChild, i) {
    let newChild = newChildren[i];
    currentNodeIndex = (leftNode && isVNode(leftNode)) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1;
    walk(oldChild, newChild, currentNodeIndex, patches);
    leftNode = oldChild;
  });
}

function isVNode(node: VNode | string): node is VNode {
  return typeof node !== 'string';
}

function listDiff(oldList: Array<VNode | string>, newList: Array<VNode | string>, key: string): { moves: Array<{ index: number, type: string, from?: number, to?: number }>, children: Array<VNode | string> } {
  let oldMap = makeKeyIndexAndFree(oldList, key);
  let newMap = makeKeyIndexAndFree(newList, key);

  let newFreeList = newMap.freeList;

  let moves = [];
  let children = [];
  let i = 0;
  let freeIndex = 0;

  while (i < oldList.length) {
    let item = oldList[i];
    let itemIndex = getItemIndex(item, newMap, key);
    if (itemIndex != null) {
      children.push(newList[itemIndex]);
      i++;
      freeIndex = itemIndex;
    } else {
      let freeItem = newFreeList[freeIndex++];
      children.push(freeItem || null);
      moves.push({ index: i, type: 'REMOVE' });
      i++;
    }
  }

  let lastIndex = 0;
  newList.forEach(function(item, i) {
    let itemIndex = getItemIndex(item, oldMap, key);
    if (itemIndex == null) {
      moves.push({ index: i, type: 'INSERT' });
      children.splice(i, 0, null);
    } else {
      if (itemIndex < lastIndex) {
        moves.push({ index: i, type: 'MOVE', from: itemIndex, to: lastIndex });
      }
      lastIndex = Math.max(lastIndex, itemIndex);
    }
  });

  return {
    moves: moves,
    children: children
  };
}

function makeKeyIndexAndFree(list: Array<VNode | string>, key: string): { keyIndex: { [key: string]: number }, freeList: Array<VNode | string> } {
  let keyIndex = {};
  let freeList = [];
  for (let i = 0; i < list.length; i++) {
    let item = list[i];
    let itemKey = isVNode(item) ? item[key] : undefined;
    if (itemKey != null) {
      keyIndex[itemKey] = i;
    } else {
      freeList.push(item);
    }
  }
  return {
    keyIndex: keyIndex,
    freeList: freeList
  };
}

function getItemIndex(item: VNode | string, map: { keyIndex: { [key: string]: number }, freeList: Array<VNode | string> }, key: string): number {
  let itemKey = isVNode(item) ? item[key] : undefined;
  if (itemKey != null) {
    return map.keyIndex[itemKey];
  } else {
    return null;
  }
}

以下是通过 JS 实现

function diff(oldTree, newTree) {
  var index = 0;
  var patches = {};
  walk(oldTree, newTree, index, patches);
  return patches;
}

function walk(oldNode, newNode, index, patches) {
  var current = [];

  if (newNode == null) {
    // Node has been removed
  } else if (typeof oldNode === 'string' && typeof newNode === 'string') {
    // Text content has changed
    if (newNode !== oldNode) {
      current.push({ type: 'TEXT', content: newNode });
    }
  } else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
    // Node is the same, but properties may have changed
    var props = diffProps(oldNode.props, newNode.props);
    if (props.length > 0) {
      current.push({ type: 'PROPS', props: props });
    }
    diffChildren(oldNode.children, newNode.children, index, patches, current);
  } else {
    // Node is different, need to replace old node with new node
    current.push({ type: 'REPLACE', node: newNode });
  }

  if (current.length > 0) {
    patches[index] = current;
  }
}

function diffProps(oldProps, newProps) {
  var props = [];
  for (var key in oldProps) {
    if (oldProps.hasOwnProperty(key) && !newProps.hasOwnProperty(key)) {
      props.push({ key: key, value: undefined });
    }
  }
  for (var key in newProps) {
    if (newProps.hasOwnProperty(key)) {
      var oldValue = oldProps[key];
      var newValue = newProps[key];
      if (oldValue !== newValue) {
        props.push({ key: key, value: newValue });
      }
    }
  }
  return props;
}

function diffChildren(oldChildren, newChildren, index, patches, current) {
  var diffs = listDiff(oldChildren, newChildren, 'key');
  newChildren = diffs.children;

  if (diffs.moves.length > 0) {
    var reorderPatch = { type: 'REORDER', moves: diffs.moves };
    current.push(reorderPatch);
  }

  var leftNode = null;
  var currentNodeIndex = index;
  oldChildren.forEach(function(oldChild, i) {
    var newChild = newChildren[i];
    currentNodeIndex = (leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1;
    walk(oldChild, newChild, currentNodeIndex, patches);
    leftNode = oldChild;
  });
}

function listDiff(oldList, newList, key) {
  var oldMap = makeKeyIndexAndFree(oldList, key);
  var newMap = makeKeyIndexAndFree(newList, key);

  var newFreeList = newMap.freeList;

  var moves = [];
  var children = [];
  var i = 0;
  var freeIndex = 0;

  while (i < oldList.length) {
    var item = oldList[i];
    var itemIndex = getItemIndex(item, newMap, key);
    if (itemIndex != null) {
      children.push(newList[itemIndex]);
      i++;
      freeIndex = itemIndex;
    } else {
      var freeItem = newFreeList[freeIndex++];
      children.push(freeItem || null);
      moves.push({ index: i, type: 'REMOVE' });
      i++;
    }
  }

  var lastIndex = 0;
  newList.forEach(function(item, i) {
    var itemIndex = getItemIndex(item, oldMap, key);
    if (itemIndex == null) {
      moves.push({ index: i, type: 'INSERT' });
      children.splice(i, 0, null);
    } else {
      if (itemIndex < lastIndex) {
        moves.push({ index: i, type: 'MOVE', from: itemIndex, to: lastIndex });
      }
      lastIndex = Math.max(lastIndex, itemIndex);
    }
  });

  return {
    moves: moves,
    children: children
  };
}

function makeKeyIndexAndFree(list, key) {
  var keyIndex = {};
  var freeList = [];
  for (var i = 0; i < list.length; i++) {
    var item = list[i];
    var itemKey = item[key];
    if (itemKey != null) {
      keyIndex[itemKey] = i;
    } else {
      freeList.push(item);
    }
  }
  return {
    keyIndex: keyIndex,
    freeList: freeList
  };
}

function getItemIndex(item, map, key) {
  var itemKey = item[key];
  if (itemKey != null) {
    return map.keyIndex[itemKey];
  } else {
    return null;
  }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值