Senior React Native Technical Interview Q&A

  • react-native
  • interview
  • javascript
  • typescript
  • native
  • performance
  • system-design

How to use this document#

  • Answer aloud before reading the suggested answer.
  • Treat each answer as a concise backbone, not a memorized script.
  • For case studies, add only details that are true in your implementation.
  • For Senior-level questions, explain trade-offs, failure modes, measurement, rollout, and ownership—not only APIs.

1. JavaScript fundamentals and runtime#

1. What is the JavaScript event loop?#

JavaScript executes synchronous work on a call stack. Asynchronous operations enqueue callbacks; the event loop moves eligible work to the stack when it is empty. Promise callbacks are microtasks and normally run before the next macrotask such as a timer.

2. What is the difference between a microtask and a macrotask?#

Promise reactions and queueMicrotask use the microtask queue; timers and many I/O callbacks use task/macrotask queues. After a task, the runtime drains microtasks before rendering or processing the next task, so an unbounded microtask chain can starve the UI.

3. Explain closures and a practical React Native use case.#

A closure retains access to variables from its lexical scope after the outer function returns. It is useful for factories and encapsulation, but stale closures in hooks can read outdated state unless dependencies or functional updates are used correctly.

4. What is the difference between var, let, and const?#

var is function-scoped and hoisted with undefined; let and const are block-scoped and remain in the temporal dead zone until initialized. const prevents rebinding, not mutation of the referenced object.

5. What is prototype inheritance?#

Objects can delegate property lookup to another object through the prototype chain. JavaScript classes are syntax over this mechanism; methods usually live on the prototype rather than being copied to each instance.

6. What are common causes of memory leaks in JavaScript applications?#

Unremoved listeners, timers, subscriptions, retained closures, unbounded caches, detached views, global references, and pending async work retaining large objects. Diagnose with heap snapshots, allocation timelines, and controlled reproduction.

7. Shallow copy versus deep copy?#

A shallow copy duplicates only the top-level container, so nested references remain shared. A deep copy recursively duplicates supported values; structuredClone handles many built-ins but not functions and has platform/runtime constraints.

8. What is referential equality and why does it matter in React?#

Objects and functions compare by reference. Creating new references on every render may invalidate memoization or trigger effects, while mutating an existing reference can hide real changes. Stabilize references only where measurement shows value.

9. Debounce versus throttle?#

Debounce waits until calls stop, useful for search input. Throttle limits execution frequency, useful for scroll or resize. Both need cancellation and a clear leading/trailing-edge policy.

10. How do you handle errors in asynchronous JavaScript?#

Use try/catch around awaited promises, handle intentional cancellation separately, preserve causal context, and convert low-level errors into typed domain errors. Avoid swallowed promises and add global reporting only as a final safety net.

11. What is the risk of Promise.all?#

It rejects on the first rejection and can create excessive concurrency. Use it for independent bounded work; use allSettled for complete outcomes, and a concurrency limiter when operations are expensive or rate-limited.

12. How would you avoid race conditions in client-side data fetching?#

Cancel obsolete requests, attach request/version identifiers, ignore stale responses, make mutations idempotent, and centralize server state. Correctness should not depend on response order.


2. TypeScript#

13. type versus interface?#

Both describe shapes. Interfaces support declaration merging and are natural for extensible object contracts; type aliases support unions, intersections, tuples, and mapped types. Choose consistently rather than treating one as universally superior.

14. any, unknown, and never?#

any disables checking; unknown requires narrowing before use; never represents an impossible value or a function that cannot return. Prefer unknown at untrusted boundaries and never for exhaustive checks.

15. What is a discriminated union?#

It is a union whose members share a literal discriminator, such as status: 'loading' | 'success' | 'error'. It models state machines safely and enables exhaustive narrowing.

16. How do generics improve an API client?#

They preserve relationships between inputs and outputs without losing type safety. However, a generic response type is only a compile-time claim; external JSON still requires runtime validation.

17. How do you validate data at runtime?#

Validate network, storage, deep-link, and native-module boundaries with a schema library or explicit parsers. Infer TypeScript types from schemas where possible to avoid drift.

18. What are conditional and mapped types used for?#

Conditional types select a type based on a constraint; mapped types transform properties across a type. They are valuable for reusable libraries but should not make application types unreadable.

19. How do you enforce exhaustive handling?#

Use a discriminated union and pass the default branch value to a function accepting never. A new variant then causes a compile-time error at every incomplete switch.

20. What does strict mode give you?#

It enables a family of stronger checks including null safety and function variance. For a mature app, introduce it incrementally by boundary or package, track escape hatches, and prevent new unsafe code.

21. Why should API DTOs not automatically become domain models?#

Transport shapes change for backend reasons and may contain nullability or naming unsuitable for the domain. Mapping at the boundary isolates change, validates data, and gives the app a stable internal model.

22. How would you type navigation routes?#

Define a central route-to-parameter map, use navigator-specific generics, and type deep-link parsing. Avoid passing large domain objects; pass stable identifiers and load current data at the destination.


3. React fundamentals and advanced patterns#

23. What causes a React component to render?#

Its state changes, its parent renders, consumed context changes, or an external store subscription reports a change. A render is calculation; it does not necessarily mean a native or DOM update.

24. How does reconciliation work?#

React compares element trees by type and key, preserves compatible instances, and applies the minimum host updates it determines. Keys express identity among siblings and must be stable.

25. Why is using an array index as a key risky?#

When items reorder or are inserted, identities shift and state can attach to the wrong row. Use a stable domain identifier unless the list is static and never reordered.

26. useEffect versus useLayoutEffect?#

useEffect runs after commit and should handle synchronization with external systems. useLayoutEffect runs synchronously after layout but before paint, useful for measurements requiring no visible flicker; it can block rendering.

27. What belongs outside an effect?#

Pure derivation from props/state belongs in render or memoization; event-specific work belongs in event handlers. Effects are for synchronizing with systems outside React, not for building chains of derived state.

28. When do useMemo and useCallback help?#

When a measured expensive calculation or reference-sensitive child/subscription benefits. They add comparison and complexity, so do not apply them mechanically.

29. Controlled versus uncontrolled components?#

Controlled components take value from React state and are predictable; uncontrolled components keep state in the host element and can reduce update overhead. Choose based on validation, synchronization, and performance needs.

30. How do error boundaries work?#

They catch rendering and lifecycle errors below them and render fallback UI. They do not catch all async or event-handler errors, so combine them with explicit handling and observability.

31. Context versus a state-management library?#

Context is dependency propagation, not automatically an optimized store. It is good for stable global dependencies; frequently changing shared state may need selectors, normalized state, or a dedicated external store.

32. How would you model complex UI state?#

Use a reducer or state machine with explicit states and events. Avoid multiple booleans that permit impossible combinations such as loading, success, and error simultaneously.

33. What is concurrent rendering conceptually?#

React may prepare, pause, discard, or prioritize render work before committing. Render logic must be pure because an attempted render is not guaranteed to commit.

34. What is optimistic UI and when is it unsafe?#

It updates the UI before server confirmation, then confirms or rolls back. It needs stable mutation IDs, rollback/reconciliation, and conflict handling; avoid naïve optimism for irreversible or high-risk actions.


4. React Native foundations#

35. How is React Native different from React on the web?#

React supplies the component model in both. React Native renders platform views through native host components, uses Yoga for layout, has different styling/events/accessibility, and does not provide the browser DOM.

36. What are the main execution threads in a React Native app?#

Typically the UI/main thread, JavaScript runtime thread, and platform/background threads. Rendering and gesture performance depend on what runs where; exact threading differs by architecture and library.

37. What is Yoga?#

Yoga is the cross-platform layout engine used by React Native. It implements a Flexbox-like model, but defaults and supported CSS features differ from the web.

38. What happens from a state update to pixels on screen?#

React schedules and reconciles the component tree, computes host changes, React Native coordinates layout and native mutations, and the platform renders frames. New Architecture can execute host operations through JSI/Fabric without serializing every call over the legacy bridge.

39. How do you choose between React Native, native, and a hybrid approach?#

Evaluate product scope, team skills, platform-specific UX, performance constraints, native SDK dependency, release cadence, and lifetime cost. Use native modules or native screens for narrow platform-critical paths rather than rewriting the entire app prematurely.

40. What are common React Native production failure modes?#

JS crashes, native crashes, ANRs/watchdogs, excessive memory, blocked UI or JS threads, bad OTA bundles, incompatible native dependencies, network/state races, and device/OS-specific behavior.

41. How do you structure a large React Native codebase?#

Organize by product feature/domain with clear public boundaries; separate presentation, application/domain logic, data adapters, and platform integrations. Keep cross-cutting packages intentional and enforce boundaries with linting and ownership.

42. Expo managed workflow versus bare/native projects?#

Expo provides integrated tooling, modules, builds, routing, updates, and config plugins. Native projects offer maximum direct control. Modern Expo supports custom native code, so the decision is about operational fit and required control rather than a simplistic capability gap.

43. What is a config plugin?#

It programmatically modifies generated iOS/Android native configuration during prebuild. It makes native setup reproducible and avoids undocumented manual changes.

44. How do you manage environment configuration?#

Separate build-time public configuration from server-held secrets. Generate typed config per environment, validate it in CI, use distinct application IDs and credentials, and never assume a value embedded in the app is secret.

The OS routes a URL or verified app/universal link to the app; the app parses it into a navigation state. Validate parameters, handle cold/warm starts, auth gating, fallback routes, and malicious inputs.

46. How do you support offline behavior?#

Define which workflows are offline-capable, persist normalized data and an operation queue, use client IDs/idempotency, expose sync state, resolve conflicts explicitly, and test clock/network/process-death scenarios.

47. How do you manage app lifecycle transitions?#

Observe foreground/background/inactive states, pause expensive work, refresh security-sensitive state on return, persist incomplete work, and avoid assuming a backgrounded process will survive.

48. What should happen when connectivity changes?#

Connectivity is a hint, not proof that an API is reachable. Retry transient failures with bounded exponential backoff and jitter, avoid retrying invalid requests, and keep user actions idempotent.

49. How do you handle keyboard and safe areas reliably?#

Use safe-area insets, test notched/tablet devices, understand resize/pan behavior on Android, use keyboard-aware layouts sparingly, and verify focus/scroll behavior with real devices and external keyboards.

50. How do you make a React Native app accessible?#

Use semantic roles/labels/states, logical focus order, sufficient contrast and target sizes, scalable text, reduced-motion support, and screen-reader testing on both platforms.


5. Legacy Bridge, JSI, Fabric, TurboModules, and New Architecture#

51. How did the legacy React Native bridge work?#

JavaScript and native sides exchanged asynchronous serialized messages through a batched bridge. This separation was robust but introduced serialization, scheduling, and chatty-call overhead.

52. What is JSI?#

JavaScript Interface is a C++ API allowing a JavaScript runtime to hold and invoke host objects/functions more directly. It is an enabling layer, not a thread-safety guarantee or automatic performance fix.

53. What is Fabric?#

Fabric is React Native’s newer rendering system. It integrates more directly with React’s renderer, supports synchronous capabilities where needed, uses a C++ shadow tree, and improves interoperability with concurrent React features.

54. What are TurboModules?#

TurboModules are the New Architecture native-module system. They use typed code generation, support lazy loading, and use JSI-based interop rather than the old bridge contract.

55. What is Codegen?#

Codegen turns typed JavaScript/TypeScript specs into native interface glue. It catches contract mismatches earlier and reduces handwritten bridging boilerplate.

56. Does New Architecture mean everything runs synchronously?#

No. It enables more direct and sometimes synchronous access, but long operations must remain asynchronous and off critical threads. Synchronous calls can make blocking problems worse if misused.

57. What changes when migrating a legacy app to New Architecture?#

Audit library compatibility and custom modules, enable it in a controlled branch, resolve generated-spec/build issues, run platform/device tests, compare performance/crashes, and stage rollout with rollback capability.

58. How would you explain a New Architecture regression?#

Separate renderer, module, threading, memory, and dependency compatibility hypotheses. Reproduce on a minimal screen, compare old/new architecture builds, capture native/JS traces, and bisect dependencies or commits.

59. When would you use a native module instead of pure JavaScript?#

For unavailable platform APIs, vendor SDKs, compute/I/O paths where measurement proves JS is unsuitable, background services, or strict native lifecycle integration. The boundary must be small, typed, testable, and observable.

60. What is a good native-module API design?#

Expose domain-level operations rather than many low-level chatty calls, define ownership and cancellation, type all values, avoid unnecessary copies, return structured errors, and keep platform behavior consistent where feasible.

61. How do you avoid blocking across JSI/native boundaries?#

Perform heavy CPU/I/O on dedicated native workers, copy or share buffers deliberately, marshal results back safely, keep UI-thread work minimal, and document thread affinity.

62. How do you debug native crashes caused by a module?#

Collect symbolicated crash reports and breadcrumbs, identify thread and failing frame, reproduce with sanitizers/debug tooling, inspect lifetime/nullability/thread access, and add a minimal regression test before rollout.


6. Native Android#

63. What is the Android activity lifecycle relevant to React Native?#

Activities transition through creation, start, resume, pause, stop, and destruction. Configuration changes or process death can recreate them, so critical state must not rely only on in-memory component state.

64. What is an ANR?#

Application Not Responding occurs when the main thread fails to respond within platform thresholds. Find blocked main-thread work, lock contention, binder/I/O calls, or expensive startup using traces and Play Console diagnostics.

65. Service, foreground service, and WorkManager?#

A service is an Android component for ongoing work, but background limits apply. Foreground services require a visible notification and approved use case. WorkManager is appropriate for deferrable guaranteed background work with constraints.

66. What is Android process death?#

The OS may kill the app process while its task appears restorable. Recreate state from persisted data and saved state; do not assume singleton or JS memory survives.

67. How do Android permissions differ by OS version?#

Runtime permission behavior, background location, notifications, media access, and nearby devices evolve by API level. Gate behavior by capability/API level, explain purpose, and handle denial or “don’t ask again.”

68. APK versus AAB?#

An APK is an installable package. An Android App Bundle is a publishing format from which Google Play generates optimized APKs for device configurations.

69. What is R8/ProGuard and what can go wrong?#

R8 shrinks, optimizes, and obfuscates bytecode. Reflection/JNI/serialization libraries may require keep rules; always test release builds and preserve mapping files for deobfuscation.

70. How do you reduce Android startup time?#

Measure cold/warm starts, minimize Application/Activity initialization, lazy-load SDKs, avoid main-thread I/O, reduce content-provider auto-init, optimize JS/native bundle loading, and compare traces on representative devices.

71. What causes Android build-method or dependency conflicts?#

Incompatible transitive versions, duplicate classes/resources, min/compile SDK constraints, Kotlin/Gradle plugin mismatches, or exceeding method limits. Inspect the dependency graph and resolve deliberately instead of adding broad exclusions blindly.

Use verified HTTPS app links with a correctly hosted asset-links file, validate all route parameters, and avoid treating a deep link as authentication or authorization.


7. Native iOS#

73. Explain the iOS application lifecycle.#

Apps move among foreground-active, inactive, background, suspended, and terminated states, with modern apps often managed through scenes. Background execution is limited and use-case-specific.

74. What is the iOS watchdog termination?#

iOS may terminate an app that blocks launch, main-thread responsiveness, or lifecycle completion too long. Diagnose device logs, MetricKit/Organizer data, and time profiles rather than expecting a normal exception.

75. What are provisioning profiles and signing certificates?#

Certificates establish signing identity; identifiers, entitlements, devices, and distribution type are bound through profiles and App Store configuration. Automate securely, rotate intentionally, and separate environments.

76. What are entitlements?#

Entitlements authorize capabilities such as push notifications, associated domains, keychain groups, or HealthKit. The app signature, provisioning profile, and portal configuration must agree.

HTTPS links are associated with an app through the site association file and app entitlement. The app must validate and route them while the website remains a safe fallback.

78. Keychain versus UserDefaults?#

Keychain is for small sensitive credentials and supports access-control policies. UserDefaults is ordinary preferences storage and is not a secret store.

79. What is ATS?#

App Transport Security enforces secure network defaults. Broad exceptions weaken protection and can create review/compliance issues; fix endpoints or scope exceptions narrowly.

80. How do you diagnose iOS memory issues?#

Use Instruments allocations/leaks, memory graph, os_signposts, and crash/jetsam reports. Look for retained closures/delegates, image buffers, native resources, caches, and cross-language ownership problems.

81. CocoaPods versus Swift Package Manager?#

Both resolve native dependencies. CocoaPods is common in React Native integrations; SPM is Apple-native and increasingly supported. Evaluate ecosystem compatibility, build reproducibility, binary size, and maintenance.

82. What should a Swift native module expose to React Native?#

A small typed contract with explicit threading, lifecycle, cancellation, and error mapping. Keep implementation details native and provide parity or documented differences on Android.


8. Navigation, state, data, and app architecture#

83. Server state versus client state?#

Server state is remote, asynchronous, shared, and potentially stale; use caching, invalidation, and mutation reconciliation. Client state represents local UI/workflow decisions. Mixing them creates unnecessary synchronization bugs.

84. Redux, Zustand, Context, or a query library?#

Choose by problem: query libraries for server cache; Context for stable dependency propagation; lightweight stores for local shared state; Redux when explicit events, tooling, middleware, and large-team conventions add value.

85. What is feature-based clean architecture?#

Code is grouped by business capability, with dependency direction toward stable domain/application logic. UI, API, storage, and native services are adapters; avoid ceremony that adds layers without protecting change.

86. How do you enforce module boundaries?#

Define public entry points, dependency rules, lint/import restrictions, package ownership, API contracts, and architecture tests. Code review alone is too late and inconsistent.

87. How would you design authentication state?#

Separate secure credential storage, token lifecycle, current user/profile, and navigation decisions. Support refresh serialization, revocation, logout cleanup, clock skew, 401 replay limits, and app restore.

88. How do you prevent multiple token refresh requests?#

Use a single-flight refresh promise/lock. Queue eligible requests behind it, retry once after success, and fail all consistently if refresh fails.

89. How do you design a resilient API layer?#

Typed request/response validation, consistent errors, cancellation, timeouts, correlation IDs, bounded retries, idempotency support, observability, and domain mapping. Never retry all failures indiscriminately.

90. How would you handle schema evolution in persisted client data?#

Version storage, write deterministic migrations, keep migrations backward-aware for rollback, test real historical fixtures, and provide safe reset only for non-critical/cache data.

91. How do you manage feature flags?#

Define owner, purpose, targeting, defaults, offline behavior, metrics, and expiry. Evaluate security-sensitive permission server-side; remove stale flags after rollout.

92. Modular monolith versus micro-frontends in mobile?#

A modular monolith usually gives simpler builds, navigation, and runtime consistency. Micro-frontends or independently delivered modules add organizational autonomy but also dependency, UX, observability, security, and release complexity.


9. Lists, images, animation, and UI performance#

93. Why does a long list become slow?#

Too many mounted rows, unstable props, expensive item rendering, layout measurement, large images, synchronous JS, or poor virtualization settings. Profile before changing window parameters.

94. How do you optimize FlatList?#

Use stable keys and item identities, lightweight rows, memoization where useful, deterministic layout when possible, sensible batching/window settings, pagination, and optimized images. Confirm improvements using release builds.

