更改 table 结构会导致 React 失去对 DOM 的跟踪
Changing table structure causes React to lose track of the DOM
我正在尝试在 React 中创建一个 table 组件,其中的列和行都是动态的并且会随时间变化。但是,当数据发生变化时,React 会抛出意外的 DOM 突变不变性违规。
这里是 fiddle 演示问题 http://jsfiddle.net/69z2wepo/1797/。如您所见,初始渲染后数据发生变化,React 无法再跟踪 DOM.
的状态
代码在这里:
var Table = React.createClass({
getDefaultProps: function() {
return {
data: [
{
colA: { value: 'foo' },
},
],
};
},
render: function() {
return (
<table>
<thead>
<tr> {
Object.keys(this.props.data[0]).map(function(key) {
return (<th>{ key }</th>);
}.bind(this))
} </tr>
</thead>
<tbody> {
this.props.data.map(function(item) {
return (<tr> { Object.keys(item).map(function(key) {
return (
<td>{ item[key] }</td>
);
}.bind(this)) } </tr>);
}.bind(this))
} </tbody>
</table>
);
}
});
var Main = React.createClass({
componentWillMount: function() {
setTimeout(function() {
var data = [
{
colA: {
value: 'foobar',
},
},
];
this.setState({
data: data,
});
}.bind(this), 3000);
},
getInitialState: function() {
var data = [
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
},
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
}
];
return {
data: data,
};
},
render: function() {
return (<Table data={ this.state.data } />);
},
});
React.render(<Main />, document.body);
console.log(React.renderToString(<Table/>));
我已经尝试了所有添加关键属性以跟踪各种元素的方法,但似乎无法解决这个问题。
使用 renderToString 渲染 table 组件显示 React 正在 table 的不同级别插入一堆元素。这是可能的原因吗?请参阅此处呈现的 DOM:
<table data-reactid=".1" data-react-checksum="1098817301">
<thead data-reactid=".1.0">
<tr data-reactid=".1.0.0">
<span data-reactid=".1.0.0.0"> </span>
<th data-reactid=".1.0.0.1:0">colA</th>
<span data-reactid=".1.0.0.2"> </span>
</tr>
</thead>
<tbody data-reactid=".1.1">
<span data-reactid=".1.1.0"> </span>
<tr data-reactid=".1.1.1:0">
<span data-reactid=".1.1.1:0.0"> </span>
<td data-reactid=".1.1.1:0.1:0"><span data-reactid=".1.1.1:0.1:0.$value:0">foo</span></td>
<span data-reactid=".1.1.1:0.2"> </span>
</tr>
<span data-reactid=".1.1.2"> </span>
</tbody>
</table>
原来问题出在你的缩进上。如果你在一个新行开始花括号 {}
(花括号在 JSX 中写 javascript),你的代码就可以工作。不知道为什么会这样。
jsfiddle:http://jsfiddle.net/cwn2nebs/
var Table = React.createClass({
getDefaultProps: function() {
return {
data: [
{
colA: { value: 'foo' },
},
],
};
},
render: function() {
return (
<table>
<thead>
<tr>
{
Object.keys(this.props.data[0]).map(function(key, idx) {
return (<th key={ idx } >{ key }</th>);
}.bind(this))
}
</tr>
</thead>
<tbody>
{
this.props.data.map(function(item, idx) {
return (
<tr key={ idx }>
{
Object.keys(item).map(function(key, i) {
return (
<td key={ i }>{ item[key] }</td>
);
}.bind(this)) }
</tr>);
}.bind(this))
}
</tbody>
</table>
);
}
});
var Main = React.createClass({
componentWillMount: function() {
setTimeout(function() {
var data = [
{
colA: {
value: 'foobar',
}
},
];
this.setState({
data: data,
});
}.bind(this), 3000);
},
getInitialState: function() {
var data = [
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
},
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
}
];
return {
data: data,
};
},
render: function() {
return (<Table data={ this.state.data } />);
},
});
React.render(<Main />, document.body);
更新
使用 JSX compiler 我尝试将您的部分代码转换为纯 JS:
render: function() {
return (
React.createElement("table", null,
React.createElement("thead", null,
React.createElement("tr", null, " ",
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this)),
" ")
),
React.createElement("tbody", null, " ",
this.props.data.map(function(item, idx) {
return (React.createElement("tr", {key: idx }, " ", Object.keys(item).map(function(key, i) {
return (
React.createElement("td", {key: i }, item[key] )
);
}.bind(this)), " "));
}.bind(this)),
" ")
)
);
}
这就是 React.createElement
的工作原理:
React.createElement(type, props, children);
注意 tr
元素的空 children:
React.createElement("tr", null, " ",
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this)),
" ")
但是换行加上花括号,编译后的代码是这样的(没有空(" ")children):
React.createElement("tr", null,
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this))
)
我相信 React 将 " "
children 转换为 span
元素,正如您所发现的那样,这是问题所在。
我正在尝试在 React 中创建一个 table 组件,其中的列和行都是动态的并且会随时间变化。但是,当数据发生变化时,React 会抛出意外的 DOM 突变不变性违规。
这里是 fiddle 演示问题 http://jsfiddle.net/69z2wepo/1797/。如您所见,初始渲染后数据发生变化,React 无法再跟踪 DOM.
的状态代码在这里:
var Table = React.createClass({
getDefaultProps: function() {
return {
data: [
{
colA: { value: 'foo' },
},
],
};
},
render: function() {
return (
<table>
<thead>
<tr> {
Object.keys(this.props.data[0]).map(function(key) {
return (<th>{ key }</th>);
}.bind(this))
} </tr>
</thead>
<tbody> {
this.props.data.map(function(item) {
return (<tr> { Object.keys(item).map(function(key) {
return (
<td>{ item[key] }</td>
);
}.bind(this)) } </tr>);
}.bind(this))
} </tbody>
</table>
);
}
});
var Main = React.createClass({
componentWillMount: function() {
setTimeout(function() {
var data = [
{
colA: {
value: 'foobar',
},
},
];
this.setState({
data: data,
});
}.bind(this), 3000);
},
getInitialState: function() {
var data = [
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
},
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
}
];
return {
data: data,
};
},
render: function() {
return (<Table data={ this.state.data } />);
},
});
React.render(<Main />, document.body);
console.log(React.renderToString(<Table/>));
我已经尝试了所有添加关键属性以跟踪各种元素的方法,但似乎无法解决这个问题。
使用 renderToString 渲染 table 组件显示 React 正在 table 的不同级别插入一堆元素。这是可能的原因吗?请参阅此处呈现的 DOM:
<table data-reactid=".1" data-react-checksum="1098817301">
<thead data-reactid=".1.0">
<tr data-reactid=".1.0.0">
<span data-reactid=".1.0.0.0"> </span>
<th data-reactid=".1.0.0.1:0">colA</th>
<span data-reactid=".1.0.0.2"> </span>
</tr>
</thead>
<tbody data-reactid=".1.1">
<span data-reactid=".1.1.0"> </span>
<tr data-reactid=".1.1.1:0">
<span data-reactid=".1.1.1:0.0"> </span>
<td data-reactid=".1.1.1:0.1:0"><span data-reactid=".1.1.1:0.1:0.$value:0">foo</span></td>
<span data-reactid=".1.1.1:0.2"> </span>
</tr>
<span data-reactid=".1.1.2"> </span>
</tbody>
</table>
原来问题出在你的缩进上。如果你在一个新行开始花括号 {}
(花括号在 JSX 中写 javascript),你的代码就可以工作。不知道为什么会这样。
jsfiddle:http://jsfiddle.net/cwn2nebs/
var Table = React.createClass({
getDefaultProps: function() {
return {
data: [
{
colA: { value: 'foo' },
},
],
};
},
render: function() {
return (
<table>
<thead>
<tr>
{
Object.keys(this.props.data[0]).map(function(key, idx) {
return (<th key={ idx } >{ key }</th>);
}.bind(this))
}
</tr>
</thead>
<tbody>
{
this.props.data.map(function(item, idx) {
return (
<tr key={ idx }>
{
Object.keys(item).map(function(key, i) {
return (
<td key={ i }>{ item[key] }</td>
);
}.bind(this)) }
</tr>);
}.bind(this))
}
</tbody>
</table>
);
}
});
var Main = React.createClass({
componentWillMount: function() {
setTimeout(function() {
var data = [
{
colA: {
value: 'foobar',
}
},
];
this.setState({
data: data,
});
}.bind(this), 3000);
},
getInitialState: function() {
var data = [
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
},
{
colA: {
value: 'foo',
},
colB: {
value: 'bar',
}
}
];
return {
data: data,
};
},
render: function() {
return (<Table data={ this.state.data } />);
},
});
React.render(<Main />, document.body);
更新
使用 JSX compiler 我尝试将您的部分代码转换为纯 JS:
render: function() {
return (
React.createElement("table", null,
React.createElement("thead", null,
React.createElement("tr", null, " ",
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this)),
" ")
),
React.createElement("tbody", null, " ",
this.props.data.map(function(item, idx) {
return (React.createElement("tr", {key: idx }, " ", Object.keys(item).map(function(key, i) {
return (
React.createElement("td", {key: i }, item[key] )
);
}.bind(this)), " "));
}.bind(this)),
" ")
)
);
}
这就是 React.createElement
的工作原理:
React.createElement(type, props, children);
注意 tr
元素的空 children:
React.createElement("tr", null, " ",
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this)),
" ")
但是换行加上花括号,编译后的代码是这样的(没有空(" ")children):
React.createElement("tr", null,
Object.keys(this.props.data[0]).map(function(key, idx) {
return (React.createElement("th", {key: idx }, key ));
}.bind(this))
)
我相信 React 将 " "
children 转换为 span
元素,正如您所发现的那样,这是问题所在。