首页 > 解决方案 > 使用 DOM 子节点和数据传播进行更新

问题描述

我想通过数据传播更新包含子节点的 DOM。

第一次填充 DOM 时,数据会正确传播给它们的子级。但是,当我用新数据更新父 DOM 时,子 DOM 不会随之更新。

同样令人惊讶的是,旧数据可以从子元素中检索,我强烈反对,因为数据太大而且我不希望保留旧/额外数据,它保存引用并保留旧数据。(在我的意义上内存泄漏)

const DATA = [{
  name: "data1",
  left: 70,
  top: 70,
  radius: 20
}, {
  name: "data2",
  left: 200,
  top: 100,
  radius: 50
}];
const svg = d3.select("#svg");

// Not working as expected
function loadData(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data);
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");

  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.selectAll(".container circle")
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.selectAll(".container text")
    .text(entry => entry.name);
}

// Working, but not desired
function expected(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data);
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");
  
  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.selectAll(".container circle")
    .data(data)
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.selectAll(".container text")
    .data(data)
    .text(entry => entry.name);
}
let i = 0;
loadData(DATA);

function updateData() {
  i = (i+1) % 2;
  loadData(DATA.slice(i,i+1));
}
function expectedUpdate() {
  i = (i+1) % 2;
  expected(DATA.slice(i,i+1));
}
button {
  display: inline-block;
}
svg {
  width: 300px;
  height: 300px;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button onclick="updateData();">Update</button>
<button onclick="expectedUpdate();">Expected</button>
<svg id="svg"></svg>

这只是演示,我有一个父<g>元素,还有 2个子元素<circle>&<text>附加到它上面。

第一次渲染时,我使用该loadData()方法填充 DOM,相应的子 DOM(圆圈和文本)能够检索插入到其父 DOM (g) 中的数据,这显示了沿 DOM 树的正确数据传播.

但是,当我多次按下“更新”按钮时,只有父 DOM 正在更新,子 DOM 能够访问不存在的数据(旧数据)。

“预期”按钮显示了预期的行为,但我可以看到数据被多次绑定,作为一名 javascript 工程师,我非常害怕这种数据绑定。

标签: javascriptd3.js

解决方案


您的非工作功能 ( ) 中的主要问题loadData是您正在使用selectAll. 与, 不同selectselectAll 传播数据。

看一下我总结的这张表,总结了它们之间的区别:

方法 选择() 全选()
选择 选择与选择器字符串匹配的第一个元素 选择与选择器字符串匹配的所有元素
分组 不影响分组 影响分组
数据传播 传播数据 不传播数据

如您所见,您的主要信息是:

  • select:传播数据。
  • selectAll:不传播数据。

鉴于您的片段每组只有一个selectAll孩子(圆圈和文本),最简单的解决方案就是更改为select. 但是,如果每个父母有多个孩子,最好的(也是惯用的)解决方案是创建一个嵌套的进入-更新-退出选择,这有点费力。

除此之外,您还缺少关键功能:

const container = svg.selectAll(".container")
    .data(data, function(d) {
        return d.name;
    });

这是您进行这些更改的代码:

const DATA = [{
  name: "data1",
  left: 70,
  top: 70,
  radius: 20
}, {
  name: "data2",
  left: 200,
  top: 100,
  radius: 50
}];
const svg = d3.select("#svg");

// Not working as expected
function loadData(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data, function(d) {
      return d.name;
    });
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");

  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.select(".container circle")
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.select(".container text")
    .text(entry => entry.name);
}

// Working, but not desired
function expected(data) {
  console.log(data);
  const container = svg.selectAll(".container")
    .data(data);
  container.exit().remove();
  const enter = container.enter().append("g")
    .attr("class", "container");
  enter.append("circle");
  enter.append("text");

  container.merge(enter)
    .attr("transform", entry => `translate(0,${entry.top})`)
  svg.selectAll(".container circle")
    .data(data)
    .attr("r", entry => entry.radius)
    .attr("cx", entry => entry.left);
  svg.selectAll(".container text")
    .data(data)
    .text(entry => entry.name);
}
let i = 0;
loadData(DATA);

function updateData() {
  i = (i + 1) % 2;
  loadData(DATA.slice(i, i + 1));
}

function expectedUpdate() {
  i = (i + 1) % 2;
  expected(DATA.slice(i, i + 1));
}
button {
  display: inline-block;
}

svg {
  width: 300px;
  height: 300px;
  display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<button onclick="updateData();">Update</button>
<button onclick="expectedUpdate();">Expected</button>
<svg id="svg"></svg>


推荐阅读