95. ScrollView versus virtualized list?#

ScrollView mounts all children and suits small bounded content. Virtualized lists render a window and are safer for large or unbounded collections.

96. What is a dropped frame?#

A frame misses its display deadline because the UI/rendering pipeline cannot complete work in time. Determine whether the UI thread, JS thread, GPU, or data/layout work is responsible.

97. Why can Reanimated improve animations?#

Worklets and native/UI-side execution can update animations and gestures without depending on every JS-thread round trip. It does not make expensive layout, images, or native rendering free.

98. How do you optimize images?#

Request the displayed dimensions, use efficient formats, cache with an explicit policy, prefetch critical assets, avoid decoding huge images, release memory, and measure network, decode, and render separately.

99. How would you investigate a janky gesture?#

Reproduce in a release build, inspect UI and JS FPS/traces, remove expensive callbacks, keep gesture-driven values off JS when possible, reduce layout churn, and test low-end devices.

100. What is overdraw?#

The GPU draws the same pixel multiple times due to overlapping opaque layers. Reduce unnecessary backgrounds/layers and verify with platform GPU tools.

101. When can memoization make performance worse?#

When comparison/allocation overhead exceeds saved work, dependencies invalidate constantly, or it increases retained memory and complexity. Optimize measured bottlenecks.

102. How do you avoid expensive rerenders from context?#

Split contexts by change frequency, stabilize provider values, use selector-based stores, move state closer to consumers, and verify with React profiling.


10. Profiling, observability, and performance optimization#

103. What performance metrics matter for a mobile app?#

Cold/warm start, time to first meaningful/interactable UI, frame stability, input latency, API latency, memory, CPU, battery, binary size, crashes, ANRs, and user journey completion. Use percentiles and device cohorts.

104. Debug build or release build for profiling?#

Use release-like builds for trustworthy performance because debugging, source maps, assertions, and development tooling distort behavior. Debug builds remain useful for functional inspection.

105. How do you profile React rendering?#

Use React DevTools Profiler to find frequent or expensive commits, inspect why components rendered, form a hypothesis, change one factor, and remeasure.

106. How do you profile native performance?#

Use Android Studio profilers, Perfetto/system traces, Play vitals, Xcode Instruments, MetricKit, and crash diagnostics. Correlate native traces with JS marks and backend request IDs.

107. How do you measure startup correctly?#

Define cold/warm/hot precisely, start from controlled process state, mark milestones, run repeated tests on representative devices, report distributions, and separate native launch, JS initialization, data hydration, and rendering.

108. What is a performance budget?#

A measurable limit such as p75 cold start, frame-drop rate, bundle size, or API latency. Enforce key budgets in CI or release gates and review them by device/network cohort.

109. How do you optimize without hiding correctness bugs?#

Establish baselines and invariants, change one bottleneck at a time, add tests/telemetry, validate lifecycle and failure cases, and use staged rollout with rollback.

110. What makes observability actionable?#

Structured errors, release/build identifiers, device/app context, breadcrumbs, traces, correlation IDs, privacy-safe user/session linkage, alerts tied to SLOs, and ownership/runbooks.

111. Logging versus metrics versus tracing?#

Logs describe events, metrics aggregate behavior, and traces connect latency across operations/services. A production system typically needs all three with shared identifiers.

112. How would you diagnose a slow screen?#

Break it into navigation, data fetch, JS computation, React render, layout/native draw, and images. Add marks/traces, compare cached/uncached states, inspect backend latency, then optimize the dominant segment.

113. How do you prevent performance regressions?#

Keep representative benchmarks, automate budgets where stable, review bundle/native dependency changes, monitor production percentiles by release, and use canary/phased rollout.

114. What is the danger of average latency?#

Averages hide poor tail behavior. Use p50/p75/p95/p99 and segment by device, OS, geography, and network to see the experience of affected users.


11. Networking, backend services, and distributed systems#

115. REST, GraphQL, WebSocket, MQTT, and NATS—when would you use each?#

REST fits resource APIs; GraphQL fits flexible client-selected graphs; WebSocket supports interactive bidirectional sessions; MQTT suits lightweight device messaging; NATS suits backend messaging. Choose from delivery guarantees, topology, scale, and operations—not fashion.

116. What is idempotency?#

Repeating an operation has the same intended effect as performing it once. For payments or create actions, send a stable idempotency key and enforce uniqueness/results on the server.

117. At-most-once, at-least-once, and exactly-once delivery?#

At-most-once may lose messages; at-least-once may duplicate them; exactly-once is usually achieved only within constrained systems through deduplication and transactional guarantees. Consumers should generally be idempotent.

118. How do you make a mobile workflow resilient to retries?#

Persist operation intent, use client-generated IDs/idempotency keys, distinguish retryable failures, reconcile server truth, bound retries, and show an honest pending state.

119. How do you handle pagination?#

Cursor pagination is more stable under concurrent inserts than offsets. Keep a deterministic sort, deduplicate by ID, handle refresh versus load-more separately, and recover from invalid cursors.

120. How do you version APIs without breaking older apps?#

Prefer additive changes and tolerant readers, keep old contracts for a supported window, negotiate capabilities, track active app versions, and provide forced upgrade only for justified security/compatibility cases.

121. How do you handle partial failure in a distributed feature?#

Define the source of truth and state machine, use timeouts/retries/idempotency, compensate rather than assume rollback across services, expose pending/failed states, and trace the workflow end to end.

122. What should a mobile engineer know about caching?#

Cache keys, freshness, invalidation, authorization scope, storage limits, offline semantics, and stale data UX. Never cache sensitive or user-specific data under unsafe shared keys.

123. What is backpressure?#

It is controlling producers when consumers cannot keep up. Bound queues/concurrency, batch carefully, drop or coalesce non-critical updates, and propagate pressure rather than exhausting memory.

124. How would you secure WebSocket reconnects?#

Refresh credentials safely, reauthorize subscriptions server-side, use backoff/jitter, restore sequence position where supported, deduplicate events, and stop reconnecting on terminal auth failures.


12. Testing and quality strategy#

125. What should the React Native test pyramid look like?#

Many fast unit/domain tests, focused component/integration tests, contract tests at boundaries, and a small set of high-value E2E journeys. Production monitoring completes the strategy.

126. Jest versus React Native Testing Library?#

Jest is the runner/assertion/mocking foundation. RNTL renders components and tests behavior through user-facing queries and interactions.

127. Detox versus Maestro?#

Detox synchronizes deeply with React Native and supports code-driven E2E; Maestro emphasizes readable black-box flows and fast authoring. Evaluate reliability, native coverage, CI environment, debugging, and team skills.

128. What should not be mocked?#

Do not mock away the behavior under test or all critical integration boundaries. Prefer realistic adapters/fakes and contract tests; excessive mocking verifies implementation details rather than outcomes.

129. How do you test a native module?#

Unit-test native implementation, test generated/interface contracts, provide JS adapter tests, run platform integration tests on real/emulated devices, and cover threading, cancellation, errors, lifecycle, and large inputs.

130. How do you test offline sync?#

Cover loss before/during/after requests, retries, process death, duplicated operations, out-of-order responses, conflict resolution, clock skew, expired auth, and storage migration.

131. How do you reduce flaky E2E tests?#

Remove arbitrary sleeps, wait on observable states, control backend/test data, isolate tests, capture artifacts, track flake rate, quarantine only briefly, and assign ownership.

132. What is contract testing?#

It verifies that consumer expectations and provider behavior remain compatible. It is especially useful where mobile releases cannot be updated immediately after a backend change.

133. How would you define CI quality gates?#

Fast deterministic gates such as formatting, lint, types, unit/component tests, architecture checks, security scans, and build verification. E2E/performance gates can run selectively based on cost and risk.

134. What is your strategy for testing multiple OS/device versions?#

Use analytics to define a representative matrix, cover minimum/current/beta-risk versions, include low-end Android and key form factors, automate the stable core, and use real devices for hardware/lifecycle cases.

135. How do you test feature flags and phased rollouts?#

Test both states, default/offline behavior, targeting, migration between states, metrics, rollback, and flag removal. Avoid combining many flags into an untestable state space.

136. What does “coverage” mean at Senior level?#

Line coverage is a signal, not proof. Focus on business risk, decision paths, boundary contracts, failure handling, mutation/regression value, and production escape rate.


13. CI/CD, releases, EAS, App Store, and Google Play#

137. Describe a mature mobile delivery pipeline.#

Immutable source and lockfiles produce signed artifacts through repeatable builds; gates verify quality/security; artifacts move through internal testing, staged rollout, monitoring, and rollback/mitigation with full traceability.

138. How do you manage mobile signing secrets in CI?#

Use a managed secret store, least privilege, short-lived access where possible, protected branches/environments, audit logs, rotation, and no secrets in repository or logs.

139. What is a phased rollout?#

A release reaches an increasing percentage or cohort while health metrics are monitored. Define guardrails, pause criteria, ownership, and rollback/mitigation before starting.

140. What can and cannot be rolled back on mobile?#

Store binaries usually cannot be instantly replaced on installed devices. You can halt rollout, disable features server-side, ship an expedited fix, or roll back compatible OTA JS/assets. Native/schema/backend compatibility must anticipate mixed versions.

141. What is safe OTA update design?#

Only ship compatible JS/assets for the installed native runtime, sign/verify updates, stage rollout, monitor startup/crashes, keep a known-good fallback, and avoid changing native capabilities through OTA assumptions.

142. EAS Build versus EAS Update?#

EAS Build creates native binaries in a reproducible build service. EAS Update delivers compatible JS/assets to existing binaries; runtime/version policy determines compatibility.

143. How do you handle database/storage migration with rollback?#

Prefer forward/backward-compatible migrations, delay destructive deletion, record schema version, test historical states, and ensure an older binary or OTA bundle will not corrupt newer data.

144. What information should a release checklist include?#

Scope, risk, migrations, flags, permissions/privacy changes, store metadata, test evidence, monitoring dashboards, rollout stages, owners, communication, rollback/kill-switch plan, and post-release verification.

145. Common reasons for App Store rejection?#

Broken functionality, inaccurate privacy declarations, missing account deletion or review access, payment-rule violations, misleading metadata, permission misuse, or policy-incompatible content. Verify current guidelines before submission.

146. Common Google Play release concerns?#

Target API compliance, data-safety declarations, permissions, signing, policy-sensitive SDKs/content, crashes/ANRs, testing-track requirements, and store listing accuracy. Verify current Play policies for each release.

147. How do you manage release trains across multiple squads?#

Define integration contracts, code freeze/cut rules, ownership, compatibility windows, automated integration tests, release branches only when justified, and a single transparent readiness dashboard.

148. Git flow, trunk-based development, or release branches?#

Prefer short-lived branches and frequent integration where the organization can support it. Mobile may need temporary release branches due to store lead time; minimize divergence and automate cherry-pick/release tracking.

149. How do A/B tests differ from feature flags?#

A flag controls availability; an experiment assigns stable cohorts and measures a predefined hypothesis. Experiments need guardrails, sample sizing, exposure logging, and protection against overlapping-test contamination.

150. What would make you stop a rollout?#

Regression beyond agreed thresholds in crashes, ANRs, startup, core funnel, security, data integrity, payments/payroll, or customer support signals. Criteria and decision owners must be defined in advance.


14. Security, privacy, and healthcare/compliance#

151. What are the primary mobile security threats?#

Credential theft, insecure local storage, weak transport, tampering/repackaging, exposed secrets, vulnerable dependencies/SDKs, unsafe deep links/webviews, excessive logging, authorization flaws, and privacy leakage.

152. Why can a mobile app not safely hold a secret?#

The binary and runtime are controlled by the user and can be inspected or instrumented. Public client identifiers may exist, but privileged secrets must remain server-side.

153. How should tokens be stored?#

Store the minimum necessary in Keychain/Keystore-backed secure storage, use short-lived access tokens and controlled refresh, clear on logout/revocation, and avoid logs/backups/clipboard.

It can reduce some interception risk but adds rotation, outage, debugging, and proxy/compliance complexity and does not replace TLS or server authorization. Use it only with a threat model and a safe rotation/bypass design.

155. How do you secure a WebView?#

Avoid it for sensitive flows when possible; allowlist origins/navigation, disable unnecessary file/content access and JS bridges, validate messages, use HTTPS, isolate cookies, and prevent arbitrary script injection.

156. Authentication versus authorization?#

Authentication establishes identity; authorization decides permitted actions/resources. The server must enforce authorization for every protected operation regardless of client UI.

157. How do you prevent sensitive-data leakage through logs?#

Define data classification, redact by default, prohibit tokens/health/PII in logs, sanitize third-party reporting, restrict access and retention, and test telemetry payloads.

158. What does GDPR/privacy-by-design mean for a mobile engineer?#

Collect only necessary data for explicit purposes, obtain valid consent where required, secure processing, support access/deletion, minimize retention, document processors/SDKs, and make privacy defaults safe.

159. How do you evaluate a third-party SDK?#

Review permissions, collected data, network endpoints, security history, maintenance, licenses, binary/performance impact, platform policy/privacy disclosures, data residency, and removal strategy.

Show understandable purpose/version, record auditable consent server-side, support withdrawal and policy changes, and ensure processing actually respects consent rather than treating it as UI state only.

161. How do you approach root/jailbreak detection?#

Treat it as a risk signal, not an absolute trust boundary; it is bypassable and may affect legitimate users. Protect sensitive actions with server-side controls, attestation where appropriate, and layered defenses.

162. What is threat modeling?#

Identify assets, actors, trust boundaries, abuse cases, impact, and mitigations early. Revisit when adding native SDKs, storage, deep links, payments, identity, or cross-module capabilities.

163. How should encryption keys be managed for media transfer?#

Use vetted cryptography, unique nonces/IVs, authenticated encryption, secure key generation/storage/exchange, rotation and revocation, and never invent a custom cipher. Minimize plaintext lifetime and log nothing sensitive.

164. What is dependency/supply-chain security?#

Pin and review dependencies, verify lockfiles/artifacts, scan vulnerabilities and licenses, minimize install scripts, protect CI identities, generate provenance/SBOM where required, and update with tested rollouts.


15. Third-party services and platform SDK integration#

165. How would you integrate push notifications robustly?#

Model permission and token lifecycle, register tokens server-side per installation/user, handle rotation/logout, route foreground/background/tapped messages, deduplicate, secure payloads, and measure delivery-to-action.

166. Why should push payloads avoid sensitive data?#

Notification services and lock screens may expose payload content. Send minimal identifiers and fetch authorized content securely after app activation.

167. How do you integrate analytics without damaging privacy or quality?#

Define a governed event schema, ownership/versioning, consent and minimization, validation, sampling, offline batching, and data-quality monitoring. Do not let each feature invent unreviewed events.

168. How do crash-reporting tools map minified/native stacks?#

Upload JavaScript source maps and native symbols/mapping files for the exact build. Tag releases consistently and restrict sensitive context.

169. How do you design authentication with an external identity provider such as Zitadel?#

Use standard OAuth/OIDC flows appropriate for public clients, PKCE, system browser, validated redirect URIs/state/nonce, secure token storage, refresh/revocation, and server-side authorization.

170. What is the risk of multiple analytics and marketing SDKs?#

Startup cost, binary growth, network/battery use, privacy leakage, dependency conflicts, crashes, and duplicated events. Load conditionally where valid and maintain an SDK inventory.

171. How would you wrap a vendor SDK?#

Place it behind an internal interface, map vendor models/errors to domain concepts, centralize initialization/config, add telemetry and tests, and preserve the ability to replace or disable it.

172. How do you handle an unreliable third-party service?#

Use timeouts, circuit breaking or fallback where appropriate, cached/degraded UX, bounded retries, status monitoring, feature isolation, and clear ownership/escalation.


16. React web, Next.js, Webpack, and Tailwind CSS#

173. React DOM versus React Native rendering?#

The React programming model is shared, but host components, events, styling, accessibility, layout details, navigation, and platform APIs differ. Share domain logic and design tokens more readily than view code.

174. CSR, SSR, SSG, and incremental regeneration?#

CSR renders mainly in the browser; SSR renders per request; SSG renders at build time; incremental regeneration updates static output after deployment. Choose per SEO, freshness, personalization, latency, and cost.

175. What does hydration mean?#

React attaches behavior to server-rendered HTML. A hydration mismatch occurs when server and initial client output differ due to nondeterministic data, browser-only APIs, time, locale, or invalid markup.

176. What does Webpack do?#

It builds a dependency graph and transforms, bundles, splits, and optimizes application assets through loaders and plugins. Senior concerns include caching, tree shaking, chunk strategy, source maps, and build performance.

177. What is tree shaking?#

It removes statically provable unused exports, most effective with ESM and correct side-effect metadata. Dynamic imports/CommonJS/side effects can limit it; confirm with bundle analysis.

178. Code splitting versus lazy loading?#

Code splitting creates separate chunks; lazy loading decides when to fetch/execute them. Split by route or expensive feature while avoiding excessive tiny chunks and loading waterfalls.

179. How do you investigate a large web bundle?#

Use a bundle analyzer, inspect duplicate/transitive dependencies and source maps, replace oversized libraries, ensure tree shaking, split routes/features, and measure real loading and interaction metrics.

180. How does Tailwind CSS scale in a design system?#

Centralize tokens, constrain arbitrary values, build reusable accessible components, enforce responsive/state conventions, and ensure content scanning recognizes generated class names.

181. Tailwind CSS versus NativeWind?#

They share utility-style ergonomics, but NativeWind maps utilities to React Native style semantics and platform behavior. Not all CSS or responsive/interaction concepts map one-to-one.

182. How can mobile and web share code safely?#

Share domain models, validation, API clients, state machines, tokens, and selected hooks. Use platform adapters and explicit entry points; avoid the lowest-common-denominator UI trap.

183. What web security topics should a mobile-focused engineer know?#

XSS, CSRF, CSP, CORS, secure cookies, OAuth redirect risks, SSR data leakage, dependency security, clickjacking, and server/client trust boundaries.

184. How would you organize a monorepo for web and mobile?#

Use workspace packages with clear ownership and dependency direction, shared lint/types/testing, platform-specific adapters, independent builds, affected-only CI, and controlled versioning.


17. Super App architecture#

185. What makes a Super App technically difficult?#

Many domains and squads share identity, navigation, design, data, release, observability, and device resources. Without governance, it becomes a tightly coupled large binary with slow builds and risky releases.

186. How would you define Super App module boundaries?#

Use business capabilities with owned contracts, navigation entry points, permissions, data/API boundaries, telemetry, and versioning. Prevent cross-module imports except through public interfaces.

187. Monorepo or polyrepo for a Super App?#

A monorepo simplifies atomic changes, shared tooling, and discovery; polyrepos strengthen autonomy and access isolation. Choose based on team topology and release model, then invest in contract/version tooling for the chosen trade-off.

188. How can multiple squads integrate safely?#

Published module contracts, ownership, compatibility rules, automated integration/contract tests, feature flags, preview builds, release trains, observability, and clear escalation.

189. Should Super App modules be downloaded dynamically?#

Only if business value justifies security, platform-policy, compatibility, offline, startup, and rollback complexity. Store rules constrain executable code delivery; server-driven UI/content is different from arbitrary code loading.

