需求分析
消息列表里面会有一些内容需要置顶,但是点击置顶后,列表直接滚到顶部不利于消息的批量操作。
复现效果
原因分析
区别参考于这篇文章https://www.cnblogs.com/yadiblogs/p/11129206.html
react 与 vue的diff区别
判断2个节点是否相同:vue认为className不一样,就不同,react则认为相同,只是属性不同,只需要更新其属性。
同一层级对比:Vue从两端至中间对比,react从左至右对比。react策略存在短板,如果一个集合只把最后一个移到第一个,react会移动前面所有节点,vue只移动最后一个节点到最前面。
key 具体是如何起作用的和为什么不能用 index 作为 key 值参考于这篇文章
https://bbs.huaweicloud.com/blogs/297739
为什么不能用 index 作为 key 值呢?
index 作为 key ,如果我们删除了一个节点,那么数组的后一项可能会前移,这个时候移动的节点和删除的节点就是相同的 key ,在react中,如果 key 相同,就会视为相同的组件,但这两个组件是不同的,这样就会出现很麻烦的事情,例如:序号和文本不对应等问题
所以一定要保证 key 的唯一性
那 key 具体是如何起作用的呢?
首先在 React 中只允许节点右移
因此对于上图中的转化,只会进行 A,C 的移动
则只需要对移动的节点进行更新渲染,不移动的则不需要更新渲染
解决方案
getSnapshotBeforeUpdate() 方法在最近一次渲染输出(提交到 DOM 节点)之前调用。
在 getSnapshotBeforeUpdate() 方法中,我们可以访问更新前的 props 和 state。
getSnapshotBeforeUpdate() 方法需要与 componentDidUpdate() 方法一起使用,否则会出现错误。
// // 获取更新之前的快照
// getSnapshotBeforeUpdate(prevProps, prevState) {
// // let top = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
// let top = document.querySelector('main').scrollTop;
// // console.log('list.scrollHeight - list.scrollTop;', document.body.scrollHeight, top);
// if (top !== 0) {
// // return document.body.scrollHeight - top;
// return top;
// }
// return null
// }
// componentDidUpdate(prevProps, prevState, snapshot) {
// console.log('snapshot', snapshot);
// //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
// if(snapshot !== null) {
// const rootEle = document.querySelector('main');
// rootEle.scrollTop = this.state.top;
// this.listRef.current.scrollTop = snapshot
// }
// }
不足与补充
getSnapshotBeforeUpdate() 与 componentDidUpdate() 方法一起使用只能解决比较简单组件的场景,组件稍微复杂会有明显回弹效果是不佳的。问题根源不在于diff,diff只是个更新的手段和过程。最终都是要在页面上更新掉那些东西,滚动与否,不是diff和react控制的,是chrome控制的。
{/* <main style={{height: '900px', overflow: 'auto', overflowAnchor: 'none'}} ref={this.listRef}> */}
overflowAnchor: ‘none’
CSS 属性提供一种退出浏览器滚动锚定行为的方法,该行为会调整滚动位置以最大程度地减少内容偏移。
默认情况下,在任何支持滚动锚定行为的浏览器中都将其启用。因此,仅当您在文档或文档的一部分中遇到滚动锚定问题并且需要关闭行为时,才通常需要更改此属性的值。
通过查阅 MDN 发现适配上不支持safari,也发现的确源码在safari上不会出现滚动bug。
源码
class List1 extends React.Component {
constructor(props) {
super(props)
this.listRef= React.createRef();
}
state= {
list: [],
};
componentDidMount() {
const list = this.state.list.slice();
for (let i = 0; i < 100 ; i++) {
list.push({
id: i,
name: i.toString(),
});
}
this.setState({ list });
}
// // 获取更新之前的快照
// getSnapshotBeforeUpdate(prevProps, prevState) {
// // let top = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
// let top = document.querySelector('main').scrollTop;
// // console.log('list.scrollHeight - list.scrollTop;', document.body.scrollHeight, top);
// if (top !== 0) {
// // return document.body.scrollHeight - top;
// return top;
// }
// return null
// }
// componentDidUpdate(prevProps, prevState, snapshot) {
// console.log('snapshot', snapshot);
// //(这里的 snapshot 是 getSnapshotBeforeUpdate 的返回值)
// if(snapshot !== null) {
// const rootEle = document.querySelector('main');
// rootEle.scrollTop = this.state.top;
// this.listRef.current.scrollTop = snapshot
// }
// }
/**
* 将列表中的项目置顶
* @param index 要置顶的索引
*/
toTop(index, e) {
const list = this.state.list;
const item = list[index];
list.splice(index, 1);
list.unshift(item);
this.setState({ list });
}
render() {
return (
<div>
{/* <main style={{height: '900px', overflow: 'auto', overflowAnchor: 'none'}} ref={this.listRef}> */}
<main style={{height: '900px', overflow: 'auto'}} ref={this.listRef}>
<hr />
<ul className='list' id='mylist'>
<p ></p>
{this.state.list.map((item, index) => {
return (
<li key={item.id}>
<span>{item.name}</span>
<span>
<button onClick={e => this.toTop(index, e)}>置顶</button>
</span>
</li>
);
})}
</ul>
<hr />
</main>
</div>
);
}
}
export default class App extends Component {
render() {
return (
<div >
<div style={{height: '20px'}}>hello</div>
<List1 />
</div>
)
}
}