React -> Redux -> JSSDK

Redux vs. JSSDK

И так, это не про холивар, а ответ на вопрос

«Я хочу использовать Redux и JSSDK, как?».

Redux vs. 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 vs. JSSDK: Проблема

Тут вы должны мне сказать, ответ должен быть иммутабелен и ничего работать не будет. И будете неправы, Redux никогда не был про иммутабельность, ему всё равно, это простой EventBus + свистелки, перделки в виде `reducers` и `middelwares`.

А вот `react-redux/connect`, он как раз рассчитывает, что `state` будет иммутабелен, сделано это, что лишний раз не трогать vdom и жизненный цикл компонентов.

react-redux-jssdk

react-redux-jssdk

			import {connect} from "react-redux-jssdk";
			export default connect(state => state)(({folders}) =>
				<ul>
					{folders.map(folder =>
						<li>{folder.get("name")}</li>
					)}
				</ul>
			);
		

react-redux-jssdk/connect

Это не какая-то своя реализация, всё сделано поверх оригинального `react-redux`, поэтому работает ровно так же, но если внутри `mapStateToProps` или `render`-компонента идёт вызов `model.get('...')`, начинается «магия».

А именно подписка на изменение этих моделей, поэтому когда кто-то вызовет `model.set`, связанный коннектор будет обновлён. И да, вы не ошибётесь, если подумаете, что это похоже на RP.

<Flow/>

<Flow/>

			// 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}));
					},
				},
			});
		

<Flow/>

			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)),
				);
			#!- }
		

<Flow/>

			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);
		

<Flow/>

			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;
				#!- },
			});
		

mail-filters

react-redux-jssdk

			export type StoreState = {
				#!+ // Только UI-состояния
				#!- ui: UIOptions;
				#!+ // «поток» редактируемого фильтра
				#!- filterFlow: FilterFlowProps;
				#!+ // «поток» списка папок
				#!- foldersFlow: FoldersFlowProps;
				#!+ // «поток» писем подходящих под условия
				#!- messagesMatchFlow: MessagesMatchFlowProps;
			};
		

foldersFlow

			#!+ 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})**),
				#!- },
			#!- });
		

filterFlow

			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;
				#!- },
			});
		

ui

			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);
		

messages-match

			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;
				#!- },
			});
		

The End