И так, это не про холивар, а ответ на вопрос
«Я хочу использовать 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; #!- }, });