И так, это не про холивар, а ответ на вопрос
«Я хочу использовать Redux и JSSDK, как?».
// Пихаете модели в редьюсеры
export function folderListReducer(folders, action) {
if (action.type === MAIL_FOLDER_CLEAN) {
// и в завсимисоти от `action.type`
// вызываете у модели `set`
const folder = folders.get(action.payload.id);
folder.set({unread: 0, total: 0});
}
return folders;
}
Тут вы должны мне сказать, ответ должен быть иммутабелен и ничего работать не будет.
И будете неправы, Redux никогда не был про иммутабельность, ему всё равно, это простой
EventBus + свистелки, перделки в виде `reducers` и `middelwares`.
А вот `react-redux/connect`, он как раз рассчитывает, что `state` будет иммутабелен, сделано это,
что лишний раз не трогать vdom и жизненный цикл компонентов.
import {connect} from "react-redux-jssdk";
export default connect(state => state)(({folders}) =>
<ul>
{folders.map(folder =>
<li>{folder.get("name")}</li>
)}
</ul>
);
Это не какая-то своя реализация, всё сделано поверх оригинального `react-redux`, поэтому
работает ровно так же, но если внутри `mapStateToProps` или `render`-компонента идёт вызов
`model.get('...')`, начинается «магия».
А именно подписка на изменение этих моделей, поэтому когда кто-то вызовет `model.set`,
связанный коннектор будет обновлён. И да, вы не ошибётесь, если подумаете, что это похоже на RP.
// foldersFlow.js
import {createFlow} from "react-redux-jssdk";
export default createFlow({
name: "MAIL/FOLDERS",
effects: {
[APP_STARTED]() { // <- action.type
return Folder.find().then(folders => ({folders}));
},
},
});
import {createStore, applyMiddleware, combineReducers, compose} from "redux";
#! import foldersFlow, {StoreState} from "./folders.flow";
#!+ export default function configureStore(initialState) {
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
#!+ const rootReducer = combineReducers({
foldersFlow: foldersFlow.reducer,
#!- });
#!+ const middlewares = [
foldersFlow.middleware,
#!- ];
return createStore(
rootReducer,
initialState,
composeEnhancers(applyMiddleware(...middlewares)),
);
#!- }
import {Flow} from "react-redux-jssdk";
const FolderList = ({foldersFlow}) => (
#!+ <Flow
#! source={foldersFlow}
#! pending={<Loading/>} // необязателен
#! failed={<MyError/>} // по умолчанию <FlowError/>
>
#!+ {() =>
<ul>
{foldersFlow.folders.map(folder =>
<li>{folder.get("name")}</li>
)}
</ul>
#!- }
#!- </Flow>
);
export default connect(({foldersFlow}) => ({foldersFlow}))(FolderList);
export default createFlow({
name: "MAIL/FOLDERS",
effects: { /*...*/ },
#!+ reducer(flow, action) {
#!+ if (action.type === MAIL_FOLDER_CLEAN) {
// и в завсимисоти от `action.type`
// вызываете у модели `set`
const folder = flow.folders.get(action.payload.id);
fodlers.set({unread: 0, total: 0});
#!- }
return flow;
#!- },
});
export type StoreState = {
#!+ // Только UI-состояния
#!- ui: UIOptions;
#!+ // «поток» редактируемого фильтра
#!- filterFlow: FilterFlowProps;
#!+ // «поток» списка папок
#!- foldersFlow: FoldersFlowProps;
#!+ // «поток» писем подходящих под условия
#!- messagesMatchFlow: MessagesMatchFlowProps;
};
#!+ import {createFlow, FlowProps} from "react-redux-jssdk";
import * as Folder from "mail/Folder";
#!- import {APP_START} from "../app.constants";
#! export type FoldersFlowProps = {folders: FolderList} & FlowProps;
#!+ export default createFlow<FoldersFlowProps, {}>({
name: "MAIL/FILTER/FOLDERS",
#!+ defaults: {
folders: Folder.map([]),
#!- },
#!+ effects: {
[APP_START]: () => Folder.find().then(**folders => ({folders})**),
#!- },
#!- });
export type FilterFlowProps = {filter: FilterModel} & FlowProps;
export default createFlow<FilterFlowProps, {}>({
name: "MAIL/FILTER",
defaults: {
filter: null,
},
#!+ effects: {
[APP_START]: ({payload:{id}}) => id
? Filter.findOne(id).then(filter => ({filter}))
: Promise.resolve({filter: new Filter()}),
#!-},
#!+ reducer(flow, {type, payload}) {
const {filter} = flow;
#!+ switch (type) {
#!+ case UI_UPDATE_AUTO_REPLY_TEXT:
filter.set("actions.reply", payload);
#!- break;
#!+ case UI_TOGGLE_FILTER_FLAG:
filter.set(payload, !filter.is(payload));
#!- break;
// ...
#!- }
return flow;
#!- },
});
export default handleActions({
[UI_TOGGLE_DETAIL_OPTIONS]: (state) => ({
...state,
showDetailOptions: !state.showDetailOptions,
}),
[UI_TOGGLE_FORWARD]: ...
[UI_TOGGLE_AUTO_REPLY]: ...,
#!+ [filterFlow.ACTIONS.SUCCESS]: (state, {payload: {filter}}: Action<FilterFlowProps>) => ({
#!+ ...state,
forward: filter.is("actions.forward.length"),
#!- autoReply: filter.is("actions.reply"),
#!- }),
}, initialState);
export default createFlow<MessagesMatchFlowProps, StoreState>({
name: "MAIL/FILTER/MESSAGES-MATCH",
defaults: {
count: 0,
limit_reached: false,
},
#!+ effects(action, {getState}) {
#!+ // Получаем поток фильтра
#!- const {filterFlow} = getState();
#!+ // Проверяем, что с ним всё ОК
if (filterFlow.success) {
#!+ // Опрашиваем сервер
#!- return fetchMessagesMatch(filterFlow.filter.toJSON(true));
#!- }
return null;
#!- },
});