Mobile Development 29 min read

Key Lessons from Building a Large‑Scale React Native App

This article shares the Yueqi Reading team's three‑month journey of building a large‑scale React Native app, covering business background, technology selection, navigation management, state persistence, performance tuning, release automation, error monitoring, common pitfalls, and practical tips for mobile developers.

Yuewen Frontend Team
Yuewen Frontend Team
Yuewen Frontend Team
Key Lessons from Building a Large‑Scale React Native App

1. Business Background and Technology Selection

Before adopting React Native (RN), the team used a hybrid H5‑in‑native approach. Although they optimized static resources and first‑screen loading, H5 still lagged behind native performance, prompting a switch to a more mature hybrid solution.

RN and Weex are both relatively mature hybrid solutions that satisfy the requirements:

User Experience : Compared with H5 pages, RN and Weex provide a much better user experience, almost native.

Man‑power Cost : A single codebase can run on both iOS and Android, with high code reuse.

Flexible Release : Both RN and Weex support hot‑update capabilities.

The team finally chose RN because of several factors:

Community : RN has a more active community and a richer React ecosystem than Weex.

Big‑Company Backing : Companies such as Tencent, JD, Baidu and Ctrip have large RN products in production.

Team Experience : Since early 2017 the front‑end team standardized on React, and most members are comfortable with it.

2. Application Scenarios

About 70 % of the "Yuanqi Reading" app is built with RN. Apart from the bookshelf, login and reading engine, almost all other modules are RN‑based, making the app one of the largest RN deployments in domestic products.

3. Navigation Management

Planning navigation early is crucial. The team chose react‑navigation as the navigation component and aimed to make it more generic for business scenarios.

1) Unified Jump Rules

Native ↔ RN jumps are common. By maintaining a single URL map and implementing an open interface, both sides can navigate easily.

In react‑navigation, navigation uses the routeName + params pattern, so the following adjustment is needed before calling router.getStateForAction:

// Fix action: allow navigate/push/reset to pass url
if (isPushLikeAction(action) || isReplaceAction(action)) {
  if (isRouteUrl(action.routeName)) {
    // Use path‑to‑regexp to parse url → routeName + params
    const route = parseRouteByUrl(action.routeName);
    if (route) {
      action.routeName = route.name;
      action.params = route.params;
    }
  }
}

2) Implement 404 Jump

Web development often handles 404 pages; RN can implement similar logic:

// Fix action: when navigate/push/replace to unknown routeName, use notFoundRouteName
if (isPushLikeAction(action) || isReplaceAction(action)) {
  if (allRouteNames.indexOf(action.routeName) === -1) {
    const oldAction = { ...action };
    action.routeName = notFoundRouteName;
    action.params = { action: oldAction };
  }
}

3) Control Page Lifecycle

When returning to a previous page, data often needs to be refreshed (e.g., after login or after completing an operation on a detail page).

In RN 0.x, onNavigationStateChange + context was required to detect focus/blur. From 1.x onward, addListener can listen for didFocus or didBlur events.

4) Optimize Second‑Open of Pages

Because the app frequently switches between RN and native, multiple RN root components exist. The team uses initialRouteUrl or initialRouteName + initialRouteParams to tell RN which page to display on initialization.

const navigator = getActiveNavigator(); // maintain a global navigator stack
let nextState = originGetStateForAction(action, state);
if (navigator) {
  const { initialRouteName, initialRouteParams, goBackOnTop } = navigator.props;
  if (isInitAction(action)) {
    if (initialRouteName) {
      const initialActionPayload = { routeName: initialRouteName, params: initialRouteParams };
      const initialAction = NavigationActions.navigate(initialActionPayload);
      nextState = router.getStateForAction(initialAction, nextState);
      if (!isTopNavigator() && nextState.index > 0) {
        nextState = { ...nextState, index: 0, routes: nextState.routes.slice(-1) };
      }
    }
  } else if (isBackAction(action)) {
    if (isTopScren(state) && !isTopNavigator() && typeof goBackOnTop === 'function') {
      goBackOnTop();
      if (nextState === state) { nextState = { ...nextState }; }
    }
  }
}
return nextState;

5) State Local Storage

react‑navigation

2.x added state persistence, allowing the app to restore the previous page after a reload. In large‑scale RN apps, each root navigator should be marked (e.g., by index) to differentiate stored states. Errors can be cleared in componentDidCatch to avoid staying on a crash screen.

4. State Management and Data Persistence

To avoid white screens and improve offline experience, the team caches user info, book details and messages. They use redux together with redux‑persist for shared state and persistence.

1) Redux

Redux provides a single‑direction data flow, making debugging easier. The redux‑actions library reduces boilerplate.

// Common reducer
export default (state = {}, action) => {
  switch (action.type) {
    case INCREASE:
      return { ...state, total: state.total + 1 };
    case DECREASE:
      return { ...state, total: state.total - 1 };
    default:
      return state;
  }
};

// Using redux‑actions
import { handleActions } from 'redux-actions';
export default handleActions({
  [INCREASE]: state => ({ ...state, total: state.total + 1 }),
  [DECREASE]: state => ({ ...state, total: state.total - 1 })
}, {});

2) Redux‑Persist

Redux‑Persist subscribes to the store and writes changes to local storage. A whitelist/blacklist can control which slices are persisted.

import { createStore, applyMiddleware, combineReducers } from 'redux';
import { persistReducer } from 'redux-persist';
import thunkMiddleware from 'redux-thunk';
import storage from 'redux-persist/lib/storage';