190. How do you avoid a Super App startup penalty?#

Keep the shell small, lazily initialize modules/SDKs, load only critical identity/config, defer secondary work, budget each module’s startup cost, and enforce it in integration tests.

191. How would navigation work across independently owned modules?#

Use a typed route registry and capability-based navigation contract. Modules expose public destinations and parameters; the shell handles auth, deep links, analytics, and unavailable-module fallback.

192. How do you keep authentication consistent?#

The shell owns identity/session and exposes a minimal auth capability. Modules request authorized domain operations, not raw long-lived credentials; server authorization remains authoritative.

193. How do you govern shared dependencies?#

Define supported versions, ownership, upgrade cadence, compatibility tests, and exceptions. Prevent each module from shipping duplicate heavy runtimes or incompatible singleton libraries.

194. How do you isolate a failing module?#

Use error boundaries/native crash containment where possible, timeouts, circuit breakers for remote dependencies, kill switches, route-level rollback, and module-specific telemetry.

195. How do you design a Super App platform team?#

Treat the platform as an internal product: stable APIs, documentation, templates, paved roads, service levels, migration support, metrics, and feedback from consuming squads—not centralized gatekeeping alone.

196. What metrics show Super App platform health?#

Startup/frame/crash metrics, binary/build time, release lead time, integration failure rate, module adoption, dependency duplication, incident ownership, and product journey outcomes.


18. AI-first software engineering, especially React Native#

197. How do you use AI beyond code completion?#

Use it for requirement decomposition, repository discovery, design alternatives, ADR drafts, test generation, implementation, review support, debugging hypotheses, migration planning, and documentation—with human decisions and machine-verifiable gates.

198. What should not be delegated blindly to AI?#

Product intent, security/privacy decisions, irreversible production actions, architecture trade-offs, policy interpretation, and final accountability. AI output is untrusted until verified.

199. What is an AI agent harness?#

The controlled environment around an agent: source-of-truth specs, scoped tools, coding rules, reusable skills, plan/review checkpoints, test commands, security boundaries, and completion evidence.

200. How do you stop an AI agent from making broad unsafe changes?#

Limit permissions and scope, use isolated branches/worktrees, prohibit secret/production access, require explicit plans for risky work, block destructive commands, cap diffs, and enforce review plus CI.

201. How do you evaluate AI-generated code quality?#

Apply the same or stronger standards: correctness tests, types/lint, security checks, architecture boundaries, performance, maintainability, dependency review, and human inspection of intent and edge cases.

202. What does a high ratio of AI-co-authored commits prove—and not prove?#

It shows AI participated heavily in implementation, not that engineering judgment was automated or quality was proportionally better. Explain how work was attributed, reviewed, tested, and measured in outcomes.

203. How would you use AI to migrate a React Native app to New Architecture?#

Let agents inventory modules/dependencies, classify compatibility, create incremental plans and repetitive adapters, and generate tests. Humans own architecture, native debugging, performance baselines, security, and rollout decisions.

204. How can AI help with cross-platform native modules?#

It can scaffold specs/Codegen bindings, compare Android/iOS parity, generate boundary tests, and document thread/error contracts. Validate every native API, lifecycle assumption, and memory/thread behavior on real platforms.

205. How do you prevent hallucinated APIs and packages?#

Ground the agent in pinned repository code and official docs, require it to inspect installed versions, compile early, prohibit unapproved dependencies, and verify APIs through types/tests/builds.

206. How do you protect private code and healthcare data when using AI?#

Use approved enterprise tooling and data controls, never provide production secrets/PII/PHI, minimize context, redact fixtures/logs, enforce retention/access policies, and audit tool permissions.

207. What metrics should measure AI-assisted delivery?#

Lead time, review time, escaped defects, rework, CI failure, security findings, developer throughput/satisfaction, and business outcomes. Commit percentage or lines generated alone can reward the wrong behavior.

208. How would you create reusable AI skills for React Native?#

Encode repository-specific architecture, commands, platform conventions, acceptance criteria, known failure modes, examples, and verification steps. Version and test skills like code, with owners and feedback loops.

209. Why is source-of-truth documentation essential for agents?#

Agents amplify ambiguity. Versioned requirements, API contracts, design decisions, and acceptance criteria reduce inconsistent implementations across repositories and humans.

210. How should AI participate in code review?#

Use it to find patterns, missing tests, security issues, and explain diffs, but require human review for intent, architecture, product behavior, and subtle risk. AI should augment—not approve its own work.

211. How do you keep multiple parallel agents from conflicting?#

Partition work by clear contracts/files, use independent worktrees, define shared interfaces first, sequence dependent changes, run integration gates, and assign a human integrator.

212. What is TDD’s value in agentic development?#

Executable expectations constrain generation and expose regressions quickly. Tests must still reflect correct requirements and avoid overfitting to an agent’s implementation.


19. Senior engineering judgement, leadership, and product trade-offs#

213. How do you decide whether to refactor or ship?#

Compare customer value and deadline with change risk, defect history, future cost, reversibility, and available mitigations. Make the debt explicit with owner/trigger; avoid both perfectionism and invisible debt.

214. What does “strong opinions, loosely held” mean in practice?#

Present a clear evidence-based recommendation and trade-offs, invite challenge, define decision criteria, and change position when better evidence appears. After the decision, commit to execution.

215. How do you make an architecture decision?#

Clarify forces and constraints, identify realistic options, prototype unknowns, compare trade-offs, record an ADR, define success/rollback, and revisit when assumptions change.

216. How do you communicate technical risk to non-technical stakeholders?#

Explain user/business impact, likelihood, time horizon, options, cost, and mitigation in plain language. Make the decision and accountable owner explicit.

217. How do you mentor without becoming a bottleneck?#

Set standards and context, ask guiding questions, delegate real ownership, use examples and pairing selectively, create reusable documentation/tooling, and measure whether others can decide independently.

218. How do you handle an underperforming engineer?#

Use specific observed behavior and expected outcomes, check context/support gaps, agree on a measurable improvement plan and cadence, provide help, document fairly, and involve management appropriately.

219. How do you balance platform consistency and squad autonomy?#

Standardize high-leverage cross-cutting concerns through paved roads and contracts; allow local choice within guardrails. Exceptions should be explicit, evidence-based, and reviewed for platform impact.

220. What belongs in a definition of done for mobile?#

Acceptance criteria, tests, accessibility, analytics, security/privacy, performance, migration/backward compatibility, documentation, feature flags, observability, release notes, and rollout readiness proportional to risk.

221. How do you respond to a production incident?#

Stabilize users first, establish incident ownership/comms, collect evidence, mitigate or roll back, verify recovery, preserve a timeline, then run a blameless review with owned prevention actions.

222. How do you measure engineering effectiveness?#

Use a balanced view of customer outcomes, reliability, delivery flow, quality, maintainability, and team health. Avoid optimizing a single proxy such as commits, story points, or raw velocity.


20. Deep dives: AI-assisted delivery, performance, native modules, and CI/CD#

225. How do you deliver a large set of screens quickly without sacrificing quality?#

We decomposed work into independent features, established shared architecture and contracts first, used parallel AI agents, required human plan review, followed TDD, and blocked completion on typecheck, lint, tests, and review. I would also explain the scope definition and what “production” included.

226. What are the risks of parallel AI agents?#

Conflicting changes, inconsistent architecture, duplicated abstractions, stale assumptions, and integration failures. I mitigate them with work partitioning, a shared source of truth, ADRs, worktrees, bounded permissions, integration ownership, and blocking gates.

227. How do you improve cold start from ~10s to ~2s?#

I first measured startup milestones to locate critical-path work. Then I cached reusable startup data, lazy-loaded noncritical functionality, and deferred initialization until after the app became usable. I validated repeated cold starts on representative devices and monitored regression after release.

228. How do you prove a cold-start result is not cherry-picked?#

Define cold start and measurement boundaries, test repeated runs across representative devices/builds, report distributions rather than one run, compare the same conditions before/after, and validate production telemetry by release cohort.

229. When is a native module required for a large encrypted file transfer?#

The measured bottleneck required more efficient chunked processing and native access than the existing JS path provided. The native boundary handled chunked encrypted transfer while preserving security and reliability. Explain the exact bottleneck, threading, memory, and cross-platform implementation.

230. What trade-offs exist in chunked encrypted transfer?#

Chunk size affects memory, throughput, retry granularity, and overhead. The design also needs authenticated encryption, nonce/key correctness, ordering, integrity, cancellation, resumability, backpressure, progress, and cleanup of partial/plaintext data.

231. How would you design a chunked-transfer native module for the New Architecture?#

Define a typed TurboModule specification, expose coarse asynchronous operations and progress/cancellation, process I/O/crypto off critical threads, minimize copies across the boundary, map native errors consistently, and test large files, interruption, lifecycle, and memory.

232. How do you replace slow local builds with GitLab CI and Fastlane?#

I moved repeatable signing, build, and distribution steps into a self-hosted GitLab pipeline and used Fastlane to automate platform workflows and Firebase distribution. The key outcomes were removing developer blocking time and making releases consistent and auditable.

233. How would you improve a mobile CI/CD design?#

Add reproducible dependency caching, parallel quality gates, protected signing identities, artifact promotion rather than rebuild, SBOM/security checks, release provenance, staged store rollout, automated health gates, and documented disaster recovery.

234. How do four or five blocking CI gates improve quality without slowing delivery?#

Keep gates deterministic, fast, parallel, and risk-based; run cheap feedback first and cache aggressively. Track gate duration, failure usefulness, flakes, escaped defects, and remove low-signal checks.

235. How does a healthcare or regulated domain influence architecture?#

It raises the standard for privacy, consent, identity, auditability, secure media, data minimization, failure handling, and third-party review. Compliance must be embedded in design and operations rather than added before release.

236. How do you design a cross-repo source-of-truth pipeline?#

It aligned mobile, web, backend, product, design, engineers, and AI agents around versioned requirements, API contracts, and ADRs. The goal was to reduce conflicting interpretations and make implementation and verification traceable across repositories.

237. How does native Android knowledge help in React Native?#

It helps me reason about lifecycle, threading, permissions, Gradle, performance, native SDKs, background work, and production crashes instead of treating the platform as a black box. It also lets me choose the correct boundary between JS and native.

239. How does full-stack experience improve mobile delivery?#

I can reason across API contracts, authentication, messaging, caching, databases, observability, and deployment, so I diagnose end-to-end problems and design resilient mobile behavior. I still preserve service ownership and avoid bypassing domain experts.


21. Mobile engineering scenarios#

241. Design a release strategy for a new payroll feature in the Super App.#

Separate backend capability, client UI, and activation flags; maintain compatibility with older clients; test high-risk payroll cases; release internally, then controlled cohorts; monitor correctness, crashes, latency, and support; define kill switches and reconciliation before rollout.

242. A mobile release passes QA but crashes increase after 5% rollout. What do you do?#

Pause rollout, compare crash signature/device/OS/cohort, disable the responsible flag or compatible OTA path if safe, communicate ownership and impact, reproduce and patch, then resume only after guardrails recover.

243. Product wants a feature this week; the robust architecture takes a month. What do you recommend?#

Clarify the customer outcome, identify the smallest reversible slice, protect security/data integrity, quantify debt and operational risk, add observability/kill switch, and agree on a dated follow-up trigger. Reject shortcuts whose failure is irreversible or non-compliant.

244. A subsequent squad needs to integrate into the mobile platform. What is your onboarding design?#

Provide module contracts, templates, design tokens, CI/release standards, sample implementation, local/preview environments, observability, ownership, and an integration checklist. Pair initially, then measure whether the squad can ship independently.

245. How would you run an A/B test on mobile onboarding?#

State the hypothesis and primary/guardrail metrics, assign stable server-controlled cohorts, log exposure, keep versions compatible, calculate sample requirements, monitor crashes/drop-off, avoid cross-experiment contamination, and remove the losing path/flag afterward.

246. A backend team changes an API while old app versions remain active. What do you do?#

Keep additive/backward-compatible responses or version/capability negotiation, use contract tests, measure active versions, define a deprecation window, and only force upgrades when security or data correctness justifies it.

247. The Super App has slow startup after several squads added SDKs. Lead the investigation.#

Create a startup trace and per-module budget, inventory auto-initializers, identify critical-path and main-thread work, defer/lazy-load SDKs, remove duplicates, hold module owners to budgets, and enforce regression checks in integration CI.

248. Mobile and web teams implement the same employment workflow differently. How do you align them?#

Agree on domain rules and API contract as shared source of truth, share validation/state machines where useful, keep platform UI idiomatic, add contract/acceptance tests, and assign product ownership for intentional differences.

249. A senior engineer strongly disagrees with your architecture proposal.#

Make constraints and decision criteria explicit, ask them to challenge assumptions, compare options with evidence or a small prototype, document the ADR, seek the appropriate decider if needed, and commit to the chosen path while defining review triggers.

250. How would you establish observability for a Super App?#

Standardize release IDs, traces, structured events, privacy-safe identity, crash/native symbols, performance marks, and module ownership. Build dashboards and SLOs for shared shell plus critical journeys, with drill-down by module/version/device.

251. What mobile security baseline would you mandate?#

Threat modeling, secure token/key storage, TLS, server-side authorization, dependency/SDK governance, safe links/WebViews, redacted telemetry, privacy declarations, signing protection, security testing, incident response, and platform-specific controls.

252. How do you decide whether to buy or build a third-party capability?#

Compare time-to-value with security/privacy/compliance, feature fit, uptime/support, SDK quality, performance/binary impact, data ownership, lock-in, cost at scale, and exit strategy. Prototype the highest-risk assumptions.

253. How would you evolve Git flow and code review standards across teams?#

Prefer small short-lived changes, required checks and ownership, review SLAs, risk-based approvals, protected release paths, and automated conventions. Measure lead time, rework, escaped defects, and review concentration.#

22. System-design drills#

256. Design an offline-capable employee leave-request feature.#

Model local drafts and submitted operations separately, assign client IDs/idempotency keys, encrypt sensitive storage, queue mutations, show sync state, reconcile authoritative server status, handle conflicts/expired auth, and observe retry/failure rates.

257. Design secure document upload for employee records.#

Request short-lived authorized upload metadata, validate size/type, stream/chunk with progress and cancellation, encrypt transport/storage, scan server-side, make completion idempotent, restrict access, redact telemetry, and support resume/cleanup.

258. Design app-wide notifications for many Super App modules.#

Create a central notification capability with typed module payloads, server-side authorization/preferences, token lifecycle, deep-link registry, deduplication, inbox/read state, analytics, privacy controls, and module ownership.

259. Design feature flags for mobile and web.#

Use a shared flag service with typed definitions, environment/cohort targeting, safe defaults, cached offline evaluation where allowed, server enforcement for sensitive rules, exposure logging, audit history, and expiry/removal ownership.

260. Design a shared design system across React Native and React web.#

Share semantic tokens, accessibility standards, component contracts, documentation and visual tests. Implement platform-native components behind consistent intent rather than forcing identical DOM/native code.

261. Design chat or real-time messaging for a mobile app.#

Use authenticated realtime transport, local optimistic IDs, ordered server sequence, deduplication, pagination, offline queue, delivery/read states, reconnection with resume, attachment security, push fallback, and observability.

262. Design a mobile analytics platform.#

Govern typed events and schema versions, consent, privacy classification, local queue/batching, retry/backpressure, release/session context, sampling, data-quality checks, and ownership. Business events should remain stable across UI refactors.

263. Design modular navigation for a Super App.#

Let the shell own route registry, auth/deep-link resolution, analytics and fallbacks. Modules expose typed destinations and capabilities, can be flag-disabled, and cannot reach private routes across boundaries.

264. Design an app version compatibility strategy.#

Expose server capabilities/minimum-supported versions, keep APIs additive, gate client features, monitor active versions, define deprecation windows, offer graceful read-only/degraded states, and reserve forced upgrades for real risk.

265. Design a crash-free staged release process.#

Build once, promote the signed artifact, validate internal/beta cohorts, roll out progressively, watch crash/ANR/startup/core-journey guardrails, pause automatically or manually, use flags/OTA only within safe compatibility, and complete a post-release review.


23. Rapid-fire checks#

266. Why must UI-thread work be short?#

It must meet each frame deadline; blocking it causes jank, ANRs, or watchdog termination.

267. Why are large JSON payloads expensive?#

Network transfer, allocation, parsing, copies across boundaries, garbage collection, and rendering all increase.

268. Why test on low-end Android devices?#

They expose CPU, memory, storage, GPU, and vendor-specific constraints hidden by flagship simulators.

269. Why are retries dangerous?#

They amplify load and can duplicate non-idempotent actions; bound them and add backoff/jitter.

270. Why should timestamps come from the server for critical workflows?#

Device clocks can be wrong or manipulated; server time provides authoritative ordering and expiry decisions.

271. Why should feature flags have expiry dates?#

Permanent flags create dead paths, combinatorial tests, and hidden architecture complexity.

272. Why use semantic design tokens?#

They express intent, support themes/accessibility, and decouple product meaning from raw values.

273. Why avoid storing navigation state as business truth?#

Navigation is presentation state; domain truth must survive process death, deep links, and alternate flows.

274. Why upload source maps and native symbols?#

Without exact build artifacts, production stack traces are difficult or impossible to interpret.

275. Why can an OTA update be risky?#

New JS may expect native capabilities or data schemas absent in the installed binary; runtime compatibility and rollback are mandatory.

276. Why is client-side authorization insufficient?#

Clients can be modified or bypassed; only the server controls protected resources.

277. Why avoid high-cardinality metric labels?#

They explode storage/cost and reduce query reliability; keep unique IDs in traces/logs instead.

278. Why is console.log not production observability?#

It lacks structure, correlation, governance, privacy controls, aggregation, and actionable alerting.

279. Why should architecture decisions be reversible where possible?#

Requirements and evidence change; reversible decisions reduce the cost of learning and uncertainty.

280. What distinguishes a Senior answer from a Junior answer?#

A Senior answer connects mechanics to trade-offs, failure modes, measurement, security, rollout, maintenance, team ownership, and customer outcomes.


24. System-design answer framework and distributed-systems review#

