gpt4 book ai didi

reactjs - 为什么当 props 没有改变时 React 组件会重新渲染?

转载 作者:行者123 更新时间:2023-12-03 23:48:07 28 4
gpt4 key购买 nike

我已经在 ReactJS 16.8.5 和 React-Redux 3.7.2 上构建了一个应用程序。当应用加载应用挂载时,会设置初始存储并针对 Firebase 实时数据库设置数据库订阅。该应用程序包含侧边栏、标题和内容部分。通过使用 React Developer Tools 分析应用程序,我可以看到 Sidebar被渲染多次 - 触发子组件的重新渲染。我已实现 React.memo避免在 Prop 改变时重新渲染。
从我可以看到的 Prop 没有改变,但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重新渲染 八次 Prop 什么时候没变?需要重新渲染一次吗?

亲切的问候/K

最佳答案

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

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

您的 mapStateToProps 必须在第 2 层返回一个新引用才能使用相同的值重新渲染,因此 {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>

关于reactjs - 为什么当 props 没有改变时 React 组件会重新渲染?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61301937/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com