现在需要创建一个Block组件,需要根据scroll value来控制其显示的位置。

const MainScrollableArea = () => {
  const [position, setPosition] = useState(300);
  const onScroll = (e) => {
    // calculate position based on the scrolled value 
    const calculated = getPosition(e.target.scrollTop); 
    // save it to state
    setPosition(calculated);
  };

  return (
    <div className="scrollable-block" onScroll={onScroll}>
      {/* pass position value to the new movable component */}
      <MovingBlock position={position} />
      <VerySlowComponent />
      <BunchOfStuff />
      <OtherStuffAlsoComplicated />
    </div>
  );

根据 React Rerender-Move State Down模式我们了解到这会导致整个MainScrollableArea都会重新渲染。

于是我们可以将不依赖于position的组件提取出来。

const ScrollableWithMovingBlock = ({children}) => {
  const [position, setPosition] = useState(300);

  const onScroll = (e) => {
    const calculated = getPosition(e.target.scrollTop); 
    setPosition(calculated);
  };

  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      {/* slow bunch of stuff used to be here, but not anymore */}
      {children}
    </div>
  );
};
const App = () => { 
  return (
    <ScrollableWithMovingBlock>
      <VerySlowComponent />
      <BunchOfStuff />
      <OtherStuffAlsoComplicated />
    </ScrollableWithMovingBlock>
  );
};

这样VerySlowComponentBunchOfStuffOtherStuffAlsoComplicated这些组件就不会重新渲染了。有人可能会疑惑,为什么这样就不会重新渲染了呢?VerySlowComponentBunchOfStuffOtherStuffAlsoComplicated这些组件仍然在ScrollableWithMovingBlock中,position发生改变了,为啥它们不会重新渲染呢?

这就需要了解Rerender的机制了。Rerender的时候,React会重新执行创建组件的函数,然后通过Object.is来判断是否有变化来判断是否重新创建。以上面的ScrollableWithMovingBlock为例,看看Rerender的过程。

const ScrollableWithMovingBlock = ({children}) => {
  const [position, setPosition] = useState(300);

  const onScroll = (e) => {
    const calculated = getPosition(e.target.scrollTop); 
    setPosition(calculated);
  };

  return (
    <div className="scrollable-block" onScroll={onScroll}>
      <MovingBlock position={position} />
      {/* slow bunch of stuff used to be here, but not anymore */}
      {children}
    </div>
  );
};

position发生变化的时候,会重新执行ScrollableWithMovingBlock函数,判断其返回值中所有的Object是否有变化。MovingBlock是在本地创建的,所以每次都是新创建的,Object.is判断为false。所有MovingBlock会重新创建。对于children由于是外部创建的,所以Object.is判断为true,children不会重新渲染。