防止使用useContext挂钩设置组件的全局状态来重新渲染组件

时间:2020-01-05 20:57:04

标签: javascript react-native react-redux react-hooks react-context

我正在尝试实现一个MiniGlobalPlayer,它在整个应用程序中都是全局的,并且某些组件可以设置MiniGlobalPlayer的状态。我认为React Context将是useContext和useReducer Hook的一个很好的解决方案。但是,重新渲染组件会使我的应用变慢( 延迟与使用useContext(setGlobalVideo )安装在DOM上的VideoItem组件的数量成比例。)

https://beebom.com/youtubes-new-ui-trick-is-a-mini-player-bar-for-collapsed-video-playback/(Youtube的MiniGlobalPlayer)

完整方案:

MiniGlobalPlayer的状态由VideoItem组件上的onPress()确定, 它使用useContext钩子从setGlobalVideo上下文中获取方法setVideo,该方法将设置MiniGlobalPlayer的状态

const areEqual = (prevProps, nextProps) => true
 const VideoItem= React.memo((props)=> {
   const context = useContext(setGlobalVideo)
        return ( 
          <View style={[
            styles.flex, styles.column, styles.recommendation, styles.shadow, 
            {marginLeft: theme.sizes.margin },
          ]} key ={props.index}>
          <View style={[styles.flex, styles.recommendationHeader]}>
          <TouchableOpacity  onPress={()=>{context.setGlobalVideo(props.video)}}>
          <Image style={[styles.recommendationImage]} source={ {uri: props.video.Video_Pictures["0"]}} />
          </TouchableOpacity>
          <ExpensiveVideoRendering index={props.index} video={props.video}/>  
          </View>
        </View>
          );

  }, areEqual);

export default VideoItem;

MiniGlobalPlayerProvider存储全局视频状态,PlayerReducer可以根据分派的操作更改其状态。这些动作由VideoItem组件调度,该组件位于应用程序各个部分中,用于设置全局视频状态。

const MiniGlobalPlayerProvider=({children})=>{
  const initialState ={
    video: null, 
    isPlaying : false, 
    isBuffering: false
  }
  const [state, dispatch]= useReducer(PlayerReducer,initialState )
  const {video} =state
function togglePodcast(podcast)  {
    animation = new Value(0);
    timing(
      animation,
      {
        toValue: video ? 1 : 0,
        duration: 300,
        easing: Easing.inOut(Easing.ease),
      },
    ).start();
  };
  animation = new Value(0);
  const setVideo=(video)=>
  {
    dispatch(
      {
        type: SET_GLOBAL_VIDEO, 
        payload: video    
      }
    )
    toggleVideo(video)

  }  
    const translateY = animation.interpolate({
      inputRange: [0, 1],
      outputRange: [height, 0],
    });
    return (
      <setGlobalVideo.Provider value={{setVideo}}>  
      <PlayerContext.Provider value={video}>


        <StatusBar barStyle="dark-content" />
        <View style={styles.container}>
          <View style={StyleSheet.absoluteFill}>
            {children}
          </View>
          {

            !isOS && video && <VideoPlayer {...{video}} />
          }
        </View>
      </PlayerContext.Provider>
      </setGlobalVideo.Provider>  
    );
  }

PlayerReducer.js

export default PlayerReducer=(state, action )=>
{
    switch(action.type){
        case SET_GLOBAL_VIDEO:
            return {...state, video:action.payload}
        default: {
                throw new Error(`Unhandled action type: ${action.type}`)
              }

    }
}

我尝试减少不必要的重新渲染的解决方案: 1.更改提供者状态后,将重新渲染任何使用useContext的组件。因此,昂贵的计算将传递给内部组件,该内部组件不会使用 React.memo()重新呈现。 2. use useCallback(通过一个内联回调和一系列依赖项。useCallback将返回仅在其中一个依赖项发生更改时才会更改的回调的备注版本。当将回调传递给已优化的回调时,这很有用。依赖于引用相等性的子组件,以防止不必要的渲染(例如,shouldComponentUpdate)。

const setVideo=useCallback((video)=>
      {
        dispatch(
          {
            type: SET_GLOBAL_VIDEO, 
            payload: video    
          }
        )
        toggleVideo(video)

      }  )
  1. 单独的上下文(setGlobalVideo.Provider,
    PlayerContext.Provider)
  2. https://www.robinwieruch.de/react-prevent-rerender-component 5。https://github.com/facebook/react/issues/15156 6。https://github.com/facebook/react/issues/14110

我仍然无法阻止这些重新渲染。有人可以帮我吗?

0 个答案:

没有答案