Framework trả lời (thuộc lòng — 6 bước)#

  1. Clarify requirements (5'): functional (user làm gì) + non-functional (scale bao nhiêu user, offline?, realtime?, security/compliance?, platform range?). Đừng nhảy vào vẽ ngay — hỏi trước là tín hiệu senior.
  2. API contract (5'): endpoints/payloads chính, pagination, error shape. Nêu trade-off REST vs GraphQL/BFF nếu relevant.
  3. Data model & local storage (5'): entities, gì cache local (MMKV/SQLite/WatermelonDB), normalize thế nào.
  4. Data flow & sync (10'): fetch/cache/invalidate, offline queue, conflict resolution, realtime channel.
  5. Edge cases & failure modes (5–10'): mất mạng giữa chừng, app killed, token hết hạn, duplicate request (idempotency), clock skew, permission denied.
  6. Quality & rollout (5'): đo lường gì (analytics/monitoring), test gì, ship sau flag + phased rollout.

Từ khóa phải dùng tự nhiên: idempotency key, optimistic update, stale-while-revalidate, cursor pagination, exponential backoff + jitter, at-least-once delivery, eventual consistency, kill-switch.


Ôn nhanh distributed systems#

Khái niệm1 câu + ví dụ
IdempotencyRequest retry không gây hiệu ứng đôi — idempotency key cho payment/message send
Pub/SubDecouple producer/consumer — publish events, consumers subscribe độc lập (message broker như NATS/Kafka)
At-least-once vs at-most-onceQueue thường at-least-once → consumer phải idempotent
Eventual consistencyMobile cache/offline chính là eventual consistency — reconcile bằng server truth
BackpressureConsumer chậm hơn producer → buffer/drop/throttle (vd: sensor events)
Rate limitingToken bucket; client tôn trọng 429 + Retry-After, backoff + jitter
Cursor vs offset paginationCursor ổn định khi data đổi, offset trượt — mobile infinite scroll dùng cursor
Cache invalidationTTL + event-driven invalidate; client: React Query invalidateQueries theo mutation
DB indexingIndex cột trong WHERE/ORDER BY; trade-off write cost cao hơn

25. Coding drills to practice#

C. Coding drill (tự luyện, 30–45p/ngày)#

  1. Implement debounce, throttle, once, memoize, deepEqual (shallow trước, deep sau).
  2. Implement Promise.all / allSettled từ đầu; pTimeout(promise, ms); retry với exponential backoff.
  3. Flatten nested array/object; group by key; dedupe theo field.
  4. Event emitter class (on/off/emit) — hay được hỏi vì liên quan native events.
  5. Typed API client nhỏ với generics + discriminated union result ({ok:true,data}|{ok:false,error}).
  6. LeetCode easy–medium: two pointers, hashmap, sliding window — 15 bài là đủ cảm giác.

26. ITviec React Native question bank (Vietnamese, verbatim)#

Nguồn: https://itviec.com/blog/cau-hoi-phong-van-react-native/ Toàn bộ nội dung bên dưới được giữ nguyên văn từ bài viết gốc — không thêm/sửa/bớt.


Bài viết tổng hợp các câu hỏi phỏng vấn React Native phổ biến dành cho lập trình viên React Native, giúp hiểu rõ hơn về các kiến thức kỹ thuật quan trọng mà các ứng viên cần nắm vững. Từ việc trả lời các câu hỏi liên quan đến Redux, API, async/await, cho đến việc giải thích các khái niệm như Metro Bundler hay TurboModules, các câu hỏi này không chỉ kiểm tra khả năng nắm vững công nghệ mà còn đánh giá cách thức giải quyết vấn đề và tư duy logic của các lập trình viên React Native.

Đọc bài viết này để hiểu thêm về:

  • Câu hỏi phỏng vấn React Native cơ bản về React Native
  • Câu hỏi phỏng vấn React Native về thiết kế và cấu trúc ứng dụng
  • Câu hỏi phỏng vấn React Native về các thành phần giao diện
  • Câu hỏi phỏng vấn React Native về xử lý dữ liệu và API
  • Câu hỏi phỏng vấn React Native về tích hợp Native Modules và Native Code
  • Câu hỏi phỏng vấn React Native về kiểm thử
  • Câu hỏi phỏng vấn React Native về hiệu suất và tối ưu hoá
  • Câu hỏi phỏng vấn React Native về công cụ và thư viện hỗ trợ

React Native Developer là ai? Vai trò của React Native Developer là gì?#

Lập trình viên React Native là những chuyên gia trong việc phát triển các ứng dụng di động đa nền tảng, cho phép tạo ra sản phẩm hoạt động mượt mà trên cả iOS và Android. Với vai trò này, lập trình viên cần có kiến thức vững vàng về JavaScript và React, hiểu biết sâu sắc về hệ sinh thái React Native, và nắm bắt các khái niệm cơ bản như state management, navigation, và API integration.

Ngoài ra, khả năng giải quyết vấn đề, tối ưu hóa hiệu suất, và hiểu biết về các nguyên tắc thiết kế giao diện người dùng cũng là những kỹ năng quan trọng.

Để thành công, lập trình viên React Native không chỉ cần kỹ năng lập trình tốt mà còn phải có khả năng làm việc nhóm, tư duy sáng tạo và khả năng giao tiếp hiệu quả trong việc giải quyết các thách thức công nghệ và phối hợp với các thành viên khác trong dự án.

Đọc thêm: React Native: Chi tiết React Native là gì và Cách sử dụng A-Z

Câu hỏi phỏng vấn React Native cơ bản#

React Native là gì? Điểm khác biệt chính giữa React Native và React.js?

React Native là một framework mã nguồn mở được Facebook phát triển, cho phép các lập trình viên xây dựng ứng dụng di động cho cả hai nền tảng iOS và Android bằng JavaScript và React.

Điểm nổi bật của React Native là khả năng tạo ra ứng dụng mobile sử dụng các thành phần giao diện gốc (native components), giúp ứng dụng có hiệu suất và trải nghiệm người dùng gần giống như một ứng dụng native.

Điểm khác biệt chính giữa React Native và React.js:

Tiêu chíReact NativeReact.js
Mục đích sử dụngPhát triển ứng dụng di động (iOS và Android).Phát triển ứng dụng web.
Thành phần UISử dụng các thành phần native nhưView,Text,Image.Sử dụng các thành phần web nhưdiv,span,img.
Khả năng tái sử dụng mã nguồnCó thể tái sử dụng nhiều mã nguồn giữa các nền tảng di động.Chủ yếu sử dụng cho các ứng dụng web và không tương thích với ứng dụng mobile.
Giao diện người dùng (UI)Tạo giao diện native với các thành phần native UI.Tạo giao diện trên web sử dụng HTML và CSS.
Công cụ phát triểnSử dụng các công cụ nhưExpo,React Native CLI.Sử dụngWebpack,Babel, và các công cụ khác cho web.
Hiệu suấtHiệu suất gần tương đương với ứng dụng native.Hiệu suất phụ thuộc vào trình duyệt và JavaScript engine.
Khả năng truy cập phần cứngCó thể truy cập dễ dàng vào phần cứng như máy ảnh, GPS.Truy cập phần cứng hạn chế, cần sử dụng API của trình duyệt.

Các lợi ích của việc sử dụng React Native cho phát triển ứng dụng mobile?

Dưới đây là một số lợi ích nổi bật của React Native mà ứng viên có thể nhấn mạnh:

  • Phát triển đa nền tảng (Cross-platform): React Native cho phép phát triển ứng dụng cùng lúc cho cả iOS và Android chỉ với một mã nguồn duy nhất. Điều này giúp tiết kiệm thời gian và công sức khi không cần phải viết mã riêng cho từng nền tảng, từ đó tăng tốc độ phát triển sản phẩm.
  • Tiết kiệm chi phí phát triển: Do sử dụng chung một mã nguồn, chi phí phát triển và bảo trì ứng dụng được giảm thiểu đáng kể. Điều này rất hữu ích đối với các startup hoặc các công ty có ngân sách hạn chế.
  • Hiệu suất gần như ứng dụng gốc (Near-native performance): React Native cung cấp hiệu suất tốt và gần tương đương với ứng dụng native nhờ vào việc sử dụng các thành phần UI native của iOS và Android. Các ứng dụng xây dựng bằng React Native thường chạy mượt mà và đáp ứng tốt, đặc biệt đối với các ứng dụng không yêu cầu hiệu suất cao.
  • Cộng đồng lớn và hệ sinh thái phong phú: React Native có một cộng đồng lớn mạnh và hệ sinh thái phong phú với nhiều thư viện, công cụ và tài nguyên hỗ trợ. Điều này giúp các nhà phát triển dễ dàng tìm kiếm giải pháp cho các vấn đề họ gặp phải và học hỏi từ các kinh nghiệm thực tiễn.
  • Cập nhật dễ dàng với Hot Reloading: Tính năng Hot Reloading cho phép nhà phát triển cập nhật và xem ngay những thay đổi trong mã nguồn mà không cần phải biên dịch lại toàn bộ ứng dụng. Điều này giúp tăng hiệu suất làm việc và tiết kiệm thời gian phát triển.
  • Khả năng tích hợp với mã native: Trong trường hợp cần sử dụng các tính năng đặc thù của thiết bị hoặc hiệu suất cao hơn, React Native cho phép tích hợp mã native (Java, Swift, hoặc Objective-C) vào dự án. Điều này cung cấp sự linh hoạt cao hơn khi phát triển ứng dụng.

So sánh giữa React Native và các framework khác như Flutter, Xamarin

Bảng dưới đây phân tích sự khác nhau giữa React Native và các Framework khác:

Nền tảngReact NativeFlutterXamarin
Ngôn ngữ sử dụngJavaScript (React)DartC#
Hiệu suấtHiệu suất tốt nhưng phụ thuộc vào bridge giữa JavaScript và nativeHiệu suất cao, chạy mã native nhờ vào Dart và engine SkiaHiệu suất tốt, hỗ trợ native và Xamarin.Forms
Thời gian phát triểnNhanh, cộng đồng lớn, nhiều thư viện hỗ trợTương đối nhanh, nhưng học Dart cần thời gianNhanh, tích hợp tốt với hệ sinh thái của Microsoft
UI/UXSử dụng các thành phần native, UI thân thiện, dễ tùy chỉnhCung cấp các widget tùy chỉnh, UI nhất quán trên các nền tảngSử dụng Xamarin.Forms hoặc native UI tuỳ chỉnh
Tính nhất quán giữa nền tảngTương đối tốt, nhưng cần điều chỉnh cho từng nền tảngCao, hầu hết các tính năng giống nhau trên iOS và AndroidTốt với Xamarin.Forms, nhưng có thể gặp vấn đề với native
Khả năng mở rộngTốt, dễ dàng mở rộng và tích hợp thư viện bên ngoàiMạnh mẽ, dễ mở rộng với các gói bổ trợ (plugins)Tích hợp tốt với hệ sinh thái Microsoft, khả năng mở rộng mạnh
Chi phí duy trì ứng dụngTương đối thấp, phụ thuộc vào các thư viện bên thứ baThấp, nhiều công cụ hỗ trợ từ cộng đồng và GoogleCó thể cao hơn nếu sử dụng các công cụ Microsoft có phí
Khả năng tái sử dụng mãTốt, dễ tái sử dụng mã giữa các nền tảngRất tốt, Flutter cho phép viết một lần chạy trên mọi nền tảngTốt, đặc biệt với Xamarin.Forms
Khả năng tiếp cận code nativeDễ dàng, hỗ trợ các module native thông qua bridgeKhó hơn, cần viết plugin riêng cho native codeDễ dàng, sử dụng C# để viết mã native trực tiếp

Khái niệm Component trong React Native là gì?

Trong React Native, Component là thành phần cơ bản để xây dựng giao diện người dùng (UI).

Bạn có thể hiểu Component như các “khối xây dựng” của ứng dụng, mỗi Component đảm nhận một phần nhỏ của giao diện, giúp tạo ra các thành phần có thể tái sử dụng và quản lý dễ dàng hơn. Mỗi Component có thể nhận dữ liệu đầu vào (props) và trả về cấu trúc giao diện (UI) để hiển thị.

Có hai loại Component phổ biến trong React Native: Functional Component và Class Component.

Phân biệt giữa Functional Component và Class Component

Functional Component

Functional Component là dạng component được khai báo dưới dạng hàm (function). Đây là cách khai báo phổ biến hơn, dễ hiểu, và ngắn gọn. Kể từ khi React Hooks ra đời, Functional Component đã trở thành xu hướng chính vì khả năng dễ dàng quản lý state và các side effects.

Đặc điểm của Functional Component:

  • Được khai báo dưới dạng một hàm JavaScript.
  • Không có this context như Class Component.
  • Sử dụng React Hooks như useState, useEffect để quản lý state và lifecycle.
  • Nhẹ hơn và hiệu quả hơn về hiệu suất so với Class Component.

Ví dụ về Functional Component:

import React, { useState } from 'react';

const Greeting = (props) => {
  const [message, setMessage] = useState('Hello');

  return (
    <View>
      <Text>{message}, {props.name}!</Text>
    </View>
  );
};

export default Greeting;
Class Component

Class Component là kiểu component truyền thống trong React, được khai báo dưới dạng lớp (class). Trước khi React Hooks xuất hiện, Class Component là lựa chọn chính khi cần quản lý state hoặc các lifecycle methods.

Đặc điểm của Class Component:

  • Được khai báo dưới dạng một class và mở rộng từ React.Component.
  • Sử dụng this để truy cập vào các thuộc tính và phương thức của component.
  • Sử dụng các lifecycle methods như componentDidMount, componentDidUpdate, componentWillUnmount để quản lý chu kỳ sống của component.

Ví dụ về Class Component:

import React, { Component } from 'react';

class Greeting extends Component {
  constructor(props) {
    super(props);
    this.state = {
      message: 'Hello',
    };
  }

  render() {
    return (
      <View>
        <Text>{this.state.message}, {this.props.name}!</Text>
      </View>
    );
  }
}

export default Greeting;
So sánh Functional Component và Class Component
ComponentFunctional ComponentClass Component
Khai báoSử dụng hàmSử dụng class
Quản lý StateuseState,useReducer(Hooks)Sử dụngthis.statevàsetState
LifecycleuseEffect(Hooks)componentDidMount,componentDidUpdate,…
Độ phức tạpĐơn giản, ngắn gọnPhức tạp hơn khi sử dụng lifecycle
Hiệu suấtNhanh hơn vì ít phức tạpCó thể chậm hơn nếu không được tối ưu

Functional Component đang dần thay thế Class Component trong hầu hết các trường hợp nhờ sự đơn giản, dễ đọc, và khả năng tận dụng React Hooks để quản lý state và lifecycle một cách linh hoạt.

Tuy nhiên, hiểu biết về cả hai loại component là cần thiết để xử lý các dự án React Native một cách hiệu quả, đặc biệt là khi cần duy trì các codebase cũ sử dụng Class Component.

State và Props trong React Native là gì? Làm thế nào để quản lý chúng?

State

Trong React Native, State là một đối tượng được sử dụng để lưu trữ dữ liệu động có thể thay đổi trong suốt vòng đời của component. Khi State thay đổi, component sẽ tự động render lại để hiển thị giá trị mới. Đây là dữ liệu chỉ có thể được thay đổi bên trong chính component đó, vì vậy nó được gọi là “local” hoặc “internal” state.

Để quản lý State trong một component, bạn thường sử dụng useState hook (trong function component) hoặc this.setState() (trong class component). Dữ liệu trong State có thể được thay đổi thông qua các sự kiện hoặc các hành động từ người dùng (như bấm nút, nhập liệu, v.v.).

import React, { useState } from 'react';

const ExampleComponent = () => {
  // Khởi tạo state với useState hook
  const [count, setCount] = useState(0);

  return (
    <View>
      <Text>{'Count: ${count}`}</Text>
      {/* Cập nhật state khi bấm nút */}
     <Button onPress={() => setCount(count + 1)} title="Increase Count" />
    </View>
  );
};
Props

Props (viết tắt của “properties”) là một đối tượng truyền dữ liệu từ component cha đến component con. Đây là dữ liệu chỉ đọc, không thể thay đổi trực tiếp từ component nhận props. Props cho phép bạn truyền thông tin giữa các component, giúp chia sẻ dữ liệu và giữ cho UI đồng bộ.

Để quản lý Props, Props được truyền từ component cha thông qua JSX và có thể truy cập bằng cú pháp this.props (trong class component) hoặc truy cập trực tiếp thông qua đối số trong function component. Vì Props là chỉ đọc, nếu cần thay đổi dữ liệu, bạn cần cập nhật từ component cha.

const ChildComponent = ({ message }) => {
  return <Text>{message}</Text>;
};

const Parent Component = () => {
  const greeting = 'Hello from Parent!';
  return <ChildComponent message={greeting} />;
};
Khi nào sử dụng State và Props?
  • Sử dụng State khi dữ liệu cần thay đổi dựa trên các sự kiện hoặc tương tác từ người dùng.
  • Sử dụng Props khi cần truyền dữ liệu từ component cha đến component con mà không cần thay đổi dữ liệu trong component con.
Các kỹ thuật quản lý State nâng cao
  • React Context: Nếu cần chia sẻ State giữa nhiều component mà không muốn truyền qua nhiều cấp, React Context có thể giúp tạo một nơi lưu trữ State chung.
  • State Management Libraries: Với các ứng dụng lớn, quản lý State có thể trở nên phức tạp, vì vậy bạn có thể sử dụng các thư viện như Redux, MobX, hoặc React Query để quản lý State toàn cục một cách hiệu quả.

Câu hỏi phỏng vấn React Native về thiết kế và cấu trúc ứng dụng#

Redux là gì?

Redux là một thư viện quản lý state (trạng thái) phổ biến cho các ứng dụng JavaScript, đặc biệt là các ứng dụng xây dựng bằng React hoặc React Native. Mục tiêu chính của Redux là giúp quản lý trạng thái một cách rõ ràng và dễ đoán thông qua việc sử dụng một store trung tâm, nơi chứa toàn bộ trạng thái của ứng dụng.

Khi sử dụng Redux, bạn có thể quản lý và chia sẻ dữ liệu giữa các thành phần của ứng dụng mà không cần phải truyền dữ liệu qua nhiều cấp độ khác nhau, nhờ đó giúp mã nguồn dễ bảo trì, kiểm tra, và mở rộng hơn. Redux sử dụng mô hình “Unidirectional Data Flow” (dòng dữ liệu một chiều), giúp việc theo dõi thay đổi trở nên đơn giản hơn và tránh được các lỗi liên quan đến việc cập nhật trạng thái không đồng bộ.

Đọc thêm: Redux React Native: Chi tiết sử dụng và Lỗi thường gặp

Cách tích hợp Redux vào một ứng dụng React Native

Để tích hợp Redux vào một ứng dụng React Native, bạn cần thực hiện các bước sau:

Bước 1: Cài đặt thư viện Redux và các phụ thuộc cần thiết

Trước tiên, bạn cần cài đặt redux và react-redux để sử dụng Redux trong ứng dụng. Bạn có thể thêm chúng bằng cách sử dụng npm hoặc yarn:

npm install redux react-redux 
Bước 2: Tạo Store

Khởi tạo store bằng cách kết hợp các reducer của bạn thông qua combineReducers và sử dụng createStore.

import { createStore } from 'redux';
import rootReducer from './reducers'; // Tập hợp các reducer

const store = createStore (rootReducer);
Bước 3: Cung cấp Store cho Ứng dụng

Sử dụng <Provider> từ react-redux để cung cấp store cho toàn bộ ứng dụng.

import React from 'react';
import { Provider } from 'react-redux';
import App from './App';
import store from './store';

const Root = () =>
  <Provider store={store}>
    <App />
  </Provider>
);

export default Root;
Bước 4: Kết nối Component với Redux

Sử dụng connect hoặc useSelector và useDispatch từ react-redux để kết nối component với store.

import React from 'react';
import { useSelector, useDispatch } from 'react-redux';

const MyComponent() => {
  const data = useSelector((state) => state.data);
  const dispatch = useDispatch();

  const handleAction = () => {
    dispatch({ type: 'ACTION_TYPE' });
  };

  return (
    <div>
      <h1>{data}</h1>
      <button onClick={handleAction}>Dispatch Action</button>
    </div>
  );
};

export default MyComponent;

Tóm lại, việc tích hợp Redux giúp cho việc quản lý trạng thái trở nên dễ dàng hơn, đặc biệt là khi ứng dụng của bạn có cấu trúc phức tạp và yêu cầu chia sẻ dữ liệu giữa nhiều component.

Context API hoạt động như thế nào?

Context API trong React cho phép truyền dữ liệu từ một component cha đến các component con ở nhiều cấp độ khác nhau mà không cần phải sử dụng props truyền trực tiếp qua từng cấp component. Nó được xem như một cách để quản lý state ở phạm vi nhỏ hoặc cụ thể trong ứng dụng. Context API bao gồm hai phần chính: Provider và Consumer.

  • Provider: Cung cấp dữ liệu (state) cho toàn bộ cây component con của nó. Khi một giá trị trong Provider thay đổi, tất cả các component con sử dụng context này sẽ được cập nhật tự động.
  • Consumer: Là các component con có thể truy cập dữ liệu từ Provider. Consumer sử dụng useContext hook để truy xuất giá trị từ Context.

Khi nào nên sử dụng Context API thay cho Redux?

Context API thường phù hợp trong các trường hợp quản lý state không quá phức tạp hoặc chỉ cần quản lý state ở phạm vi nhỏ. Dưới đây là một số trường hợp nên cân nhắc sử dụng Context API thay vì Redux:

  • State đơn giản hoặc không phức tạp: Nếu ứng dụng chỉ cần chia sẻ một vài state hoặc không yêu cầu quản lý state phức tạp với nhiều logic, Context API là lựa chọn tốt. Nó giúp giảm bớt sự phức tạp khi không phải thiết lập một hệ thống Redux đầy đủ.
  • Quản lý state theo phạm vi cụ thể: Khi một phần của ứng dụng cần quản lý state riêng biệt mà không ảnh hưởng đến các phần khác, Context API giúp định rõ phạm vi của state đó, giảm thiểu rủi ro tác động không mong muốn đến các phần khác của ứng dụng.
  • Ứng dụng nhỏ hoặc trung bình: Với các ứng dụng có quy mô nhỏ hoặc trung bình, nơi số lượng state không lớn và không có quá nhiều component con cần chia sẻ dữ liệu, Context API giúp đơn giản hóa cách quản lý và giảm sự phức tạp khi không cần thiết phải sử dụng Redux.

Tuy nhiên, nếu bạn cần quản lý state ở cấp độ lớn hơn, yêu cầu nhiều middleware cho việc xử lý như logging, debugging, hoặc muốn có một hệ thống quản lý state thống nhất cho toàn bộ ứng dụng, Redux vẫn là một lựa chọn tốt hơn.

**Làm thế nào để tối ưu hóa hiệu suất của một ứng dụng React Native? **

Để tối ưu hóa hiệu suất của một ứng dụng React Native, có một số phương pháp và kỹ thuật mà bạn có thể áp dụng:

  • **Sử dụng PureComponent hoặc **React.memo: Nếu một component không thay đổi thường xuyên, bạn có thể sử dụng React.PureComponent hoặc React.memo để ngăn chặn việc render lại không cần thiết. Điều này sẽ giảm thiểu việc tiêu thụ tài nguyên và tăng tốc độ ứng dụng.
  • Tối ưu hóa danh sách bằng FlatList hoặc SectionList: Khi làm việc với dữ liệu danh sách lớn, hãy sử dụng FlatList hoặc SectionList thay vì ScrollView. FlatList và SectionList chỉ render những item hiển thị trên màn hình và sẽ tải thêm khi người dùng cuộn, giúp tiết kiệm bộ nhớ.
  • Sử dụng các thư viện Native Module: Đối với các tác vụ nặng (như xử lý hình ảnh, mã hóa/giải mã dữ liệu,…), hãy cân nhắc sử dụng các thư viện Native Module được viết bằng Swift/Objective-C hoặc Java/Kotlin để tăng tốc độ xử lý thay vì sử dụng JavaScript thuần.
  • Tối ưu hóa hình ảnh: Hình ảnh có thể ảnh hưởng lớn đến hiệu suất của ứng dụng. Hãy tối ưu hóa hình ảnh bằng cách sử dụng các định dạng ảnh nhẹ hơn như WebP, sử dụng các thư viện như react-native-fast-image để caching hình ảnh, và đảm bảo chỉ tải những hình ảnh cần thiết cho mỗi màn hình.
  • Sử dụng Performance Monitor: React Native cung cấp Performance Monitor để giúp bạn kiểm tra hiệu suất ứng dụng trong quá trình phát triển. Sử dụng công cụ này để xác định các điểm yếu và tìm giải pháp cải thiện.
  • Chỉ sử dụng các component Animation khi cần thiết: Các animation có thể gây ảnh hưởng đến hiệu suất. Sử dụng react-native-reanimated hoặc react-native-gesture-handler để tối ưu hóa các animation phức tạp thay vì sử dụng các animation mặc định của React Native.
  • Quản lý trạng thái hiệu quả: Việc quản lý trạng thái không hợp lý có thể làm ứng dụng trở nên chậm chạp. Hãy sử dụng Redux hoặc Context API một cách tối ưu và tránh việc truyền state không cần thiết qua các component không liên quan.

Khái niệm về Navigation trong React Native và các thư viện phổ biến như React Navigation, React Native Navigation

Navigation trong React Native là một phần quan trọng của bất kỳ ứng dụng di động nào, cho phép người dùng di chuyển giữa các màn hình khác nhau trong ứng dụng. Khái niệm này bao gồm việc quản lý trạng thái, chuyển đổi màn hình, và duy trì lịch sử điều hướng để tạo ra trải nghiệm người dùng mượt mà và dễ sử dụng.

Có hai loại điều hướng chính trong React Native:

  • Stack Navigation (chuyển đổi giữa các màn hình theo dạng stack)
  • Tab Navigation (điều hướng giữa các màn hình qua các tab
  • Một số trường hợp khác bao gồm Drawer Navigation để mở các menu ẩn từ cạnh màn hình.

Các thư viện phổ biến:

React Navigation

  • Đây là thư viện phổ biến nhất và được sử dụng rộng rãi trong cộng đồng React Native. Nó cung cấp một bộ công cụ linh hoạt để tạo ra các loại điều hướng khác nhau như Stack, Tab, Drawer, Bottom Tab và nhiều loại khác.
  • React Navigation dễ cấu hình và có một cộng đồng hỗ trợ mạnh mẽ, với tài liệu chi tiết và các ví dụ phong phú.
  • Ưu điểm: Linh hoạt, dễ tích hợp, có thể tùy chỉnh cao, hỗ trợ chuyển động mượt mà giữa các màn hình.
  • Nhược điểm: Đôi khi phức tạp khi tùy chỉnh các yêu cầu đặc biệt và có thể cần nhiều cấu hình thủ công khi ứng dụng có quy mô lớn.

React Native Navigation (do Wix phát triển)

  • Đây là một thư viện điều hướng cấp cao được thiết kế với mục tiêu cung cấp hiệu suất tốt nhất bằng cách sử dụng các thành phần native thực sự (native components) của nền tảng.
  • Thư viện này lý tưởng cho các ứng dụng phức tạp và có yêu cầu hiệu năng cao vì nó tận dụng các chức năng native để cung cấp trải nghiệm tốt hơn.
  • Ưu điểm: Hiệu suất cao hơn nhờ việc sử dụng thành phần native, chuyển động mượt mà, hỗ trợ tốt các tính năng của hệ điều hành như thanh điều hướng và thanh công cụ (Toolbar).
  • Nhược điểm: Cấu hình phức tạp hơn, yêu cầu hiểu biết sâu về các thành phần native của Android và iOS.

Giải thích về khái niệm Higher-Order Components (HOC) và sử dụng chúng trong React Native

Higher-Order Components (HOC) là một mẫu thiết kế trong React và React Native, cho phép bạn tái sử dụng logic giữa các component. HOC thực chất là một hàm nhận một component và trả về một component mới với các tính năng hoặc dữ liệu bổ sung. Nói cách khác, nó giúp chia sẻ các logic chung mà không cần lặp lại code trong nhiều component khác nhau.

HOC thường được dùng để:

  • Xử lý việc lấy dữ liệu từ server (fetch data).
  • Quản lý quyền truy cập (authorization) hoặc kiểm tra điều kiện trước khi render component.
  • Thêm các tính năng đặc biệt cho component như tracking hoặc logging.

Ví dụ về HOC trong React Native:

Giả sử bạn có một component UserList để hiển thị danh sách người dùng và bạn muốn thêm chức năng lấy dữ liệu từ server. Bạn có thể sử dụng HOC để chia sẻ logic lấy dữ liệu này mà không cần tích hợp trực tiếp vào component:

import React, { useEffect, useState } from 'react';

// Đây là HOC nhận một component và trả về một component mới
function withDataFetching(WrappedComponent, url) {
  return function () {
    const [data, setData] = useState([]);
    const [loading, setLoading] = useState(true);

    useEffect(() => {
      fetch(url)
        .then(response => response.json())
        .then(data => {
          setData(data);
          setLoading(false);
        });
    }, [url]);
    
    // Trả về WrappedComponent với data và loading
    return <WrappedComponent data={data} loading={loading} />;
  };
}

// Component UserList nhận props data và loading
function UserList({ data, loading }) {
  if (loading) {
    return <Text>Loading...</Text>;
  }

  return (
    <FlatList
      data={data}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => <Text>{item.name}</Text>}
    />
  );
}

// Sử dụng HOC để tạo component UserListWithData
const UserListWithData = withDataFetching(UserList, 'https://api.example.com/users');

Trong ví dụ trên, withDataFetching là một HOC nhận vào component UserList và URL để fetch data. Kết quả là bạn có thể tái sử dụng logic lấy dữ liệu cho các component khác nhau mà không cần viết lại.

Câu hỏi phỏng vấn React Native về các thành phần giao diện (UI Components)#

**Làm thế nào để tạo một Custom Component trong React Native? **

Để tạo một Custom Component trong React Native, tôi thường sử dụng Functional Components, vì nó giúp mã nguồn ngắn gọn hơn và dễ bảo trì. Dưới đây là các bước:

Bước 1: Tạo File Component

Tạo một file JavaScript mới, ví dụ như MyButton.js, để chứa Custom Component.

Bước 2: Định nghĩa Component

Trong file MyButton.js, tôi định nghĩa một Functional Component sử dụng cú pháp ES6. Ví dụ:

import React from 'react';
import {TouchableOpacity, Text, StyleSheet } from 'react-native';

const MyButton = ({ title, onPress, color }) => {
  return (
    <TouchableOpacity
      style={[styles.button, { backgroundColor: color }]}
      onPress={onPress}
    >
      <Text style={styles.text}>{title}</Text>
    </TouchableOpacity>
  );
};

const styles StyleSheet.create({
  button: {
    padding: 10,
    borderRadius: 5,
    alignItems: 'center',
  },
  text: {
    color: '#ffffff',
    fontSize: 16,
  },
});

export default MyButton;
Bước 3: Sử dụng Props

Trong ví dụ trên, tôi sử dụng props để nhận các giá trị từ component cha như title, onPress, và color. Điều này giúp component trở nên linh hoạt và có thể tái sử dụng trong nhiều trường hợp khác nhau.

Bước 4: Sử dụng Component

Cuối cùng, để sử dụng component này trong một file khác, tôi sẽ import nó và cung cấp các props cần thiết, ví dụ:

import React from 'react';
import { View } from 'react-native';
import MyButton from './MyButton';

const App = () => {
  return (
    <View>
      <MyButton
        title="Click Me"
        onPress={() => alert('Button Pressed!')}
        color="#4CAF50"
      />
    </View>
  );
};

export default App;

Sự khác nhau giữa ScrollView và FlatList là gì?

  • ScrollView: Là một thành phần dùng để hiển thị danh sách hoặc nội dung cuộn, phù hợp khi số lượng phần tử ít và có thể tải toàn bộ vào bộ nhớ cùng lúc. Nó không tối ưu cho danh sách lớn vì mọi phần tử đều được render trước.
  • FlatList: Là thành phần được tối ưu hóa để hiển thị danh sách lớn. Nó chỉ render các phần tử hiển thị trên màn hình hoặc gần đó, giúp tiết kiệm bộ nhớ và cải thiện hiệu suất. FlatList hỗ trợ các tính năng như phân trang, kéo tải thêm dữ liệu, và tùy chỉnh hiệu quả.
Thành phầnScrollViewFlatList
Mục đích sử dụngSử dụng khi có một lượng nhỏ dữ liệu cần cuộn hoặc hiển thị toàn bộ dữ liệu cùng lúc.Dùng để hiển thị danh sách lớn với nhiều mục mà chỉ tải dữ liệu khi cần thiết (lazy loading).
Hiệu suấtKhông tối ưu khi có nhiều phần tử do tất cả dữ liệu được render một lần.Hiệu suất tốt hơn khi làm việc với danh sách lớn nhờ cơ chế virtual rendering (chỉ render các phần tử hiển thị).
Render dữ liệuRender toàn bộ dữ liệu trong danh sách, dẫn đến tốn nhiều bộ nhớ nếu dữ liệu lớnRender theo từng trang (paging) hoặc từng phần tử visible, tiết kiệm bộ nhớ hơn.
Hỗ trợ lazy loadingKhông có hỗ trợ lazy loading; toàn bộ dữ liệu được tải khi mở trangCó hỗ trợ lazy loading để tải dần dữ liệu khi người dùng cuộn xuống.
Tính năng phân trangKhông hỗ trợ trực tiếp phân trang.Hỗ trợ phân trang dễ dàng thông qua thuộc tínhonEndReachedvàonEndReachedThreshold
Chức năng item separatorKhông có hỗ trợ item separator mặc địnhCó hỗ trợ thuộc tínhItemSeparatorComponentđể tùy chỉnh separator giữa các item.
Hiển thị header/footerCó thể thêm header/footer nhưng cần tạo custom layoutCó hỗ trợ sẵn các thuộc tínhListHeaderComponentvàListFooterComponentđể hiển thị header/footer
KeyKhông cầnkeyExtractor, không tối ưu với danh sách động hoặc thay đổi nhiềuCần cókeyExtractorđể định danh từng phần tử, giúp quản lý hiệu quả danh sách thay
Khả năng làm việc với dữ liệu lớnKhông phù hợp với dữ liệu lớn do tất cả các phần tử được render cùng lúcPhù hợp với dữ liệu lớn do chỉ render những phần tử cần thiết

**Làm sao để xử lý sự kiện touch (bấm, kéo, thả) trong React Native? **

Để xử lý sự kiện touch (bấm, kéo, thả) trong React Native, bạn có thể sử dụng các thành phần và API được cung cấp sẵn như TouchableOpacity, TouchableHighlight, TouchableWithoutFeedback, TouchableNativeFeedback, hoặc Pressable.

Ngoài ra, bạn cũng có thể sử dụng các thư viện bên thứ ba như react-native-gesture-handler để có thể xử lý các sự kiện phức tạp hơn.

Cách 1: Sử dụng các thành phần Touchable

Các thành phần thuộc nhóm Touchable trong React Native được thiết kế để xử lý sự kiện chạm. Một ví dụ phổ biến là TouchableOpacity:

import React from 'react';
import { View, Text, TouchableOpacity } from 'react-native';

const Example = () => {
  return (
    <View>
      <TouchableOpacity onPress={() => alert('Button Pressed!')}>
        <Text>Press Me</Text>
      </TouchableOpacity>
    </View>
  );
};

Trong ví dụ này, sự kiện onPress sẽ được kích hoạt khi người dùng chạm vào vùng có chứa Text. Các thuộc tính khác như onLongPress, onPressIn, và onPressOut cũng có thể được sử dụng để bắt các sự kiện khác như giữ lâu, bắt đầu chạm, và kết thúc chạm.

**Cách 2: Sử dụng **Pressable

Pressable là thành phần mới hơn được giới thiệu trong các phiên bản mới của React Native, giúp bạn dễ dàng tùy chỉnh hơn trong việc xử lý các sự kiện touch:

import React from 'react';
import { View, Text, Pressable } from 'react-native';

const Example = () => {
  return (
    <View>
      <Pressable
        onPress={() => console.log('Pressed')}
        onLongPress={() => console.log('Long Pressed')}
        onPressIn={() => console.log('Press In')}
        onPressOut={() => console.log('Press Out')}
      >
        <Text>Touch Me</Text>
      </Pressable>
    </View>
  );
};
**Cách 3: Sử dụng **react-native-gesture-handler

Trong những trường hợp cần xử lý các cử chỉ phức tạp như kéo, vuốt, hoặc xử lý các cử chỉ cùng lúc, bạn có thể sử dụng thư viện react-native-gesture-handler. Ví dụ:

import React from 'react';
import { GestureHandlerRootView, PanGestureHandler } from 'react-native-gesture-handler';
import { View, Text } from 'react-native';

const Example = () => {
  const onGestureEvent = (event) => {
    console.log('Gesture Detected', event.nativeEvent);
  };

  return (
    <GestureHandlerRootView style={{ flex: 1 }}>
      <PanGestureHandler onGestureEvent={onGestureEvent}>
        <View style={{ width: 200, height: 200, backgroundColor: 'lightblue', justifyContent: 'center', alignItems: 'center' }}>
          <Text>Drag Me</Text>
        </View>
      </PanGestureHandler>
    </GestureHandlerRootView>
  );
};

Giải thích cách tạo Animation trong React Native và các thư viện hỗ trợ như React Native Reanimated

Trong React Native, việc tạo Animation giúp cải thiện trải nghiệm người dùng và làm cho ứng dụng trở nên sống động hơn. Để tạo Animation, React Native cung cấp một số API và thư viện hỗ trợ, phổ biến nhất là Animated** API** và React Native Reanimated.

Cách tạo Animation với Animated API

Animated là API gốc của React Native, hỗ trợ việc tạo Animation đơn giản. Bạn có thể sử dụng Animatedđể tạo các hiệu ứng chuyển động cho các thuộc tính như vị trí, kích thước, độ mờ, hoặc màu sắc của một component.

Ví dụ: Tạo Animation đơn giản với Animated API

import React, { useRef, useEffect } from 'react';
import { Animated, View, Button } from 'react-native';

const SimpleAnimation = () => {
  const opacity = useRef(new Animated.Value(0)).current;

  const fadeIn = () => {
    Animated.timing(opacity, {
      toValue: 1,
      duration: 1000,
      useNativeDriver: true,
    }).start();
  };

  return (
    <View>
      <Animated.View style={{ opacity, width: 100, height: 100, backgroundColor: 'blue' }} />
      <Button title="Fade In" onPress={fadeIn} />
    </View>
  );
};

Trong ví dụ này, Animated.Value tạo ra một giá trị có thể thay đổi (ở đây là opacity), và Animated.timing giúp thực hiện Animation theo thời gian.

Thư viện hỗ trợ: React Native Reanimated

Trong khi Animated API có thể đáp ứng nhu cầu cơ bản, React Native Reanimated mang đến nhiều khả năng mạnh mẽ hơn khi cần xử lý Animation phức tạp hoặc tối ưu hóa hiệu năng.

React Native Reanimated cho phép thực hiện Animation mượt mà, đồng thời giảm tải cho JS thread, nhờ đó ứng dụng hoạt động ổn định và nhanh hơn, đặc biệt là với các Animation phức tạp.

**Các tính năng nổi bật của **React Native Reanimated

  • Mượt mà hơn: React Native Reanimated giúp Animation chạy mượt mà nhờ việc chạy trực tiếp trên UI thread thay vì phụ thuộc hoàn toàn vào JS thread.
  • Cấu trúc rõ ràng: Cung cấp một cú pháp rõ ràng, dễ sử dụng để tạo các Animation phức tạp, ví dụ như Gesture Handling và các Interaction.
  • Khả năng mở rộng: Cho phép kết hợp Animation với các hành vi phức tạp như Spring, Decay hoặc các hiệu ứng vật lý.

**Ví dụ: Tạo Animation với **React Native Reanimated

import React from 'react';
import { Button } from 'react-native';
import { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';

const ReanimatedExample = () => {
  const opacity = useSharedValue(0);

  const animatedStyle = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
    };
  });

  const fadeIn = () => {
    opacity.value = withTiming(1, { duration: 1000 });
  };

  return (
    <>
      <Animated.View style={[{ width: 100, height: 100, backgroundColor: 'red' }, animatedStyle]} />
      <Button title="Fade In" onPress={fadeIn} />
    </>
  );
};

Trong ví dụ này, useSharedValue tạo ra một giá trị chia sẻ giữa các Animation, còn useAnimatedStyle giúp áp dụng hiệu ứng Animation vào style của component.

Cách sử dụng StyleSheet trong React Native và sự khác biệt giữa inline styles và StyleSheet

Trong React Native, StyleSheet giúp định nghĩa các style tái sử dụng một cách tách biệt, dễ quản lý và tối ưu hiệu suất hơn so với inline styles. StyleSheet biến các style thành mã native ngay từ lúc biên dịch, trong khi inline styles cần được tính toán lại mỗi lần render, có thể ảnh hưởng đến hiệu suất nếu sử dụng rộng rãi.

Do đó, mình thường dùng StyleSheet cho các style cố định và inline styles khi cần điều chỉnh style động.

Nội dungStyleSheetInline Styles
Khái niệmSử dụngStyleSheet.createđể định nghĩa các kiểu một cách tập trung.Định nghĩa các kiểu trực tiếp trong thuộc tínhstyle.
Cách sử dụngjavascript const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: ‘white’, padding: 10, }, }); javascript <View style={styles.container}></View>javascript <View style={{ flex: 1, backgroundColor: ‘white’, padding: 10 }}></View>
Tái sử dụngCó thể tái sử dụng kiểu được định nghĩa nhiều lần trong ứng dụng.Mỗi lần sử dụng phải khai báo kiểu trực tiếp, khó tái sử dụng.
Hiệu suấtHiệu quả hơn khi tối ưu bộ nhớ vì các kiểu được quản lý dưới dạng tĩnh.Ít hiệu quả hơn vì mỗi lần khai báo tạo ra một đối tượng mới.
Tính rõ ràng và tổ chứcGiúp mã nguồn rõ ràng và dễ quản lý hơn khi các kiểu được tách biệt.Có thể gây khó khăn trong quản lý nếu kiểu quá dài hoặc phức tạp.
Trường hợp sử dụng phù hợpKhi cần định nghĩa kiểu phức tạp hoặc tái sử dụng nhiều lần.Khi chỉ cần định nghĩa kiểu đơn giản, nhanh chóng.

Câu hỏi phỏng vấn React Native về xử lý dữ liệu và API#

Cách sử dụng Fetch API hoặc Axios để gọi API trong React Native

Fetch API

Fetch API là một công cụ có sẵn trong JavaScript và có thể được sử dụng trực tiếp trong React Native để thực hiện các yêu cầu HTTP.

Ví dụ:

const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');

    if (!response.ok) {
      throw new Error('Network response was not ok');
    }

    const data = await response.json();
    console.log(data);
  } catch (error) {
    console.error('Fetch error: ', error);
  }
};

