簡體   English   中英

使用 react-redux 記憶功能組件,重新選擇和 React.memo()

[英]Memoize functional component using react-redux, reselect and React.memo()

我在 ReactJS 16.8.5 和 React-Redux 3.7.2 上構建了一個應用程序。 當應用程序加載應用程序裝載時,會設置初始存儲並針對 Firebase 實時數據庫設置數據庫訂閱。 該應用程序包含 header、 Sidebar和內容部分。
我已經與React.memo一起實現了reselect以避免在 props 更改時重新渲染,但Sidebar組件仍在重新渲染。 在 React.memo 中使用React 分析器 APIareEqual比較 function 我可以看到Sidebar被渲染了幾次,盡管道具是相等的。

app.js

//Imports etc...
const jsx = (
  <React.StrictMode>
    <Provider store={store}>
      <AppRouter />
    </Provider>
  </React.StrictMode>
)

let hasRendered = false
const renderApp = () => {
  if (!hasRendered) { //make sure app only renders one time
    ReactDOM.render(jsx, document.getElementById('app'))
    hasRendered = true
  }
}

firebase.auth().onAuthStateChanged((user) => {
  if (user) {
    // Set initial store and db subscriptions
    renderApp()
  }
})

AppRouter.js

//Imports etc...
const AppRouter = ({}) => {
  //...
  return (
    <React.Fragment>
      //uses Router instead of BrowserRouter to use our own history and not the built in one
      <Router history={history}>    
        <div className="myApp">
          <Route path="">
            <Sidebar ...props />
          </Route>
          //More routes here...
        </div>
      </Router>
    </React.Fragment>
  )
}
//...
export default connect(mapStateToProps, mapDispatchToProps)(AppRouter)

Sidebar.js

//Imports etc...
export const Sidebar = (props) => {
  const onRender = (id, phase, actualDuration, baseDuration, startTime, commitTime) => {
    if (id !== 'Sidebar') { return }
    console.log('onRender', phase, actualDuration)
  }
  return (
    <Profiler id="Sidebar" onRender={onRender}>
      <React.Fragment>
        {/* Contents of Sidebar */}
      </React.Fragment>
    </Profiler>
}

const getLang = state => (state.usersettings) ? state.usersettings.language : 'en'
const getMediaSize = state => (state.route) ? state.route.mediaSize : 'large'
const getNavigation = state => state.navigation
const getMyLang = createSelector(
  [getLang], (lang) => console.log('Sidebar lang val changed') || lang
)
const getMyMediaSize = createSelector(
  [getMediaSize], (mediaSize) => console.log('Sidebar mediaSize val changed') || mediaSize
)
const getMyNavigation = createSelector(
  [getNavigation], (navigation) => console.log('Sidebar navigation val changed') || navigation
)
const mapStateToPropsMemoized = (state) => {
  return {
    lang: getMyLang(state),
    mediaSize: getMyMediaSize(state),
    navigation: getMyNavigation(state)
  }
}

const areEqual = (prevProps, nextProps) => {
  const areStatesEqual = _.isEqual(prevProps, nextProps)
  console.log('Sidebar areStatesEqual', areStatesEqual)
  return areStatesEqual
}
export default React.memo(connect(mapStateToPropsMemoized, mapDispatchToProps)(Sidebar),areEqual)

初始渲染看起來沒問題,直到Sidebar navigation val changed - 之后組件重新渲染了很多次 - 為什么?

Console output - initial render

onRender Sidebar mount 572 
Sidebar mediaSize val changed 
Profile Sidebar areEqual true 
Sidebar navigation val changed 
onRender Sidebar update 153 
Sidebar navigation val changed 
onRender Sidebar update 142 
onRender Sidebar update 103 
onRender Sidebar update 49 
onRender Sidebar update 5 
onRender Sidebar update 2 
onRender Sidebar update 12 
onRender Sidebar update 3 
onRender Sidebar update 2 
onRender Sidebar update 58 
onRender Sidebar update 2 
onRender Sidebar update 4 
onRender Sidebar update 5 
onRender Sidebar update 4

隨后的渲染不會影響 store 中映射到 props(位置)的任何部分,但組件仍在重新渲染。

Console output - subsequent render

Profile Sidebar areEqual true
onRender Sidebar update 76
onRender Sidebar update 4

我希望Sidebar被記憶,並且在初始加載期間掛載/更新存儲期間僅渲染/重新渲染幾次。

為什么Sidebar組件會被渲染這么多次?

親切的問候/K

不需要 React.memo,因為 react-redux connect 將返回一個純組件,僅當您更改傳遞的道具或在調度的操作導致 state 發生任何更改后才會重新渲染。

您的mapStateToPropsMemoized應該可以工作(請參閱更新),但最好這樣寫:

const mapStateToPropsMemoized = createSelector(
  getMyLang,
  getMyMediaSize,
  getMyNavigation,
  (lang, mediaSize, navigation) => ({
    lang,
    mediaSize,
    navigation,
  })
);
//using react.redux connect will return a pure component and passing that
//  to React.memo should cause an error because connect does not return a
//  functional component.
export default connect(
  mapStateToPropsMemoized,
  mapDispatchToProps
)(Sidebar);

更新

您的 getState 應該可以工作。

我無法使用您的代碼重現組件重新渲染。 從 mapState 返回的 object 每次都是一個新的 object 但它的直接屬性永遠不會改變,因為選擇器總是返回記憶的結果。 請參閱下面的示例

 const { useRef, useEffect } = React; const { Provider, useDispatch, connect, useSelector, } = ReactRedux; const { createStore } = Redux; const { createSelector } = Reselect; const state = { someValue: 2, unrelatedCounter: 0 }; //returning a new state every action someValue // never changes, only counter const reducer = (state) => ({...state, unrelatedCounter: state.unrelatedCounter + 1, }); const store = createStore( reducer, {...state }, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); //selectors const selectSomeValue = (state) => state.someValue; //selectors only return a new object if someValue changes const selectA = createSelector( [selectSomeValue], () => ({ value: 'A' }) //returns new object if some value changes ); const selectB = createSelector( [selectSomeValue], () => ({ vale: 'B' }) //returns new object if some value changes ); const selectC = createSelector( [selectSomeValue], () => ({ vale: 'C' }) //returns new object if some value changes ); const Counter = () => { const counter = useSelector( (state) => state.unrelatedCounter ); return <h4>Counter: {counter}</h4>; }; const AppComponent = (props) => { const dispatch = useDispatch(); const r = useRef(0); //because state.someValue never changes this component // never gets re rendered r.current++; useEffect( //dispatch an action every second, this will create a new // state but state.someValue never changes () => { setInterval(() => dispatch({ type: 88 }), 1000); }, [dispatch] //dispatch never changes but linting tools don't know that ); return ( <div> <h1>Rendered {r.current} times</h1> <Counter /> <pre>{JSON.stringify(props, undefined, 2)}</pre> </div> ); }; const mapStateToProps = (state) => { return { A: selectA(state), B: selectB(state), C: selectC(state), }; }; const App = connect(mapStateToProps)(AppComponent); ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/redux/4.0.5/redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.0/react-redux.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/reselect/4.0.0/reselect.min.js"></script> <div id="root"></div>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM