使用DOM子项和数据传播进行更新

时间:2019-05-29 05:36:43

标签: javascript d3.js

我想通过数据传播来更新包含子级的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工程师,我非常害怕这种数据绑定。

1 个答案:

答案 0 :(得分:2)

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

看看这张桌子,我总结了它们之间的区别:

+------------------+----------------------------------+----------------------------+
| Method           |              select()            |         selectAll()        |
+------------------+----------------------------------+----------------------------+
| Selection        | selects the first element        | selects all elements that  |
|                  | that matches the selector string | match the selector string  |
+------------------+----------------------------------+----------------------------+
| Grouping         | Does not affect grouping         | Affects grouping           |
+------------------+----------------------------------+----------------------------+
| Data propagation | Propagates data                  | Doesn't propagate data     |
+------------------+----------------------------------+----------------------------+

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

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

鉴于您的代码段每个组只有一个子级(圆圈和文字),最简单的解决方案是将selectAll更改为select。但是,如果每个父母有一个以上的孩子,最好的(也是惯用的)解决方案是创建一个嵌套的enter-update-exit选择,这比较麻烦。

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

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>