Giải thích:

  • Sử dụng fetch để thực hiện yêu cầu GET tới API.
  • Sử dụng response.json() để chuyển đổi phản hồi thành đối tượng JavaScript.
  • Xử lý lỗi nếu phản hồi không thành công.
Axios

Axios là một thư viện bên thứ ba được sử dụng phổ biến vì cú pháp ngắn gọn và khả năng cấu hình cao.

Cài đặt Axios:

npm install axios

Ví dụ:

import axios from 'axios';

const fetchData = async () => {
  try {
    const response = await axios.get('https://api.example.com/data');
    console.log(response.data);
  } catch (error) {
    console.error('Axios error: ', error);
  }
};

Giải thích:

  • axios.get để thực hiện yêu cầu GET.
  • Phản hồi của Axios dễ làm việc hơn vì dữ liệu đã có sẵn trong response.data.
  • Xử lý lỗi với try…catch.

async/await trong JavaScript là gì? Cách sử dụng chúng trong React Native

Trong JavaScript, async/await là cú pháp dùng để xử lý các tác vụ bất đồng bộ (asynchronous operations) một cách dễ đọc và rõ ràng hơn so với việc sử dụng các hàm Promise hoặc callback.

  • async: Từ khóa này được sử dụng trước một hàm để đánh dấu rằng hàm đó là bất đồng bộ (asynchronous). Khi một hàm được khai báo với từ khóa async, nó sẽ tự động trả về một Promise. Điều này có nghĩa là có thể sử dụng await bên trong hàm đó.
  • await: Từ khóa này chỉ có thể được sử dụng bên trong một hàm async. Nó được sử dụng để tạm dừng việc thực thi của hàm cho đến khi một Promise được hoàn thành, giúp viết mã đồng bộ hơn và dễ đọc hơn.

