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