const rootPersistConfig = {
  storage,
  key: 'root',
  blacklist: ['someTransientSlice'] // black‑list example
};

const enhancer = applyMiddleware(thunkMiddleware);
export const store = createStore(persistReducer(rootPersistConfig, rootReducer), enhancer);
export const persistor = persistStore(store);

5. Performance Optimization

A compelling reason for using React Native instead of WebView‑based tools is to achieve 60 fps and a native look and feel. However, some scenarios still require manual intervention.

1) First‑Screen Optimization

Pre‑load the RN bundle during app launch to reduce the white‑screen time, especially on low‑end Android devices.

Use the splash screen to hide the RN loading process and close it via the bridge once the bundle is ready.

2) Interaction‑First

When the JS thread is busy, frame drops occur. Prioritize user‑perceived operations by executing them after interactions:

InteractionManager.runAfterInteractions(() => {
  // navigation transition logic here
});

Render only essential components on the initial screen, using placeholders or skeletons for the rest.

3) Long‑List Optimization

Control shouldComponentUpdate to skip unnecessary renders. Adding unique key values to list items improves Virtual DOM diff efficiency.

// Always return true (bad)
shouldComponentUpdate(nextProps, nextState) {
  return true;
}

// Filter by image URI (good)
shouldComponentUpdate(nextProps, nextState) {
  if (nextProps.imgSrc.uri === this.props.imgSrc.uri) {
    return false;
  }
  return true;
}

4) Animation Optimization

Use native‑driven animations to avoid JS thread blockage:

Animated.timing(value, {
  toValue: 1,
  duration: 300,
  useNativeDriver: true
}).start();

For one‑time animations, LayoutAnimation leverages Core Animation and is not affected by JS thread performance.

6. Release and Update

The team uses Jenkins for automated packaging, placing the RN bundle in a fixed location to avoid manual steps.

Hot updates are handled by Microsoft CodePush. After registering an app on CodePush, separate deployment keys are used for iOS and Android, and for Staging vs. Production environments.

code-push deployment ls <appName> -k

Integrating CodePush in RN:

import React, { Component } from 'react';
import codePush from 'react-native-code-push';

const codePushOptions = __DEV__ ? {
  updateDialog: true,
  installMode: codePush.InstallMode.IMMEDIATE
} : {
  checkFrequency: codePush.CheckFrequency.ON_APP_RESUME,
  installMode: codePush.InstallMode.ON_NEXT_RESTART
};

@codePush(codePushOptions)
export default class App extends Component {
  render() { return /* ... */; }
}

7. Exception Monitoring

The team relies on Tencent Bugly for crash and performance monitoring, which provides error reporting, log upload, and statistical analysis across versions, devices and systems.

8. Pitfalls and Tips

Image memory leak on Android : Set resizeMethod="resize" for large images.

InteractionManager caveat : Infinite loading animations prevent callbacks from executing.

FlatList getItemLayout : Only enable when item height is fixed; otherwise it causes jumps.

Rapid duplicate clicks : Guard against repeated navigation by checking if the target route already exists in the current state.

function isInCurrentState(state, nextState, routeName) {
  if (nextState && nextState.routeName === routeName && !deepDiffer(state.params, nextState.params)) {
    return true;
  }
  if (nextState && nextState.routes) {
    return isInCurrentState(state.routes[state.index], nextState.routes[nextState.index], routeName);
  }
  return false;
}

const nextState = originGetStateForAction(action, state);
if (nextState && action.type === StackActions.PUSH) {
  if (isInCurrentState(state, nextState, action.routeName)) {
    return state; // prevent duplicate push
  }
}

Useful RN tips:

iOS simulator: toggle software keyboard via Hardware → Keyboard → Toggle Software Keyboard.

Disable slow animations in the simulator via Debug → Slow Animations (shortcut: ⌘+T).

Synchronously expose constants by attaching them to NativeModules at startup.

Image component useful props: defaultSource (iOS), getSize, prefetch, queryCache.

Text component: allowFontScaling (iOS), selectable.

FlatList multi‑column layout via numColumns.

Debugging tools: react-native-debugger integrates Chrome DevTools, react‑devtools and Redux inspection.

Performance profiling: Xcode Instruments for iOS, Android Studio Profiler for Android.

9. Summary

Heavy‑operation scenarios : RN suits pages with frequent updates such as book city, welfare pages, etc.

Rapid‑iteration scenarios : Features still evolving benefit from RN’s fast iteration, e.g., community circles, novel and comic city.

Static‑information display : Ranking, author pages, primary category pages are ideal for RN.

Long‑list scenarios :

Lists with many unknown‑size images are not recommended for RN due to potential white‑screen flashes.

Lists with known‑size images or pure text perform acceptably.

Choosing between Native and RN depends on team composition and business scenarios. For mature, high‑interaction pages, Native may still be preferred.

Final Thoughts

Although some companies have announced abandoning RN, many of the listed issues are optimizable. Ongoing work from the Facebook team on threading, asynchronous rendering and bridge improvements gives confidence that RN’s future remains bright. The authors hope more developers join the RN ecosystem and contribute to its evolution.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Performance OptimizationReduxState ManagementReact NativenavigationCodePush
Yuewen Frontend Team
Written by

Yuewen Frontend Team

Click follow to learn the latest frontend insights in the cultural content industry. We welcome you to join us.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.