Khi trả lời câu hỏi về async/await trong phỏng vấn cho vị trí React Native Developer, ứng viên nên tập trung giải thích khái niệm cơ bản và trình bày cách sử dụng nó trong bối cảnh ứng dụng React Native.

Trong React Native, async/await thường được sử dụng khi cần lấy dữ liệu từ API hoặc xử lý các tác vụ tốn thời gian như lưu trữ dữ liệu cục bộ. Ví dụ:

const fetchData = async () => {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log('Dữ liệu nhận được:', data);
  } catch (error) {
    console.error('Lỗi khi lấy dữ liệu:', error);
  }
};

Cách quản lý dữ liệu từ API trong Redux Store

Để quản lý dữ liệu từ API trong Redux Store, tôi sẽ bắt đầu bằng việc tạo một action để gọi API, ví dụ như fetchData. Sau đó, tôi sẽ sử dụng một middleware như redux-thunk hoặc redux-saga để thực hiện gọi API bất đồng bộ.

Khi API được gọi, tôi sẽ dispatch ba loại action khác nhau:

  • Một action để báo hiệu quá trình gọi API đang được tiến hành (FETCH_DATA_REQUEST).
  • Một action khi dữ liệu được tải thành công (FETCH_DATA_SUCCESS) để cập nhật dữ liệu vào Store.
  • Một action khi xảy ra lỗi trong quá trình gọi API (FETCH_DATA_FAILURE) để hiển thị thông báo lỗi.

Reducer sẽ lắng nghe các action này và cập nhật Redux Store dựa trên kết quả. Ví dụ, nếu thành công, tôi sẽ cập nhật dữ liệu vào Store, còn nếu gặp lỗi, tôi sẽ lưu thông tin lỗi vào Store để có thể hiển thị thông báo cho người dùng. Trong component, tôi sẽ sử dụng useSelector để truy xuất dữ liệu từ Store và useDispatch để dispatch các action khi cần.

Dưới đây là một số lỗi phổ biến khi gọi API trong React Native là gì?

**Lỗi **Network Request Failed

Nguyên nhân:

  • Thiết bị không có kết nối internet.
  • API không được bảo mật bằng HTTPS (chỉ sử dụng HTTP).
  • URL API sai hoặc không tồn tại.
  • Thiếu quyền truy cập mạng trên Android (AndroidManifest.xml).
** CORS Error (Cross-Origin Resource Sharing)**

Nguyên nhân:

  • Máy chủ API không cho phép yêu cầu từ nguồn gốc (origin) của ứng dụng.
  • Thiếu cấu hình header Access-Control-Allow-Origin trên server.
** Lỗi không bắt được response hoặc **error

Nguyên nhân:

  • Không xử lý đầy đủ các trạng thái trong try-catch.
  • Không thêm catch khi sử dụng Promise.
** Lỗi Timeout hoặc API không phản hồi**

Nguyên nhân:

  • Máy chủ phản hồi quá chậm.
  • Ứng dụng không thiết lập giới hạn thời gian cho yêu cầu.
** Lỗi **JSON Parse Error

Nguyên nhân:

  • Phản hồi từ server không phải JSON hợp lệ.
  • Đã gọi response.json() trên một phản hồi không có dữ liệu.
** Lỗi 401 Unauthorized hoặc **403 Forbidden

Nguyên nhân:

  • Thiếu hoặc sai token trong header Authorization.
  • Người dùng không có quyền truy cập tài nguyên.
** Lỗi 500 Internal Server Error hoặc **503 Service Unavailable

Nguyên nhân:

  • Lỗi máy chủ hoặc server đang bảo trì.

Các cách xử lý lỗi khi gọi API trong ứng dụng React Native

Khi xử lý lỗi khi gọi API trong ứng dụng React Native, nhà tuyển dụng thường muốn đánh giá cách bạn quản lý các tình huống lỗi trong quá trình giao tiếp với server, bao gồm các lỗi về mạng, lỗi từ server, hay dữ liệu không hợp lệ. Dưới đây là các bước và phương pháp thông dụng để xử lý các lỗi được đề cập ở trên:

**Sử dụng try…catch với **async/await

Một cách phổ biến để xử lý lỗi là sử dụng try…catch với cú pháp async/await. Điều này giúp kiểm soát lỗi xảy ra khi gọi API một cách rõ ràng và dễ hiểu:

async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    if (!response.ok) {
      *// Xử lý lỗi khi server trả về response không hợp lệ*
     throw new Error(Server error: ${response.status}`);
    }
    const data = await response.json();
    return data;
  } catch (error) {
    *// Xử lý các lỗi khác như lỗi mạng*
    console.error('Fetch error:', error);
    throw error; *// Có thể trả về lỗi để xử lý thêm ở nơi khác*
  }
}
Kiểm** tra mã trạng thái HTTP**

Khi nhận được phản hồi từ server, cần kiểm tra mã trạng thái HTTP (status code). Các mã như 404, 500 thường cho thấy lỗi từ server:

if (response.status === 404) {
  console.warn('Not found: The requested resource does not exist.');
} else if (response.status === 500) {
  console.error('Server error: Something went wrong on the server.');
}
Hiển thị thông báo lỗi cho người dùng

Khi gặp lỗi, nên hiển thị thông báo dễ hiểu cho người dùng thay vì chỉ log lỗi. Điều này cải thiện trải nghiệm người dùng:

catch (error) {

  Alert.alert('Lỗi', 'Không thể tải dữ liệu. Vui lòng kiểm tra kết nối mạng và thử lại.');

}
Quản lý lỗi toàn cục với Redux hoặc Context API

Để xử lý lỗi một cách nhất quán trong ứng dụng, có thể quản lý trạng thái lỗi thông qua Redux hoặc Context API, giúp lưu trữ và hiển thị lỗi ở các phần khác nhau của ứng dụng một cách đồng bộ.

Thêm cơ chế Retry (Thử lại)

Nếu xảy ra lỗi do kết nối mạng, có thể thêm tính năng thử lại (retry) sau một khoảng thời gian:

function fetchDataWithRetry (retryCount = 3) {
  let attempts = 0;
  const fetchData = async () => {
    try {
      const response = await fetch('https://api.example.com/data');
      if (!response.ok) throw new Error('Server error');
      return await response.json();
    } catch (error) {
      if (attempts < retryCount) {
        attempts++;
        console.log('Retrying... (${attempts}/${retryCount})`);
        return fetchData();
      }
      throw error;
    }
  };
  return fetchData();
}
Sử dụng các thư viện hỗ trợ

Ngoài fetch, các thư viện như Axios cung cấp nhiều tính năng tiện lợi như interceptors để xử lý lỗi tập trung, retry tự động, và dễ dàng cấu hình:

import axios from 'axios';

axios.interceptors.response.use(
  response => response,
  error => {
    console.error('API error:', error);
    return Promise. reject (error);
  }
);

Những kỹ thuật này thể hiện khả năng của ứng viên trong việc quản lý các tình huống lỗi phức tạp và cung cấp trải nghiệm người dùng tốt hơn, điều mà nhà tuyển dụng đánh giá cao ở một React Native Developer.

Sử dụng FlatList để hiển thị danh sách dữ liệu từ API

Fetch dữ liệu từ API: Đầu tiên, tôi sẽ sử dụng các phương thức như fetch hoặc axios để gửi yêu cầu đến API và lấy dữ liệu về.

Quản lý trạng thái dữ liệu: Tôi sẽ sử dụng useState để quản lý dữ liệu được nhận về từ API

const [dataList, setDataList] = useState([]);

**Hiển thị dữ liệu với **FlatList: Chúng ta sử dụng FlatList để hiển thị danh sách dữ liệu. FlatList là một thành phần tối ưu để hiển thị danh sách lớn vì nó chỉ render những item xuất hiện trên màn hình.

<FlatList
  data={dataList}
  keyExtractor={(item) => item.id.toString()}
  renderItem={( { item }) => (
    <View style={styles.itemContainer}>
      <Text>{item.title}</Text>
    </View>
  )}
/>

Tối ưu hóa hiệu suất: Để đảm bảo FlatList hoạt động hiệu quả với danh sách lớn, tôi sẽ cung cấp keyExtractor để định danh từng phần tử và tránh render lại không cần thiết. Ngoài ra, có thể sử dụng thuộc tính initialNumToRender để kiểm soát số lượng phần tử được render lần đầu.

Xử lý khi tải dữ liệu: Tôi có thể thêm một biến trạng thái loading để hiển thị màn hình tải khi đang fetch dữ liệu:

const [loading, setLoading] = useState(true);

useEffect(() => {
  const fetchData = async () => {
    setLoading (true);
    try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      setDataList(data);
    } catch (error) {
      console.error('Error fetching data:', error);
    } finally {
      setLoading (false);
    }
  };

  fetchData();
}, []);

if (loading) {
  return <ActivityIndicator size="large" color="#0000ff" />;
}

Câu hỏi phỏng vấn React Native về tích hợp Native Modules và Native Code#

**Làm thế nào để tích hợp một module native vào ứng dụng React Native? **

Để tích hợp một module native vào ứng dụng React Native, quy trình bao gồm các bước cơ bản sau:

Tạo module native (Android và iOS)

Android:

  • Thêm code Java hoặc Kotlin trong thư mục android. Tạo một class mới để xử lý logic của module native.
  • Sử dụng annotation @ReactMethod để đánh dấu các phương thức sẽ được sử dụng từ React Native.
  • Đăng ký module này với ReactPackage trong code Java/Kotlin và thêm package này vào MainApplication.java.

iOS:

  • Thêm code Swift hoặc Objective-C trong thư mục ios. Tạo một class mới kế thừa từ RCTBridgeModule.
  • Implement các phương thức muốn gọi từ React Native và sử dụng RCT_EXPORT_METHOD để export các phương thức này.
  • Đảm bảo module đã được đăng ký với Bridge bằng cách thêm vào file AppDelegate.m hoặc AppDelegate.swift.
Sử dụng module trong React Native

Sử dụng module native trong JavaScript bằng cách import module vừa tạo. Có thể sử dụng NativeModules từ react-native để truy cập module đã tích hợp.

Ví dụ:

import {NativeModules} from 'react-native';
const {MyNativeModule } = NativeModules;

MyNativeModule.someMethod('data', (result) => {
  console.log('Kết quả từ module native:', result);
});
Kiểm tra và xử lý lỗi
  • Chạy ứng dụng để kiểm tra xem module native có hoạt động đúng không.
  • Debug các lỗi có thể phát sinh khi kết nối giữa React Native và module native.
  • Đảm bảo các phương thức trong module native tương thích với các yêu cầu từ phía React Native.

Khác nhau giữa Bridge và TurboModules trong React Native

Bridge và TurboModules là hai cách để React Native xử lý giao tiếp giữa mã JavaScript và mã gốc:

Bridge
  • Là cơ chế giao tiếp ban đầu của React Native. Nó hoạt động bằng cách sử dụng một lớp trung gian (bridge) để truyền dữ liệu giữa JavaScript và mã gốc thông qua JSON.
  • Điểm mạnh: Đơn giản và dễ hiểu, dễ mở rộng khi cần tích hợp các module gốc (native modules) mới.
  • Điểm yếu: Giao tiếp thông qua JSON có thể gây ra độ trễ (latency), đặc biệt khi có lượng dữ liệu lớn hoặc yêu cầu hiệu suất cao do cần chuyển đổi nhiều lần giữa các ngôn ngữ.
TurboModules
  • Là một cải tiến mới trong kiến trúc của React Native nhằm thay thế Bridge truyền thống. TurboModules giảm thiểu độ trễ bằng cách tận dụng các tính năng tối ưu hóa và giao tiếp trực tiếp hơn giữa JavaScript và mã gốc.
  • Điểm mạnh: Hiệu suất tốt hơn với độ trễ thấp, khả năng tải module một cách linh hoạt (on-demand), giúp giảm bộ nhớ sử dụng.
  • Điểm yếu: Cần kiến thức sâu hơn về cấu trúc nội bộ của React Native và đòi hỏi sự thay đổi trong cách viết mã khi so sánh với Bridge truyền thống.
So sánh Bridge vs TurboModules
Đặc điểmBridgeTurboModules
Cơ chế giao tiếpSử dụng lớp trung gian (bridge) để truyền dữ liệu qua JSON giữa JavaScript và mã gốc.Giao tiếp trực tiếp hơn, giảm thiểu chuyển đổi giữa JavaScript và mã gốc.
Điểm mạnh– Đơn giản, dễ hiểu.– Dễ mở rộng khi tích hợp module native mới.– Hiệu suất cao, độ trễ thấp.– Hỗ trợ tải module linh hoạt (on-demand), giảm bộ nhớ sử dụng.
Điểm yếu– Gây độ trễ do chuyển đổi JSON, đặc biệt với dữ liệu lớn hoặc yêu cầu hiệu suất cao.– Đòi hỏi kiến thức sâu về React Native.– Cần thay đổi cách viết mã so với Bridge truyền thống.
Ứng dụngPhù hợp với các dự án không yêu cầu hiệu suất cao hoặc không xử lý dữ liệu phức tạp.Thích hợp cho các dự án yêu cầu hiệu suất tối ưu, giao tiếp nhanh giữa JavaScript và mã gốc.

Trong ứng dụng thực tế, TurboModules mang lại lợi ích về hiệu suất, đặc biệt khi xử lý các tác vụ yêu cầu tài nguyên lớn hoặc khi cần cải thiện trải nghiệm người dùng với các ứng dụng có độ phức tạp cao.

Giải thích về cách giao tiếp giữa JavaScript và Native Code trong React Native

