React中渲染节点的一点小思考

对于react的渲染机制其实我不是很了解的,最近在编写一个react拖拽插件的时候为此吃了一点小苦头


对于react的渲染机制其实我不是很了解的,最近在编写一个react拖拽插件的时候为此吃了一点小苦头。

我们都知道react会事先生成一个虚拟DOM,然后插入到真实节点中,如果state发生改变后,会比较需要更新的DOM节点,实现最小化的更新。
然后问题来了。我们可以发现每一个DOM节点都有一个data-reactid,(其实是我的猜测)目前的react根据这个来与state中的数据进行比对,但是利用dragula进行拖拽排序,其实是会改变真实DOM的顺序的,因此data-reactid的顺序也就改变了,重新渲染的时候,react会按照data-reactid的顺序渲染,自然而然数据渲染的顺序就乱序了。

也就是说,我们的state里有一个数组[1, 2, 3, 4],通过遍历渲染这个state,产生一个DOM列表,列表中有四个<li>,他们对应的data-reactid是[r1, r2, r3, r4].
现在我们拖动了实际DOM的顺序,改变数组的顺序为[1, 2, 4, 3],此时data-reactid变成了[r1, r2, r4, r3].
按照预想,重新渲染的时候,四个<li>的文本内容顺序应当与state中的数组顺序一致,但是实际情况是,react会根据data-reactid的顺序去渲染,结果就是这样:

1
2
3
4
r1 -> 1
r2 -> 2
r4 -> 3
r3 -> 4

可以来看这样一个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Test extends React.Component {
constructor(...arg) {
super(...arg);
this.state = {
data: [1,2,3,4]
};
}
componentDidMount() {
setTimeout(this.moveNode, 5000)
setTimeout(() => {
this.setState({
data: [1,2,4,3]
})
}, 10000)
}
moveNode() {
var li_3 = document.querySelector('.data-2');
var li_4 = document.querySelector('.data-3');
li_4.parentNode.insertBefore(li_4, li_3);
}
render() {
return (
<ul>
{
this.state.data.map((item, i) => (
<li key={i} className={'data-' + i}>
{item}
</li>
))
}
</ul>
);
}
}

最终结果与前述相同。

react在15.0之后取消了data-reactid,但是我猜测其内部的渲染机制依然按照上面这样一种逻辑。
因此项目中如果有拖拽真实节点改变其顺序的需求,就要注意节点顺序变化的同时,要保证state按照旧的节点顺序来渲染。可以创建一个状态副本用以保存改变后的state,但是渲染节点的state不做变化。