Window Resize - React + Redux

2020-02-17 03:46发布

问题:

I'm new to Redux and I'm wondering if anyone has some tips on best practices for handling non React events like window resize. In my research, I found this link from the official React documentation: https://facebook.github.io/react/tips/dom-event-listeners.html

My questions is, when using Redux, should I store the window size in my Store or should I be keeping it in my individual component state?

回答1:

Good question. I like to to have a ui part to my store. The reducer for which might look like this:

const initialState = {
    screenWidth: typeof window === 'object' ? window.innerWidth : null
};

function uiReducer(state = initialState, action) {
    switch (action.type) {
        case SCREEN_RESIZE:
            return Object.assign({}, state, {
                screenWidth: action.screenWidth
            });
    }
    return state;
}

The action for which is pretty boilerplate. (SCREEN_RESIZE being a constant string.)

function screenResize(width) {
    return {
        type: SCREEN_RESIZE,
        screenWidth: width
    };
}

Finally you wire it together with an event listener. I would put the following code in the place where you initialise your store variable.

window.addEventListener('resize', () => {
    store.dispatch(screenResize(window.innerWidth));
});

Media Queries

If your app takes a more binary view of screen size (e.g. large/small), you might prefer to use a media query instead. e.g.

const mediaQuery = window.matchMedia('(min-width: 650px)');

if (mediaQuery.matches) {
    store.dispatch(setLargeScreen());
} else {
    store.dispatch(setSmallScreen());
}

mediaQuery.addListener((mq) => {
    if (mq.matches) {
        store.dispatch(setLargeScreen());
    } else {
        store.dispatch(setSmallScreen());
    }
});

(I'll leave out the action and reducer code this time. It's fairly obvious what they look like.)

One drawback of this approach is that the store may be initialised with the wrong value, and we're relying on the media query to set the correct value after the store has been initialised. Short of shoving the media query into the reducer file itself, I don't know the best way around this. Feedback welcome.

UPDATE

Now that I think about it, you can probably get around this by doing something like the following. (But beware, I have not tested this.)

const mediaQuery = window.matchMedia('(min-width: 650px)');

const store = createStore(reducer, {
    ui: {
        largeScreen: mediaQuery.matches
    }
});

mediaQuery.addListener((mq) => {
    if (mq.matches) {
        store.dispatch(setLargeScreen());
    } else {
        store.dispatch(setSmallScreen());
    }
});

UPDATE II: The drawback of this last approach is that the ui object will replace the entire ui state not just the largeScreen field. Whatever else there is of the initial ui state gets lost.



回答2:

Use redux-responsive to handle the responsive state of your application. It uses a store enhancer to manage a dedicated area(property) of your store's state (normally called 'browser') via its own reducer, so that you don't have to implicitly add event listeners to the document object.

All you need to do is to map the browser.width, browser.height, etc. to your component's props. Please note that only the reducer defined in redux-responsive is responsible for updating these values.



回答3:

I have a similar case where I need the window size for purposes other than responsiveness. According to this, you could also use redux-thunk:

function listenToWindowEvent(name, mapEventToAction, filter = (e) => true) {
  return function (dispatch) {
    function handleEvent(e) {
      if (filter(e)) {
        dispatch(mapEventToAction(e));
      }
    }

    window.addEventListener(name, handleEvent);

    // note: returns a function to unsubscribe
    return () => window.removeEventListener(name, handleEvent);
  };
}

// turns DOM event into action,
// you can define many of those
function globalKeyPress(e) {
  return {
    type: 'GLOBAL_KEY_PRESS',
    key: e.key
  };
}

// subscribe to event
let unlistenKeyPress = store.dispatch(listenToWindowEvent('keypress', globalKeyPress));
// eventually unsubscribe
unlistenKeyPress();

Although in reality, if your use case is a simple one you don't even need to use a thunk function. Simply create a listener function that takes Redux dispatch as a parameter and use it to dispatch desired action. See the reference for an example. But the currently accepted answer pretty much covers this case