Интересно, имеет ли смысл иметь компонент-контейнер redux для каждого элемента списка?
Помимо, возможно, лучшей производительности, есть также лучшее повторное использование кода. Если logi c того, что элемент является определенным в списке, то как вы можете повторно использовать список для визуализации других элементов? item будет воссоздан для всех элементов, если вы создадите реквизиты в List вместо Item.
Список также нельзя использовать в качестве общего списка, который передает идентификатор в компонент Item.
const { Provider, useDispatch, useSelector } = ReactRedux;
const { createStore, applyMiddleware, compose } = Redux;
const { useMemo } = React;
const { createSelector } = Reselect;
const { produce } = immer;
const initialState = {
people: {
data: {
1: { id: 1, name: 'Jon' },
2: { id: 2, name: 'Marie' },
},
edit: {},
},
places: {
data: {
1: { id: 1, name: 'Rome' },
2: { id: 2, name: 'Paris' },
},
edit: {},
},
};
//action types
const SET_EDIT = 'SET_EDIT';
const CANCEL_EDIT = 'CANCEL_EDIT';
const SAVE = 'SAVE';
const CHANGE_TEXT = 'CHANGE_TEXT';
//action creators
const setEdit = (dataType, id) => ({
type: SET_EDIT,
payload: { dataType, id },
});
const cancelEdit = (dataType, id) => ({
type: CANCEL_EDIT,
payload: { dataType, id },
});
const save = (dataType, item) => ({
type: SAVE,
payload: { dataType, item },
});
const changeText = (dataType, id, field, value) => ({
type: CHANGE_TEXT,
payload: { dataType, id, field, value },
});
const reducer = (state, { type, payload }) => {
if (type === SET_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id] = draft[dataType].data[id];
});
}
if (type === CANCEL_EDIT) {
const { dataType, id } = payload;
return produce(state, (draft) => {
delete draft[dataType].edit[id];
});
}
if (type === CHANGE_TEXT) {
const { dataType, id, field, value } = payload;
return produce(state, (draft) => {
draft[dataType].edit[id][field] = value;
});
}
if (type === SAVE) {
const { dataType, item } = payload;
return produce(state, (draft) => {
const newItem = { ...item };
delete newItem.edit;
draft[dataType].data[item.id] = newItem;
delete draft[dataType].edit[item.id];
});
}
return state;
};
//selectors
const createSelectData = (dataType) => (state) =>
state[dataType];
const createSelectDataList = (dataType) =>
createSelector([createSelectData(dataType)], (result) =>
Object.values(result.data)
);
const createSelectDataById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => dataResult.data[itemId]
);
const createSelectEditById = (dataType, itemId) =>
createSelector(
[createSelectData(dataType)],
(dataResult) => (dataResult.edit || {})[itemId]
);
const createSelectItemById = (dataType, itemId) =>
createSelector(
[
createSelectDataById(dataType, itemId),
createSelectEditById(dataType, itemId),
],
(item, edit) => ({
...item,
...edit,
edit: Boolean(edit),
})
);
//creating store with redux dev tools
const composeEnhancers =
window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
reducer,
initialState,
composeEnhancers(
applyMiddleware(() => (next) => (action) =>
next(action)
)
)
);
const Item = ({ item, dataType }) => {
const dispatch = useDispatch();
return (
<li>
{item.edit ? (
<React.Fragment>
<input
type="text"
value={item.name}
onChange={(e) =>
dispatch(
changeText(
dataType,
item.id,
'name',
e.target.value
)
)
}
/>
<button
onClick={() =>
dispatch(cancelEdit(dataType, item.id))
}
>
cancel
</button>
<button
onClick={() => dispatch(save(dataType, item))}
>
save
</button>
</React.Fragment>
) : (
<React.Fragment>
{item.name}
<button
onClick={() =>
dispatch(setEdit(dataType, item.id))
}
>
edit
</button>
</React.Fragment>
)}
</li>
);
};
const createItem = (dataType) =>
React.memo(function ItemContainer({ id }) {
const selectItem = useMemo(
() => createSelectItemById(dataType, id),
[id]
);
const item = useSelector(selectItem);
return <Item item={item} dataType={dataType} />;
});
const Person = createItem('people');
const Location = createItem('places');
const List = React.memo(function List({ items, Item }) {
return (
<ul>
{items.map(({ id }) => (
<Item key={id} id={id} />
))}
</ul>
);
});
const App = () => {
const [selectPeople, selectPlaces] = useMemo(
() => [
createSelectDataList('people'),
createSelectDataList('places'),
],
[]
);
const people = useSelector(selectPeople);
const places = useSelector(selectPlaces);
return (
<div>
<List items={people} Item={Person} />
<List items={places} Item={Location} />
</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>
<script src="https://unpkg.com/immer@7.0.5/dist/immer.umd.production.min.js"></script>
<div id="root"></div>
Если ваше приложение имеет повторяющиеся logi c, вы можете подумать о разделении компонента на контейнер и презентацию (контейнер также называется подключенным компонентом в redux) . Вы можете повторно использовать контейнер, но изменить его представление.