ReactJS访问Virtual DOM协调结果

Myk*_*yev 13 javascript reactjs

我正在通过标准this.setState机制更新页面的一部分.我想抓住网页上已更改的元素,并向用户提供视觉反馈.

假设我们有一个RichText获得data道具的组件.要渲染的富文本将委托渲染到像小部件Paragraph,Header,BulletPoints,Text,等最后的结果是正确呈现丰富的文本.

后来data道具改变(例如套接字推送).因此Paragraph可以添加s,或者更改文本,或者可以移动.我想通过简单地突出显示已更改的HTML节点来向用户提供视觉反馈.

简而言之,我希望在您查看HTML树时实现Chrome检查器所显示的内容.它会使DOM更改闪烁.

ReactJS知道改变了什么.理想情况下,我希望能够获得这些知识.

虽然较小的组件Paragraph可能负责突出自身内部的差异,但我认为他们没有足够的外部世界知识来使其按预期工作.

格式(简化版)

{
  content: [{
    type: 'Document',
    content: [{
      type: 'Paragraph',
      content: [{
        type: 'Text', 
        text: 'text text'
      }, {
        type: 'Reference', 
        content: 'text text'
      },
    ]}, {
        type: 'BulletPoints', 
        content: [{
          type: 'ListEntry', content: [{
            type: 'Paragraph', content: [{
              type: 'Text', 
              text: 'text text'
            }, {
              type: 'Reference', 
              content: 'text text'
            }]
          }]
        }]
      }]
Run Code Online (Sandbox Code Playgroud)

我目前的解决方案

我有一个顶级组件,它知道如何Document通过将作业委托给其他组件来呈现整个组件.我有一个实时版本的HOC:LiveDocument它负责变更可视化.

所以我在之前setState和之后捕获DOM setState.然后我使用HtmlTreeWalker来发现第一个区别(当我走树时忽略某些元素).

Tia*_*gel 6

React已经为这些情况添加了一个插件.ReactCSSTransitionGroup

ReactCSSTransitionGroup是一个基于ReactTransitionGroup的高级API,是一个在React组件进入或离开DOM时执行CSS转换和动画的简单方法.它的灵感来自优秀的ng-animate库.

您可以轻松为进入或离开特定父级的项目添加动画.

var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;

const nextId = (() => {
  let lastId = 0;
  return () => ++lastId;
})();

class TodoList extends React.Component {
  constructor(props) {
    super(props);
    this.state = {items: [
      {id: nextId(), text: 'hello'}, 
      {id: nextId(), text: 'world'}, 
      {id: nextId(), text: 'click'}, 
      {id: nextId(), text: 'me'}
    ]};
    this.handleAdd = this.handleAdd.bind(this);
  }

  handleAdd() {
    const newItems = this.state.items.concat([
      {id: nextId(), text: prompt('Enter some text')}
    ]);
    this.setState({items: newItems});
  }

  handleRemove(toRemove) {
    let newItems = this.state.items.filter(item => item.id !== toRemove.id);
    this.setState({items: newItems});
  }

  render() {
    const items = this.state.items.map((item) => (
      <div key={item.id} onClick={() => this.handleRemove(item)}>
        {item.text}
      </div>
    ));

    return (
      <div>
        <button className="add-todo" onClick={this.handleAdd}>Add Item</button>        
        <ReactCSSTransitionGroup
          transitionName="example"
          transitionEnterTimeout={500}
          transitionLeaveTimeout={300}>
          {items}
        </ReactCSSTransitionGroup>
      </div>
    );
  }
}

ReactDOM.render(<TodoList/>, document.getElementById("app"));
Run Code Online (Sandbox Code Playgroud)
.example-enter {  
  background-color: #FFDCFF;
  color: white;
}

.example-enter.example-enter-active {
  background-color: #9E1E9E;  
  transition: background-color 0.5s ease;
}

.example-leave {
  background-color: #FFDCFF;
  color: white;
}

.example-leave.example-leave-active {
  background-color: #9E1E9E;  
  transition: background-color 0.3s ease;
}

.add-todo {
  margin-bottom: 10px;
}
Run Code Online (Sandbox Code Playgroud)
<script src="https://unpkg.com/react@15/dist/react-with-addons.js"></script>
<script src="https://unpkg.com/react-dom@15/dist/react-dom.js"></script>

<div id="app"></div>
Run Code Online (Sandbox Code Playgroud)

  • 我对此的看法是用CSS过渡包装所有孩子.您不一定需要在每个组件中手动执行此操作,例如,您可以创建一个HOC来添加转换并在所需的所有组件中使用它. (2认同)

Fre*_*eez 2

不幸的是,React 没有提供一个钩子来监听外部组件的状态变化。您可以用来componentDidUpdate(prevProps, nextProps)获取组件状态更改的通知,但您必须保留先前生成的 DOM 的引用,并手动将其与新的 DOM 进行比较(例如,使用dom-compare )。我认为您当前的解决方案已经做到了这一点。

我尝试了使用MutationObserver的替代解决方案,并使用此技术来获取相对于文档的修改后的元素位置,并在变异元素上方显示边框层。看起来效果不错,但我没有检查性能。

突变观察者.js

const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;

const observer = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation) {
    if (mutation.addedNodes) {
      mutation.addedNodes.forEach(showMutationLayer);
    }
  });
});

const showMutationLayer = (node) => {
  const mutationLayer = document.createElement('div');
  mutationLayer.style.position = 'absolute';
  mutationLayer.style.border = '2px solid red';
  document.body.appendChild(mutationLayer);
  if (node.nodeType === Node.TEXT_NODE) {
    node = node.parentNode;
  } 
  if (node.nodeType !== Node.ELEMENT_NODE) {
    return;
  }
  const { top, left, width, height } = getCoords(node);
  mutationLayer.style.top = `${top}px`;
  mutationLayer.style.left = `${left}px`;
  mutationLayer.style.width = `${width}px`;
  mutationLayer.style.height = `${height}px`;
  setTimeout(() => {
    document.body.removeChild(mutationLayer);
  }, 500);
};

function getCoords(elem) { // crossbrowser version
    const box = elem.getBoundingClientRect();
    const body = document.body;
    const docEl = document.documentElement;
    const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
    const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
    const clientTop = docEl.clientTop || body.clientTop || 0;
    const clientLeft = docEl.clientLeft || body.clientLeft || 0;
    const top  = box.top +  scrollTop - clientTop;
    const left = box.left + scrollLeft - clientLeft;
    return { 
      top: Math.round(top), 
      left: Math.round(left), 
      width: box.width,
      height: box.height
    };
}

export default {
   init(container) {
     observer.observe(container, {
       attributes: true,
       childList: true,
       characterData: true,
       subtree: true
     });
   } 
}
Run Code Online (Sandbox Code Playgroud)

main.js

import React from 'react';
import {render} from 'react-dom';
import App from './App.js';
import mutationObserver from './mutationObserver.js';

const appContainer = document.querySelector('#app');

// Observe mutations when you are in a special 'debug' mode
// for example
if (process.env.NODE_ENV === 'debug') {
   mutationObserver.init(appContainer);
}

render(<App />, appContainer);
Run Code Online (Sandbox Code Playgroud)

这种技术的优点是您不必修改每个组件代码来观察更改。您也不要修改生成 DOM 的组件(该层位于#app元素之外)。可以轻松启用/禁用此功能以保持应用程序性能。

在这个小提琴中查看它的实际效果 (您可以编辑图层样式,添加 CSS 过渡以获得更好的图层)