Applying Rematch State Management in Ctrip Train Ticket Module: Experience, Issues, and Solutions
This article details Ctrip's practical experience of refactoring its train‑ticket RN module with the Rematch state‑management framework, outlining the background problems of Redux, the three‑phase migration plan, specific challenges such as store compatibility, component decoupling, reuse, and the resulting code‑size reduction and readability improvements.
Background – Ctrip's train‑ticket business has been fully RN‑based, but the original Redux implementation suffered from complex syntax, scattered state‑change logic, and tight coupling between components and pages, prompting a search for a better state‑management solution.
Why Rematch – Rematch wraps Redux, reduces boilerplate, and provides a global dispatcher that helps decouple pages and components, making it a suitable candidate for the migration.
Refactor Process Design – The migration was split into three phases: (1) trial on a new, low‑coupling page; (2) refactor the train‑ticket detail page to accumulate experience; (3) apply Rematch to the entire train‑ticket flow.
3.1 Store Compatibility – To coexist with existing Redux stores, a separate Rematch store was created per page, using a routing‑to‑store map to switch stores dynamically.
const newStorePages = [
"Page1",
"Page2"
];
render() {
......
return (
-1 ? newStore : store}>
);
}3.2 Full Decoupling Between Modules – Components are placed in isolated folders with index.js (UI) and model.js (state & logic). The traditional approach required state in the parent page and heavy prop passing, while the Rematch approach moves state handling into the model and connects via connect , eliminating page‑level state declarations.
// Traditional state in page
this.state = { showManualSpeed: false };
// Rematch model
const manualSpeedLayer = {
state: false,
reducers: {
show(state) { return true; },
hide(state) { return false; }
}
};
// Connecting component
const mapState = state => ({ isShow: state?.manualSpeedLayer });
const mapDispatch = ({ manualSpeedLayer }) => ({
hide: () => { manualSpeedLayer.hide(); }
});
export default memo(connect(mapState, mapDispatch)(ManualSpeedPopupView));3.3 Component Reuse – By exposing asynchronous actions (e.g., setDataSource ) that accept custom parameters, components can receive data sources without modifying entry files, enabling clean reuse across different pages.
const noticeDetail = {
state: null,
reducers: {
setNoticeDetail(state, payload) { return payload; },
clear(state) { return null; }
},
effects: dispatch => ({
async setDataSource(params, rootState) {
let res = await getRecommendForOrder({ orderNumber: params.orderNumber });
if (res) dispatch.noticeDetail.setNoticeDetail(res);
}
})
};
// Usage in pages
dispatch.noticeDetail.setDataSource({ orderNumber: pageA.orderNumber });
dispatch.noticeDetail.setDataSource({ orderNumber: pageB.orderNumber });3.4 Other Issues – (a) Immediate state retrieval after dispatch requires accessing the store directly (e.g., newStore.getState() ) because rootState is not updated synchronously. (b) Pre‑loaded component caches cause stale data on re‑entry; each component provides a clear reducer and is invoked on page unmount to reset state.
useEffect(() => {
return () => { props?.clear(); };
}, []);
const mapDispatch = ({ component1, component2, component3 }) => ({
clear: () => {
component1.clear();
component2.clear();
component3.clear();
}
});Results and Review – Phases 1 and 2 are completed; new pages using Rematch are simpler, with code size reduced to ~32 % of the original, and components become independent and reusable. Phase 3 (full‑process migration) is planned.
Ctrip Technology
Official Ctrip Technology account, sharing and discussing growth.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.