Как прокомментировано; когда mapStateToProps возвращает новый объект, он будет визуализировать подключенный компонент, даже если соответствующие значения не изменятся.
Это потому, что {} !== {}
, объект с такими же параметрами и значениями не равен другому объекту с такими же параметрами и значениями. потому что 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 (
rendered:{rendered.current} times
props:{JSON.stringify(props)}
); }; const selectVal = (state) => state.val; const selectMapStateToProps = createSelector (selectVal, // создаст этот объект заново только при изменении val (val) => console.log ('val updated) || {mem: {val}}); const memoizedMapStateToProps = selectMapStateToProps; const mapStateToProps = ({val}) => ({nonMem: {val}}); // каждый раз заново создаем props.nonMem const MemoizedConnected = connect (memoizedMapStateToProps) (Component); // этот mapStateToProps создаст реквизиты из {val: 1} // чистые компоненты (возвращаемые connect) будут сравнивать каждое свойство // объекта реквизита, а не реквизит в целом. Поскольку props.val // никогда не менялся между рендерами, он не рендерится const OneLevelConnect = connect (({val}) => ({val})) (Component); const Connected = connect (mapStateToProps) (Компонент); const Pure = memo (function Pure () {// реквизиты никогда не меняются, поэтому они будут отображаться только один раз: console.log («реквизиты никогда не меняются, поэтому не рендерится Pure»); return (
); }); const App = () => {const dispatch = useDispatch (); useEffect (// отправлять действие каждую секунду, это создаст новое // состояние ref, но state.val никогда не изменится () => {setInterval (() => dispatch ({type: 88}), 1000);}, [рассылка] // рассылка никогда не меняется, но инструменты для раскрашивания этого не знают); возвращение ; }; ReactDOM.render ( , 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 (
rendered:{rendered.current} times
props:{JSON.stringify(props)}
); }; // оптимизированный 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 (// отправлять действие каждую секунду, это создаст новое // состояние ref, но state.val никогда не изменится () => {setInterval (() => dispatch ({type: 88}), 1000);}, [рассылка] // рассылка никогда не меняется, но инструменты для раскрашивания этого не знают); return (
приложение отображено {rendered.current} раз
Подключенное лицо (будет отображаться повторно)
Оптимизированный Связанный человек (не будет визуализироваться)
); }; ReactDOM.render ( , 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>