Trong React Native, giao tiếp giữa JavaScript và Native Code được thực hiện chủ yếu thông qua Bridge hoặc TurboModules. Bridge là cách truyền thống, dùng để gửi và nhận dữ liệu giữa hai môi trường thông qua quá trình tuần tự hóa.

Tuy nhiên, nó có nhược điểm về hiệu suất. TurboModules, giải pháp mới hơn, tối ưu hóa quy trình này bằng cách cho phép truy cập trực tiếp giữa mã Native và JavaScript, giảm thiểu thời gian phản hồi và cải thiện hiệu suất của ứng dụng.

**Khi nào cần sử dụng các thư viện native trong React Native thay vì các thư viện React Native có sẵn? **

Trong các trường hợp khi ứng dụng yêu cầu hiệu suất cao, tiếp cận các chức năng phần cứng đặc biệt, hoặc cần sử dụng các tính năng chưa được hỗ trợ đầy đủ bởi React Native, tôi sẽ xem xét sử dụng các thư viện native.

Ví dụ, với những tác vụ đòi hỏi xử lý đồ họa phức tạp như hiển thị bản đồ 3D, video streaming hoặc xử lý hình ảnh nâng cao, các thư viện native có thể cung cấp hiệu suất tốt hơn.

Ngoài ra, khi có yêu cầu về việc tích hợp với các dịch vụ hoặc SDK từ bên thứ ba chỉ hỗ trợ native, tôi cũng sẽ cân nhắc việc sử dụng các thư viện native để đảm bảo ứng dụng hoạt động đúng mong đợi. Việc sử dụng code native có thể phức tạp hơn, nhưng đôi khi đó là lựa chọn tốt nhất để đạt được chất lượng và hiệu suất cao nhất cho ứng dụng.

Khi nào sử dụng thư viện native?
  • Ứng dụng yêu cầu hiệu suất cao.
  • Tiếp cận các chức năng phần cứng đặc biệt không được hỗ trợ đầy đủ bởi React Native.
  • Sử dụng các tính năng chưa được hỗ trợ hoặc không tối ưu trong React Native.
Trường hợp cụ thể cần thư viện native
  • Xử lý đồ họa phức tạp: Hiển thị bản đồ 3D, xử lý hình ảnh nâng cao.
  • Video streaming: Phát trực tuyến video với hiệu suất cao.
  • Tích hợp dịch vụ/SDK từ bên thứ ba: Khi dịch vụ hoặc SDK chỉ hỗ trợ nền tảng native.
Lợi ích của việc sử dụng thư viện native
  • Đảm bảo hiệu suất và chất lượng cao nhất cho ứng dụng.
  • Hỗ trợ tốt hơn cho các tác vụ phức tạp hoặc yêu cầu đặc thù.

Cách xử lý các lỗi thường gặp khi tích hợp module native

** Lỗi biên dịch (Compilation Errors)**

Mô tả: Lỗi này xảy ra khi module native không tương thích với phiên bản React Native hoặc các thư viện native khác.

Cách xử lý:

  • Kiểm tra lại phiên bản React Native và module native để đảm bảo chúng tương thích với nhau.
  • Đọc tài liệu chính thức của module để biết các yêu cầu cụ thể về phiên bản.
  • Cập nhật hoặc hạ cấp module cho phù hợp với phiên bản React Native hiện tại của dự án.
** Lỗi không tìm thấy module (Module Not Found)**

Mô tả: Thông báo lỗi xuất hiện khi ứng dụng không thể tìm thấy module native được tích hợp.

Cách xử lý:

  • Kiểm tra tên module có đúng không và module đã được liên kết (linked) chính xác chưa.
  • Sử dụng lệnh react-native link <tên_module> để liên kết module native hoặc kiểm tra file cấu hình Podfile (iOS) và build.gradle (Android) để đảm bảo chúng đã được thêm vào đúng cách.
  • Nếu dùng React Native 0.60+, kiểm tra tính năng auto-linking để xem module đã được liên kết tự động hay chưa.
** Lỗi thời gian chạy (Runtime Errors)**

Mô tả: Lỗi này xảy ra khi ứng dụng chạy nhưng module native không hoạt động như mong đợi.

Cách xử lý:

  • Sử dụng công cụ debug như React Native Debugger hoặc Chrome Developer Tools để kiểm tra các thông tin chi tiết.
  • Sử dụng log (console.log, Log.d, NSLog) trong mã JavaScript hoặc native code (Java/Kotlin cho Android, Objective-C/Swift cho iOS) để xác định vị trí xảy ra lỗi.
  • Đảm bảo rằng các phương thức và thuộc tính trong module native đã được export và gọi chính xác trong React Native.
** Lỗi liên quan đến quyền truy cập (Permission Issues)**

Mô tả: Các module native yêu cầu quyền truy cập, ví dụ như camera, vị trí, hoặc bộ nhớ, có thể gây lỗi nếu quyền truy cập không được cấp.

Cách xử lý:

  • Kiểm tra và thêm các quyền cần thiết vào AndroidManifest.xml (Android) hoặc Info.plist (iOS).
  • Sử dụng thư viện như react-native-permissions để xử lý yêu cầu quyền truy cập trong thời gian thực.
** Lỗi tương thích với hệ điều hành (OS Compatibility Issues)**

Mô tả: Module native có thể hoạt động không như mong đợi trên một số phiên bản Android hoặc iOS cụ thể.

Cách xử lý:

  • Kiểm tra và cập nhật các thư viện native cho phù hợp với phiên bản hệ điều hành.
  • Đảm bảo rằng module được viết theo các chuẩn mới nhất của nền tảng Android/iOS.
  • Sử dụng các điều kiện kiểm tra phiên bản hệ điều hành trong code để điều chỉnh hành vi của module nếu cần thiết.
** Lỗi không đồng bộ hóa (Asynchronous Issues)**

Mô tả: Lỗi xảy ra khi việc gọi các phương thức native không đồng bộ hóa chính xác, gây ra lỗi trạng thái không nhất quán.

Cách xử lý:

  • Sử dụng Promises hoặc async/await trong mã JavaScript để đảm bảo việc gọi các phương thức native là không đồng bộ và chính xác.
  • Đảm bảo rằng các phương thức native trả về giá trị hoặc callback chính xác để xử lý kết quả.

Việc chuẩn bị và hiểu rõ cách xử lý các lỗi này không chỉ giúp đảm bảo quá trình tích hợp module native thành công mà còn thể hiện được kỹ năng giải quyết vấn đề và khả năng debug chuyên nghiệp của ứng viên trong mắt nhà tuyển dụng.

Câu hỏi phỏng vấn React Native về kiểm thử (Testing)#

Cách thực hiện Unit Testing trong React Native

Unit Testing trong React Native giúp đảm bảo các chức năng nhỏ của ứng dụng hoạt động chính xác. Để thực hiện Unit Testing trong React Native, các thư viện phổ biến nhất là JestReact Testing Library.

Sau đây là các bước cơ bản:

Cài đặt thư viện Jest:

  • Jest là thư viện mặc định cho Unit Testing trong các dự án React Native. Để cài đặt, có thể sử dụng lệnh: npm install –save-dev jest@testing-library/react-native
  • Sau khi cài đặt, cấu hình Jest trong file package.json hoặc tạo file cấu hình riêng jest.config.js.

Viết bài kiểm tra cho các thành phần:

Dùng @testing-library/react-native để kiểm tra các component. Ví dụ, nếu muốn kiểm tra một button hiển thị đúng, ta có thể viết như sau:

import React from 'react';
import { render } from '@testing-library/react-native';
import MyButton from './MyButton';

test('Button renders with correct label', () => {
  const getByText } render(<MyButton label="Click me!" />);
  expect(getByText('Click me!')).toBeTruthy();
});

Bài kiểm tra này kiểm tra xem button có render đúng với label “Click me!” hay không.

Mock các thành phần liên quan:

Để đơn giản hóa Unit Test, các component phức tạp hoặc các API call có thể được mock. Ví dụ:

jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

Chạy bài kiểm tra:

  • Sau khi viết các bài kiểm tra, bạn có thể chạy chúng bằng lệnh: npm test
  • Kết quả của bài kiểm tra sẽ hiển thị thông tin chi tiết về các test case đã pass hay fail.

Lợi ích của Unit Testing:

  • Phát hiện lỗi sớm: Giúp phát hiện lỗi từ sớm khi code thay đổi.
  • Dễ bảo trì: Khi code có Unit Test tốt, việc thay đổi hoặc bảo trì code sẽ trở nên dễ dàng hơn.
  • Tăng độ tin cậy: Đảm bảo rằng các thay đổi không ảnh hưởng tiêu cực đến các chức năng hiện có.

Giới thiệu về các công cụ kiểm thử phổ biến như Jest, Detox trong React Native

Jest

Jest là một framework kiểm thử phổ biến do Facebook phát triển, thường được sử dụng để kiểm thử các ứng dụng JavaScript, bao gồm cả ứng dụng React Native. Jest được biết đến với tốc độ nhanh và khả năng dễ dàng tích hợp với các dự án React Native.

Mục đích: Được sử dụng chủ yếu cho Unit Testing và Snapshot Testing. Unit Testing giúp kiểm tra các thành phần riêng lẻ của mã, trong khi Snapshot Testing kiểm tra giao diện của ứng dụng để phát hiện những thay đổi không mong muốn.

Ưu điểm:

  • Thiết lập dễ dàng và có cấu hình mặc định tốt cho các dự án React Native.
  • Có khả năng thực hiện các bài kiểm tra không đồng bộ hiệu quả.
  • Cộng đồng lớn và tài liệu hỗ trợ phong phú.

Ví dụ: Sử dụng Jest để kiểm tra một component đơn giản, đảm bảo rằng nó render đúng với các thuộc tính đầu vào đã cho.

Detox

Detox là một công cụ kiểm thử end-to-end (E2E) được phát triển bởi Wix. Công cụ này giúp tự động hóa quá trình kiểm thử ứng dụng từ giao diện người dùng đến backend, kiểm tra toàn bộ ứng dụng từ đầu đến cuối.

Mục đích: Kiểm thử toàn bộ quá trình của ứng dụng, từ việc mở ứng dụng, điều hướng, nhập liệu, đến kiểm tra phản hồi của ứng dụng. Điều này giúp đảm bảo rằng ứng dụng hoạt động chính xác trên các thiết bị và nền tảng khác nhau.

Ưu điểm:

  • Tích hợp tốt với các ứng dụng React Native, cho phép kiểm tra giao diện thực tế của ứng dụng.
  • Hỗ trợ kiểm thử trên các thiết bị thật và giả lập.
  • Giúp phát hiện các lỗi không chỉ trong logic mã mà còn trong giao diện và trải nghiệm người dùng.

Ví dụ: Sử dụng Detox để kiểm thử chức năng đăng nhập, kiểm tra các luồng điều hướng, và xác nhận rằng các tương tác người dùng được xử lý đúng.

**Làm sao để kiểm tra UI Component trong React Native? **

Kiểm thử bằng cách sử dụng Jest

Jest là một framework kiểm thử phổ biến trong cộng đồng React và React Native. Để kiểm tra UI Component, bạn có thể sử dụng Jest kết hợp với thư viện như react-test-renderer hoặc @testing-library/react-native.

Ví dụ, bạn có thể tạo một snapshot test để kiểm tra xem UI Component có thay đổi không sau mỗi lần chỉnh sửa mã. react-test-renderer sẽ tạo ra một bản ghi của UI hiện tại của component và so sánh với kết quả trước đó.

Đoạn mã mẫu sử dụng react-test-renderer:

import React from 'react';
import renderer from 'react-test-renderer';
import MyComponent from '../MyComponent';

test('renders correctly', () => {
  const tree = renderer.create(<MyComponent />).toJSON();
  expect(tree).toMatchSnapshot();
});
Kiểm thử hành vi với @testing-library/react-native

Thư viện @testing-library/react-native giúp kiểm thử hành vi của component bằng cách mô phỏng các tương tác của người dùng. Với thư viện này, bạn có thể kiểm tra sự hiện diện của các thành phần UI, các sự kiện như nhấn nút, hoặc nhập liệu.

Ví dụ để kiểm tra xem một nút có hiển thị đúng không và mô phỏng việc nhấn nút:

import React from 'react';
import { render, fireEvent } from '@testing-library/react-native';
import MyComponent from '../MyComponent';

test('button press triggers event', () => {
  const getByText } render(<MyComponent />);
  const button = getByText('Click me');
  fireEvent.press (button);
  expect (someMockFunction).toHave BeenCalled();
});
Kiểm thử trên nhiều nền tảng

React Native cho phép ứng dụng chạy trên cả iOS và Android, do đó kiểm thử trên cả hai nền tảng là cần thiết. Một số công cụ như Jest hoặc @testing-library/react-native hỗ trợ việc kiểm thử đa nền tảng, nhưng cần đảm bảo rằng component tương thích và hoạt động tốt trên cả hai.

Cách mô phỏng một API response trong quá trình kiểm thử

Để mô phỏng API response trong quá trình kiểm thử, tôi sử dụng các công cụ như jest kết hợp với axios-mock-adapter hoặc msw. Với jest, tôi có thể sử dụng phương thức jest.mock() để thay thế các cuộc gọi API thực tế bằng các giá trị mô phỏng. Nếu sử dụng msw, tôi có thể dễ dàng mô phỏng các API endpoints và các phản hồi dựa trên các yêu cầu HTTP.

Ví dụ, với axios-mock-adapter, tôi có thể làm như sau:

import axios from 'axios';
import MockAdapter from 'axios-mock-adapter';
import { fetchData } from './api'; *// Giả sử fetchData là function gọi API*

*// Tạo mock adapter*
const mock = new MockAdapter(axios);

*// Mô phỏng API response*
mock.onGet('/data').reply (200, { data: 'response data' });

*// Kiểm thử fetchData function*
test('fetchData returns data', async () => {
  const response = await fetchData('/data');
  expect(response.data).toBe('response data');
});

Ngoài ra, nếu sử dụng msw, tôi có thể mô phỏng API với cách tiếp cận dễ dàng hơn và kiểm thử theo các kịch bản phức tạp. Việc này giúp kiểm thử ứng dụng React Native của tôi trong mọi trường hợp, từ phản hồi thành công đến các lỗi mạng, đảm bảo ứng dụng hoạt động ổn định trong mọi tình huống.

Thông qua các kỹ thuật này, tôi có thể đảm bảo ứng dụng React Native của mình sẽ xử lý API hiệu quả mà không cần phải kết nối với API thực tế trong quá trình phát triển và kiểm thử.

Làm thế nào để theo dõi và sửa lỗi trong một ứng dụng React Native?

Sử dụng Console.log và Debugging:

  • Đầu tiên, ứng viên nên đề cập đến việc sử dụng console.log() để in ra thông tin về trạng thái của ứng dụng, giá trị của các biến, và các điểm nghi ngờ trong mã nguồn. Việc này giúp phát hiện lỗi logic hoặc các vấn đề trong dòng chảy dữ liệu của ứng dụng.
  • Sử dụng công cụ React Native Debugger (hoặc Chrome DevTools) để thực hiện debug JavaScript và xem các console.log, theo dõi call stack, và xem các state trong ứng dụng.

Error Boundaries:

  • Để xử lý lỗi trong React Native, ứng viên cần biết cách sử dụng Error Boundaries để ngăn ngừa ứng dụng bị crash khi gặp lỗi không lường trước trong các component. Điều này giúp giữ cho ứng dụng vẫn hoạt động mượt mà, ngay cả khi có lỗi.

Sử dụng các công cụ ghi lại lỗi:

  • Sử dụng công cụ như Sentry hoặc Bugsnag để ghi lại và theo dõi các lỗi runtime trong ứng dụng. Các công cụ này giúp phát hiện lỗi trên cả môi trường sản xuất và phát triển, cung cấp thông tin chi tiết như stack trace, thông tin về người dùng gặp lỗi và các tình huống dẫn đến lỗi.

Theo dõi hiệu suất ứng dụng:

  • Dùng các công cụ như Flipper hoặc Reactotron để theo dõi và phân tích hiệu suất của ứng dụng. Những công cụ này cho phép theo dõi các chỉ số như thời gian phản hồi, thời gian render của component, và kiểm tra các vấn đề về bộ nhớ hoặc CPU.

Sử dụng công cụ kiểm tra tự động (Unit Testing và Integration Testing):

  • Để tránh các lỗi xảy ra sau khi triển khai, ứng viên cần nêu rõ việc viết các bài test unit và integration sử dụng các công cụ như Jest hoặc Detox để đảm bảo mã nguồn không có lỗi logic hoặc giao diện bị lỗi trong quá trình phát triển.

Câu hỏi phỏng vấn React Native về hiệu suất và tối ưu hóa#

Làm thế nào để tối ưu hóa FlatList trong React Native?

**Sử dụng **keyExtractor

FlatList yêu cầu một giá trị key duy nhất cho mỗi phần tử trong danh sách. Nếu không có key hoặc key không thay đổi khi dữ liệu thay đổi, React Native sẽ phải render lại toàn bộ danh sách, điều này gây lãng phí tài nguyên.

Sử dụng thuộc tính keyExtractor giúp đảm bảo rằng mỗi item trong danh sách có một key duy nhất và ổn định.

<FlatList
  data={data}
  keyExtractor={(item) => item.id.toString()}
  renderItem={( { item }) => <Text>{item.name}</Text>}
/>
**Sử dụng **getItemLayout

Khi bạn biết trước chiều cao của mỗi phần tử trong danh sách (hoặc chiều rộng đối với danh sách ngang), bạn có thể sử dụng getItemLayout để tối ưu hóa việc tính toán vị trí của các phần tử, giúp giảm thiểu việc tính toán lại layout khi cuộn qua danh sách.

<FlatList
  data={data}
  getItemLayout={(data, index) => (
    { length: ITEM_HEIGHT, offset: ITEM_HEIGHT✶ index, index }
  )}
  renderItem={( { item }) => <Text>{item.name}</Text>}
/>
**Sử dụng initialNumToRender và **maxToRenderPerBatch

FlatList cho phép bạn xác định số lượng phần tử đầu tiên được render và số phần tử tối đa được render trong mỗi lần batch. Việc cấu hình các thuộc tính này giúp giảm lượng phần tử được render ban đầu và khi cuộn, từ đó tối ưu hóa hiệu suất.

<FlatList
  data={data}
  initialNumToRender={10}
  maxToRenderPerBatch={5}
  renderItem={({ item }) => <Text>{item.name}</Text>}
/>
**Sử dụng **windowSize

Thuộc tính windowSize xác định số lượng các phần tử FlatList sẽ được giữ trong bộ nhớ trong khi người dùng cuộn. Giá trị mặc định của windowSize là 21, nhưng bạn có thể điều chỉnh nó để tối ưu hóa hiệu suất cho ứng dụng của mình.

<FlatList
  data={data}
  windowSize={5}
  renderItem={( { item }) => <Text>{item.name}</Text>}
/>
**Sử dụng **removeClippedSubviews

FlatList hỗ trợ thuộc tính removeClippedSubviews giúp loại bỏ các phần tử không còn hiển thị trên màn hình khỏi bộ nhớ. Điều này giúp giảm tải cho bộ nhớ của ứng dụng khi cuộn qua danh sách.

<FlatList
  data={data}
  removeClippedSubviews={true}
  renderItem={( { item }) => <Text>{item.name}</Text>}
