В статье, перевод которой мы публикуем сегодня, речь пойдёт о том, как создавать в React-приложениях компоненты-контейнеры, которые связаны с состоянием Redux. Этот материал основан на описании механизма управления состоянием в React с применением пакета react-redux. Предполагается, что у вас уже есть базовое понимание архитектуры и API библиотек, о которых мы будем говорить. Если это не так — обратитесь к документации по React и Redux.
import { createStore } from 'redux';
const initialState = {
auth: { loggedIn: false }
}
const store = createStore((state = initialState, action) => {
switch (action.type) {
case "LOG_IN":
return { ...state, auth: { loggedIn: true } };
break;
case "LOG_OUT":
return { ...state, auth: { loggedIn: false } };
break;
default:
return state;
break;
}
})
<Provider store>
— позволяет создавать обёртку для React-приложения и делать состояние Redux доступным для всех компонентов-контейнеров в его иерархии.connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
— позволяет создавать компоненты высшего порядка. Это нужно для создания компонентов-контейнеров на основе базовых компонентов React.npm install react-redux --save
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import createStore from './createReduxStore';
const store = createStore();
const rootElement = document.getElementById('root');
ReactDOM.render((
<Provider store={store}>
<AppRootComponent />
</Provider>
), rootElement);
AppRootComponent
с использованием API connect()
.connect()
используется для создания компонентов-контейнеров, которые подключены к хранилищу Redux. Хранилище, к которому осуществляется подключение, получают от самого верхнего предка компонента с использованием механизма контекста React. Функция connect()
не понадобится вам в том случае, если вы создаёте лишь презентационные компоненты.connect()
из react-redux. Вот как это выглядит:import React from 'react';
import { connect } from 'react-redux';
import Profile from './components/Profile';
function ProfileContainer(props) {
return (
props.loggedIn
? <Profile profile={props.profile} />
: <div>Please login to view profile.</div>
)
}
const mapStateToProps = function(state) {
return {
profile: state.user.profile,
loggedIn: state.auth.loggedIn
}
}
export default connect(mapStateToProps)(ProfileContainer);
store.subscribe()
. Однако использование функции connect()
означает применение некоторых улучшений и оптимизаций производительности, которые, вы, возможно, не сможете задействовать при использовании других механизмов.import React, { Component } from 'react';
import store from './reduxStore';
import Profile from './components/Profile';
class ProfileContainer extends Component {
state = this.getCurrentStateFromStore()
getCurrentStateFromStore() {
return {
profile: store.getState().user.profile,
loggedIn: store.getState().auth.loggedIn
}
}
updateStateFromStore = () => {
const currentState = this.getCurrentStateFromStore();
if (this.state !== currentState) {
this.setState(currentState);
}
}
componentDidMount() {
this.unsubscribeStore = store.subscribe(this.updateStateFromStore);
}
componentWillUnmount() {
this.unsubscribeStore();
}
render() {
const { loggedIn, profile } = this.state;
return (
loggedIn
? <Profile profile={profile} />
: <div>Please login to view profile.</div>
)
}
}
export default ProfileContainer;
connect()
, кроме того, даёт разработчику дополнительную гибкость, позволяя настраивать компоненты-контейнеры на получение динамических свойств, основываясь на свойствах, первоначально им переданных. Это оказывается очень кстати для получения выборок из состояния, основываясь на свойствах, или для привязки генераторов действий к конкретной переменной из свойств.connect()
позволяет легко указывать конкретное хранилище, к которому должен быть подключён компонент-контейнер.connect()
, предоставляемая пакетом react-redux, может принимать до четырёх аргументов, каждый из которых является необязательным. После вызова функции connect()
возвращается компонент высшего порядка, который можно использовать для оборачивания любого компонента React.const ContainerComponent = connect()(BaseComponent);
connect()
:connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
mapStateToProps
является функцией, которая возвращает либо обычный объект, либо другую функцию. Передача этого аргумента connect()
приводит к подписке компонента-контейнера на обновления хранилища Redux. Это означает, что функция mapStateToProps
будет вызываться каждый раз, когда состояние хранилища изменяется. Если вам слежение за обновлениями состояния не интересно, передайте connect()
в качестве значения этого аргумента undefined
или null
.mapStateToProps
объявляется с двумя параметрами, второй из которых является необязательным. Первый параметр представляет собой текущее состояние хранилища Redux. Второй параметр, если его передают, представляет собой объект свойств, переданных компоненту:const mapStateToProps = function(state) {
return {
profile: state.user.profile,
loggedIn: state.auth.loggedIn
}
}
export default connect(mapStateToProps)(ProfileComponent);
mapStateToProps
будет возвращён обычный объект, то возвращённый объект stateProps
объединяется со свойствами компонента. Получить доступ к этим свойствам в компоненте можно так:function ProfileComponent(props) {
return (
props.loggedIn
? <Profile profile={props.profile} />
: <div>Please login to view profile.</div>
)
}
mapStateToProps
возвращает функцию, то эта функция используется как mapStateToProps
для каждого экземпляра компонента. Это может пригодиться для улучшения производительности рендеринга и для мемоизации.mapDispatchToProps
может быть либо объектом, либо функцией, которая возвращает либо обычный объект, либо другую функцию. Для того чтобы лучше проиллюстрировать работу mapDispatchToProps
, нам понадобятся генераторы действий. Предположим, у нас имеются следующие генераторы:export const writeComment = (comment) => ({
comment,
type: 'WRITE_COMMENT'
});
export const updateComment = (id, comment) => ({
id,
comment,
type: 'UPDATE_COMMENT'
});
export const deleteComment = (id) => ({
id,
type: 'DELETE_COMMENT'
});
mapDispatchToProps
.mapDispatchToProps
, представленную объектом или функцией, будет использована стандартная реализация, при применении которой осуществляется внедрение метода хранилища dispatch()
в качестве свойства для компонента. Пользоваться этим свойством в компоненте можно так:import React from 'react';
import { connect } from 'react-redux';
import { updateComment, deleteComment } from './actions';
function Comment(props) {
const { id, content } = props.comment;
// Вызов действий через props.dispatch()
const editComment = () => props.dispatch(updateComment(id, content));
const removeComment = () => props.dispatch(deleteComment(id));
return (
<div>
<p>{ content }</p>
<button type="button" onClick={editComment}>Edit Comment</button>
<button type="button" onClick={removeComment}>Remove Comment</button>
</div>
)
}
export default connect()(Comment);
mapDispatchToProps
используется объект, то каждая функция в объекте будет воспринята в качестве генератора действий Redux и обёрнута в вызов метода хранилища dispatch()
, что позволит вызывать его напрямую. Получившийся в результате объект с генераторами действий, dispatchProps
, будет объединён со свойствами компонента.mapDispatchToProps
, представляющего собой объект с генераторами действий, а так же то, как генераторы могут быть использованы в виде свойств компонента React:import React from 'react';
import { connect } from 'react-redux';
import { updateComment, deleteComment } from './actions';
function Comment(props) {
const { id, content } = props.comment;
// Действия, представленные свойствами компонента, вызываются напрямую
const editComment = () => props.updatePostComment(id, content);
const removeComment = () => props.deletePostComment(id);
return (
<div>
<p>{ content }</p>
<button type="button" onClick={editComment}>Edit Comment</button>
<button type="button" onClick={removeComment}>Remove Comment</button>
</div>
)
}
// Объект с генераторами действий
const mapDispatchToProps = {
updatePostComment: updateComment,
deletePostComment: deleteComment
}
export default connect(null, mapDispatchToProps)(Comment);
mapDispatchToProps
функции программист должен самостоятельно позаботиться о возврате объекта dispatchProps
, который осуществляет привязку генераторов действий с использованием метода хранилища dispatch()
. Эта функция принимает, в качестве первого параметра, метод хранилища dispatch()
. Как и в случае с mapStateToProps
, функция также может принимать необязательный второй параметр ownProps
, который описывает маппинг с исходными свойствами, переданными компоненту.mapDispatchToProps
, что может быть полезным для целей повышения производительности рендеринга и мемоизации.bindActionCreators()
из Redux может быть использована внутри этой функции для осуществления привязки генераторов действий к методу хранилища dispatch()
.mapDispatchToProps
, функции. Здесь же продемонстрирована работа со вспомогательной функцией bindActionCreators()
, применяемой для привязки генераторов действий для работы с комментариями к props.actions
компонента React:import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as commentActions from './actions';
function Comment(props) {
const { id, content } = props.comment;
const { updateComment, deleteComment } = props.actions;
// Вызов действий из props.actions
const editComment = () => updateComment(id, content);
const removeComment = () => deleteComment(id);
return (
<div>
<p>{ content }</p>
<button type="button" onClick={editComment}>Edit Comment</button>
<button type="button" onClick={removeComment}>Remove Comment</button>
</div>
)
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(commentActions, dispatch)
}
}
export default connect(null, mapDispatchToProps)(Comment);
connect()
передаётся аргумент mergeProps
, то он представляет собой функцию, которая принимает следующие три параметра:stateProps
— объект свойств, возвращённый из вызова mapStateToProps()
.dispatchProps
— объект свойств с генераторами действий из mapDispatchToProps()
.ownProps
— исходные свойства, полученные компонентом.connect()
не передают эту функцию, то используется её стандартная реализация:const mergeProps = (stateProps, dispatchProps, ownProps) => {
return Object.assign({}, ownProps, stateProps, dispatchProps)
}
connect()
в качестве четвёртого аргумента, содержит параметры, предназначенные для изменения поведения этой функции. Так, connect()
представляет собой специальную реализации функции connectAdvanced()
, она принимает большинство параметров, доступных connectAdvanced()
, а также некоторые дополнительные параметры.connect()
, и о том, как они модифицируют поведение этой функции.connect()
, нужно создать хранилище Redux, к которому будет подключён этот компонент.NewComment
, который используется для добавления новых комментариев к публикации, и, кроме того, выводит кнопку для отправки комментария. Код, описывающий этот компонент, может выглядеть так:import React from 'react';
import { connect } from 'react-redux';
class NewComment extends React.Component {
input = null
writeComment = evt => {
evt.preventDefault();
const comment = this.input.value;
comment && this.props.dispatch({ type: 'WRITE_COMMENT', comment });
}
render() {
const { id, content } = this.props.comment;
return (
<div>
<input type="text" ref={e => this.input = e} placeholder="Write a comment" />
<button type="button" onClick={this.writeComment}>Submit Comment</button>
</div>
)
}
}
export default connect()(NewComment);
store
компонента:import React from 'react';
import store from './reduxStore';
import NewComment from './components/NewComment';
function CommentsApp(props) {
return <NewComment store={store} />
}
<Provider>
, который можно использоваться для оборачивания корневого компонента приложения. Он принимает свойство store
. Предполагается, что оно представляет собой ссылку на хранилище Redux, которое планируется использовать в приложении. Свойство store
передаётся, в соответствии с иерархией приложения, компонентам-контейнерам, с использованием механизма контекста React:import React from 'react';
import ReactDOM from 'react-dom';
import store from './reduxStore';
import { Provider } from 'react-redux';
import NewComment from './components/NewComment';
function CommentsApp(props) {
return <NewComment />
}
ReactDOM.render((
<Provider store={store}>
<CommentsApp />
</Provider>
), document.getElementById('root'))
mapStateToProps
и mapDispatchToProps
, переданные connect()
, могут быть объявлены со вторым параметром ownProps
, представляющим собой свойства компонента.ownProps
передаваться не будет. Но если функция объявлена с отсутствием обязательных параметров или, как минимум, с 2 параметрами, ownProps
будет передаваться.ownProps
.const mapStateToProps = function() {
console.log(arguments[0]); // state
console.log(arguments[1]); // ownProps
};
ownProps
передаётся, так как функция объявлена без обязательных параметров. В результате будет работать и следующий код, написанный с использованием нового синтаксиса оставшихся параметров ES6:const mapStateToProps = function(...args) {
console.log(args[0]); // state
console.log(args[1]); // ownProps
};
const mapStateToProps = function(state) {
console.log(state); // state
console.log(arguments[1]); // undefined
};
state
. В результате arguments[1]
принимает значение undefined
из-за того, что ownProps
не передаётся.const mapStateToProps = function(state, ownProps = {}) {
console.log(state); // state
console.log(ownProps); // {}
};
state
, так как второй параметр, ownProps
, является необязательным из-за того, что для него задано значение по умолчанию. В результате, так как тут имеется лишь один обязательный параметр, ownProps
не передаётся, и осуществляется маппинг со значением по умолчанию, которое было ему назначено, то есть, с пустым объектом.const mapStateToProps = function(state, ownProps) {
console.log(state); // state
console.log(ownProps); // ownProps
};
ownProps
из-за того, что функция объявлена с двумя обязательными параметрами.connect()
, предоставляемое пакетом react-redux и предназначенное для создания компонентов-контейнеров, подключённых к состоянию Redux. Здесь мы довольно подробно рассказали об устройстве функции connect()
и о работе с ней, однако, если вы хотите больше узнать об этом механизме, в частности — ознакомиться с вариантами его использования — взгляните на этот раздел документации по react-redux.К сожалению, не доступен сервер mySQL