为什么React组件在道具没有改变的情况下重新绘制



我在ReactJS 16.8.5和React Redux 3.7.2上构建了一个应用程序。当应用程序加载应用程序装载时,将设置初始存储,并根据Firebase实时数据库设置数据库订阅。该应用程序包含侧边栏、标题和内容部分。通过使用React Developer Tools对应用程序进行分析,我可以看到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

为什么Sidebar在道具没有改变的情况下重新渲染八次?预计会有一个重读者吗?

问候/K

如有评论;当mapStateToProps返回一个新对象时,即使没有相关值更改,它也会重新渲染连接的组件。

这是因为{} !== {},一个具有相同道具和值的对象不等于另一个具有同样道具和值,因为React比较的是对象引用,而不是对象的值。这就是为什么你不能通过改变它来改变状态。改变会改变对象中的值,但不会改变对对象的引用。

您的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函数也可以通过传递返回函数的函数来进行更多优化。通过这种方式,您可以在组件安装时创建一个内存化选择器。这可以在列表项中使用(请参阅下面的代码(。

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>

相关内容

最新更新