/>
**Tránh rendering quá nhiều component phức tạp trong **renderItem

Đảm bảo rằng phần tử trong renderItem là nhẹ và không thực hiện quá nhiều tác vụ tính toán phức tạp. Bạn có thể tối ưu hóa bằng cách sử dụng React.memo để ngăn chặn việc re-render lại component nếu dữ liệu không thay đổi.

const MemoizedItem = React.memo(({ item }) => {
  return <Text>{item.name}</Text>;
});

<FlatList
  data={data}
  renderItem={( { item }) => <MemoizedItem item={item} />}
/>

Một vài nguyên nhân dẫn đến xảy ra việc tăng thời gian tải ứng dụng React Native là gì?

** Kích thước lớn của bundle JavaScript**

  • Nguyên nhân: Quá nhiều mã nguồn, thư viện không cần thiết hoặc các dependency thừa dẫn đến kích thước bundle lớn.
  • Hậu quả: Tăng thời gian tải và parse bundle khi ứng dụng khởi chạy.

** Asset không được tối ưu hóa**

  • Nguyên nhân: Hình ảnh, video, và các tài nguyên tĩnh khác không được nén hoặc định dạng phù hợp.
  • Hậu quả: Gây mất thời gian tải các asset lớn trong quá trình khởi động.

** Quá trình khởi tạo module native chậm**

  • Nguyên nhân: Các module native hoặc third-party sử dụng hàm khởi tạo phức tạp, xử lý nhiều logic không cần thiết khi ứng dụng khởi động.
  • Hậu quả: Làm tăng thời gian khởi động ứng dụng.

** Sử dụng nhiều màn hình hoặc navigation stack nặng**

  • Nguyên nhân: Tải trước quá nhiều màn hình hoặc khởi tạo navigation stack lớn.
  • Hậu quả: Tiêu tốn tài nguyên và làm chậm quá trình tải.

** Fetch dữ liệu đồng bộ trong quá trình khởi chạy**

  • Nguyên nhân: Lấy dữ liệu từ API hoặc xử lý các yêu cầu không cần thiết trước khi hiển thị màn hình đầu tiên.
  • Hậu quả: Tăng thời gian hiển thị giao diện chính.

** Thiếu tối ưu hóa ở chế độ phát hành (production)**

  • Nguyên nhân: Không bật chế độ minification, obfuscation, hoặc không bật Hermes engine trong phiên bản production.
  • Hậu quả: Ứng dụng mất nhiều thời gian xử lý hơn so với ứng dụng đã được tối ưu hóa.

** Xử lý logic phức tạp trong mã nguồn**

  • Nguyên nhân: Thực hiện các thao tác tính toán, xử lý logic nặng hoặc chờ đợi không cần thiết trong khi khởi động.
  • Hậu quả: Ứng dụng phải mất thêm thời gian để xử lý trước khi hiển thị giao diện chính.

Các phương pháp để cải thiện thời gian tải ứng dụng React Native là gì?

Để cải thiện thời gian tải ứng dụng React Native, tôi sẽ áp dụng một số phương pháp sau:

Giảm kích thước bundle
  • Sử dụng Code Splitting để chia mã nguồn thành các phần nhỏ, giúp tải các phần cần thiết trước và các phần khác khi người dùng cần.
  • Loại bỏ mã thừa và chỉ giữ lại các thư viện và mã cần thiết. Cũng nên sử dụng các công cụ như tree-shaking để loại bỏ mã không sử dụng.
Tối ưu hóa hình ảnh và tài nguyên
  • Dùng các hình ảnh có độ phân giải phù hợp với từng loại thiết bị, giúp giảm dung lượng tải về. Các công cụ như react-native-fast-image có thể giúp tải ảnh nhanh hơn.
  • Sử dụng các định dạng ảnh nén như WebP hoặc AVIF để giảm kích thước file ảnh.
Cải thiện tốc độ khởi tạo ứng dụng
  • Lazy loading: Tải các màn hình hoặc thành phần không cần thiết ngay lập tức mà chỉ tải khi người dùng tương tác.
  • Tối ưu hóa các Native Modules để giảm độ trễ trong việc giao tiếp giữa JavaScript và Native Code.
Tối ưu hóa Redux Store
  • Sử dụng Redux Toolkit để giúp giảm thiểu việc tải lại trạng thái không cần thiết, từ đó giảm bớt số lần render lại của UI.
Tối ưu hóa JavaScript và UI rendering
  • Tối ưu hóa việc render lại các component bằng cách sử dụng React.memoPureComponent để tránh render lại các component không thay đổi.
  • Đảm bảo rằng các component chỉ render khi thực sự cần thiết, sử dụng shouldComponentUpdate hoặc React.memo để kiểm soát quá trình này.
Preload các tài nguyên quan trọng
  • Preload các tài nguyên quan trọng như font, hình ảnh, hoặc API ngay khi ứng dụng khởi động để người dùng không phải chờ đợi trong lúc sử dụng ứng dụng.
Sử dụng Hermes Engine
  • Kích hoạt Hermes, một JavaScript engine tối ưu cho React Native, giúp giảm thời gian khởi tạo ứng dụng và cải thiện hiệu suất tổng thể.
  • Bằng cách áp dụng các phương pháp này, ứng dụng React Native sẽ được tối ưu hóa để có thời gian tải nhanh hơn, mang lại trải nghiệm mượt mà cho người dùng.

Giải thích về VirtualizedList và cách sử dụng nó

VirtualizedList là một component thay thế cho FlatList và SectionList khi bạn cần làm việc với các danh sách có số lượng phần tử lớn. Thay vì render tất cả các phần tử của danh sách cùng lúc, VirtualizedList chỉ render các phần tử hiện thị trong viewport, tức là các phần tử mà người dùng có thể thấy được trên màn hình tại thời điểm đó.

Điều này giúp giảm thiểu việc sử dụng bộ nhớ và cải thiện tốc độ tải ứng dụng, đặc biệt khi danh sách chứa hàng nghìn phần tử.

Lợi ích của VirtualizedList:

  • Hiệu suất cao: Chỉ render các phần tử hiện thấy trên màn hình, giúp tiết kiệm bộ nhớ và giảm tải cho ứng dụng.
  • Dễ dàng tùy chỉnh: Cho phép bạn tùy chỉnh cách lấy dữ liệu và render các phần tử, rất hữu ích khi làm việc với các dữ liệu phức tạp hoặc cần tối ưu hóa thêm.

Cách sử dụng VirtualizedList: VirtualizedList nhận vào các prop cơ bản như data, renderItem, và getItemCount, tương tự như FlatList, nhưng cần một số thông tin bổ sung để tính toán số lượng phần tử và chỉ render những phần tử cần thiết.

Ví dụ cơ bản về cách sử dụng VirtualizedList:

import React from 'react';
import { VirtualizedList, Text, View } from 'react-native';

const data = [...Array(1000).keys()]; // Một danh sách dài 1000 phần tử

const getItemCount = (data) => data.length; // Số lượng phần tử trong danh sách
const getItem = (data, index) => ({ key: index, title: `Item ${data[index]}` });

const renderItem = ({ item }) => (
  <View style={{ padding: 20 }}>
    <Text>{item.title}</Text>
  </View>
);

const MyList = () => (
  <VirtualizedList
    data={data}
    getItemCount={getItemCount}
    getItem={getItem}
    renderItem={renderItem}
    keyExtractor={(item) => item.key.toString()}
  />
);

export default MyList;

Trong ví dụ trên:

  • getItemCount: Xác định số lượng phần tử trong danh sách.
  • getItem: Trả về một đối tượng phần tử tại một chỉ mục nhất định.
  • renderItem: Định nghĩa cách mỗi phần tử trong danh sách sẽ được render.

Cách tối ưu hóa ảnh và các tài nguyên trong ứng dụng React Native

  • Sử dụng ảnh với kích thước phù hợp: Ứng viên cần giải thích cách sử dụng các công cụ để giảm kích thước ảnh trước khi đưa vào ứng dụng, như sử dụng các định dạng ảnh nén (WebP, JPEG) thay vì PNG. Ngoài ra, cần giới thiệu việc sử dụng ảnh với độ phân giải phù hợp với từng loại thiết bị (xhdpi, xxhdpi, xxxhdpi), để tránh việc tải các ảnh có độ phân giải quá cao trên các thiết bị có màn hình nhỏ.
  • Lazy loading (Tải ảnh khi cần): Một cách tối ưu khác là sử dụng kỹ thuật lazy loading để chỉ tải ảnh khi người dùng thực sự cần nhìn thấy nó (ví dụ: khi ảnh xuất hiện trong phần visible của màn hình). Điều này giúp giảm bớt tải trên mạng và giúp ứng dụng khởi động nhanh hơn.
  • **Sử dụng thư viện như **react-native-fast-image: Đây là một thư viện nổi bật giúp tối ưu hóa việc tải và hiển thị ảnh trong React Native. Thư viện này hỗ trợ caching (bộ nhớ đệm), giảm thiểu việc tải lại ảnh khi người dùng quay lại trang cũ, giúp ứng dụng phản hồi nhanh hơn.
  • Cải thiện tốc độ tải ảnh bằng cách sử dụng cache: Việc sử dụng bộ nhớ đệm cho ảnh giúp giảm thiểu số lần tải lại và tiết kiệm băng thông. Ứng viên cần biết cách tích hợp các giải pháp cache hiệu quả, như sử dụng Image.prefetch trong React Native để tải ảnh vào bộ nhớ đệm trước khi hiển thị.
  • Tối ưu hóa tài nguyên khác (Font, Icons, SVGs): Bên cạnh ảnh, ứng viên cũng cần biết cách tối ưu hóa các tài nguyên khác như font chữ và icons. Sử dụng SVGs thay vì ảnh bitmap cho các icon có thể giúp giảm kích thước ứng dụng. Đồng thời, việc sử dụng các font chữ hệ thống thay vì font tùy chỉnh (hoặc tải chúng từ nguồn ngoài) cũng giúp giảm bớt tài nguyên tải về.
  • Sử dụng công cụ xây dựng và phân phối ứng dụng tối ưu: Trong một số trường hợp, ứng viên có thể đề xuất sử dụng các công cụ xây dựng ứng dụng như react-native-image-resizer để tự động tối ưu hóa ảnh khi xây dựng ứng dụng, cũng như giảm dung lượng của ảnh khi phân phối qua các nền tảng.

Khi nào nên sử dụng PureComponent hoặc memo trong React Native?

Trong React Native, bạn nên sử dụng PureComponent hoặc memo khi bạn muốn tối ưu hóa hiệu suất của ứng dụng bằng cách ngừng render lại component không cần thiết. Nếu bạn làm việc với các class component và các props hoặc state không thay đổi nhiều, sử dụng PureComponent là một lựa chọn tốt. Còn nếu bạn làm việc với các functional component, memo là một lựa chọn phù hợp.

Tuy nhiên, bạn cần chú ý đến các props phức tạp, vì cả hai đều chỉ so sánh shallow. Đôi khi, việc cung cấp một hàm so sánh tùy chỉnh có thể là cần thiết để đạt được tối ưu hiệu suất.

Tình huốngGiải pháp đề xuấtLưu ý
Sử dụngclass componentSử dụngPureComponentđể tránh render lại khipropshoặcstatekhông thay đổi.PureComponent chỉ thực hiện so sánh shallow, không hiệu quả vớiprops phức tạp(như object, array lồng nhau).
Sử dụngfunctional componentSử dụngReact.memođể tối ưu hóa render.React.memo cũng chỉ so sánh shallow, cần cung cấp hàm so sánh tùy chỉnh nếu cần thiết.

Câu hỏi phỏng vấn React Native về công cụ và thư viện hỗ trợ#

Các công cụ phổ biến để debug ứng dụng React Native

  • React Native Debugger: Đây là công cụ chính thức được tích hợp với Chrome Developer Tools, giúp debug cả JavaScript và các vấn đề về giao diện người dùng. React Native Debugger cho phép theo dõi trạng thái Redux, xem log, và thực hiện các thao tác debugging mạnh mẽ.
  • Flipper: Flipper là công cụ đa năng dùng để debug ứng dụng React Native. Flipper hỗ trợ nhiều tính năng như theo dõi API, kiểm tra hiệu suất, và xem log. Nó cũng cung cấp các plugin cho việc kiểm tra cơ sở dữ liệu SQLite, kiểm tra dữ liệu và trạng thái của Redux, và thậm chí là debug native code.
  • Console.log(): Mặc dù đơn giản, console.log() vẫn là công cụ debug cơ bản nhất trong quá trình phát triển ứng dụng React Native. Việc chèn các dòng log trong mã giúp theo dõi luồng dữ liệu và phát hiện vấn đề trong ứng dụng.
  • React Developer Tools: Đây là một extension cho Chrome, giúp theo dõi và phân tích các component trong React Native, xem các props và state của chúng, cũng như kiểm tra các thay đổi trong quá trình ứng dụng chạy.
  • Xcode/Android Studio: Đối với các vấn đề native, việc sử dụng Xcode trên iOS hoặc Android Studio trên Android là rất quan trọng. Các IDE này có công cụ debug mạnh mẽ, giúp kiểm tra các vấn đề liên quan đến native code, cấu hình, và hiệu suất của ứng dụng.

**Sử dụng Expo trong React Native có lợi ích gì? **

Dưới đây là một số lợi ích khi sử dụng Expo:

  • Cài đặt và cấu hình đơn giản: Expo giúp giảm thiểu các bước cài đặt và cấu hình ban đầu. Người dùng không cần phải lo lắng về việc thiết lập môi trường phát triển hoặc cấu hình các công cụ như Android Studio, Xcode, vì Expo cung cấp sẵn tất cả các công cụ này trong một package dễ sử dụng.
  • Chạy ứng dụng ngay lập tức trên điện thoại: Với Expo Go, bạn có thể chạy ứng dụng trực tiếp trên thiết bị di động mà không cần phải biên dịch lại. Điều này giúp tiết kiệm thời gian phát triển và thử nghiệm.
  • Hỗ trợ nhiều tính năng sẵn có: Expo cung cấp rất nhiều API và thư viện sẵn có như camera, location, notifications, và nhiều tính năng khác mà bạn không cần phải cài đặt riêng biệt hoặc xử lý native code. Điều này giúp giảm thiểu khối lượng công việc và tăng tốc độ phát triển.
  • Cộng đồng mạnh mẽ và tài liệu đầy đủ: Expo có một cộng đồng hỗ trợ lớn và tài liệu chi tiết, dễ tiếp cận. Điều này giúp lập trình viên dễ dàng tìm kiếm và giải quyết vấn đề trong quá trình phát triển.
  • Khả năng xuất bản nhanh chóng: Expo giúp bạn dễ dàng xuất bản ứng dụng lên App Store và Google Play mà không cần phải làm việc với native code hoặc công cụ như Xcode và Android Studio.

Các thư viện hỗ trợ cho việc xây dựng UI trong React Native như NativeBase, React Native Paper

NativeBaseReact Native Paper. Đây là các thư viện phổ biến giúp tăng tốc quá trình phát triển ứng dụng, cung cấp các thành phần UI đã được tối ưu hóa và thiết kế sẵn.

  • NativeBase là một thư viện UI được thiết kế để hoạt động trên cả Android và iOS, với mục tiêu giúp lập trình viên xây dựng giao diện người dùng đẹp và nhất quán mà không cần phải tự tay tạo ra các thành phần như buttons, forms, modals, tabs, v.v. NativeBase hỗ trợ cả theme tùy chỉnh, giúp dễ dàng thay đổi giao diện ứng dụng một cách linh hoạt.
  • React Native Paper là một thư viện UI được xây dựng dựa trên các nguyên lý của Material Design, giúp ứng dụng của bạn có một giao diện hiện đại, đẹp mắt và dễ sử dụng. React Native Paper cung cấp các thành phần UI như buttons, dialogs, cards, lists, và nhiều thành phần khác, đồng thời hỗ trợ chủ động tùy chỉnh giao diện theo yêu cầu của dự án.

**Làm thế nào để sử dụng React Native CLI để tạo và quản lý dự án? **

Để tạo và quản lý dự án React Native, tôi sẽ sử dụng React Native CLI, một công cụ dòng lệnh mạnh mẽ hỗ trợ tạo và phát triển các ứng dụng React Native. Các bước cơ bản như sau:

Bước 1: Cài đặt React Native CLI

Trước tiên, tôi cần cài đặt React Native CLI nếu chưa có. Điều này có thể thực hiện bằng cách sử dụng npm (hoặc yarn):

npm install -g react-native-cli

Bước 2: Tạo dự án mới

Sau khi cài đặt xong, tôi có thể tạo một dự án mới bằng lệnh:

npx react-native init MyProject

Lệnh này sẽ tạo một thư mục dự án với cấu trúc mặc định, bao gồm các tệp cần thiết để bắt đầu phát triển ứng dụng React Native.

Bước 3: Chạy dự án trên thiết bị mô phỏng hoặc thiết bị thật

Sau khi tạo xong dự án, tôi có thể sử dụng các lệnh để chạy ứng dụng trên thiết bị mô phỏng (iOS hoặc Android):

  • Đối với iOS:
npx react-native run-ios
  • Đối với Android:
npx react-native run-android

Bước 4: Quản lý các phụ thuộc và cấu hình dự án

React Native CLI hỗ trợ quản lý các phụ thuộc (dependencies) thông qua npm hoặc yarn. Tôi có thể thêm các thư viện cần thiết cho dự án bằng lệnh:

npm install <package-name>

Bước 5: Xử lý các vấn đề khi phát triển

Khi phát triển, tôi sẽ thường xuyên sử dụng các công cụ như react-native start để khởi động server Metro (bundler của React Native) và theo dõi các thay đổi của ứng dụng.

Ngoài ra, React Native CLI còn hỗ trợ các lệnh hữu ích khác như link (để liên kết các thư viện native), clean (để làm sạch bộ nhớ cache), và upgrade (để nâng cấp React Native và các phụ thuộc).

Giới thiệu về Metro Bundler và vai trò của nó trong React Native

Metro Bundler là công cụ đóng gói (bundling) chính trong React Native, chịu trách nhiệm biên dịch và tối ưu hóa mã JavaScript của ứng dụng, chuyển chúng thành các tệp mà ứng dụng có thể sử dụng.

Vai trò của Metro Bundler trong phát triển React Native là cực kỳ quan trọng, vì nó giúp tiết kiệm thời gian phát triển nhờ vào tính năng hot reload, giúp lập trình viên thấy các thay đổi trong mã nguồn ngay lập tức mà không phải tải lại ứng dụng hoàn toàn.

Tổng kết câu hỏi phỏng vấn React Native#

Bằng việc chuẩn bị kỹ càng các câu hỏi phỏng vấn React Native kể trên, ứng viên sẽ có thể tự tin hơn khi đối mặt với những câu hỏi phỏng vấn về React Native, thể hiện được kiến thức sâu rộng và khả năng áp dụng các công cụ, thư viện để phát triển ứng dụng hiệu quả.

Ngoài ra, việc hiểu rõ các khái niệm và công cụ quan trọng sẽ giúp lập trình viên React Native có thể phát triển ứng dụng mượt mà và tối ưu hóa quá trình phát triển, mang lại trải nghiệm người dùng tốt nhất.