React-Redux
Для полноценной работы приложения работающего на React, обычно подключают Redux.js - библиотека которая отвечает за слой Store архитектуры Flux. Для этого необходимо подключить библиотеку react-redux.js. Которая содержит компонент Provider
и функцию connect
.
Provider
ReactDOM.render(
<Provider store = {store}>
<MyComponent>
</Provider>,
document.getElementById('root')
);
Компонент Provider
получает в качестве парамерта store. Он делает так что все его child
могут получить доступ к store через this.context
.
Заметка Provider реализует передачу store посредством context-а. Мы можем и сами пробрасывать store без Provider и connect:
class Provider {
static childContextTypes = {
userActionCreators: PropTypes.object,
userStore: PropTypes.object
};
getChildContext() {
return {
...
}
}
}
class MyApp {
static contextType = { userACtionCreators: PropTypes.object.isRequired };
}
Connect
class MyComponent extends React.Component{
...
}
mapStateToProps(state){
return {
user: state.user,
page: state.page
}
}
mapDispatchToProps(dispatch){
return{
pageAct : bindActionCreator(pageAction, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);
connect
это функция высшего порядка - она оборачивает наш компонент (оберточный компонент). Предоставляя доступ и state
через this.props.user
и к действиям this.props.pageAct
метод mapStateToProps
- функция которая подписывает наши компоненты на изменения состояния store. При каждом изменении это функция будет вызываться. Тут часто используют селекторы, функции которые получают необходимые данные из redux-хранилища, особенно полезно если хранилище у нас нормализованно, так же это отличное место для бизнес-логики (при условии что все данные хранятся локально, без вызова асинхронных запросов на сервер)
метод mapDispatchToProps
- функция которая оборачивает actionCreator
в dispatch
(с помощью метода bindActionCreator
. В результате компоненты могут вызывать action напрямую.
action: bindActionCreator(myAction, dispatch);
// можно переписать в
action: (value) => dispatch(myAction(value));
connect также, для большей оптимизации, проверяет изменилось состояние или нет (не глубокое сравнение), и если только состояние изменилось вызывает mapStateToProps.
Третий (опциональный) агрумент - mergeProps. Выдержка из документации по данному параметру:
You may specify this function to select a slice of the state based on props, or to bind action creators to a particular variable from props.
Если дословно переводить - данная функция позволяет получить текущее состояние, либо передать событие путем привязки нашего экшена к переменной из props (применяется если например, при изменении состояния, возвращать ту или иную функцию).
Пример кода:
const mapStateToProps = (state) => {
return state.play;
}
const mergeProps = (stateProps, dispatchProps) => {
const {play} = stateProps;
const {dispatch} = dispatchProps;
const toggle = () => {
dispatch(togglePlay());
if (play != true) {
this.playAction();
} else {
this.stopAction();
}
};
return {
play,
togglePlay: () => toggle()
}
}
const ButtonPlayComponentContainer = connect(mapStateToProps, null, mergeProsp)(ButtonPlayComponentView);
Тут все просто, в mergeProps
прилетает текущее состояние stateProps
и dispatchProps
, которое даёт возможность отправить событие. Далее по коду делаем проверку состояния, результатом которой будет нужная функция и возвращаем объект с текущем state
и событие, которое благополучно попадет в props
нашего компонента.
Селекторы
data proccessing лучше выносить из компонента - чтобы не нагружать его (становится медленным) - размещать внутри mapStateToProps, а лучше внутри select-ов. т.к. при вызове любого action, будут вызваны абсолютно все mapStateToProps всех компонентов. И если там будет бизнес логика - то она будет вычисляться каждый раз. Для решения этой проблеммы нужно сравнивать пред результат с текущим, и хранить пред результат в самих селекторах при помощи техники memoriz-ации.