【react】列表置顶操作导致滚动问题的分析和解决,react dom-diff右移策略,overflowAnchor: ‘none‘遇到滚动锚定问题并且需要关闭行为

需求分析

消息列表里面会有一些内容需要置顶,但是点击置顶后,列表直接滚到顶部不利于消息的批量操作。

复现效果

在这里插入图片描述

原因分析

区别参考于这篇文章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>
        )
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值