React Redux - могу ли я сделать mapStateToProps принимать только часть состояния?
Я хочу сделать повторно используемые модули, которые могут быть подключены к любому приложению response-redux. В идеале, мой модуль будет иметь компонент контейнера, действия и редуктор на верхнем уровне (а затем любые презентационные компоненты под контейнером). Я бы хотел, чтобы модуль работал только с собственного фрагмента состояния приложения и в идеале не должен был ничего знать о остальной части состояния приложения (поэтому он действительно модульный).
Редукторы работают только от части состояния (используя combReducers), поэтому я счастлив там. Однако, с компонентами контейнера, похоже, что mapStateToProps всегда принимает полное состояние приложения.
Мне бы хотелось, чтобы mapStateToProps принимал только тот "срез состояния", который я обрабатываю в своем модуле (например, редуктор). Таким образом, мой модуль будет действительно модульным. Это возможно? Думаю, я мог бы просто передать этот срез состояния, чтобы быть реквизитом этого компонента (так что я мог бы просто использовать второй аргумент mapStateToProps, ownProps), но не уверен, что это будет иметь тот же эффект.
Ответы
Ответ 1
Это на самом деле нечто сложное. Поскольку Redux является одним глобальным хранилищем, идея полностью инкапсулированного, полностью многоразового логического набора логики становится довольно сложной. В частности, хотя логика редуктора может быть довольно общей и не осведомленной о том, где она живет, функции селектора должны знать, где в дереве найти эти данные.
Конкретный ответ на ваш вопрос: "нет, mapState
всегда дается полное дерево состояний".
У меня есть ссылки на ряд релевантных ресурсов, которые могут помочь в вашей ситуации:
Ответ 2
В Redux есть только один магазин, как вам известно, поэтому все, что он знает, - это передать весь магазин в функцию mapStateToProps. Однако, используя деструктурирование объектов, вы можете указать, какие свойства в нужном вами хранилище и игнорировать остальные. Что-то вроде "function mapStateToProps ({prop1, prop2})" будет захватывать только эти два свойства в хранилище и игнорировать остальные. Ваша функция все еще получает весь магазин, но вы указываете, что вас интересуют только эти реквизиты.
В моем примере "prop1" и "prop2" будут именами, которые вы назначили своим редукторам во время вызова "combReducers".
Ответ 3
В идеале, как это работает, вы получаете состояние и извлекаете из них значения с помощью деконструкторов. redux работает над концепцией единого состояния
Например: -
function mapStateToProps(state){
const { auth } = state //just taking a auth as example.
return{
auth
}
}
Ответ 4
Я столкнулся с той же проблемой, потому что, как вы сказали, текущая реализация redux/react-redux позволяет разделить редукторы на состояние просто отлично, но mapDispatchToProps всегда передает все дерево состояний.
fooobar.com/questions/841878/... - это не то, что я хочу, потому что это означает, что мы должны дублировать всю нашу селекторную логику в каждом приложении response-redux, использующем наш модуль.
Мое текущее обходное решение состояло в том, чтобы передать срез состояния вниз в качестве опоры. Это следует за своего рода композиционным рисунком, но в то же время устраняет чистоту доступа к государству напрямую, что я разочарован.
Пример:
Как правило, вы хотите сделать это:
const mapStateToProps = (state) => {
return {
items: mySelector(state)
}
}
const mapDispatchToProps = (dispatch) => {
return {
doStuff: (item) => {
dispatch(doStuff(item))
}
}
}
class ModularComponent extends React.Component {
render() {
return (
<div>
{ this.props.items.map((item) => {
<h1 onclick={ () => this.props.doStuff(item) }>{item.title}</h1>
})}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ModularComponent)
но поскольку этот модуль включен в приложение, в котором состояние теперь имеет несколько вещей (то есть ключевые значения), а не список элементов, это не сработает. Мое обходное решение выглядит следующим образом:
const mapStateToProps = (_, ownProps) => {
return {
items: mySelector(ownProps.items)
}
}
const mapDispatchToProps = (dispatch) => {
return {
doStuff: (item) => {
dispatch(doStuff(item))
}
}
}
class ModularComponent extends React.Component {
render() {
return (
<div>
{ this.props.items.map((item) => {
<h1 onclick={ () => this.props.doStuff(item) }>{item.title}</h1>
})}
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ModularComponent)
И приложение, использующее модуль, выглядит так:
const mapStateToProps = (state) => {
return {
items: state.items
stuffForAnotherModule: state.otherStuff
}
}
class Application extends React.Component {
render() {
return (
<div>
<ModularComponent items={ this.props.items } />
<OtherComponent stuff={ this.props.stuffForAnotherModule } />
</div>
)
}
}
export default connect(mapStateToProps)(Application)
Ответ 5
Хотя mapStateToProps
(первая функция, которую вы передаете для подключения), передается всем магазином, как вы сказали, его задачей является сопоставление определенных частей состояния с компонентом. Таким образом, только то, что возвращается из mapStateToProps, будет отображаться как ссылка на ваш компонент.
Итак, скажем, ваше состояние выглядит так:
{
account: {
username: "Jane Doe",
email: "[email protected]",
password: "12345",
....
},
someOtherStuff: {
foo: 'bar',
foo2: 'bar2'
},
yetMoreStuff: {
usuless: true,
notNeeded: true
}
}
а вашему компоненту требуется все: от account
и foo
от someOtherStuff
, тогда ваш mapStateToProps
будет выглядеть следующим образом:
const mapStateToProps = ({ account, someOtherStuff }) => ({
account,
foo: { someOtherStuff }
});
export default connect(mapStateToProps)(ComponentName)
то ваш компонент будет иметь prop account
и foo
, отображаемые из вашего состояния редукции.
Ответ 6
У вас есть возможность написать пару утилит-оболочек для ваших модулей, которые будут выполнять следующие действия: 1) запускать mapStateToProps только тогда, когда срез состояния модуля изменяется, и 2) передавать срез модуля только в mapStateToProps.
Все это предполагает, что фрагменты состояния вашего модуля являются корневыми свойствами объекта состояния приложения (например, state.module1
, state.module2
).
- Пользовательская
areStatesEqual
обертка areStatesEqual
, обеспечивающая выполнение mapStateToProps
, только если изменяется mapStateToProps
модуля:
function areSubstatesEqual(substateName) {
return function areSubstatesEqual(next, prev) {
return next[substateName] === prev[substateName];
};
}
Затем передайте его в connect
:
connect(mapStateToProps, mapConnectToProps, null, {
areStatesEqual: areSubstatesEqual('myModuleSubstateName')
})(MyModuleComponent);
- Пользовательская оболочка
mapStateToProps
которая передается только в подсостояние модуля:
function mapSubstateToProps(substateName, mapStateToProps) {
var numArgs = mapStateToProps.length;
if (numArgs !== 1) {
return function(state, ownProps) {
return mapStateToProps(state[substateName], ownProps);
};
}
return function(state) {
return mapStateToProps(state[substateName]);
};
}
И вы бы использовали это так:
function myComponentMapStateToProps(state) {
// Transform state
return props;
}
var mapSubstate = mapSubstateToProps('myModuleSubstateName', myComponentMapStateToProps);
connect(mapSubstate, mapDispatchToState, null, {
areStatesEqual: areSubstatesEqual('myModuleSubstateName')
})(MyModuleComponent);
Не проверенный, последний пример должен запускать myComponentMapStateToProps
только при изменении состояния "myModuleSubstateName", и он будет получать только подсостояние модуля.
Еще одним усовершенствованием может быть написание собственной функции connect
основе модулей, которая принимает один дополнительный параметр moduleName
:
function moduleConnect(moduleName, mapStateToProps, mapDispatchToProps, mergeProps, options) {
var _mapState = mapSubstateToProps(moduleName, mapStateToProps);
var _options = Object.assign({}, options, {
areStatesEqual: areSubstatesEqual('myModuleSubstateName')
});
return connect(_mapState, mapDispatchToProps, mergeProps, _options);
}
Тогда каждый компонент модуля должен был бы просто сделать:
moduleConnect('myModuleName', myMapStateToProps)(MyModuleComponent);
Ответ 7
Ответ на ваш вопрос - да. Оба эти ответы охватывают разные аспекты одной и той же вещи. Во-первых, Redux создает единый магазин с несколькими редукторами. Поэтому вы захотите объединить их так:
export default combineReducers({
people: peopleReducer,
departments: departmentsReducer,
auth: authenticationReducer
});
Затем, скажем, у вас есть компонент DepartmentsList, вам может просто нужно отобразить departments
из хранилища в ваш компонент (и, возможно, некоторые действия сопоставлены с реквизитами):
function mapStateToProps(state) {
return { departments: state.departments.departmentsList };
}
export default connect(mapStateToProps, { fetchDepartments: fetchDepartments })(DepartmentsListComponent);
Тогда внутри вашего компонента это в основном:
this.props.departments
this.props.fetchDepartments()