簡體   English   中英

為什么在 props 沒有改變的情況下 React 組件會重新渲染?

[英]Why is React component rerendering when props has not changed?

我在 ReactJS 16.8.5 和 React-Redux 3.7.2 上構建了一個應用程序。 當應用程序加載應用程序裝載時,會設置初始存儲並針對 Firebase 實時數據庫設置數據庫訂閱。 該應用程序包含一個側邊欄 header 和內容部分。 通過使用 React 開發者工具分析應用程序,我可以看到Sidebar被渲染了好幾次——觸發了子組件的重新渲染。 我已經實現了 React.memo以避免在道具更改時重新渲染。 從我可以看到的道具沒有改變,但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 />
          </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('Profile', phase, actualDuration)
  }
  return (
    <Profiler id="Sidebar" onRender={onRender}>
      <React.Fragment>
        {/* Contents of Sidebar */}
      </React.Fragment>
    </Profiler>
}

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

Console output

Sidebar mapStateToProps 2 
Profile Sidebar mount 225 
Sidebar mapStateToProps 
Profile Sidebar isEqual true 
Sidebar mapStateToProps 
Profile Sidebar update 123 
Sidebar mapStateToProps 2 
Profile Sidebar update 21 
Sidebar mapStateToProps 
Profile Sidebar update 126 
Sidebar mapStateToProps 
Profile Sidebar update 166 
Sidebar mapStateToProps 
Profile Sidebar update 99 
Sidebar mapStateToProps 
Sidebar mapStateToProps 
Sidebar mapStateToProps 
Sidebar mapStateToProps
Sidebar mapStateToProps 
Sidebar mapStateToProps 
Profile Sidebar update 110 
Sidebar mapStateToProps 
Sidebar mapStateToProps 
Sidebar mapStateToProps 
Profile Sidebar update 4

為什么在 props 沒有變化的情況下Sidebar會重新渲染8 次 預計會重新渲染一次?

親切的問候/K

正如評論; 當 mapStateToProps 返回一個新的 object 時,即使相關值沒有變化,它也會重新渲染連接的組件。

This is because {} !== {} , an object with same props and values does not equal another object with same props and values because React compares object reference and not the values of the object. 這就是為什么你不能通過改變 state 來改變它。 變異會更改 object 中的值,但不會更改對 object 的引用。

您的 mapStateToProps 必須在第二級返回一個新的引用,以便它使用相同的值重新渲染,因此{val:1}不會重新渲染,但{something:{val:1}}會。

下面的代碼顯示了不記憶 mapStateToProps 的結果如何導致重新渲染:

 const { Provider, connect, useDispatch } = ReactRedux; const { createStore } = Redux; const { createSelector } = Reselect; const { useRef, useEffect, memo } = React; const state = { val: 1 }; //returning a new state every action but no values // have been changed const reducer = () => ({...state }); const store = createStore( reducer, {...state }, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); const Component = (props) => { const rendered = useRef(0); rendered.current++; return ( <div> <div>rendered:{rendered.current} times</div> props:<pre>{JSON.stringify(props)}</pre> </div> ); }; const selectVal = (state) => state.val; const selectMapStateToProps = createSelector( selectVal, //will only re create this object when val changes (val) => console.log('val changed') || { mem: { val } } ); const memoizedMapStateToProps = selectMapStateToProps; const mapStateToProps = ({ val }) => ({ nonMem: { val } }); //re creates props.nonMem every time const MemoizedConnected = connect(memoizedMapStateToProps)( Component ); //this mapStateToProps will create a props of {val:1} // pure components (returned by connect) will compare each property // of the prop object and not the props as a whole. Since props.val // never changed between renders it won't re render const OneLevelConnect = connect(({ val }) => ({ val }))( Component ); const Connected = connect(mapStateToProps)(Component); const Pure = memo(function Pure() { //props never change so this will only be rendered once console.log('props never change so wont re render Pure'); return ( <div> <Connected /> <MemoizedConnected /> <OneLevelConnect /> </div> ); }); const App = () => { const dispatch = useDispatch(); useEffect( //dispatch an action every second, this will create a new // state ref but state.val never changes () => { setInterval(() => dispatch({ type: 88 }), 1000); }, [dispatch] //dispatch never changes but linting tools don't know that ); return <Pure />; }; 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>

mapStateToProps function 也可以通過傳遞返回 function 的 function 進行更多優化。 這樣,您可以在組件掛載時創建一個記憶選擇器。 這可以在列表項中使用(參見下面的代碼)。

 const { useRef, useEffect } = React; const { Provider, useDispatch, useSelector, connect, } = ReactRedux; const { createStore } = Redux; const { createSelector } = Reselect; const state = { data: [ { id: 1, firstName: 'Ben', lastName: 'Token', }, { id: 2, firstName: 'Susan', lastName: 'Smith', }, ], }; //returning a new state every action but no values // have been changed const reducer = () => ({...state }); const store = createStore( reducer, {...state }, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__() ); //selectors const selectData = (state) => state.data; const selectPerson = createSelector( selectData, (_, id) => id, //pass second argument to select person by id (people, _id) => people.find(({ id }) => id === _id) ); //function that will create props for person component // from person out of state const asPersonProps = (person) => ({ person: { fullName: person.firstName + ' ' + person.lastName, }, }); //in ConnectedPerson all components share this selector const selectPersonProps = createSelector( (state, { id }) => selectPerson(state, id), asPersonProps ); //in OptimizedConnectedPerson each component has it's own // selector const createSelectPersonProps = () => createSelector( (state, { id }) => selectPerson(state, id), asPersonProps ); const Person = (props) => { const rendered = useRef(0); rendered.current++; return ( <li> <div>rendered:{rendered.current} times</div> props:<pre>{JSON.stringify(props)}</pre> </li> ); }; //optimized mapStateToProps const mapPersonStateToProps = createSelectPersonProps; const OptimizedConnectedPerson = connect( mapPersonStateToProps )(Person); const ConnectedPerson = connect(selectPersonProps)(Person); const App = () => { const dispatch = useDispatch(); const people = useSelector(selectData); const rendered = useRef(0); rendered.current++; useEffect( //dispatch an action every second, this will create a new // state ref but state.val never changes () => { setInterval(() => dispatch({ type: 88 }), 1000); }, [dispatch] //dispatch never changes but linting tools don't know that ); return ( <div> <h2>app rendered {rendered.current} times</h2> <h3>Connected person (will re render)</h3> <ul> {people.map(({ id }) => ( <ConnectedPerson key={id} id={id} /> ))} </ul> <h3> Optimized Connected person (will not re render) </h3> <ul> {people.map(({ id }) => ( <OptimizedConnectedPerson key={id} id={id} /> ))} </ul> </div> ); }; 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